Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 42 additions & 8 deletions collector/ethtool_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,42 +45,54 @@ 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)
if err != nil {
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 &ethtoolCollector{
fs: fs,
ethtool: &ethtoolLibrary{e},
ignoredDevicesPattern: regexp.MustCompile(*ethtoolIgnoredDevices),
metricsPattern: regexp.MustCompile(*ethtoolIncludedMetrics),
logger: logger,
stats: &ethtoolStats{},
entries: map[string]*prometheus.Desc{
"rx_bytes": prometheus.NewDesc(
prometheus.BuildFQName(namespace, "ethtool", "received_bytes_total"),
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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.
Expand Down
47 changes: 45 additions & 2 deletions collector/ethtool_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,57 @@ import (

"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/safchain/ethtool"
"golang.org/x/sys/unix"
)

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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(),
Expand Down
11 changes: 11 additions & 0 deletions collector/fixtures/ethtool/eth0/driver
Original file line number Diff line number Diff line change
@@ -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