From 351fc778f91efd216a1e3ac938c17e3773a22d17 Mon Sep 17 00:00:00 2001 From: hys Date: Thu, 9 Apr 2026 21:59:43 +0800 Subject: [PATCH] feat: add cpu power related metric --- collector/lsblk_linux.go | 17 +++++++++---- collector/rapl_linux.go | 55 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/collector/lsblk_linux.go b/collector/lsblk_linux.go index 9e67c3f640..fd63dabcc7 100644 --- a/collector/lsblk_linux.go +++ b/collector/lsblk_linux.go @@ -8,6 +8,7 @@ import ( "fmt" "log/slog" "os" + "strconv" "github.com/alecthomas/kingpin/v2" "github.com/prometheus/client_golang/prometheus" @@ -53,10 +54,10 @@ type lsblkJSONRoot struct { type lsblkJSONDevice struct { Name string `json:"name"` - Size *string `json:"size"` + Size *int64 `json:"size"` Fstype *string `json:"fstype"` Mountpoint *string `json:"mountpoint"` - Fsused *string `json:"fsused"` + Fsused *int64 `json:"fsused"` FsusePct *string `json:"fsuse%"` Children []lsblkJSONDevice `json:"children"` } @@ -67,6 +68,12 @@ func lsblkStringPtr(s *string) string { } return *s } +func int64PtrToString(i *int64) string { + if i == nil { + return "" + } + return strconv.FormatInt(*i, 10) +} // lsblkMountpointForLabels maps mount paths under --path.rootfs to the host view (same as filesystem collector). func lsblkMountpointForLabels(mp *string) string { @@ -83,8 +90,8 @@ func (c *lsblkCollector) emitRecursive(ch chan<- prometheus.Metric, devices []ls parent, lsblkStringPtr(d.Fstype), lsblkMountpointForLabels(d.Mountpoint), - lsblkStringPtr(d.Size), - lsblkStringPtr(d.Fsused), + int64PtrToString(d.Size), + int64PtrToString(d.Fsused), lsblkStringPtr(d.FsusePct), ) if len(d.Children) > 0 { @@ -103,7 +110,7 @@ func (c *lsblkCollector) Update(ch chan<- prometheus.Metric) error { return ErrNoData } - args := []string{"--json", "-o", "NAME,SIZE,FSTYPE,MOUNTPOINT,FSUSED,FSUSE%"} + args := []string{"--json", "-b", "-o", "NAME,SIZE,FSTYPE,MOUNTPOINT,FSUSED,FSUSE%"} cmd := execCommand(path, args...) out, err := CombinedOutputTimeout(cmd, *lsblkTimeout) if err != nil { diff --git a/collector/rapl_linux.go b/collector/rapl_linux.go index 25d78f295c..4cfe786350 100644 --- a/collector/rapl_linux.go +++ b/collector/rapl_linux.go @@ -19,13 +19,13 @@ package collector import ( "errors" "fmt" - "log/slog" - "os" - "strconv" - "github.com/alecthomas/kingpin/v2" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/procfs/sysfs" + "log/slog" + "os" + "path/filepath" + "strconv" ) const raplCollectorSubsystem = "rapl" @@ -35,6 +35,10 @@ type raplCollector struct { logger *slog.Logger joulesMetricDesc *prometheus.Desc + + constraint0MaxPowerUWDesc *prometheus.Desc + constraint0PowerLimitUWDesc *prometheus.Desc + constraint1PowerLimitUWDesc *prometheus.Desc } func init() { @@ -59,10 +63,30 @@ func NewRaplCollector(logger *slog.Logger) (Collector, error) { []string{"index", "path", "rapl_zone"}, nil, ) + constraintLabels := []string{"index", "path"} + if *raplZoneLabel { + constraintLabels = []string{"index", "path", "rapl_zone"} + } + collector := raplCollector{ fs: fs, logger: logger, joulesMetricDesc: joulesMetricDesc, + constraint0MaxPowerUWDesc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, raplCollectorSubsystem, "constraint_0_max_power_uw"), + "RAPL constraint_0 maximum power in microwatts (powercap constraint_0_max_power_uw).", + constraintLabels, nil, + ), + constraint0PowerLimitUWDesc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, raplCollectorSubsystem, "constraint_0_power_limit_uw"), + "RAPL constraint_0 power limit in microwatts (powercap constraint_0_power_limit_uw).", + constraintLabels, nil, + ), + constraint1PowerLimitUWDesc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, raplCollectorSubsystem, "constraint_1_power_limit_uw"), + "RAPL constraint_1 power limit in microwatts (powercap constraint_1_power_limit_uw).", + constraintLabels, nil, + ), } return &collector, nil } @@ -100,10 +124,33 @@ func (c *raplCollector) Update(ch chan<- prometheus.Metric) error { } else { ch <- c.joulesMetric(rz, joules) } + + c.emitRAPLConstraintUW(ch, c.constraint0MaxPowerUWDesc, rz, "constraint_0_max_power_uw") + c.emitRAPLConstraintUW(ch, c.constraint0PowerLimitUWDesc, rz, "constraint_0_power_limit_uw") + c.emitRAPLConstraintUW(ch, c.constraint1PowerLimitUWDesc, rz, "constraint_1_power_limit_uw") } return nil } +func (c *raplCollector) emitRAPLConstraintUW(ch chan<- prometheus.Metric, desc *prometheus.Desc, rz sysfs.RaplZone, fileName string) { + p := filepath.Join(rz.Path, fileName) + v, err := readUintFromFile(p) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return + } + c.logger.Debug("skipping RAPL constraint file", "path", p, "err", err) + return + } + + index := strconv.Itoa(rz.Index) + if *raplZoneLabel { + ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(v), index, rz.Path, rz.Name) + return + } + ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(v), index, rz.Path) +} + func (c *raplCollector) joulesMetric(z sysfs.RaplZone, v float64) prometheus.Metric { index := strconv.Itoa(z.Index) descriptor := prometheus.NewDesc(