package main import ( "bufio" "os" "strconv" "strings" "sync" "time" ) type NetworkMetrics struct { Interface string `json:"interface"` ReceiveBytes uint64 `json:"receiveBytes"` TransmitBytes uint64 `json:"transmitBytes"` ReceivePerSec float64 `json:"receivePerSec"` TransmitPerSec float64 `json:"transmitPerSec"` Interfaces []NetworkInterface `json:"interfaces"` } type NetworkInterface struct { Name string `json:"name"` ReceiveBytes uint64 `json:"receiveBytes"` TransmitBytes uint64 `json:"transmitBytes"` ReceiveErrors uint64 `json:"receiveErrors"` TransmitErrors uint64 `json:"transmitErrors"` ReceiveDrops uint64 `json:"receiveDrops"` TransmitDrops uint64 `json:"transmitDrops"` ReceivePerSec float64 `json:"receivePerSec"` TransmitPerSec float64 `json:"transmitPerSec"` VMID int `json:"vmid,omitempty"` GuestName string `json:"guestName,omitempty"` GuestType string `json:"guestType,omitempty"` } type networkSnapshot struct { at time.Time received uint64 sent uint64 } type networkCollector struct { mu sync.Mutex path string previous networkSnapshot previousInterfaces map[string]NetworkInterface } func newNetworkCollector(path string) (*networkCollector, error) { snapshot, _, _, err := readNetworkSnapshot(path) if err != nil { return nil, err } _, _, details, _ := readNetworkSnapshot(path) previous := map[string]NetworkInterface{} for _, v := range details { previous[v.Name] = v } return &networkCollector{path: path, previous: snapshot, previousInterfaces: previous}, nil } func (c *networkCollector) collect() (NetworkMetrics, error) { c.mu.Lock() defer c.mu.Unlock() current, interfaceNames, details, err := readNetworkSnapshot(c.path) if err != nil { return NetworkMetrics{}, err } elapsed := current.at.Sub(c.previous.at).Seconds() var receivedRate, sentRate float64 if elapsed > 0 && current.received >= c.previous.received && current.sent >= c.previous.sent { receivedRate = float64(current.received-c.previous.received) / elapsed sentRate = float64(current.sent-c.previous.sent) / elapsed } for i := range details { v := &details[i] if old, ok := c.previousInterfaces[v.Name]; ok && elapsed > 0 && v.ReceiveBytes >= old.ReceiveBytes && v.TransmitBytes >= old.TransmitBytes { v.ReceivePerSec = float64(v.ReceiveBytes-old.ReceiveBytes) / elapsed v.TransmitPerSec = float64(v.TransmitBytes-old.TransmitBytes) / elapsed } } c.previousInterfaces = map[string]NetworkInterface{} for _, v := range details { c.previousInterfaces[v.Name] = v } c.previous = current return NetworkMetrics{ Interface: strings.Join(interfaceNames, ", "), Interfaces: details, ReceiveBytes: current.received, TransmitBytes: current.sent, ReceivePerSec: receivedRate, TransmitPerSec: sentRate, }, nil } func readNetworkSnapshot(path string) (networkSnapshot, []string, []NetworkInterface, error) { file, err := os.Open(path) if err != nil { return networkSnapshot{}, nil, nil, err } defer file.Close() var received, sent uint64 var interfaces []string var details []NetworkInterface scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) name, values, found := strings.Cut(line, ":") if !found || strings.TrimSpace(name) == "lo" { continue } fields := strings.Fields(values) if len(fields) < 9 { continue } rx, errRX := strconv.ParseUint(fields[0], 10, 64) tx, errTX := strconv.ParseUint(fields[8], 10, 64) if errRX == nil && errTX == nil { received += rx sent += tx interfaces = append(interfaces, strings.TrimSpace(name)) rxErr, _ := strconv.ParseUint(fields[2], 10, 64) rxDrop, _ := strconv.ParseUint(fields[3], 10, 64) txErr, _ := strconv.ParseUint(fields[10], 10, 64) txDrop, _ := strconv.ParseUint(fields[11], 10, 64) details = append(details, NetworkInterface{Name: strings.TrimSpace(name), ReceiveBytes: rx, TransmitBytes: tx, ReceiveErrors: rxErr, ReceiveDrops: rxDrop, TransmitErrors: txErr, TransmitDrops: txDrop}) } } return networkSnapshot{at: time.Now(), received: received, sent: sent}, interfaces, details, scanner.Err() }