ethtool: Expose node_ethtool_info metric
Add a `node_ethtool_info` metric to all ethtool devices to expose driver information with following labels: * bus_info * driver * expansion_rom_version * firmware_version * version This metric is useful to monitor the firmware version to be up-to-date. Note: The version label might be malformed due to bug #39 in ethtool: https://github.com/safchain/ethtool/issues/39 Signed-off-by: Benjamin Drung <benjamin.drung@ionos.com>
This commit is contained in:
parent
6ac6ea2d13
commit
26ca609183
|
@ -192,7 +192,7 @@ Name | Description | OS
|
|||
buddyinfo | Exposes statistics of memory fragments as reported by /proc/buddyinfo. | Linux
|
||||
devstat | Exposes device statistics | Dragonfly, FreeBSD
|
||||
drbd | Exposes Distributed Replicated Block Device statistics (to version 8.4) | Linux
|
||||
ethtool | Exposes network interface and network driver statistics equivalent to `ethtool -S`. | Linux
|
||||
ethtool | Exposes network interface and network driver statistics equivalent to `ethtool -S` and `ethtool -i`. | Linux
|
||||
interrupts | Exposes detailed interrupts statistics. | Linux, OpenBSD
|
||||
ksmd | Exposes kernel and system statistics from `/sys/kernel/mm/ksm`. | Linux
|
||||
logind | Exposes session counts from [logind](http://www.freedesktop.org/wiki/Software/systemd/logind/). | Linux
|
||||
|
|
|
@ -45,28 +45,35 @@ var (
|
|||
transmittedRegex = regexp.MustCompile(`(^|_)tx(_|$)`)
|
||||
)
|
||||
|
||||
type EthtoolStats interface {
|
||||
type Ethtool interface {
|
||||
DriverInfo(string) (ethtool.DrvInfo, error)
|
||||
Stats(string) (map[string]uint64, error)
|
||||
}
|
||||
|
||||
type ethtoolStats struct {
|
||||
type ethtoolLibrary struct {
|
||||
ethtool *ethtool.Ethtool
|
||||
}
|
||||
|
||||
func (e *ethtoolStats) Stats(intf string) (map[string]uint64, error) {
|
||||
return ethtool.Stats(intf)
|
||||
func (e *ethtoolLibrary) DriverInfo(intf string) (ethtool.DrvInfo, error) {
|
||||
return e.ethtool.DriverInfo(intf)
|
||||
}
|
||||
|
||||
func (e *ethtoolLibrary) Stats(intf string) (map[string]uint64, error) {
|
||||
return e.ethtool.Stats(intf)
|
||||
}
|
||||
|
||||
type ethtoolCollector struct {
|
||||
fs sysfs.FS
|
||||
entries map[string]*prometheus.Desc
|
||||
ethtool Ethtool
|
||||
ignoredDevicesPattern *regexp.Regexp
|
||||
infoDesc *prometheus.Desc
|
||||
metricsPattern *regexp.Regexp
|
||||
logger log.Logger
|
||||
stats EthtoolStats
|
||||
}
|
||||
|
||||
// makeEthtoolCollector is the internal constructor for EthtoolCollector.
|
||||
// This allows NewEthtoolTestCollector to override its .stats interface
|
||||
// This allows NewEthtoolTestCollector to override its .ethtool interface
|
||||
// for testing.
|
||||
func makeEthtoolCollector(logger log.Logger) (*ethtoolCollector, error) {
|
||||
fs, err := sysfs.NewFS(*sysPath)
|
||||
|
@ -74,13 +81,18 @@ func makeEthtoolCollector(logger log.Logger) (*ethtoolCollector, error) {
|
|||
return nil, fmt.Errorf("failed to open sysfs: %w", err)
|
||||
}
|
||||
|
||||
e, err := ethtool.NewEthtool()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize ethtool library: %w", err)
|
||||
}
|
||||
|
||||
// Pre-populate some common ethtool metrics.
|
||||
return ðtoolCollector{
|
||||
fs: fs,
|
||||
ethtool: ðtoolLibrary{e},
|
||||
ignoredDevicesPattern: regexp.MustCompile(*ethtoolIgnoredDevices),
|
||||
metricsPattern: regexp.MustCompile(*ethtoolIncludedMetrics),
|
||||
logger: logger,
|
||||
stats: ðtoolStats{},
|
||||
entries: map[string]*prometheus.Desc{
|
||||
"rx_bytes": prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "ethtool", "received_bytes_total"),
|
||||
|
@ -118,6 +130,11 @@ func makeEthtoolCollector(logger log.Logger) (*ethtoolCollector, error) {
|
|||
[]string{"device"}, nil,
|
||||
),
|
||||
},
|
||||
infoDesc: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "ethtool", "info"),
|
||||
"A metric with a constant '1' value labeled by bus_info, device, driver, expansion_rom_version, firmware_version, version.",
|
||||
[]string{"bus_info", "device", "driver", "expansion_rom_version", "firmware_version", "version"}, nil,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -175,7 +192,24 @@ func (c *ethtoolCollector) Update(ch chan<- prometheus.Metric) error {
|
|||
continue
|
||||
}
|
||||
|
||||
stats, err = c.stats.Stats(device)
|
||||
drvInfo, err := c.ethtool.DriverInfo(device)
|
||||
|
||||
if err == nil {
|
||||
ch <- prometheus.MustNewConstMetric(c.infoDesc, prometheus.GaugeValue, 1.0,
|
||||
drvInfo.BusInfo, device, drvInfo.Driver, drvInfo.EromVersion, drvInfo.FwVersion, drvInfo.Version)
|
||||
} else {
|
||||
if errno, ok := err.(syscall.Errno); ok {
|
||||
if err == unix.EOPNOTSUPP {
|
||||
level.Debug(c.logger).Log("msg", "ethtool driver info error", "err", err, "device", device, "errno", uint(errno))
|
||||
} else if errno != 0 {
|
||||
level.Error(c.logger).Log("msg", "ethtool driver info error", "err", err, "device", device, "errno", uint(errno))
|
||||
}
|
||||
} else {
|
||||
level.Error(c.logger).Log("msg", "ethtool driver info error", "err", err, "device", device)
|
||||
}
|
||||
}
|
||||
|
||||
stats, err = c.ethtool.Stats(device)
|
||||
|
||||
// If Stats() returns EOPNOTSUPP it doesn't support ethtool stats. Log that only at Debug level.
|
||||
// Otherwise log it at Error level.
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/safchain/ethtool"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
|
@ -32,10 +33,49 @@ type EthtoolFixture struct {
|
|||
fixturePath string
|
||||
}
|
||||
|
||||
func (e *EthtoolFixture) DriverInfo(intf string) (ethtool.DrvInfo, error) {
|
||||
res := ethtool.DrvInfo{}
|
||||
|
||||
fixtureFile, err := os.Open(filepath.Join(e.fixturePath, intf, "driver"))
|
||||
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOENT {
|
||||
// The fixture for this interface doesn't exist. Translate that to unix.EOPNOTSUPP
|
||||
// to replicate an interface that doesn't support ethtool driver info
|
||||
return res, unix.EOPNOTSUPP
|
||||
}
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
defer fixtureFile.Close()
|
||||
|
||||
scanner := bufio.NewScanner(fixtureFile)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
line = strings.Trim(line, " ")
|
||||
items := strings.Split(line, ": ")
|
||||
switch items[0] {
|
||||
case "driver":
|
||||
res.Driver = items[1]
|
||||
case "version":
|
||||
res.Version = items[1]
|
||||
case "firmware-version":
|
||||
res.FwVersion = items[1]
|
||||
case "bus-info":
|
||||
res.BusInfo = items[1]
|
||||
case "expansion-rom-version":
|
||||
res.EromVersion = items[1]
|
||||
}
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (e *EthtoolFixture) Stats(intf string) (map[string]uint64, error) {
|
||||
res := make(map[string]uint64)
|
||||
|
||||
fixtureFile, err := os.Open(filepath.Join(e.fixturePath, intf))
|
||||
fixtureFile, err := os.Open(filepath.Join(e.fixturePath, intf, "statistics"))
|
||||
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOENT {
|
||||
// The fixture for this interface doesn't exist. Translate that to unix.EOPNOTSUPP
|
||||
// to replicate an interface that doesn't support ethtool stats
|
||||
|
@ -72,7 +112,7 @@ func (e *EthtoolFixture) Stats(intf string) (map[string]uint64, error) {
|
|||
|
||||
func NewEthtoolTestCollector(logger log.Logger) (Collector, error) {
|
||||
collector, err := makeEthtoolCollector(logger)
|
||||
collector.stats = &EthtoolFixture{
|
||||
collector.ethtool = &EthtoolFixture{
|
||||
fixturePath: "fixtures/ethtool/",
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -118,6 +158,9 @@ func TestBuildEthtoolFQName(t *testing.T) {
|
|||
|
||||
func TestEthtoolCollector(t *testing.T) {
|
||||
testcases := []string{
|
||||
prometheus.NewDesc("node_ethtool_info",
|
||||
"A metric with a constant '1' value labeled by bus_info, device, driver, expansion_rom_version, firmware_version, version.",
|
||||
[]string{"bus_info", "device", "driver", "expansion_rom_version", "firmware_version", "version"}, nil).String(),
|
||||
prometheus.NewDesc("node_ethtool_align_errors", "Network interface align_errors", []string{"device"}, nil).String(),
|
||||
prometheus.NewDesc("node_ethtool_received_broadcast", "Network interface rx_broadcast", []string{"device"}, nil).String(),
|
||||
prometheus.NewDesc("node_ethtool_received_errors_total", "Number of received frames with errors", []string{"device"}, nil).String(),
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# ethtool -i eth0
|
||||
driver: e1000e
|
||||
version: 5.11.0-22-generic
|
||||
firmware-version: 0.5-4
|
||||
expansion-rom-version:
|
||||
bus-info: 0000:00:1f.6
|
||||
supports-statistics: yes
|
||||
supports-test: yes
|
||||
supports-eeprom-access: yes
|
||||
supports-register-dump: yes
|
||||
supports-priv-flags: yes
|
Loading…
Reference in New Issue