diff --git a/collector/cpu_linux.go b/collector/cpu_linux.go index f7b0c3ef1f..3b65a5734a 100644 --- a/collector/cpu_linux.go +++ b/collector/cpu_linux.go @@ -219,10 +219,13 @@ func (c *cpuCollector) updateInfo(ch chan<- prometheus.Metric) error { cpu.CacheSize) } + // The cpufreq collector exports the same metric name with a different + // help string and label set. Only export the /proc/cpuinfo frequency + // when cpufreq is disabled or uses its own metric prefix. cpuFreqEnabled, ok := collectorState["cpufreq"] if !ok || cpuFreqEnabled == nil { c.logger.Debug("cpufreq key missing or nil value in collectorState map") - } else if *cpuFreqEnabled { + } else if !*cpuFreqEnabled || *useCPUFreqPrefix { for _, cpu := range info { ch <- prometheus.MustNewConstMetric(c.cpuFrequencyHz, prometheus.GaugeValue, diff --git a/collector/cpufreq_common.go b/collector/cpufreq_common.go index d9d5108bb4..64cbe7e4f0 100644 --- a/collector/cpufreq_common.go +++ b/collector/cpufreq_common.go @@ -16,48 +16,74 @@ package collector import ( + "github.com/alecthomas/kingpin/v2" "github.com/prometheus/client_golang/prometheus" ) -var ( - cpuFreqHertzDesc = prometheus.NewDesc( - prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "frequency_hertz"), - "Current CPU thread frequency in hertz.", - []string{"cpu"}, nil, - ) - cpuFreqAvgDesc = prometheus.NewDesc( - prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "frequency_avg_hertz"), - "Average CPU thread frequency in hertz.", - []string{"cpu"}, nil, - ) - cpuFreqMinDesc = prometheus.NewDesc( - prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "frequency_min_hertz"), - "Minimum CPU thread frequency in hertz.", - []string{"cpu"}, nil, - ) - cpuFreqMaxDesc = prometheus.NewDesc( - prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "frequency_max_hertz"), - "Maximum CPU thread frequency in hertz.", - []string{"cpu"}, nil, - ) - cpuFreqScalingFreqDesc = prometheus.NewDesc( - prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "scaling_frequency_hertz"), - "Current scaled CPU thread frequency in hertz.", - []string{"cpu"}, nil, - ) - cpuFreqScalingFreqMinDesc = prometheus.NewDesc( - prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "scaling_frequency_min_hertz"), - "Minimum scaled CPU thread frequency in hertz.", - []string{"cpu"}, nil, - ) - cpuFreqScalingFreqMaxDesc = prometheus.NewDesc( - prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "scaling_frequency_max_hertz"), - "Maximum scaled CPU thread frequency in hertz.", - []string{"cpu"}, nil, - ) - cpuFreqScalingGovernorDesc = prometheus.NewDesc( - prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "scaling_governor"), - "Current enabled CPU frequency governor.", - []string{"cpu", "governor"}, nil, - ) -) +const cpuFreqCollectorSubsystem = "cpufreq" + +var useCPUFreqPrefix = kingpin.Flag("collector.cpufreq.enable-cpufreq-prefix", + "Expose cpufreq metrics with the node_cpufreq_ prefix instead of node_cpu_. This avoids a metric name collision with the cpu collector and will be the default behavior in 2.x.").Bool() + +type cpuFreqDescs struct { + hertz *prometheus.Desc + avgHertz *prometheus.Desc + minHertz *prometheus.Desc + maxHertz *prometheus.Desc + scalingHertz *prometheus.Desc + scalingMinHertz *prometheus.Desc + scalingMaxHertz *prometheus.Desc + scalingGovernor *prometheus.Desc +} + +// newCPUFreqDescs builds the cpufreq metric descriptions. It must run after +// flag parsing because the metric prefix depends on +// --collector.cpufreq.enable-cpufreq-prefix. +func newCPUFreqDescs() cpuFreqDescs { + subsystem := cpuCollectorSubsystem + if *useCPUFreqPrefix { + subsystem = cpuFreqCollectorSubsystem + } + return cpuFreqDescs{ + hertz: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "frequency_hertz"), + "Current CPU thread frequency in hertz.", + []string{"cpu"}, nil, + ), + avgHertz: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "frequency_avg_hertz"), + "Average CPU thread frequency in hertz.", + []string{"cpu"}, nil, + ), + minHertz: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "frequency_min_hertz"), + "Minimum CPU thread frequency in hertz.", + []string{"cpu"}, nil, + ), + maxHertz: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "frequency_max_hertz"), + "Maximum CPU thread frequency in hertz.", + []string{"cpu"}, nil, + ), + scalingHertz: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "scaling_frequency_hertz"), + "Current scaled CPU thread frequency in hertz.", + []string{"cpu"}, nil, + ), + scalingMinHertz: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "scaling_frequency_min_hertz"), + "Minimum scaled CPU thread frequency in hertz.", + []string{"cpu"}, nil, + ), + scalingMaxHertz: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "scaling_frequency_max_hertz"), + "Maximum scaled CPU thread frequency in hertz.", + []string{"cpu"}, nil, + ), + scalingGovernor: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "scaling_governor"), + "Current enabled CPU frequency governor.", + []string{"cpu", "governor"}, nil, + ), + } +} diff --git a/collector/cpufreq_linux.go b/collector/cpufreq_linux.go index fbefdcf899..3c759d55e5 100644 --- a/collector/cpufreq_linux.go +++ b/collector/cpufreq_linux.go @@ -26,6 +26,7 @@ import ( type cpuFreqCollector struct { fs sysfs.FS + descs cpuFreqDescs logger *slog.Logger } @@ -42,6 +43,7 @@ func NewCPUFreqCollector(logger *slog.Logger) (Collector, error) { return &cpuFreqCollector{ fs: fs, + descs: newCPUFreqDescs(), logger: logger, }, nil } @@ -58,7 +60,7 @@ func (c *cpuFreqCollector) Update(ch chan<- prometheus.Metric) error { for _, stats := range cpuFreqs { if stats.CpuinfoCurrentFrequency != nil { ch <- prometheus.MustNewConstMetric( - cpuFreqHertzDesc, + c.descs.hertz, prometheus.GaugeValue, float64(*stats.CpuinfoCurrentFrequency)*1000.0, stats.Name, @@ -66,7 +68,7 @@ func (c *cpuFreqCollector) Update(ch chan<- prometheus.Metric) error { } if stats.CpuinfoAverageFrequency != nil { ch <- prometheus.MustNewConstMetric( - cpuFreqAvgDesc, + c.descs.avgHertz, prometheus.GaugeValue, float64(*stats.CpuinfoAverageFrequency)*1000.0, stats.Name, @@ -74,7 +76,7 @@ func (c *cpuFreqCollector) Update(ch chan<- prometheus.Metric) error { } if stats.CpuinfoMinimumFrequency != nil { ch <- prometheus.MustNewConstMetric( - cpuFreqMinDesc, + c.descs.minHertz, prometheus.GaugeValue, float64(*stats.CpuinfoMinimumFrequency)*1000.0, stats.Name, @@ -82,7 +84,7 @@ func (c *cpuFreqCollector) Update(ch chan<- prometheus.Metric) error { } if stats.CpuinfoMaximumFrequency != nil { ch <- prometheus.MustNewConstMetric( - cpuFreqMaxDesc, + c.descs.maxHertz, prometheus.GaugeValue, float64(*stats.CpuinfoMaximumFrequency)*1000.0, stats.Name, @@ -90,7 +92,7 @@ func (c *cpuFreqCollector) Update(ch chan<- prometheus.Metric) error { } if stats.ScalingCurrentFrequency != nil { ch <- prometheus.MustNewConstMetric( - cpuFreqScalingFreqDesc, + c.descs.scalingHertz, prometheus.GaugeValue, float64(*stats.ScalingCurrentFrequency)*1000.0, stats.Name, @@ -98,7 +100,7 @@ func (c *cpuFreqCollector) Update(ch chan<- prometheus.Metric) error { } if stats.ScalingMinimumFrequency != nil { ch <- prometheus.MustNewConstMetric( - cpuFreqScalingFreqMinDesc, + c.descs.scalingMinHertz, prometheus.GaugeValue, float64(*stats.ScalingMinimumFrequency)*1000.0, stats.Name, @@ -106,7 +108,7 @@ func (c *cpuFreqCollector) Update(ch chan<- prometheus.Metric) error { } if stats.ScalingMaximumFrequency != nil { ch <- prometheus.MustNewConstMetric( - cpuFreqScalingFreqMaxDesc, + c.descs.scalingMaxHertz, prometheus.GaugeValue, float64(*stats.ScalingMaximumFrequency)*1000.0, stats.Name, @@ -120,7 +122,7 @@ func (c *cpuFreqCollector) Update(ch chan<- prometheus.Metric) error { state = 1 } ch <- prometheus.MustNewConstMetric( - cpuFreqScalingGovernorDesc, + c.descs.scalingGovernor, prometheus.GaugeValue, float64(state), stats.Name, diff --git a/collector/cpufreq_linux_test.go b/collector/cpufreq_linux_test.go new file mode 100644 index 0000000000..0b2a782ccb --- /dev/null +++ b/collector/cpufreq_linux_test.go @@ -0,0 +1,106 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !nocpu + +package collector + +import ( + "fmt" + "io" + "log/slog" + "strings" + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +type testCPUFreqCollector struct { + mc Collector +} + +func (c testCPUFreqCollector) Collect(ch chan<- prometheus.Metric) { + c.mc.Update(ch) +} + +func (c testCPUFreqCollector) Describe(ch chan<- *prometheus.Desc) { + prometheus.DescribeByCollect(c, ch) +} + +func newTestCPUFreqCollector(logger *slog.Logger) (prometheus.Collector, error) { + mc, err := NewCPUFreqCollector(logger) + if err != nil { + return testCPUFreqCollector{}, err + } + return &testCPUFreqCollector{mc}, nil +} + +const cpuFreqFixtureMetrics = `# HELP node_%[1]s_scaling_frequency_hertz Current scaled CPU thread frequency in hertz. +# TYPE node_%[1]s_scaling_frequency_hertz gauge +node_%[1]s_scaling_frequency_hertz{cpu="0"} 1.699981e+09 +node_%[1]s_scaling_frequency_hertz{cpu="1"} 1.699981e+09 +node_%[1]s_scaling_frequency_hertz{cpu="2"} 8e+06 +node_%[1]s_scaling_frequency_hertz{cpu="3"} 8e+06 +# HELP node_%[1]s_scaling_frequency_max_hertz Maximum scaled CPU thread frequency in hertz. +# TYPE node_%[1]s_scaling_frequency_max_hertz gauge +node_%[1]s_scaling_frequency_max_hertz{cpu="0"} 3.7e+09 +node_%[1]s_scaling_frequency_max_hertz{cpu="1"} 3.7e+09 +node_%[1]s_scaling_frequency_max_hertz{cpu="2"} 4.2e+09 +node_%[1]s_scaling_frequency_max_hertz{cpu="3"} 4.2e+09 +# HELP node_%[1]s_scaling_frequency_min_hertz Minimum scaled CPU thread frequency in hertz. +# TYPE node_%[1]s_scaling_frequency_min_hertz gauge +node_%[1]s_scaling_frequency_min_hertz{cpu="0"} 8e+08 +node_%[1]s_scaling_frequency_min_hertz{cpu="1"} 8e+08 +node_%[1]s_scaling_frequency_min_hertz{cpu="2"} 1e+06 +node_%[1]s_scaling_frequency_min_hertz{cpu="3"} 1e+06 +# HELP node_%[1]s_scaling_governor Current enabled CPU frequency governor. +# TYPE node_%[1]s_scaling_governor gauge +node_%[1]s_scaling_governor{cpu="0",governor="performance"} 0 +node_%[1]s_scaling_governor{cpu="0",governor="powersave"} 1 +node_%[1]s_scaling_governor{cpu="1",governor="performance"} 0 +node_%[1]s_scaling_governor{cpu="1",governor="powersave"} 1 +node_%[1]s_scaling_governor{cpu="2",governor="performance"} 0 +node_%[1]s_scaling_governor{cpu="2",governor="powersave"} 1 +node_%[1]s_scaling_governor{cpu="3",governor="performance"} 0 +node_%[1]s_scaling_governor{cpu="3",governor="powersave"} 1 +` + +func TestCPUFreqMetrics(t *testing.T) { + *sysPath = "fixtures/sys" + + for _, tc := range []struct { + name string + cpufreqPrefix bool + subsystem string + }{ + {name: "cpu prefix", cpufreqPrefix: false, subsystem: "cpu"}, + {name: "cpufreq prefix", cpufreqPrefix: true, subsystem: "cpufreq"}, + } { + t.Run(tc.name, func(t *testing.T) { + *useCPUFreqPrefix = tc.cpufreqPrefix + defer func() { *useCPUFreqPrefix = false }() + + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + collector, err := newTestCPUFreqCollector(logger) + if err != nil { + t.Fatal(err) + } + + expected := fmt.Sprintf(cpuFreqFixtureMetrics, tc.subsystem) + if err := testutil.CollectAndCompare(collector, strings.NewReader(expected)); err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/collector/cpufreq_solaris.go b/collector/cpufreq_solaris.go index 41826a999c..69b7c57400 100644 --- a/collector/cpufreq_solaris.go +++ b/collector/cpufreq_solaris.go @@ -28,6 +28,7 @@ import ( import "C" type cpuFreqCollector struct { + descs cpuFreqDescs logger *slog.Logger } @@ -37,6 +38,7 @@ func init() { func NewCpuFreqCollector(logger *slog.Logger) (Collector, error) { return &cpuFreqCollector{ + descs: newCPUFreqDescs(), logger: logger, }, nil } @@ -68,14 +70,14 @@ func (c *cpuFreqCollector) Update(ch chan<- prometheus.Metric) error { lcpu := strconv.Itoa(cpu) ch <- prometheus.MustNewConstMetric( - cpuFreqHertzDesc, + c.descs.hertz, prometheus.GaugeValue, float64(cpuFreqV.UintVal), lcpu, ) // Multiply by 1e+6 to convert MHz to Hz. ch <- prometheus.MustNewConstMetric( - cpuFreqMaxDesc, + c.descs.maxHertz, prometheus.GaugeValue, float64(cpuFreqMaxV.IntVal)*1e+6, lcpu, diff --git a/collector/fixtures/e2e-output.txt b/collector/fixtures/e2e-output.txt index d03fb08f44..2e2a42b0e5 100644 --- a/collector/fixtures/e2e-output.txt +++ b/collector/fixtures/e2e-output.txt @@ -902,16 +902,6 @@ node_cpu_flag_info{flag="aes"} 1 node_cpu_flag_info{flag="avx"} 1 node_cpu_flag_info{flag="avx2"} 1 node_cpu_flag_info{flag="constant_tsc"} 1 -# HELP node_cpu_frequency_hertz CPU frequency in hertz from /proc/cpuinfo. -# TYPE node_cpu_frequency_hertz gauge -node_cpu_frequency_hertz{core="0",cpu="0",package="0"} 7.99998e+08 -node_cpu_frequency_hertz{core="0",cpu="4",package="0"} 7.99989e+08 -node_cpu_frequency_hertz{core="1",cpu="1",package="0"} 8.00037e+08 -node_cpu_frequency_hertz{core="1",cpu="5",package="0"} 8.00083e+08 -node_cpu_frequency_hertz{core="2",cpu="2",package="0"} 8.0001e+08 -node_cpu_frequency_hertz{core="2",cpu="6",package="0"} 8.00017e+08 -node_cpu_frequency_hertz{core="3",cpu="3",package="0"} 8.00028e+08 -node_cpu_frequency_hertz{core="3",cpu="7",package="0"} 8.0003e+08 # HELP node_cpu_guest_seconds_total Seconds the CPUs spent in guests (VMs) for each mode. # TYPE node_cpu_guest_seconds_total counter node_cpu_guest_seconds_total{cpu="0",mode="nice"} 0.01