package probe import ( "bufio" "bytes" "context" "errors" "fmt" "os" "strconv" "strings" ) const netDevPath = "/proc/net/dev" // InterfaceStats contains received and transmitted byte counters. type InterfaceStats struct { Name string RXBytes uint64 TXBytes uint64 } // ReadInterfaceStats reads byte counters for a network interface. func ReadInterfaceStats(ctx context.Context, name string) (InterfaceStats, error) { if err := ctx.Err(); err != nil { return InterfaceStats{}, fmt.Errorf("checking context before reading interface stats: %w", err) } data, err := os.ReadFile(netDevPath) if err != nil { return InterfaceStats{}, fmt.Errorf("reading %s: %w", netDevPath, err) } if err := ctx.Err(); err != nil { return InterfaceStats{}, fmt.Errorf("checking context after reading interface stats: %w", err) } stats, err := parseNetDev(data, name) if err != nil { return InterfaceStats{}, err } return stats, nil } func parseNetDev(data []byte, name string) (InterfaceStats, error) { scanner := bufio.NewScanner(bytes.NewReader(data)) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if !strings.Contains(line, ":") { continue } parts := strings.SplitN(line, ":", 2) if strings.TrimSpace(parts[0]) != name { continue } fields := strings.Fields(parts[1]) if len(fields) < 16 { return InterfaceStats{}, fmt.Errorf("parsing interface %s stats: expected at least 16 fields, got %d", name, len(fields)) } rxBytes, err := strconv.ParseUint(fields[0], 10, 64) if err != nil { return InterfaceStats{}, fmt.Errorf("parsing interface %s rx bytes: %w", name, err) } txBytes, err := strconv.ParseUint(fields[8], 10, 64) if err != nil { return InterfaceStats{}, fmt.Errorf("parsing interface %s tx bytes: %w", name, err) } return InterfaceStats{Name: name, RXBytes: rxBytes, TXBytes: txBytes}, nil } if err := scanner.Err(); err != nil { return InterfaceStats{}, fmt.Errorf("scanning %s contents: %w", netDevPath, err) } return InterfaceStats{}, errors.New("interface not found") }