node_exporter/collector/sysctl_linux.go

219 lines
5.5 KiB
Go
Raw Permalink Normal View History

// Copyright 2022 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.
package collector
import (
"fmt"
"log/slog"
"strconv"
"strings"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/procfs"
)
var (
sysctlInclude = kingpin.Flag("collector.sysctl.include", "Select sysctl metrics to include").Strings()
sysctlIncludeInfo = kingpin.Flag("collector.sysctl.include-info", "Select sysctl metrics to include as info metrics").Strings()
sysctlInfoDesc = prometheus.NewDesc(prometheus.BuildFQName(namespace, "sysctl", "info"), "sysctl info", []string{"name", "value", "index"}, nil)
)
type sysctlCollector struct {
fs procfs.FS
logger *slog.Logger
sysctls []*sysctl
}
func init() {
registerCollector("sysctl", defaultDisabled, NewSysctlCollector)
}
func NewSysctlCollector(logger *slog.Logger) (Collector, error) {
fs, err := procfs.NewFS(*procPath)
if err != nil {
return nil, fmt.Errorf("failed to open sysfs: %w", err)
}
c := &sysctlCollector{
logger: logger,
fs: fs,
sysctls: []*sysctl{},
}
for _, include := range *sysctlInclude {
sysctl, err := newSysctl(include, true)
if err != nil {
return nil, err
}
c.sysctls = append(c.sysctls, sysctl)
}
for _, include := range *sysctlIncludeInfo {
sysctl, err := newSysctl(include, false)
if err != nil {
return nil, err
}
c.sysctls = append(c.sysctls, sysctl)
}
return c, nil
}
func (c *sysctlCollector) Update(ch chan<- prometheus.Metric) error {
for _, sysctl := range c.sysctls {
metrics, err := c.newMetrics(sysctl)
if err != nil {
return err
}
for _, metric := range metrics {
ch <- metric
}
}
return nil
}
func (c *sysctlCollector) newMetrics(s *sysctl) ([]prometheus.Metric, error) {
var (
values interface{}
length int
err error
)
if s.numeric {
values, err = c.fs.SysctlInts(s.name)
if err != nil {
return nil, fmt.Errorf("error obtaining sysctl info: %w", err)
}
length = len(values.([]int))
} else {
values, err = c.fs.SysctlStrings(s.name)
if err != nil {
return nil, fmt.Errorf("error obtaining sysctl info: %w", err)
}
length = len(values.([]string))
}
switch length {
case 0:
return nil, fmt.Errorf("sysctl %s has no values", s.name)
case 1:
if len(s.keys) > 0 {
return nil, fmt.Errorf("sysctl %s has only one value, but expected %v", s.name, s.keys)
}
return []prometheus.Metric{s.newConstMetric(values)}, nil
default:
if len(s.keys) == 0 {
return s.newIndexedMetrics(values), nil
}
if length != len(s.keys) {
return nil, fmt.Errorf("sysctl %s has %d keys but only %d defined in f lag", s.name, length, len(s.keys))
}
return s.newMappedMetrics(values)
}
}
type sysctl struct {
numeric bool
name string
keys []string
}
func newSysctl(include string, numeric bool) (*sysctl, error) {
parts := strings.SplitN(include, ":", 2)
s := &sysctl{
numeric: numeric,
name: parts[0],
}
if len(parts) == 2 {
s.keys = strings.Split(parts[1], ",")
s.name = parts[0]
}
return s, nil
}
func (s *sysctl) metricName() string {
return SanitizeMetricName(s.name)
}
func (s *sysctl) newConstMetric(v interface{}) prometheus.Metric {
if s.numeric {
return prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(namespace, "sysctl", s.metricName()),
fmt.Sprintf("sysctl %s", s.name),
nil, nil),
prometheus.UntypedValue,
float64(v.([]int)[0]))
}
return prometheus.MustNewConstMetric(
sysctlInfoDesc,
prometheus.GaugeValue,
1.0,
s.name,
v.([]string)[0],
"0",
)
}
func (s *sysctl) newIndexedMetrics(v interface{}) []prometheus.Metric {
desc := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "sysctl", s.metricName()),
fmt.Sprintf("sysctl %s", s.name),
[]string{"index"}, nil,
)
switch values := v.(type) {
case []int:
metrics := make([]prometheus.Metric, len(values))
for i, n := range values {
metrics[i] = prometheus.MustNewConstMetric(desc, prometheus.UntypedValue, float64(n), strconv.Itoa(i))
}
return metrics
case []string:
metrics := make([]prometheus.Metric, len(values))
for i, str := range values {
metrics[i] = prometheus.MustNewConstMetric(sysctlInfoDesc, prometheus.GaugeValue, 1.0, s.name, str, strconv.Itoa(i))
}
return metrics
default:
panic(fmt.Sprintf("unexpected type %T", values))
}
}
func (s *sysctl) newMappedMetrics(v interface{}) ([]prometheus.Metric, error) {
switch values := v.(type) {
case []int:
metrics := make([]prometheus.Metric, len(values))
for i, n := range values {
key := s.keys[i]
desc := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "sysctl", s.metricName()+"_"+key),
fmt.Sprintf("sysctl %s, field %d", s.name, i),
nil,
nil,
)
metrics[i] = prometheus.MustNewConstMetric(desc, prometheus.UntypedValue, float64(n))
}
return metrics, nil
case []string:
return nil, fmt.Errorf("mapped sysctl string values not supported")
default:
return nil, fmt.Errorf("unexpected type %T", values)
}
}