Skip to content

Commit 6084d55

Browse files
committed
collector/ethtool: Expose NIC channel configuration
When running a large fleet of machines running network-heavy loads and with different hardware configurations, it is important to get an overview about their configuration. Ethtool exporter already exposes a lot of useful info like port configuration, driver, and obviously stats, however did not expose the channel configuration. This commit adds two new metrics exposing the current and maximum supported channel configuration, with each metric exposing one instance for combined, other, rx, and tx. Signed-off-by: Maximilian Wilhelm <[email protected]>
1 parent 53186b3 commit 6084d55

3 files changed

Lines changed: 149 additions & 0 deletions

File tree

collector/ethtool_linux.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type Ethtool interface {
4949
DriverInfo(string) (ethtool.DrvInfo, error)
5050
Stats(string) (map[string]uint64, error)
5151
LinkInfo(string) (ethtool.EthtoolCmd, error)
52+
GetChannels(string) (ethtool.Channels, error)
5253
}
5354

5455
type ethtoolLibrary struct {
@@ -69,6 +70,10 @@ func (e *ethtoolLibrary) LinkInfo(intf string) (ethtool.EthtoolCmd, error) {
6970
return ethtoolCmd, err
7071
}
7172

73+
func (e *ethtoolLibrary) GetChannels(intf string) (ethtool.Channels, error) {
74+
return e.ethtool.GetChannels(intf)
75+
}
76+
7277
type ethtoolCollector struct {
7378
fs sysfs.FS
7479
entries map[string]*prometheus.Desc
@@ -199,6 +204,21 @@ func makeEthtoolCollector(logger *slog.Logger) (*ethtoolCollector, error) {
199204
"If this port is using autonegotiate",
200205
[]string{"device"}, nil,
201206
),
207+
208+
// channel info
209+
//
210+
// Each metric will have four instances differentiated by its type,
211+
// with type being one of rx, tx, other, or combined.
212+
"channels_max": prometheus.NewDesc(
213+
prometheus.BuildFQName(namespace, "ethtool", "channels_max"),
214+
"Maximum supported network interface channels",
215+
[]string{"device", "type"}, nil,
216+
),
217+
"channels_current": prometheus.NewDesc(
218+
prometheus.BuildFQName(namespace, "ethtool", "channels_current"),
219+
"Currently configured network interface channels",
220+
[]string{"device", "type"}, nil,
221+
),
202222
},
203223
infoDesc: prometheus.NewDesc(
204224
prometheus.BuildFQName(namespace, "ethtool", "info"),
@@ -370,6 +390,33 @@ func (c *ethtoolCollector) updateSpeeds(ch chan<- prometheus.Metric, prefix stri
370390
}
371391
}
372392

393+
func (c *ethtoolCollector) updateChannels(ch chan<- prometheus.Metric, device string) {
394+
channels, err := c.ethtool.GetChannels(device)
395+
if err != nil {
396+
if errno, ok := err.(syscall.Errno); ok {
397+
if err == unix.EOPNOTSUPP {
398+
c.logger.Debug("ethtool driver info error", "err", err, "device", device, "errno", uint(errno))
399+
} else if errno != 0 {
400+
c.logger.Error("ethtool get channels error", "err", err, "device", device, "errno", uint(errno))
401+
}
402+
} else {
403+
c.logger.Error("ethtool get channels error", "err", err, "device", device)
404+
}
405+
406+
return
407+
}
408+
409+
ch <- prometheus.MustNewConstMetric(c.entry("channels_max"), prometheus.GaugeValue, float64(channels.MaxRx), device, "rx")
410+
ch <- prometheus.MustNewConstMetric(c.entry("channels_max"), prometheus.GaugeValue, float64(channels.MaxTx), device, "tx")
411+
ch <- prometheus.MustNewConstMetric(c.entry("channels_max"), prometheus.GaugeValue, float64(channels.MaxOther), device, "other")
412+
ch <- prometheus.MustNewConstMetric(c.entry("channels_max"), prometheus.GaugeValue, float64(channels.MaxCombined), device, "combined")
413+
414+
ch <- prometheus.MustNewConstMetric(c.entry("channels_current"), prometheus.GaugeValue, float64(channels.RxCount), device, "rx")
415+
ch <- prometheus.MustNewConstMetric(c.entry("channels_current"), prometheus.GaugeValue, float64(channels.TxCount), device, "tx")
416+
ch <- prometheus.MustNewConstMetric(c.entry("channels_current"), prometheus.GaugeValue, float64(channels.OtherCount), device, "other")
417+
ch <- prometheus.MustNewConstMetric(c.entry("channels_current"), prometheus.GaugeValue, float64(channels.CombinedCount), device, "combined")
418+
}
419+
373420
func (c *ethtoolCollector) Update(ch chan<- prometheus.Metric) error {
374421
netClass, err := c.fs.NetClassDevices()
375422
if err != nil {
@@ -429,6 +476,8 @@ func (c *ethtoolCollector) Update(ch chan<- prometheus.Metric) error {
429476
}
430477
}
431478

479+
c.updateChannels(ch, device)
480+
432481
stats, err = c.ethtool.Stats(device)
433482

434483
// If Stats() returns EOPNOTSUPP it doesn't support ethtool stats. Log that only at Debug level.

collector/ethtool_linux_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,81 @@ func (e *EthtoolFixture) LinkInfo(intf string) (ethtool.EthtoolCmd, error) {
257257
return res, err
258258
}
259259

260+
func (e *EthtoolFixture) GetChannels(intf string) (ethtool.Channels, error) {
261+
res := ethtool.Channels{}
262+
263+
fixtureFile, err := os.Open(filepath.Join(e.fixturePath, intf, "channels"))
264+
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOENT {
265+
// The fixture for this interface doesn't exist. Translate that to unix.EOPNOTSUPP
266+
// to replicate an interface that doesn't support ethtool driver info
267+
return res, unix.EOPNOTSUPP
268+
}
269+
if err != nil {
270+
return res, err
271+
}
272+
defer fixtureFile.Close()
273+
274+
scanner := bufio.NewScanner(fixtureFile)
275+
currentConfig := false
276+
277+
for scanner.Scan() {
278+
line := scanner.Text()
279+
if strings.HasPrefix(line, "#") || strings.HasPrefix(line, "Channel parameters for") || strings.HasPrefix(line, "Pre-set maximums") {
280+
continue
281+
}
282+
283+
line = strings.Trim(line, " ")
284+
items := strings.Split(line, ":")
285+
fmt.Printf("line: %s\n", line)
286+
fmt.Printf("items: %s\n", strings.Join(items, ","))
287+
switch items[0] {
288+
case "Current hardware settings":
289+
currentConfig = true
290+
case "RX":
291+
if currentConfig {
292+
res.RxCount = readChannel(items[1])
293+
} else {
294+
res.MaxRx = readChannel(items[1])
295+
}
296+
case "TX":
297+
if currentConfig {
298+
res.TxCount = readChannel(items[1])
299+
} else {
300+
res.MaxTx = readChannel(items[1])
301+
}
302+
case "Other":
303+
if currentConfig {
304+
res.OtherCount = readChannel(items[1])
305+
} else {
306+
res.MaxOther = readChannel(items[1])
307+
}
308+
case "Combined":
309+
if currentConfig {
310+
res.CombinedCount = readChannel(items[1])
311+
} else {
312+
res.MaxCombined = readChannel(items[1])
313+
}
314+
}
315+
}
316+
317+
return res, nil
318+
}
319+
320+
func readChannel(val string) uint32 {
321+
val = strings.TrimSpace(val)
322+
if val == "n/a" {
323+
return 0
324+
}
325+
326+
intVal, err := strconv.ParseUint(val, 10, 32)
327+
if err != nil {
328+
return 0
329+
}
330+
331+
return uint32(intVal)
332+
333+
}
334+
260335
func NewEthtoolTestCollector(logger *slog.Logger) (Collector, error) {
261336
collector, err := makeEthtoolCollector(logger)
262337
if err != nil {
@@ -291,6 +366,18 @@ func TestEthToolCollector(t *testing.T) {
291366
testcase := `# HELP node_ethtool_align_errors Network interface align_errors
292367
# TYPE node_ethtool_align_errors untyped
293368
node_ethtool_align_errors{device="eth0"} 0
369+
# HELP node_ethtool_channels_current Currently configured network interface channels
370+
# TYPE node_ethtool_channels_current gauge
371+
node_ethtool_channels_current{device="eth0",type="combined"} 128
372+
node_ethtool_channels_current{device="eth0",type="other"} 1
373+
node_ethtool_channels_current{device="eth0",type="rx"} 0
374+
node_ethtool_channels_current{device="eth0",type="tx"} 0
375+
# HELP node_ethtool_channels_max Maximum supported network interface channels
376+
# TYPE node_ethtool_channels_max gauge
377+
node_ethtool_channels_max{device="eth0",type="combined"} 252
378+
node_ethtool_channels_max{device="eth0",type="other"} 1
379+
node_ethtool_channels_max{device="eth0",type="rx"} 252
380+
node_ethtool_channels_max{device="eth0",type="tx"} 252
294381
# HELP node_ethtool_info A metric with a constant '1' value labeled by bus_info, device, driver, expansion_rom_version, firmware_version, version.
295382
# TYPE node_ethtool_info gauge
296383
node_ethtool_info{bus_info="0000:00:1f.6",device="eth0",driver="e1000e",expansion_rom_version="",firmware_version="0.5-4",version="5.11.0-22-generic"} 1
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# ethtool --show-channels eth0
2+
Channel parameters for eth0:
3+
Pre-set maximums:
4+
RX: 252
5+
TX: 252
6+
Other: 1
7+
Combined: 252
8+
Current hardware settings:
9+
# Testing n/a (which will be translated into 0)
10+
RX: n/a
11+
TX: 0
12+
Other: 1
13+
Combined: 128

0 commit comments

Comments
 (0)