mirror of
https://github.com/prometheus/node_exporter.git
synced 2024-11-23 20:36:21 +01:00
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
|
buddyinfo | Exposes statistics of memory fragments as reported by /proc/buddyinfo. | Linux
|
||||||
devstat | Exposes device statistics | Dragonfly, FreeBSD
|
devstat | Exposes device statistics | Dragonfly, FreeBSD
|
||||||
drbd | Exposes Distributed Replicated Block Device statistics (to version 8.4) | Linux
|
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
|
interrupts | Exposes detailed interrupts statistics. | Linux, OpenBSD
|
||||||
ksmd | Exposes kernel and system statistics from `/sys/kernel/mm/ksm`. | Linux
|
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
|
logind | Exposes session counts from [logind](http://www.freedesktop.org/wiki/Software/systemd/logind/). | Linux
|
||||||
|
@ -45,28 +45,35 @@ var (
|
|||||||
transmittedRegex = regexp.MustCompile(`(^|_)tx(_|$)`)
|
transmittedRegex = regexp.MustCompile(`(^|_)tx(_|$)`)
|
||||||
)
|
)
|
||||||
|
|
||||||
type EthtoolStats interface {
|
type Ethtool interface {
|
||||||
|
DriverInfo(string) (ethtool.DrvInfo, error)
|
||||||
Stats(string) (map[string]uint64, 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) {
|
func (e *ethtoolLibrary) DriverInfo(intf string) (ethtool.DrvInfo, error) {
|
||||||
return ethtool.Stats(intf)
|
return e.ethtool.DriverInfo(intf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ethtoolLibrary) Stats(intf string) (map[string]uint64, error) {
|
||||||
|
return e.ethtool.Stats(intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ethtoolCollector struct {
|
type ethtoolCollector struct {
|
||||||
fs sysfs.FS
|
fs sysfs.FS
|
||||||
entries map[string]*prometheus.Desc
|
entries map[string]*prometheus.Desc
|
||||||
|
ethtool Ethtool
|
||||||
ignoredDevicesPattern *regexp.Regexp
|
ignoredDevicesPattern *regexp.Regexp
|
||||||
|
infoDesc *prometheus.Desc
|
||||||
metricsPattern *regexp.Regexp
|
metricsPattern *regexp.Regexp
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
stats EthtoolStats
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeEthtoolCollector is the internal constructor for EthtoolCollector.
|
// 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.
|
// for testing.
|
||||||
func makeEthtoolCollector(logger log.Logger) (*ethtoolCollector, error) {
|
func makeEthtoolCollector(logger log.Logger) (*ethtoolCollector, error) {
|
||||||
fs, err := sysfs.NewFS(*sysPath)
|
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)
|
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.
|
// Pre-populate some common ethtool metrics.
|
||||||
return ðtoolCollector{
|
return ðtoolCollector{
|
||||||
fs: fs,
|
fs: fs,
|
||||||
|
ethtool: ðtoolLibrary{e},
|
||||||
ignoredDevicesPattern: regexp.MustCompile(*ethtoolIgnoredDevices),
|
ignoredDevicesPattern: regexp.MustCompile(*ethtoolIgnoredDevices),
|
||||||
metricsPattern: regexp.MustCompile(*ethtoolIncludedMetrics),
|
metricsPattern: regexp.MustCompile(*ethtoolIncludedMetrics),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
stats: ðtoolStats{},
|
|
||||||
entries: map[string]*prometheus.Desc{
|
entries: map[string]*prometheus.Desc{
|
||||||
"rx_bytes": prometheus.NewDesc(
|
"rx_bytes": prometheus.NewDesc(
|
||||||
prometheus.BuildFQName(namespace, "ethtool", "received_bytes_total"),
|
prometheus.BuildFQName(namespace, "ethtool", "received_bytes_total"),
|
||||||
@ -118,6 +130,11 @@ func makeEthtoolCollector(logger log.Logger) (*ethtoolCollector, error) {
|
|||||||
[]string{"device"}, nil,
|
[]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
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +192,24 @@ func (c *ethtoolCollector) Update(ch chan<- prometheus.Metric) error {
|
|||||||
continue
|
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.
|
// If Stats() returns EOPNOTSUPP it doesn't support ethtool stats. Log that only at Debug level.
|
||||||
// Otherwise log it at Error level.
|
// Otherwise log it at Error level.
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/safchain/ethtool"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,10 +33,49 @@ type EthtoolFixture struct {
|
|||||||
fixturePath string
|
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) {
|
func (e *EthtoolFixture) Stats(intf string) (map[string]uint64, error) {
|
||||||
res := make(map[string]uint64)
|
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 {
|
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOENT {
|
||||||
// The fixture for this interface doesn't exist. Translate that to unix.EOPNOTSUPP
|
// The fixture for this interface doesn't exist. Translate that to unix.EOPNOTSUPP
|
||||||
// to replicate an interface that doesn't support ethtool stats
|
// 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) {
|
func NewEthtoolTestCollector(logger log.Logger) (Collector, error) {
|
||||||
collector, err := makeEthtoolCollector(logger)
|
collector, err := makeEthtoolCollector(logger)
|
||||||
collector.stats = &EthtoolFixture{
|
collector.ethtool = &EthtoolFixture{
|
||||||
fixturePath: "fixtures/ethtool/",
|
fixturePath: "fixtures/ethtool/",
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -118,6 +158,9 @@ func TestBuildEthtoolFQName(t *testing.T) {
|
|||||||
|
|
||||||
func TestEthtoolCollector(t *testing.T) {
|
func TestEthtoolCollector(t *testing.T) {
|
||||||
testcases := []string{
|
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_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_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(),
|
prometheus.NewDesc("node_ethtool_received_errors_total", "Number of received frames with errors", []string{"device"}, nil).String(),
|
||||||
|
11
collector/fixtures/ethtool/eth0/driver
Normal file
11
collector/fixtures/ethtool/eth0/driver
Normal file
@ -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
Block a user