package probes import ( "bufio" "io" "os" "strconv" "strings" ) // NetDevSnapshot is the per-interface counter row from /proc/net/dev at // a single instant. Used by the Network stage to compute deltas across // an iperf window — a rising rx_errors or tx_dropped during a loaded // link is a real NIC problem, not general noise. type NetDevSnapshot struct { Iface string RxBytes uint64 RxErrs uint64 RxDrop uint64 TxBytes uint64 TxErrs uint64 TxDrop uint64 } // NetDev reads /proc/net/dev and returns one snapshot per non-loopback // interface. Returns nil on read/parse failure (best-effort: a missing // /proc is survivable; the caller skips delta reporting that tick). func NetDev() []NetDevSnapshot { f, err := os.Open("/proc/net/dev") if err != nil { return nil } defer func() { _ = f.Close() }() return parseNetDev(f) } // parseNetDev is split from NetDev so tests can feed a fixture without // touching the real /proc. The /proc/net/dev format is two header lines // followed by rows of "iface: rx_bytes rx_packets rx_errs rx_drop ... tx_bytes tx_packets tx_errs tx_drop ..." // — 16 whitespace-separated counters, of which we pull a curated six. func parseNetDev(r io.Reader) []NetDevSnapshot { var out []NetDevSnapshot sc := bufio.NewScanner(r) // Skip the two header lines (iface || bytes ... || bytes ...). for i := 0; i < 2 && sc.Scan(); i++ { } for sc.Scan() { line := strings.TrimSpace(sc.Text()) if line == "" { continue } colon := strings.IndexByte(line, ':') if colon < 0 { continue } iface := strings.TrimSpace(line[:colon]) if iface == "" || iface == "lo" { continue } fields := strings.Fields(line[colon+1:]) if len(fields) < 16 { continue } // /proc/net/dev columns: // 0 rx_bytes 1 rx_packets 2 rx_errs 3 rx_drop 4 fifo 5 frame 6 compressed 7 multicast // 8 tx_bytes 9 tx_packets 10 tx_errs 11 tx_drop 12 fifo 13 colls 14 carrier 15 compressed snap := NetDevSnapshot{Iface: iface} snap.RxBytes = parseU64(fields[0]) snap.RxErrs = parseU64(fields[2]) snap.RxDrop = parseU64(fields[3]) snap.TxBytes = parseU64(fields[8]) snap.TxErrs = parseU64(fields[10]) snap.TxDrop = parseU64(fields[11]) out = append(out, snap) } return out } func parseU64(s string) uint64 { n, err := strconv.ParseUint(s, 10, 64) if err != nil { return 0 } return n }