From 902650fd37a68ac51c6a9ff9936de53971d139d1 Mon Sep 17 00:00:00 2001 From: pittu sharma Date: Sat, 25 Apr 2026 02:39:23 +0530 Subject: [PATCH] powersupply: standardize units to SI base units (#1817) This change implements SI base unit standardization for power supply metrics as requested in issue #1817. - Adds --collector.powersupply.use-new-names flag for dual-exposure. - Normalizes voltage (V), current (A), energy (J), charge (C), and power (W) to base units across Linux and Darwin. - Achieved platform parity between Linux and Darwin implementations. - Properly gatekeeps changes to maintain backward compatibility. Signed-off-by: pittu sharma --- collector/powersupplyclass.go | 3 ++ collector/powersupplyclass_darwin.go | 43 ++++++++++++++++++++++ collector/powersupplyclass_linux.go | 53 ++++++++++++++++++++++++++-- 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/collector/powersupplyclass.go b/collector/powersupplyclass.go index 33142617..416e8359 100644 --- a/collector/powersupplyclass.go +++ b/collector/powersupplyclass.go @@ -25,11 +25,13 @@ import ( var ( powerSupplyClassIgnoredPowerSupplies = kingpin.Flag("collector.powersupply.ignored-supplies", "Regexp of power supplies to ignore for powersupplyclass collector.").Default("^$").String() + powerSupplyClassUseNewNames = kingpin.Flag("collector.powersupply.use-new-names", "Use new metric names for powersupplyclass collector.").Bool() ) type powerSupplyClassCollector struct { subsystem string ignoredPattern *regexp.Regexp + useNewNames bool metricDescs map[string]*prometheus.Desc logger *slog.Logger } @@ -43,6 +45,7 @@ func NewPowerSupplyClassCollector(logger *slog.Logger) (Collector, error) { return &powerSupplyClassCollector{ subsystem: "power_supply", ignoredPattern: pattern, + useNewNames: *powerSupplyClassUseNewNames, metricDescs: map[string]*prometheus.Desc{}, logger: logger, }, nil diff --git a/collector/powersupplyclass_darwin.go b/collector/powersupplyclass_darwin.go index be3d778c..a6cb0190 100644 --- a/collector/powersupplyclass_darwin.go +++ b/collector/powersupplyclass_darwin.go @@ -183,6 +183,7 @@ import "C" import ( "fmt" "strconv" + "strings" "github.com/prometheus/client_golang/prometheus" ) @@ -214,6 +215,48 @@ func (c *powerSupplyClassCollector) Update(ch chan<- prometheus.Metric) error { ), prometheus.GaugeValue, *value, powerSupplyName, ) + + if c.useNewNames { + newValue := *value + newName := name + switch { + case name == "voltage_volt": + newName = "voltage_volts" + case name == "current_ampere": + newName = "current_amperes" + case strings.HasSuffix(name, "_capacity"): + newName = strings.TrimSuffix(name, "_capacity") + "_capacity_coulombs" + newValue *= 3.6 // 1 mAh = 3.6 Coulombs + case name == "charging": + } + + if newName != name { + ch <- prometheus.MustNewConstMetric( + prometheus.NewDesc( + prometheus.BuildFQName(namespace, c.subsystem, newName), + fmt.Sprintf("IOKit Power Source information field %s for .", newName), + []string{"power_supply"}, nil, + ), + prometheus.GaugeValue, newValue, powerSupplyName, + ) + } + } + } + + if c.useNewNames { + // Add capacity_ratio for consistency with Linux + data := getPowerSourceDescriptorMap(info) + if data["current_capacity"] != nil && data["max_capacity"] != nil && *data["max_capacity"] > 0 { + ratio := *data["current_capacity"] / *data["max_capacity"] + ch <- prometheus.MustNewConstMetric( + prometheus.NewDesc( + prometheus.BuildFQName(namespace, c.subsystem, "capacity_ratio"), + "IOKit Power Source capacity ratio for .", + []string{"power_supply"}, nil, + ), + prometheus.GaugeValue, ratio, powerSupplyName, + ) + } } pushEnumMetric( diff --git a/collector/powersupplyclass_linux.go b/collector/powersupplyclass_linux.go index b4fbf351..cc25a9d6 100644 --- a/collector/powersupplyclass_linux.go +++ b/collector/powersupplyclass_linux.go @@ -50,6 +50,24 @@ func (c *powerSupplyClassCollector) Update(ch chan<- prometheus.Metric) error { } { if value != nil { pushPowerSupplyMetric(ch, c.subsystem, name, float64(*value), powerSupply.Name, prometheus.GaugeValue) + if c.useNewNames { + newValue := float64(*value) + newName := name + switch name { + case "capacity": + newName = "capacity_ratio" + newValue /= 100 + case "capacity_alert_max": + newName = "capacity_alert_max_ratio" + newValue /= 100 + case "capacity_alert_min": + newName = "capacity_alert_min_ratio" + newValue /= 100 + } + if newName != name { + pushPowerSupplyMetric(ch, c.subsystem, newName, newValue, powerSupply.Name, prometheus.GaugeValue) + } + } } } @@ -88,6 +106,37 @@ func (c *powerSupplyClassCollector) Update(ch chan<- prometheus.Metric) error { } { if value != nil { pushPowerSupplyMetric(ch, c.subsystem, name, float64(*value)/1e6, powerSupply.Name, prometheus.GaugeValue) + if c.useNewNames { + newValue := float64(*value) / 1e6 + newName := name + switch { + case strings.Contains(name, "current"): + if !strings.HasSuffix(name, "_amperes") { + newName = strings.TrimSuffix(name, "_ampere") + "_amperes" + } + case strings.Contains(name, "voltage"): + if !strings.HasSuffix(name, "_volts") { + newName = strings.TrimSuffix(name, "_volt") + "_volts" + } + case strings.Contains(name, "energy"): + if !strings.HasSuffix(name, "_joules") { + newName = strings.TrimSuffix(name, "_watthour") + "_joules" + newValue *= 3600 + } + case strings.Contains(name, "charge"): + if !strings.HasSuffix(name, "_coulombs") { + newName = strings.TrimSuffix(name, "_ampere") + "_coulombs" + newValue *= 3600 + } + case strings.Contains(name, "power"): + if !strings.HasSuffix(name, "_watts") { + newName = strings.TrimSuffix(name, "_watt") + "_watts" + } + } + if newName != name { + pushPowerSupplyMetric(ch, c.subsystem, newName, newValue, powerSupply.Name, prometheus.GaugeValue) + } + } } } @@ -132,7 +181,7 @@ func (c *powerSupplyClassCollector) Update(ch chan<- prometheus.Metric) error { fieldDesc := prometheus.NewDesc( prometheus.BuildFQName(namespace, c.subsystem, "info"), - "info of /sys/class/power_supply/.", + "Power supply information from /sys/class/power_supply.", keys, nil, ) @@ -146,7 +195,7 @@ func (c *powerSupplyClassCollector) Update(ch chan<- prometheus.Metric) error { func pushPowerSupplyMetric(ch chan<- prometheus.Metric, subsystem string, name string, value float64, powerSupplyName string, valueType prometheus.ValueType) { fieldDesc := prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, name), - fmt.Sprintf("%s value of /sys/class/power_supply/.", name), + fmt.Sprintf("The %s value of /sys/class/power_supply/.", name), []string{"power_supply"}, nil, )