// SPDX-License-Identifier: Apache-2.0 // // Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build windows package registry import ( "errors" "fmt" "reflect" "strings" "sync" "github.com/prometheus-community/windows_exporter/internal/pdh" ) type Collector[T any] struct { object string query string counters map[string]Counter nameIndexValue int // bufferMu guards buffer, the raw data buffer of the last query. A Collect // takes it out for the duration of the query, so concurrent calls never // share it; they allocate their own buffer instead. bufferMu sync.Mutex buffer []byte } type Counter struct { Name string Desc string Instances map[string]uint32 Type uint32 Frequency float64 FieldIndexValue int FieldIndexSecondValue int } // NewCollector creates a collector for the counters declared by the perfdata_v1 or perfdata tags of the struct T. func NewCollector[T any](object string, _ []string) (*Collector[T], error) { valueType := reflect.TypeFor[T]() if valueType.Kind() != reflect.Struct { return nil, fmt.Errorf("expected a struct, got %s", valueType) } if err := CounterNameTable.load(); err != nil { return nil, err } query := MapCounterToIndex(object) if query == "0" { return nil, fmt.Errorf("perflib object %q not found in the counter name table", object) } collector := &Collector[T]{ object: object, query: query, nameIndexValue: -1, counters: make(map[string]Counter), } if f, ok := valueType.FieldByName("Name"); ok { if f.Type.Kind() == reflect.String { collector.nameIndexValue = f.Index[0] } } for _, f := range reflect.VisibleFields(valueType) { tag, ok := f.Tag.Lookup("perfdata_v1") if !ok { tag, ok = f.Tag.Lookup("perfdata") if !ok { continue } } if f.Type.Kind() != reflect.Float64 { return nil, fmt.Errorf("field %s must be a float64", f.Name) } counterName, secondValue := strings.CutSuffix(tag, ",secondvalue") counter, ok := collector.counters[counterName] if !ok { counter = Counter{ Name: counterName, FieldIndexSecondValue: -1, FieldIndexValue: -1, } } if secondValue { counter.FieldIndexSecondValue = f.Index[0] } else { counter.FieldIndexValue = f.Index[0] } collector.counters[counterName] = counter } var collectValues []T if err := collector.Collect(&collectValues); err != nil { return nil, fmt.Errorf("failed to collect initial data: %w", err) } return collector, nil } func (c *Collector[T]) Describe() map[string]string { return map[string]string{} } // Collect replaces the content of dst with one row per collected instance. func (c *Collector[T]) Collect(dst *[]T) error { if dst == nil { return errors.New("dst must not be nil") } c.bufferMu.Lock() buffer := c.buffer c.buffer = nil c.bufferMu.Unlock() perfObjects, buffer, err := queryPerformanceData(c.query, c.object, buffer) // Keep the larger buffer if a concurrent call put one back in the meantime. c.bufferMu.Lock() if cap(buffer) > cap(c.buffer) { c.buffer = buffer } c.bufferMu.Unlock() if err != nil { return fmt.Errorf("QueryPerformanceData: %w", err) } if len(perfObjects) == 0 || perfObjects[0] == nil || len(perfObjects[0].Instances) == 0 { return nil } *dst = make([]T, 0, len(perfObjects[0].Instances)) for _, perfObject := range perfObjects { if perfObject.Name != c.object { continue } for _, perfInstance := range perfObject.Instances { instanceName := perfInstance.Name if pdh.IsTotalInstance(c.object, instanceName) { continue } if instanceName == "" || instanceName == "*" { instanceName = pdh.InstanceEmpty } var row T rv := reflect.ValueOf(&row).Elem() if c.nameIndexValue != -1 { rv.Field(c.nameIndexValue).SetString(instanceName) } for _, perfCounter := range perfInstance.Counters { if perfCounter.Def.IsBaseValue && !perfCounter.Def.IsNanosecondCounter { continue } counter, ok := c.counters[perfCounter.Def.Name] if !ok { continue } switch perfCounter.Def.CounterType { case pdh.PERF_ELAPSED_TIME: // A zero frequency would divide by zero. The value is left unset. if perfObject.Frequency <= 0 || counter.FieldIndexValue == -1 { continue } rv.Field(counter.FieldIndexValue). SetFloat(float64((perfCounter.Value - pdh.WindowsEpoch) / perfObject.Frequency)) case pdh.PERF_100NSEC_TIMER, pdh.PERF_PRECISION_100NS_TIMER: if counter.FieldIndexValue == -1 { continue } rv.Field(counter.FieldIndexValue). SetFloat(float64(perfCounter.Value) * pdh.TicksToSecondScaleFactor) default: if counter.FieldIndexSecondValue != -1 { rv.Field(counter.FieldIndexSecondValue). SetFloat(float64(perfCounter.SecondValue)) } if counter.FieldIndexValue != -1 { rv.Field(counter.FieldIndexValue). SetFloat(float64(perfCounter.Value)) } } } *dst = append(*dst, row) } } return nil } func (c *Collector[T]) Close() {}