diff --git a/docs/collector.performancecounter.md b/docs/collector.performancecounter.md index c2ff3fb2..977fcb88 100644 --- a/docs/collector.performancecounter.md +++ b/docs/collector.performancecounter.md @@ -65,6 +65,7 @@ YAML: - name: "Cache Faults/sec" metric: windows_performancecounter_memory_cache_faults_per_second # optional type: "gauge" # optional + multiplier: 1 # optional ``` @@ -106,7 +107,8 @@ YAML: { "name": "Cache Faults/sec", "metric": "windows_performancecounter_memory_cache_faults_per_second", - "type": "gauge" + "type": "gauge", + "multiplier": 1 } ] } @@ -195,6 +197,21 @@ If not specified, the windows_exporter will try to determine the type based on t This key is optional. +##### multiplier + +A factor that the value is multiplied by before it is exported, to convert it to +the base unit the Prometheus [naming conventions](https://prometheus.io/docs/practices/naming/#base-units) +ask for. For example, `0.001` converts milliseconds to seconds, `0.01` percent to +a ratio and `3600` hours to seconds. It must be a positive number, so counters +keep counting up. If the multiplier is the reciprocal of an integer, like `0.001`, +the value is divided by that integer instead, which avoids rounding artifacts +such as `0.009000000000000001`. If not specified, the value is exported as is. + +With the `raw` type, `PERF_100NSEC_TIMER_INV` counters such as `% Processor Time` are not converted to seconds +and arrive in 100 ns ticks; a multiplier of `1e-7` converts them. + +This key is optional. + ##### labels Labels is a map of key-value pairs that will be added as labels to the metric. diff --git a/internal/collector/performancecounter/performancecounter.go b/internal/collector/performancecounter/performancecounter.go index 1aa8c473..c3d47b91 100644 --- a/internal/collector/performancecounter/performancecounter.go +++ b/internal/collector/performancecounter/performancecounter.go @@ -31,6 +31,7 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/prometheus-community/windows_exporter/internal/multiplier" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" @@ -175,6 +176,15 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { continue } + m, err := multiplier.New(counter.Multiplier) + if err != nil { + errs = append(errs, fmt.Errorf("object %s: counter %s: %w", object.Name, counter.Name, err)) + + continue + } + + object.Counters[j].multiplier = m + if _, ok := object.valueIndex[counter.Name]; ok { errs = append(errs, fmt.Errorf("object %s: counter name %s is duplicated", object.Name, counter.Name)) @@ -328,7 +338,7 @@ func (c *Collector) collectObject(ch chan<- prometheus.Metric, perfDataObject Ob labels, ), metricTypes[j], - collectedCounterValue, + counter.multiplier.Apply(collectedCounterValue), ) } } diff --git a/internal/collector/performancecounter/performancecounter_test_test.go b/internal/collector/performancecounter/performancecounter_test_test.go index ec1b54a4..11f01034 100644 --- a/internal/collector/performancecounter/performancecounter_test_test.go +++ b/internal/collector/performancecounter/performancecounter_test_test.go @@ -440,3 +440,57 @@ func TestCollectorMetricTypePerCounter(t *testing.T) { require.Equal(t, dto.MetricType_COUNTER, types["windows_performancecounter_process_io_read_bytes_sec"]) } } + +// TestCollectorMultiplier checks that the multiplier scales the exported value. +// Commit Limit only changes when a page file is resized, so both objects read +// the same value. +func TestCollectorMultiplier(t *testing.T) { + t.Parallel() + + c := performancecounter.New(&performancecounter.Config{ + Objects: []performancecounter.Object{ + { + Name: "bytes", + Object: "Memory", + Counters: []performancecounter.Counter{{Name: "Commit Limit", Metric: "windows_test_commit_limit_bytes"}}, + }, + { + Name: "kibibytes", + Object: "Memory", + Counters: []performancecounter.Counter{{Name: "Commit Limit", Metric: "windows_test_commit_limit_kibibytes", Multiplier: new(1.0 / 1024)}}, + }, + }, + }) + require.NoError(t, c.Build(slog.New(slog.DiscardHandler), nil)) + t.Cleanup(func() { require.NoError(t, c.Close()) }) + + registry := prometheus.NewRegistry() + registry.MustRegister(collectorAdapter{*c}) + + families, err := registry.Gather() + require.NoError(t, err) + + values := make(map[string]float64, len(families)) + for _, family := range families { + values[family.GetName()] = family.GetMetric()[0].GetGauge().GetValue() + } + + require.Positive(t, values["windows_test_commit_limit_bytes"]) + require.Equal(t, values["windows_test_commit_limit_bytes"]/1024, values["windows_test_commit_limit_kibibytes"]) //nolint:testifylint // The result must be exact. +} + +func TestBuildInvalidMultiplier(t *testing.T) { + t.Parallel() + + c := performancecounter.New(&performancecounter.Config{ + Objects: []performancecounter.Object{{ + Name: "memory", + Object: "Memory", + Counters: []performancecounter.Counter{{Name: "Commit Limit", Multiplier: new(-1.0)}}, + }}, + }) + + t.Cleanup(func() { require.NoError(t, c.Close()) }) + + require.ErrorContains(t, c.Build(slog.New(slog.DiscardHandler), nil), "object memory: counter Commit Limit: multiplier must be a positive finite number, got -1") +} diff --git a/internal/collector/performancecounter/types.go b/internal/collector/performancecounter/types.go index 362ceb6b..a5b45a77 100644 --- a/internal/collector/performancecounter/types.go +++ b/internal/collector/performancecounter/types.go @@ -19,6 +19,7 @@ package performancecounter import ( "github.com/prometheus-community/windows_exporter/internal/config/yamlstring" + "github.com/prometheus-community/windows_exporter/internal/multiplier" "github.com/prometheus-community/windows_exporter/internal/pdh" "go.yaml.in/yaml/v3" ) @@ -37,10 +38,14 @@ type Object struct { } type Counter struct { - Name string `json:"name" yaml:"name"` - Type string `json:"type" yaml:"type"` - Metric string `json:"metric" yaml:"metric"` - Labels map[string]string `json:"labels" yaml:"labels"` + Name string `json:"name" yaml:"name"` + Type string `json:"type" yaml:"type"` + Metric string `json:"metric" yaml:"metric"` + Labels map[string]string `json:"labels" yaml:"labels"` + Multiplier *float64 `json:"multiplier" yaml:"multiplier"` + + // Resolved at Build time. + multiplier multiplier.Multiplier } // https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/54691ebe11bb9ec32b4e35cd31fcb94a352de134/receiver/windowsperfcountersreceiver/README.md?plain=1#L150