Skip to content
This repository has been archived by the owner on Jul 19, 2023. It is now read-only.

Commit

Permalink
Multi-value counter support plus bug fixes (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
bismarck authored and leoluk committed Apr 3, 2019
1 parent 081797a commit 50015b8
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 59 deletions.
87 changes: 44 additions & 43 deletions collector/collector.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package collector

import (
"fmt"
"strings"

"github.com/leoluk/perflib_exporter/perflib"
"github.com/prometheus/client_golang/prometheus"
Expand All @@ -23,16 +23,19 @@ type Collector interface {
}

type CounterKey struct {
ObjectIndex, CounterIndex uint
ObjectIndex uint
CounterIndex uint
CounterType uint32 // This is a bit mask
}

type PerflibCollector struct {
perflibQuery string
perflibObjects []*perflib.PerfObject
perflibDescs map[CounterKey]*prometheus.Desc
func NewCounterKey(object *perflib.PerfObject, def *perflib.PerfCounterDef) CounterKey {
return CounterKey{object.NameIndex, def.NameIndex, def.CounterType}
}

var countersPerDef map[CounterKey]uint
type PerflibCollector struct {
perflibQuery string
perflibDescs map[CounterKey]*prometheus.Desc
}

func NewPerflibCollector(query string) (c PerflibCollector) {
c.perflibQuery = query
Expand All @@ -43,35 +46,16 @@ func NewPerflibCollector(query string) (c PerflibCollector) {
panic(err)
}

c.perflibObjects = objects
log.Debugf("Number of objects: %d", len(objects))

c.perflibDescs = make(map[CounterKey]*prometheus.Desc)

knownNames := make(map[string]bool)

for _, object := range objects {
for _, def := range object.CounterDefs {
name, desc := descFromCounterDef(*object, *def)
keyname := fmt.Sprintf("%s|%s", object.Name, name)
if _, ok := knownNames[keyname]; ok {
continue
}
desc := descFromCounterDef(*object, *def)

key := CounterKey{object.NameIndex, def.NameIndex}
key := NewCounterKey(object, def)
c.perflibDescs[key] = desc
knownNames[keyname] = true
}
}

// TODO: we do not handle multi-value counters yet, so we count and remove them
countersPerDef = make(map[CounterKey]uint)

for _, object := range objects {
instance := object.Instances[0]
for _, counter := range instance.Counters {
key := CounterKey{object.NameIndex, counter.Def.NameIndex}
countersPerDef[key] += 1
}
}

Expand All @@ -98,6 +82,9 @@ func (c PerflibCollector) Collect(ch chan<- prometheus.Metric) (err error) {
// _Total metrics do not fit into the Prometheus model - we try to merge similar
// metrics and give them labels, so you'd sum() them instead. Having a _Total label
// would make
if strings.HasSuffix(name, "_Total") || strings.HasPrefix(name, "Total") {
continue
}

for _, counter := range instance.Counters {
if IsDefPromotedLabel(n, counter.Def.NameIndex) {
Expand All @@ -114,10 +101,8 @@ func (c PerflibCollector) Collect(ch chan<- prometheus.Metric) (err error) {
continue
}

key := CounterKey{object.NameIndex, counter.Def.NameIndex}

if countersPerDef[key] > 1 {
log.Debugf("multi counter %s -> %s -> %s", object.Name, instance.Name, counter.Def.Name)
if counter.Def.Name == "" {
log.Debugf("no counter name for %s -> %s", object.Name, instance.Name)
continue
}

Expand All @@ -126,6 +111,8 @@ func (c PerflibCollector) Collect(ch chan<- prometheus.Metric) (err error) {
continue
}

key := NewCounterKey(object, counter.Def)

desc, ok := c.perflibDescs[key]

if !ok {
Expand All @@ -135,23 +122,32 @@ func (c PerflibCollector) Collect(ch chan<- prometheus.Metric) (err error) {

labels := []string{name}

if counter.Def.Name == "" {
if len(object.Instances) == 1 {
labels = []string{}
}

if HasPromotedLabels(n) {
labels = append(labels, PromotedLabelValuesForInstance(n, instance)...)
}

if HasMergedLabels(n) {
_, value := MergedMetricForInstance(n, counter.Def.NameIndex)

// Null string in definition means we should skip this metric (it's probably a sum)
if value == "" {
log.Debugf("Skipping %d -> %s (empty merge label)", n, counter.Def.NameIndex)
continue
}
labels = append(labels, value)
// TODO - Label merging needs to be fixed for [230] Process
//if HasMergedLabels(n) {
// _, value := MergedMetricForInstance(n, counter.Def.NameIndex)
//
// // Null string in definition means we should skip this metric (it's probably a sum)
// if value == "" {
// log.Debugf("Skipping %d -> %s (empty merge label)", n, counter.Def.NameIndex)
// continue
// }
// labels = append(labels, value)
//}

valueType, err := GetPrometheusValueType(counter.Def.CounterType)

if err != nil {
// TODO - Is this too verbose? There will always be counter types we don't support
log.Debug(err)
continue
}

value := float64(counter.Value)
Expand All @@ -160,9 +156,14 @@ func (c PerflibCollector) Collect(ch chan<- prometheus.Metric) (err error) {
value = value * hundredNsToSecondsScaleFactor
}

if IsElapsedTime(counter.Def.CounterType) {
// convert from Windows timestamp (1 jan 1601) to unix timestamp (1 jan 1970)
value = float64(counter.Value-116444736000000000) / float64(object.Frequency)
}

metric := prometheus.MustNewConstMetric(
desc,
prometheus.CounterValue,
valueType,
value,
labels...,
)
Expand Down
30 changes: 18 additions & 12 deletions collector/mangle.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@ import (
"github.com/prometheus/client_golang/prometheus"
)

func manglePerflibName(s string) (string) {
func manglePerflibName(s string) string {
s = strings.ToLower(s)
s = strings.Replace(s, " ", "_", -1)
s = strings.Replace(s, ".", "", -1)
s = strings.Replace(s, "(", "", -1)
s = strings.Replace(s, ")", "", -1)
s = strings.Replace(s, "+", "", -1)
s = strings.Replace(s, "-", "", -1)
s = strings.Replace(s, ",", "", -1)

return s
}

func manglePerflibCounterName(s string) (string) {
func manglePerflibCounterName(s string) string {
s = manglePerflibName(s)

s = strings.Replace(s, "total_", "", -1)
Expand All @@ -43,8 +44,12 @@ func manglePerflibCounterName(s string) (string) {
func MakePrometheusLabel(def *perflib.PerfCounterDef) (s string) {
s = manglePerflibCounterName(def.Name)

if def.IsCounter {
s += "_total"
if len(s) > 0 {
if IsCounter(def.CounterType) {
s += "_total"
} else if IsBaseValue(def.CounterType) && !strings.HasSuffix(s, "_base") {
s += "_max"
}
}

return
Expand All @@ -54,27 +59,28 @@ func pdhNameFromCounterDef(obj perflib.PerfObject, def perflib.PerfCounterDef) s
return fmt.Sprintf(`\%s(*)\%s`, obj.Name, def.Name)
}

func descFromCounterDef(obj perflib.PerfObject, def perflib.PerfCounterDef) (string, *prometheus.Desc) {
func descFromCounterDef(obj perflib.PerfObject, def perflib.PerfCounterDef) *prometheus.Desc {
subsystem := manglePerflibName(obj.Name)
counterName := MakePrometheusLabel(&def)

labels := []string{"name"}

if def.Name == "" {
if len(obj.Instances) == 1 {
labels = []string{}
}

if HasPromotedLabels(obj.NameIndex) {
labels = append(labels, PromotedLabelsForObject(obj.NameIndex)...)
}

if HasMergedLabels(obj.NameIndex) {
s, labelsForObject := MergedLabelsForInstance(obj.NameIndex, def.NameIndex)
counterName = s
labels = append(labels, labelsForObject)
}
// TODO - Label merging needs to be fixed for [230] Process
//if HasMergedLabels(obj.NameIndex) {
// s, labelsForObject := MergedLabelsForInstance(obj.NameIndex, def.NameIndex)
// counterName = s
// labels = append(labels, labelsForObject)
//}

return counterName, prometheus.NewDesc(
return prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, counterName),
fmt.Sprintf("perflib metric: %s (see /dump for docs) [%d]",
pdhNameFromCounterDef(obj, def), def.NameIndex),
Expand Down
91 changes: 91 additions & 0 deletions collector/mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package collector

import (
"fmt"

"github.com/prometheus/client_golang/prometheus"
)

const (
PERF_COUNTER_RAWCOUNT_HEX = 0x00000000
PERF_COUNTER_LARGE_RAWCOUNT_HEX = 0x00000100
PERF_COUNTER_TEXT = 0x00000b00
PERF_COUNTER_RAWCOUNT = 0x00010000
PERF_COUNTER_LARGE_RAWCOUNT = 0x00010100
PERF_DOUBLE_RAW = 0x00012000
PERF_COUNTER_DELTA = 0x00400400
PERF_COUNTER_LARGE_DELTA = 0x00400500
PERF_SAMPLE_COUNTER = 0x00410400
PERF_COUNTER_QUEUELEN_TYPE = 0x00450400
PERF_COUNTER_LARGE_QUEUELEN_TYPE = 0x00450500
PERF_COUNTER_100NS_QUEUELEN_TYPE = 0x00550500
PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE = 0x00650500
PERF_COUNTER_COUNTER = 0x10410400
PERF_COUNTER_BULK_COUNT = 0x10410500
PERF_RAW_FRACTION = 0x20020400
PERF_LARGE_RAW_FRACTION = 0x20020500
PERF_COUNTER_TIMER = 0x20410500
PERF_PRECISION_SYSTEM_TIMER = 0x20470500
PERF_100NSEC_TIMER = 0x20510500
PERF_PRECISION_100NS_TIMER = 0x20570500
PERF_OBJ_TIME_TIMER = 0x20610500
PERF_PRECISION_OBJECT_TIMER = 0x20670500
PERF_SAMPLE_FRACTION = 0x20c20400
PERF_COUNTER_TIMER_INV = 0x21410500
PERF_100NSEC_TIMER_INV = 0x21510500
PERF_COUNTER_MULTI_TIMER = 0x22410500
PERF_100NSEC_MULTI_TIMER = 0x22510500
PERF_COUNTER_MULTI_TIMER_INV = 0x23410500
PERF_100NSEC_MULTI_TIMER_INV = 0x23510500
PERF_AVERAGE_TIMER = 0x30020400
PERF_ELAPSED_TIME = 0x30240500
PERF_COUNTER_NODATA = 0x40000200
PERF_AVERAGE_BULK = 0x40020500
PERF_SAMPLE_BASE = 0x40030401
PERF_AVERAGE_BASE = 0x40030402
PERF_RAW_BASE = 0x40030403
PERF_PRECISION_TIMESTAMP = 0x40030500
PERF_LARGE_RAW_BASE = 0x40030503
PERF_COUNTER_MULTI_BASE = 0x42030500
PERF_COUNTER_HISTOGRAM_TYPE = 0x80000000
)

var supportedCounterTypes = map[uint32]prometheus.ValueType{
PERF_COUNTER_RAWCOUNT_HEX: prometheus.GaugeValue,
PERF_COUNTER_LARGE_RAWCOUNT_HEX: prometheus.GaugeValue,
PERF_COUNTER_RAWCOUNT: prometheus.GaugeValue,
PERF_COUNTER_LARGE_RAWCOUNT: prometheus.GaugeValue,
PERF_COUNTER_DELTA: prometheus.CounterValue,
PERF_COUNTER_COUNTER: prometheus.CounterValue,
PERF_COUNTER_BULK_COUNT: prometheus.CounterValue,
PERF_RAW_FRACTION: prometheus.GaugeValue,
PERF_LARGE_RAW_FRACTION: prometheus.GaugeValue,
PERF_100NSEC_TIMER: prometheus.CounterValue,
PERF_PRECISION_100NS_TIMER: prometheus.CounterValue,
PERF_SAMPLE_FRACTION: prometheus.GaugeValue,
PERF_100NSEC_TIMER_INV: prometheus.CounterValue,
PERF_ELAPSED_TIME: prometheus.GaugeValue,
PERF_SAMPLE_BASE: prometheus.GaugeValue,
PERF_RAW_BASE: prometheus.GaugeValue,
PERF_LARGE_RAW_BASE: prometheus.GaugeValue,
}

func IsCounter(counterType uint32) bool {
return supportedCounterTypes[counterType] == prometheus.CounterValue
}

func IsBaseValue(counterType uint32) bool {
return counterType == PERF_SAMPLE_BASE || counterType == PERF_RAW_BASE || counterType == PERF_LARGE_RAW_BASE
}

func IsElapsedTime(counterType uint32) bool {
return counterType == PERF_ELAPSED_TIME
}

func GetPrometheusValueType(counterType uint32) (prometheus.ValueType, error) {
val, ok := supportedCounterTypes[counterType]
if !ok {
return 0, fmt.Errorf("counter type %#08x is not supported", counterType)
}
return val, nil
}
11 changes: 7 additions & 4 deletions perflib/perflib.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ type PerfObject struct {
Instances []*PerfInstance
CounterDefs []*PerfCounterDef

Frequency int64

rawData *perfObjectType
}

Expand Down Expand Up @@ -168,7 +170,7 @@ type PerfCounterDef struct {
// PERF_TIMER_100NS
IsNanosecondCounter bool

rawData *perfCounterDefinition
rawData *perfCounterDefinition
}

type PerfCounter struct {
Expand Down Expand Up @@ -317,6 +319,7 @@ func QueryPerformanceData(query string) ([]*PerfObject, error) {
HelpTextIndex: uint(obj.ObjectHelpTitleIndex),
Instances: instances,
CounterDefs: counterDefs,
Frequency: obj.PerfFreq,
rawData: obj,
}

Expand All @@ -333,8 +336,8 @@ func QueryPerformanceData(query string) ([]*PerfObject, error) {

CounterType: def.CounterType,

IsCounter: def.CounterType&0x400 > 0,
IsBaseValue: def.CounterType&0x20000000 > 0,
IsCounter: def.CounterType&0x400 > 0,
IsBaseValue: def.CounterType&0x20000000 > 0,
IsNanosecondCounter: def.CounterType&0x00100000 > 0,
}
}
Expand Down Expand Up @@ -436,4 +439,4 @@ func SortObjects(p []*PerfObject) {
return p[i].NameIndex < p[j].NameIndex
})

}
}

0 comments on commit 50015b8

Please sign in to comment.