node_exporter/collector/ipvs_linux.go

232 lines
7.3 KiB
Go

// Copyright 2015 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 !noipvs
// +build !noipvs
package collector
import (
"errors"
"fmt"
"log/slog"
"os"
"sort"
"strconv"
"strings"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/procfs"
)
type ipvsCollector struct {
Collector
fs procfs.FS
backendLabels []string
backendConnectionsActive, backendConnectionsInact, backendWeight typedDesc
connections, incomingPackets, outgoingPackets, incomingBytes, outgoingBytes typedDesc
logger *slog.Logger
}
type ipvsBackendStatus struct {
ActiveConn uint64
InactConn uint64
Weight uint64
}
const (
ipvsLabelLocalAddress = "local_address"
ipvsLabelLocalPort = "local_port"
ipvsLabelRemoteAddress = "remote_address"
ipvsLabelRemotePort = "remote_port"
ipvsLabelProto = "proto"
ipvsLabelLocalMark = "local_mark"
)
var (
fullIpvsBackendLabels = []string{
ipvsLabelLocalAddress,
ipvsLabelLocalPort,
ipvsLabelRemoteAddress,
ipvsLabelRemotePort,
ipvsLabelProto,
ipvsLabelLocalMark,
}
ipvsLabels = kingpin.Flag("collector.ipvs.backend-labels", "Comma separated list for IPVS backend stats labels.").Default(strings.Join(fullIpvsBackendLabels, ",")).String()
)
func init() {
registerCollector("ipvs", defaultEnabled, NewIPVSCollector)
}
// NewIPVSCollector sets up a new collector for IPVS metrics. It accepts the
// "procfs" config parameter to override the default proc location (/proc).
func NewIPVSCollector(logger *slog.Logger) (Collector, error) {
return newIPVSCollector(logger)
}
func newIPVSCollector(logger *slog.Logger) (*ipvsCollector, error) {
var (
c ipvsCollector
err error
subsystem = "ipvs"
)
if c.backendLabels, err = c.parseIpvsLabels(*ipvsLabels); err != nil {
return nil, err
}
c.logger = logger
c.fs, err = procfs.NewFS(*procPath)
if err != nil {
return nil, fmt.Errorf("failed to open procfs: %w", err)
}
c.connections = typedDesc{prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "connections_total"),
"The total number of connections made.",
nil, nil,
), prometheus.CounterValue}
c.incomingPackets = typedDesc{prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "incoming_packets_total"),
"The total number of incoming packets.",
nil, nil,
), prometheus.CounterValue}
c.outgoingPackets = typedDesc{prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "outgoing_packets_total"),
"The total number of outgoing packets.",
nil, nil,
), prometheus.CounterValue}
c.incomingBytes = typedDesc{prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "incoming_bytes_total"),
"The total amount of incoming data.",
nil, nil,
), prometheus.CounterValue}
c.outgoingBytes = typedDesc{prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "outgoing_bytes_total"),
"The total amount of outgoing data.",
nil, nil,
), prometheus.CounterValue}
c.backendConnectionsActive = typedDesc{prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "backend_connections_active"),
"The current active connections by local and remote address.",
c.backendLabels, nil,
), prometheus.GaugeValue}
c.backendConnectionsInact = typedDesc{prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "backend_connections_inactive"),
"The current inactive connections by local and remote address.",
c.backendLabels, nil,
), prometheus.GaugeValue}
c.backendWeight = typedDesc{prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "backend_weight"),
"The current backend weight by local and remote address.",
c.backendLabels, nil,
), prometheus.GaugeValue}
return &c, nil
}
func (c *ipvsCollector) Update(ch chan<- prometheus.Metric) error {
ipvsStats, err := c.fs.IPVSStats()
if err != nil {
// Cannot access ipvs metrics, report no error.
if errors.Is(err, os.ErrNotExist) {
c.logger.Debug("ipvs collector metrics are not available for this system")
return ErrNoData
}
return fmt.Errorf("could not get IPVS stats: %w", err)
}
ch <- c.connections.mustNewConstMetric(float64(ipvsStats.Connections))
ch <- c.incomingPackets.mustNewConstMetric(float64(ipvsStats.IncomingPackets))
ch <- c.outgoingPackets.mustNewConstMetric(float64(ipvsStats.OutgoingPackets))
ch <- c.incomingBytes.mustNewConstMetric(float64(ipvsStats.IncomingBytes))
ch <- c.outgoingBytes.mustNewConstMetric(float64(ipvsStats.OutgoingBytes))
backendStats, err := c.fs.IPVSBackendStatus()
if err != nil {
return fmt.Errorf("could not get backend status: %w", err)
}
sums := map[string]ipvsBackendStatus{}
labelValues := map[string][]string{}
for _, backend := range backendStats {
localAddress := ""
if backend.LocalAddress.String() != "<nil>" {
localAddress = backend.LocalAddress.String()
}
kv := make([]string, len(c.backendLabels))
for i, label := range c.backendLabels {
var labelValue string
switch label {
case ipvsLabelLocalAddress:
labelValue = localAddress
case ipvsLabelLocalPort:
labelValue = strconv.FormatUint(uint64(backend.LocalPort), 10)
case ipvsLabelRemoteAddress:
labelValue = backend.RemoteAddress.String()
case ipvsLabelRemotePort:
labelValue = strconv.FormatUint(uint64(backend.RemotePort), 10)
case ipvsLabelProto:
labelValue = backend.Proto
case ipvsLabelLocalMark:
labelValue = backend.LocalMark
}
kv[i] = labelValue
}
key := strings.Join(kv, "-")
status := sums[key]
status.ActiveConn += backend.ActiveConn
status.InactConn += backend.InactConn
status.Weight += backend.Weight
sums[key] = status
labelValues[key] = kv
}
for key, status := range sums {
kv := labelValues[key]
ch <- c.backendConnectionsActive.mustNewConstMetric(float64(status.ActiveConn), kv...)
ch <- c.backendConnectionsInact.mustNewConstMetric(float64(status.InactConn), kv...)
ch <- c.backendWeight.mustNewConstMetric(float64(status.Weight), kv...)
}
return nil
}
func (c *ipvsCollector) parseIpvsLabels(labelString string) ([]string, error) {
labels := strings.Split(labelString, ",")
labelSet := make(map[string]bool, len(labels))
results := make([]string, 0, len(labels))
for _, label := range labels {
if label != "" {
labelSet[label] = true
}
}
for _, label := range fullIpvsBackendLabels {
if labelSet[label] {
results = append(results, label)
}
delete(labelSet, label)
}
if len(labelSet) > 0 {
keys := make([]string, 0, len(labelSet))
for label := range labelSet {
keys = append(keys, label)
}
sort.Strings(keys)
return nil, fmt.Errorf("unknown IPVS backend labels: %q", strings.Join(keys, ", "))
}
return results, nil
}