diff --git a/README.md b/README.md index 56e666ea..a8698dd6 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/collector/ethtool_linux.go b/collector/ethtool_linux.go index f00ed639..bb8877df 100644 --- a/collector/ethtool_linux.go +++ b/collector/ethtool_linux.go @@ -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. diff --git a/collector/ethtool_linux_test.go b/collector/ethtool_linux_test.go index 8a96c5e2..95d4cfbf 100644 --- a/collector/ethtool_linux_test.go +++ b/collector/ethtool_linux_test.go @@ -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(), diff --git a/collector/fixtures/ethtool/bond0 b/collector/fixtures/ethtool/bond0/statistics similarity index 100% rename from collector/fixtures/ethtool/bond0 rename to collector/fixtures/ethtool/bond0/statistics diff --git a/collector/fixtures/ethtool/eth0/driver b/collector/fixtures/ethtool/eth0/driver new file mode 100644 index 00000000..7ec84a81 --- /dev/null +++ b/collector/fixtures/ethtool/eth0/driver @@ -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 diff --git a/collector/fixtures/ethtool/eth0 b/collector/fixtures/ethtool/eth0/statistics similarity index 100% rename from collector/fixtures/ethtool/eth0 rename to collector/fixtures/ethtool/eth0/statistics