diff --git a/README.md b/README.md index 713663132b..4a10a4b6c5 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,7 @@ softirqs | Exposes detailed softirq statistics from `/proc/softirqs`. | Linux sysctl | Expose sysctl values from `/proc/sys`. Use `--collector.sysctl.include(-info)` to configure. | Linux swap | Expose swap information from `/proc/swaps`. | Linux systemd | Exposes service and system status from [systemd](http://www.freedesktop.org/wiki/Software/systemd/). | Linux +tainted | Exposes kernel taint flags from `/proc/sys/kernel/tainted`. | Linux tcpstat | Exposes TCP connection status information from `/proc/net/tcp` and `/proc/net/tcp6`. (Warning: the current version has potential performance issues in high load situations.) | Linux wifi | Exposes WiFi device and station statistics. | Linux xfrm | Exposes statistics from `/proc/net/xfrm_stat` | Linux diff --git a/collector/fixtures/proc/sys/kernel/tainted b/collector/fixtures/proc/sys/kernel/tainted new file mode 100644 index 0000000000..63fce6863a --- /dev/null +++ b/collector/fixtures/proc/sys/kernel/tainted @@ -0,0 +1 @@ +12288 diff --git a/collector/tainted_linux.go b/collector/tainted_linux.go new file mode 100644 index 0000000000..3ab3f25c6e --- /dev/null +++ b/collector/tainted_linux.go @@ -0,0 +1,79 @@ +// 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 !notainted + +package collector + +import ( + "fmt" + "log/slog" + "strconv" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/procfs" +) + +type taintedCollector struct { + fs procfs.FS + logger *slog.Logger + desc *prometheus.Desc +} + +func init() { + registerCollector("tainted", defaultDisabled, NewTaintedCollector) +} + +// NewTaintedCollector returns a Collector exposing kernel taint flags from +// /proc/sys/kernel/tainted as a labelled gauge. +// See https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html +func NewTaintedCollector(logger *slog.Logger) (Collector, error) { + fs, err := procfs.NewFS(*procPath) + if err != nil { + return nil, fmt.Errorf("failed to open procfs: %w", err) + } + return &taintedCollector{ + fs: fs, + logger: logger, + desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, "kernel", "tainted"), + "Taint flags set on the running Linux kernel, as reported by /proc/sys/kernel/tainted. "+ + "Value is 1 if the flag is set, 0 otherwise. "+ + "See https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html for flag meanings.", + []string{"bit", "flag"}, + nil, + ), + }, nil +} + +func (c *taintedCollector) Update(ch chan<- prometheus.Metric) error { + tainted, err := c.fs.KernelTainted() + if err != nil { + return fmt.Errorf("couldn't read kernel tainted state: %w", err) + } + + for _, b := range tainted.Bits { + var val float64 + if b.Set { + val = 1.0 + } + ch <- prometheus.MustNewConstMetric( + c.desc, + prometheus.GaugeValue, + val, + strconv.Itoa(b.Index), + b.Flag, + ) + } + return nil +} diff --git a/collector/tainted_linux_test.go b/collector/tainted_linux_test.go new file mode 100644 index 0000000000..0ce7e3b621 --- /dev/null +++ b/collector/tainted_linux_test.go @@ -0,0 +1,103 @@ +// 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 !notainted + +package collector + +import ( + "io" + "log/slog" + "testing" + + "github.com/prometheus/client_golang/prometheus" +) + +func TestTaintedCollector(t *testing.T) { + *procPath = "fixtures/proc" + + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + c, err := NewTaintedCollector(logger) + if err != nil { + t.Fatalf("failed to create tainted collector: %v", err) + } + + reg := prometheus.NewPedanticRegistry() + reg.MustRegister(&taintedCollectorWrapper{c.(*taintedCollector)}) + + mfs, err := reg.Gather() + if err != nil { + t.Fatalf("gather failed: %v", err) + } + if len(mfs) != 1 { + t.Fatalf("expected 1 metric family, got %d", len(mfs)) + } + + mf := mfs[0] + if got := mf.GetName(); got != "node_kernel_tainted" { + t.Errorf("metric name: want node_kernel_tainted, got %s", got) + } + + // Expect one series per known taint bit (20 defined by the kernel). + const wantBits = 20 + if got := len(mf.GetMetric()); got != wantBits { + t.Errorf("metric count: want %d, got %d", wantBits, got) + } + + // Build bit → value map for assertion. + // Fixture is 12288 = bit 12 (O) + bit 13 (E). + // Build flag → value map for assertion (labels: bit, flag). + flagVals := make(map[string]float64) + for _, m := range mf.GetMetric() { + // Each metric has exactly 2 labels: bit and flag. + for _, lp := range m.GetLabel() { + if lp.GetName() == "flag" { + flagVals[lp.GetValue()] = m.GetGauge().GetValue() + } + } + } + + // Fixture is 12288 = bit 12 (O) + bit 13 (E). + for _, tc := range []struct { + flag string + want float64 + }{ + {"O", 1}, // Externally-built (out-of-tree) module — set + {"E", 1}, // Unsigned module — set + {"L", 0}, // Soft lockup — must be clear + {"P", 0}, + {"T", 0}, + } { + got, ok := flagVals[tc.flag] + if !ok { + t.Errorf("flag %q not found in metrics", tc.flag) + continue + } + if got != tc.want { + t.Errorf("flag %q: want %.0f, got %.0f", tc.flag, tc.want, got) + } + } +} + +// taintedCollectorWrapper adapts taintedCollector to prometheus.Collector. +type taintedCollectorWrapper struct { + c *taintedCollector +} + +func (w *taintedCollectorWrapper) Describe(ch chan<- *prometheus.Desc) { + ch <- w.c.desc +} + +func (w *taintedCollectorWrapper) Collect(ch chan<- prometheus.Metric) { + _ = w.c.Update(ch) +}