mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-09 06:26:39 +00:00
This change adds 4 new CPU related metrics: * process_mperf_total * processor_rtc_total * processor_utility_total * processor_privileged_utility_total and renames the existing process_performance to processor_performance_total, since it was previously misunderstood and was unlikely to be have been useful without the above new metrics The data sources for these are not particularly well understood, and the examples show that in some cases, arbitrary scaling factors are required to actually make them useful, but in my testing on hundreds of systems with a broad range of CPUs and operating systems from 2012r2 through to 2019 has proved out that we can use them to accurately display actual CPU frequencies and CPU utilisation as it is represented in taskmgr. Things I don't particularly like and would like input on: * I would have preferred to do the scaling of processor_mperf_total in the code, but there isn't an elegant way of doing this right now. * Maybe processor_mperf_total should be called processor_mperformance_total. See #787 for discussion. Signed-off-by: Steffen Higel <higels@valvesoftware.com>
127 lines
3.3 KiB
Go
127 lines
3.3 KiB
Go
package collector
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
perflibCollector "github.com/leoluk/perflib_exporter/collector"
|
|
"github.com/leoluk/perflib_exporter/perflib"
|
|
"github.com/prometheus-community/windows_exporter/log"
|
|
)
|
|
|
|
var nametable = perflib.QueryNameTable("Counter 009") // Reads the names in English TODO: validate that the English names are always present
|
|
|
|
func MapCounterToIndex(name string) string {
|
|
return strconv.Itoa(int(nametable.LookupIndex(name)))
|
|
}
|
|
|
|
func getPerflibSnapshot(objNames string) (map[string]*perflib.PerfObject, error) {
|
|
objects, err := perflib.QueryPerformanceData(objNames)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
indexed := make(map[string]*perflib.PerfObject)
|
|
for _, obj := range objects {
|
|
indexed[obj.Name] = obj
|
|
}
|
|
return indexed, nil
|
|
}
|
|
|
|
func unmarshalObject(obj *perflib.PerfObject, vs interface{}) error {
|
|
if obj == nil {
|
|
return fmt.Errorf("counter not found")
|
|
}
|
|
rv := reflect.ValueOf(vs)
|
|
if rv.Kind() != reflect.Ptr || rv.IsNil() {
|
|
return fmt.Errorf("%v is nil or not a pointer to slice", reflect.TypeOf(vs))
|
|
}
|
|
ev := rv.Elem()
|
|
if ev.Kind() != reflect.Slice {
|
|
return fmt.Errorf("%v is not slice", reflect.TypeOf(vs))
|
|
}
|
|
|
|
// Ensure sufficient length
|
|
if ev.Cap() < len(obj.Instances) {
|
|
nvs := reflect.MakeSlice(ev.Type(), len(obj.Instances), len(obj.Instances))
|
|
ev.Set(nvs)
|
|
}
|
|
|
|
for idx, instance := range obj.Instances {
|
|
target := ev.Index(idx)
|
|
rt := target.Type()
|
|
|
|
counters := make(map[string]*perflib.PerfCounter, len(instance.Counters))
|
|
for _, ctr := range instance.Counters {
|
|
if ctr.Def.IsBaseValue && !ctr.Def.IsNanosecondCounter {
|
|
counters[ctr.Def.Name+"_Base"] = ctr
|
|
} else {
|
|
counters[ctr.Def.Name] = ctr
|
|
}
|
|
}
|
|
|
|
for i := 0; i < target.NumField(); i++ {
|
|
f := rt.Field(i)
|
|
tag := f.Tag.Get("perflib")
|
|
if tag == "" {
|
|
continue
|
|
}
|
|
secondValue := false
|
|
|
|
st := strings.Split(tag, ",")
|
|
tag = st[0]
|
|
|
|
for _, t := range st {
|
|
if t == "secondvalue" {
|
|
secondValue = true
|
|
}
|
|
}
|
|
|
|
ctr, found := counters[tag]
|
|
if !found {
|
|
log.Debugf("missing counter %q, have %v", tag, counterMapKeys(counters))
|
|
continue
|
|
}
|
|
if !target.Field(i).CanSet() {
|
|
return fmt.Errorf("tagged field %v cannot be written to", f.Name)
|
|
}
|
|
if fieldType := target.Field(i).Type(); fieldType != reflect.TypeOf((*float64)(nil)).Elem() {
|
|
return fmt.Errorf("tagged field %v has wrong type %v, must be float64", f.Name, fieldType)
|
|
}
|
|
|
|
if secondValue {
|
|
if !ctr.Def.HasSecondValue {
|
|
return fmt.Errorf("tagged field %v expected a SecondValue, which was not present", f.Name)
|
|
}
|
|
target.Field(i).SetFloat(float64(ctr.SecondValue))
|
|
continue
|
|
}
|
|
|
|
switch ctr.Def.CounterType {
|
|
case perflibCollector.PERF_ELAPSED_TIME:
|
|
target.Field(i).SetFloat(float64(ctr.Value-windowsEpoch) / float64(obj.Frequency))
|
|
case perflibCollector.PERF_100NSEC_TIMER, perflibCollector.PERF_PRECISION_100NS_TIMER:
|
|
target.Field(i).SetFloat(float64(ctr.Value) * ticksToSecondsScaleFactor)
|
|
default:
|
|
target.Field(i).SetFloat(float64(ctr.Value))
|
|
}
|
|
}
|
|
|
|
if instance.Name != "" && target.FieldByName("Name").CanSet() {
|
|
target.FieldByName("Name").SetString(instance.Name)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func counterMapKeys(m map[string]*perflib.PerfCounter) []string {
|
|
keys := make([]string, 0, len(m))
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
return keys
|
|
}
|