From 5f36a81613a33b8ada34db48d0ec7d3e8024c546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Fri, 13 Sep 2024 23:10:57 +0200 Subject: [PATCH] cpu: Fetch performance counter via PDH.dll via feature toggle. (off by default) (#1627) --- exporter.go | 4 + pkg/collector/cpu/const.go | 28 ++++++ pkg/collector/cpu/cpu.go | 193 ++++++++++++++++++++++++++++++++++++- pkg/utils/collector.go | 9 ++ 4 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 pkg/collector/cpu/const.go diff --git a/exporter.go b/exporter.go index 7d0c393e..95d468e7 100644 --- a/exporter.go +++ b/exporter.go @@ -217,6 +217,10 @@ func run() int { logger.Info("Enabled collectors: " + strings.Join(enabledCollectorList, ", ")) + if utils.PDHEnabled() { + logger.Info("Using performance data helper from PHD.dll for performance counter collection. This is in experimental state.") + } + mux := http.NewServeMux() mux.Handle("GET /health", httphandler.NewHealthHandler()) mux.Handle("GET /version", httphandler.NewVersionHandler()) diff --git a/pkg/collector/cpu/const.go b/pkg/collector/cpu/const.go new file mode 100644 index 00000000..76916364 --- /dev/null +++ b/pkg/collector/cpu/const.go @@ -0,0 +1,28 @@ +package cpu + +// Processor performance counters. +const ( + C1TimeSeconds = "% C1 Time" + C2TimeSeconds = "% C2 Time" + C3TimeSeconds = "% C3 Time" + C1TransitionsTotal = "C1 Transitions/sec" + C2TransitionsTotal = "C2 Transitions/sec" + C3TransitionsTotal = "C3 Transitions/sec" + ClockInterruptsTotal = "Clock Interrupts/sec" + DPCsQueuedTotal = "DPCs Queued/sec" + DPCTimeSeconds = "% DPC Time" + IdleBreakEventsTotal = "Idle Break Events/sec" + IdleTimeSeconds = "% Idle Time" + InterruptsTotal = "Interrupts/sec" + InterruptTimeSeconds = "% Interrupt Time" + ParkingStatus = "Parking Status" + PerformanceLimitPercent = "% Performance Limit" + PriorityTimeSeconds = "% Priority Time" + PrivilegedTimeSeconds = "% Privileged Time" + PrivilegedUtilitySeconds = "% Privileged Utility" + ProcessorFrequencyMHz = "Processor Frequency" + ProcessorPerformance = "% Processor Performance" + ProcessorTimeSeconds = "% Processor Time" + ProcessorUtilityRate = "% Processor Utility" + UserTimeSeconds = "% User Time" +) diff --git a/pkg/collector/cpu/cpu.go b/pkg/collector/cpu/cpu.go index f88d507c..cacd6593 100644 --- a/pkg/collector/cpu/cpu.go +++ b/pkg/collector/cpu/cpu.go @@ -3,12 +3,15 @@ package cpu import ( + "fmt" "log/slog" "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/pkg/perfdata" "github.com/prometheus-community/windows_exporter/pkg/perflib" "github.com/prometheus-community/windows_exporter/pkg/types" + "github.com/prometheus-community/windows_exporter/pkg/utils" "github.com/prometheus/client_golang/prometheus" "github.com/yusufpapurcu/wmi" ) @@ -22,6 +25,8 @@ var ConfigDefaults = Config{} type Collector struct { config Config + perfDataCollector *perfdata.Collector + logicalProcessors *prometheus.Desc cStateSecondsTotal *prometheus.Desc timeTotal *prometheus.Desc @@ -59,6 +64,10 @@ func (c *Collector) GetName() string { } func (c *Collector) GetPerfCounter(_ *slog.Logger) ([]string, error) { + if utils.PDHEnabled() { + return []string{}, nil + } + return []string{"Processor Information"}, nil } @@ -67,6 +76,41 @@ func (c *Collector) Close(_ *slog.Logger) error { } func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { + if utils.PDHEnabled() { + counters := []string{ + C1TimeSeconds, + C2TimeSeconds, + C3TimeSeconds, + C1TransitionsTotal, + C2TransitionsTotal, + C3TransitionsTotal, + ClockInterruptsTotal, + DPCsQueuedTotal, + DPCTimeSeconds, + IdleBreakEventsTotal, + IdleTimeSeconds, + InterruptsTotal, + InterruptTimeSeconds, + ParkingStatus, + PerformanceLimitPercent, + PriorityTimeSeconds, + PrivilegedTimeSeconds, + PrivilegedUtilitySeconds, + ProcessorFrequencyMHz, + ProcessorPerformance, + ProcessorTimeSeconds, + ProcessorUtilityRate, + UserTimeSeconds, + } + + var err error + + c.perfDataCollector, err = perfdata.NewCollector("Processor Information", []string{"*"}, counters) + if err != nil { + return fmt.Errorf("failed to create Processor Information collector: %w", err) + } + } + c.logicalProcessors = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "logical_processor"), "Total number of logical processors", @@ -184,7 +228,11 @@ func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { func (c *Collector) Collect(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error { logger = logger.With(slog.String("collector", Name)) - return c.CollectFull(ctx, logger, ch) + if utils.PDHEnabled() { + return c.collectPDH(ch) + } + + return c.collectFull(ctx, logger, ch) } type perflibProcessorInformation struct { @@ -216,8 +264,7 @@ type perflibProcessorInformation struct { UserTimeSeconds float64 `perflib:"% User Time"` } -func (c *Collector) CollectFull(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error { - logger = logger.With(slog.String("collector", Name)) +func (c *Collector) collectFull(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error { data := make([]perflibProcessorInformation, 0) err := perflib.UnmarshalObject(ctx.PerfObjects["Processor Information"], &data, logger) @@ -364,3 +411,143 @@ func (c *Collector) CollectFull(ctx *types.ScrapeContext, logger *slog.Logger, c return nil } + +func (c *Collector) collectPDH(ch chan<- prometheus.Metric) error { + data, err := c.perfDataCollector.Collect() + if err != nil { + return fmt.Errorf("failed to collect Processor Information metrics: %w", err) + } + + var coreCount float64 + + for core, coreData := range data { + coreCount++ + + ch <- prometheus.MustNewConstMetric( + c.cStateSecondsTotal, + prometheus.CounterValue, + coreData[C1TimeSeconds].FirstValue, + core, "c1", + ) + ch <- prometheus.MustNewConstMetric( + c.cStateSecondsTotal, + prometheus.CounterValue, + coreData[C2TimeSeconds].FirstValue, + core, "c2", + ) + ch <- prometheus.MustNewConstMetric( + c.cStateSecondsTotal, + prometheus.CounterValue, + coreData[C3TimeSeconds].FirstValue, + core, "c3", + ) + + ch <- prometheus.MustNewConstMetric( + c.timeTotal, + prometheus.CounterValue, + coreData[IdleTimeSeconds].FirstValue, + core, "idle", + ) + ch <- prometheus.MustNewConstMetric( + c.timeTotal, + prometheus.CounterValue, + coreData[InterruptTimeSeconds].FirstValue, + core, "interrupt", + ) + ch <- prometheus.MustNewConstMetric( + c.timeTotal, + prometheus.CounterValue, + coreData[DPCTimeSeconds].FirstValue, + core, "dpc", + ) + ch <- prometheus.MustNewConstMetric( + c.timeTotal, + prometheus.CounterValue, + coreData[PrivilegedTimeSeconds].FirstValue, + core, "privileged", + ) + ch <- prometheus.MustNewConstMetric( + c.timeTotal, + prometheus.CounterValue, + coreData[UserTimeSeconds].FirstValue, + core, "user", + ) + + ch <- prometheus.MustNewConstMetric( + c.interruptsTotal, + prometheus.CounterValue, + coreData[InterruptsTotal].FirstValue, + core, + ) + ch <- prometheus.MustNewConstMetric( + c.dpcsTotal, + prometheus.CounterValue, + coreData[DPCsQueuedTotal].FirstValue, + core, + ) + ch <- prometheus.MustNewConstMetric( + c.clockInterruptsTotal, + prometheus.CounterValue, + coreData[ClockInterruptsTotal].FirstValue, + core, + ) + ch <- prometheus.MustNewConstMetric( + c.idleBreakEventsTotal, + prometheus.CounterValue, + coreData[IdleBreakEventsTotal].FirstValue, + core, + ) + + ch <- prometheus.MustNewConstMetric( + c.parkingStatus, + prometheus.GaugeValue, + coreData[ParkingStatus].FirstValue, + core, + ) + + ch <- prometheus.MustNewConstMetric( + c.processorFrequencyMHz, + prometheus.GaugeValue, + coreData[ProcessorFrequencyMHz].FirstValue, + core, + ) + ch <- prometheus.MustNewConstMetric( + c.processorPerformance, + prometheus.CounterValue, + coreData[ProcessorPerformance].FirstValue, + core, + ) + ch <- prometheus.MustNewConstMetric( + c.processorMPerf, + prometheus.CounterValue, + coreData[ProcessorPerformance].SecondValue, + core, + ) + ch <- prometheus.MustNewConstMetric( + c.processorRTC, + prometheus.CounterValue, + coreData[ProcessorUtilityRate].SecondValue, + core, + ) + ch <- prometheus.MustNewConstMetric( + c.processorUtility, + prometheus.CounterValue, + coreData[ProcessorUtilityRate].FirstValue, + core, + ) + ch <- prometheus.MustNewConstMetric( + c.processorPrivilegedUtility, + prometheus.CounterValue, + coreData[PrivilegedUtilitySeconds].FirstValue, + core, + ) + } + + ch <- prometheus.MustNewConstMetric( + c.logicalProcessors, + prometheus.GaugeValue, + coreCount, + ) + + return nil +} diff --git a/pkg/utils/collector.go b/pkg/utils/collector.go index d62ebec6..637c0f2a 100644 --- a/pkg/utils/collector.go +++ b/pkg/utils/collector.go @@ -3,6 +3,7 @@ package utils import ( + "os" "strings" "github.com/prometheus-community/windows_exporter/pkg/types" @@ -26,3 +27,11 @@ func ExpandEnabledCollectors(enabled string) []string { return result } + +func PDHEnabled() bool { + if v, ok := os.LookupEnv("WINDOWS_EXPORTER_PERF_COUNTERS_ENGINE"); ok && v == "pdh" { + return true + } + + return false +}