wmi: add a multiplier option to convert values to base units (#2772)

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
Jan-Otto Kröpke
2026-10-16 21:33:35 +02:00
committed by GitHub
co-authored by Claude
parent f6e8e67a00
commit db1ad936dc
5 changed files with 62 additions and 7 deletions
+14
View File
@@ -79,6 +79,7 @@ YAML:
metric: windows_wmi_logical_disk_free_bytes # optional metric: windows_wmi_logical_disk_free_bytes # optional
help: Free space on the logical disk in bytes. # optional help: Free space on the logical disk in bytes. # optional
type: gauge # optional type: gauge # optional
multiplier: 1 # optional
labels: # optional labels: # optional
source: wmi source: wmi
``` ```
@@ -101,6 +102,7 @@ JSON:
"metric": "windows_wmi_logical_disk_free_bytes", "metric": "windows_wmi_logical_disk_free_bytes",
"help": "Free space on the logical disk in bytes.", "help": "Free space on the logical disk in bytes.",
"type": "gauge", "type": "gauge",
"multiplier": 1,
"labels": { "source": "wmi" } "labels": { "source": "wmi" }
} }
] ]
@@ -219,6 +221,18 @@ defaults to `gauge`.
This key is optional. 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.
This key is optional.
##### labels ##### labels
Labels is a map of key-value pairs that will be added as constant labels to the Labels is a map of key-value pairs that will be added as constant labels to the
+8 -5
View File
@@ -20,6 +20,7 @@ package wmi
import ( import (
"github.com/prometheus-community/windows_exporter/internal/config/yamlstring" "github.com/prometheus-community/windows_exporter/internal/config/yamlstring"
"github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/multiplier"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"go.yaml.in/yaml/v3" "go.yaml.in/yaml/v3"
) )
@@ -48,16 +49,18 @@ type LabelProperty struct {
} }
type Property struct { type Property struct {
Name string `json:"name" yaml:"name"` Name string `json:"name" yaml:"name"`
Metric string `json:"metric" yaml:"metric"` Metric string `json:"metric" yaml:"metric"`
Help string `json:"help" yaml:"help"` Help string `json:"help" yaml:"help"`
Type string `json:"type" yaml:"type"` Type string `json:"type" yaml:"type"`
Labels map[string]string `json:"labels" yaml:"labels"` Labels map[string]string `json:"labels" yaml:"labels"`
Multiplier *float64 `json:"multiplier" yaml:"multiplier"`
// Resolved at Build time. // Resolved at Build time.
elementName mi.ElementName elementName mi.ElementName
desc *prometheus.Desc desc *prometheus.Desc
metricType prometheus.ValueType metricType prometheus.ValueType
multiplier multiplier.Multiplier
} }
// UnmarshalYAML decodes the wmi block of the configuration file, where the // UnmarshalYAML decodes the wmi block of the configuration file, where the
+8 -1
View File
@@ -31,6 +31,7 @@ import (
"github.com/alecthomas/kingpin/v2" "github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/labelcache" "github.com/prometheus-community/windows_exporter/internal/labelcache"
"github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/multiplier"
"github.com/prometheus-community/windows_exporter/internal/scrapecache" "github.com/prometheus-community/windows_exporter/internal/scrapecache"
"github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@@ -346,6 +347,12 @@ func (c *Collector) Build(logger *slog.Logger, miSession *mi.Session) error {
continue continue
} }
if property.multiplier, err = multiplier.New(property.Multiplier); err != nil {
errs = append(errs, fmt.Errorf("query %s: property %q: %w", query.Name, property.Name, err))
continue
}
if label, ok := firstCommonLabel(query.labelNames, property.Labels); ok { if label, ok := firstCommonLabel(query.labelNames, property.Labels); ok {
errs = append(errs, fmt.Errorf("query %s: property %q: constant label %q is already set by a label property", query.Name, property.Name, label)) errs = append(errs, fmt.Errorf("query %s: property %q: constant label %q is already set by a label property", query.Name, property.Name, label))
@@ -588,7 +595,7 @@ func (c *Collector) collectQuery(ch chan<- prometheus.Metric, scrape *labelcache
ch <- scrape.MustNewConstMetric( ch <- scrape.MustNewConstMetric(
property.desc, property.desc,
property.metricType, property.metricType,
value, property.multiplier.Apply(value),
labelValues..., labelValues...,
) )
} }
+22 -1
View File
@@ -167,6 +167,21 @@ func TestCollectorMetrics(t *testing.T) {
Class: "Win32_OperatingSystem", Class: "Win32_OperatingSystem",
Properties: []wmi.Property{{Name: "Primary", Metric: "windows_wmi_test_shared", Labels: map[string]string{"query": "b"}}}, Properties: []wmi.Property{{Name: "Primary", Metric: "windows_wmi_test_shared", Labels: map[string]string{"query": "b"}}},
}, },
{
// The multiplier converts units, e.g. milliseconds to seconds.
Name: "scaled_down",
Class: "Win32_OperatingSystem",
Properties: []wmi.Property{
{Name: "Primary", Metric: "windows_wmi_test_scaled", Labels: map[string]string{"query": "down"}, Multiplier: new(0.001)},
},
},
{
Name: "scaled_up",
Class: "Win32_OperatingSystem",
Properties: []wmi.Property{
{Name: "Primary", Metric: "windows_wmi_test_scaled", Labels: map[string]string{"query": "up"}, Multiplier: new(3600.0)},
},
},
{ {
Name: "missing_class", Name: "missing_class",
Class: "Win32_WindowsExporterDoesNotExist", Class: "Win32_WindowsExporterDoesNotExist",
@@ -205,6 +220,8 @@ windows_wmi_query_success{query="kernel"} 1
windows_wmi_query_success{query="missing_class"} 0 windows_wmi_query_success{query="missing_class"} 0
windows_wmi_query_success{query="os"} 1 windows_wmi_query_success{query="os"} 1
windows_wmi_query_success{query="partial"} 0 windows_wmi_query_success{query="partial"} 0
windows_wmi_query_success{query="scaled_down"} 1
windows_wmi_query_success{query="scaled_up"} 1
windows_wmi_query_success{query="shared_a"} 1 windows_wmi_query_success{query="shared_a"} 1
windows_wmi_query_success{query="shared_b"} 1 windows_wmi_query_success{query="shared_b"} 1
windows_wmi_query_success{query="unsupported_label"} 0 windows_wmi_query_success{query="unsupported_label"} 0
@@ -212,6 +229,10 @@ windows_wmi_query_success{query="unsupported_label"} 0
# TYPE windows_wmi_test_parent_pid counter # TYPE windows_wmi_test_parent_pid counter
windows_wmi_test_parent_pid{foo="bar",handle="0",path="",pid="0",process="System Idle Process"} 0 windows_wmi_test_parent_pid{foo="bar",handle="0",path="",pid="0",process="System Idle Process"} 0
windows_wmi_test_parent_pid{foo="bar",handle="4",path="",pid="4",process="System"} 0 windows_wmi_test_parent_pid{foo="bar",handle="4",path="",pid="4",process="System"} 0
# HELP windows_wmi_test_scaled windows_exporter: custom WMI metric
# TYPE windows_wmi_test_scaled gauge
windows_wmi_test_scaled{query="down"} 0.001
windows_wmi_test_scaled{query="up"} 3600
# HELP windows_wmi_test_shared windows_exporter: custom WMI metric # HELP windows_wmi_test_shared windows_exporter: custom WMI metric
# TYPE windows_wmi_test_shared gauge # TYPE windows_wmi_test_shared gauge
windows_wmi_test_shared{query="a"} 1 windows_wmi_test_shared{query="a"} 1
@@ -232,7 +253,7 @@ windows_wmi_test_shared{query="b"} 1
require.Equal(t, expected, strings.Join(lines, "\n")) require.Equal(t, expected, strings.Join(lines, "\n"))
for _, name := range []string{"kernel", "missing_class", "os", "partial", "shared_a", "shared_b", "unsupported_label"} { for _, name := range []string{"kernel", "missing_class", "os", "partial", "scaled_down", "scaled_up", "shared_a", "shared_b", "unsupported_label"} {
require.Regexp(t, `(?m)^windows_wmi_query_duration_seconds\{query="`+name+`"\} \d`, got) require.Regexp(t, `(?m)^windows_wmi_query_duration_seconds\{query="`+name+`"\} \d`, got)
} }
} }
+10
View File
@@ -190,6 +190,16 @@ func TestCollectorBuildErrors(t *testing.T) {
queries: valid(func(q *wmi.Query) { q.Properties[0].Type = "histogram" }), queries: valid(func(q *wmi.Query) { q.Properties[0].Type = "histogram" }),
err: `invalid type "histogram"`, err: `invalid type "histogram"`,
}, },
{
name: "zero multiplier",
queries: valid(func(q *wmi.Query) { q.Properties[0].Multiplier = new(0.0) }),
err: `property "FreeSpace": multiplier must be a positive finite number, got 0`,
},
{
name: "negative multiplier",
queries: valid(func(q *wmi.Query) { q.Properties[0].Multiplier = new(-1.0) }),
err: `multiplier must be a positive finite number, got -1`,
},
{ {
name: "invalid label property name", name: "invalid label property name",
queries: valid(func(q *wmi.Query) { q.LabelProperties = []wmi.LabelProperty{{Name: ""}} }), queries: valid(func(q *wmi.Query) { q.LabelProperties = []wmi.LabelProperty{{Name: ""}} }),