Skip to content
Closed
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
5 changes: 1 addition & 4 deletions collector/netclass_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,7 @@ func NewNetClassCollector(logger log.Logger) (Collector, error) {
}

func (c *netClassCollector) Update(ch chan<- prometheus.Metric) error {
if *netclassNetlink {
return c.netClassRTNLUpdate(ch)
}
return c.netClassSysfsUpdate(ch)
return c.netClassRTNLUpdate(ch)
}

func (c *netClassCollector) netClassSysfsUpdate(ch chan<- prometheus.Metric) error {
Expand Down
86 changes: 84 additions & 2 deletions collector/netclass_rtnl_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@
package collector

import (
"bytes"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"

"github.com/go-kit/log/level"
"github.com/jsimonetti/rtnetlink"
"github.com/mdlayher/ethtool"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/procfs/sysfs"
"gopkg.in/alecthomas/kingpin.v2"
)

Expand Down Expand Up @@ -57,14 +62,27 @@ func (c *netClassCollector) netClassRTNLUpdate(ch chan<- prometheus.Metric) erro
}
}

// Get most attributes from Netlink
lMsgs, err := c.getNetClassInfoRTNL()
if err != nil {
return fmt.Errorf("could not get net class info: %w", err)
}

relevantLinks := make([]rtnetlink.LinkMessage, 0, len(lMsgs))
for _, msg := range lMsgs {
if c.ignoredDevicesPattern.MatchString(msg.Attributes.Name) {
continue
if !c.ignoredDevicesPattern.MatchString(msg.Attributes.Name) {
relevantLinks = append(relevantLinks, msg)
}
}

// Read sysfs for attributes that Netlink doesn't expose
sysfsAttrs, err := getSysfsAttributes(relevantLinks)
if err != nil {
return fmt.Errorf("could not get sysfs device info: %w", err)
}

// Parse all the info and update metrics
for _, msg := range relevantLinks {
upDesc := prometheus.NewDesc(
prometheus.BuildFQName(namespace, c.subsystem, "up"),
"Value is 1 if operstate is 'up', 0 otherwise.",
Expand Down Expand Up @@ -96,12 +114,16 @@ func (c *netClassCollector) netClassRTNLUpdate(ch chan<- prometheus.Metric) erro
duplex = lm.Duplex.String()
}

ifaceInfo := sysfsAttrs[msg.Attributes.Name]

ch <- prometheus.MustNewConstMetric(infoDesc, prometheus.GaugeValue, infoValue, msg.Attributes.Name, msg.Attributes.Address.String(), msg.Attributes.Broadcast.String(), duplex, operstateStr[int(msg.Attributes.OperationalState)], ifalias)

pushMetric(ch, c.getFieldDesc("address_assign_type"), "address_assign_type", ifaceInfo.AddrAssignType, prometheus.GaugeValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("carrier"), "carrier", msg.Attributes.Carrier, prometheus.GaugeValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("carrier_changes_total"), "carrier_changes_total", msg.Attributes.CarrierChanges, prometheus.CounterValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("carrier_up_changes_total"), "carrier_up_changes_total", msg.Attributes.CarrierUpCount, prometheus.CounterValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("carrier_down_changes_total"), "carrier_down_changes_total", msg.Attributes.CarrierDownCount, prometheus.CounterValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("device_id"), "device_id", ifaceInfo.DevID, prometheus.GaugeValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("flags"), "flags", msg.Flags, prometheus.GaugeValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("iface_id"), "iface_id", msg.Index, prometheus.GaugeValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("iface_link_mode"), "iface_link_mode", msg.Attributes.LinkMode, prometheus.GaugeValue, msg.Attributes.Name)
Expand All @@ -117,6 +139,7 @@ func (c *netClassCollector) netClassRTNLUpdate(ch chan<- prometheus.Metric) erro
}

pushMetric(ch, c.getFieldDesc("mtu_bytes"), "mtu_bytes", msg.Attributes.MTU, prometheus.GaugeValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("name_assign_type"), "name_assign_type", ifaceInfo.NameAssignType, prometheus.GaugeValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("net_dev_group"), "net_dev_group", msg.Attributes.NetDevGroup, prometheus.GaugeValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("transmit_queue_length"), "transmit_queue_length", msg.Attributes.TxQueueLen, prometheus.GaugeValue, msg.Attributes.Name)
pushMetric(ch, c.getFieldDesc("protocol_type"), "protocol_type", msg.Type, prometheus.GaugeValue, msg.Attributes.Name)
Expand Down Expand Up @@ -186,3 +209,62 @@ func (c *netClassCollector) getLinkModes() ([]*ethtool.LinkMode, error) {

return lms, err
}

// getSysfsAttributes reads attributes that are absent from netlink but provided
// by sysfs.
func getSysfsAttributes(links []rtnetlink.LinkMessage) (sysfs.NetClass, error) {
netClass := sysfs.NetClass{}
for _, msg := range links {
var err error
interfaceClass := sysfs.NetClassIface{}
ifName := msg.Attributes.Name

// These three attributes hold a device-specific lock when
// accessed, not the RTNL lock, so they are much less impactful
// than reading most of the other attributes from sysfs.
interfaceClass.AddrAssignType, err = getSysfsAttrAsInt64(ifName, "addr_assign_type")
if err != nil {
return nil, err
}
interfaceClass.DevID, err = getSysfsAttrAsInt64(ifName, "dev_id")
if err != nil {
return nil, err
}
interfaceClass.NameAssignType, err = getSysfsAttrAsInt64(ifName, "name_assign_type")
if err != nil {
return nil, err
}

netClass[ifName] = interfaceClass
}
return netClass, nil
}

func getSysfsAttrAsInt64(linkName, attr string) (*int64, error) {
name := filepath.Join("/sys", "class", "net", linkName, attr)
data, err := os.ReadFile(name)
if err != nil {
var perr *fs.PathError
// Ignore certain errors we know aren't fatal; same as
// prometheus's sysfs class does
if os.IsNotExist(err) || os.IsPermission(err) {
return nil, nil
} else if errors.As(err, &perr) {
realErr := perr.Unwrap()
if realErr.Error() == "operation not supported" || realErr.Error() == "invalid argument" {
return nil, nil
}
}
return nil, fmt.Errorf("failed to read file %q: %w", name, err)
}

// base 0 automatically handles number type prefixes (hex, octal, etc)
const base = 0
val, err := strconv.ParseInt(string(bytes.TrimSpace(data)), base, 64)
if err != nil {
// Ignore the error; can't do much with it and an error
// parsing a single attribute shouldn't stop parsing others
return nil, nil
}
return &val, nil
}