github.com/castai/kvisor@v1.7.1-0.20240516114728-b3572a2607b5/cmd/agent/daemon/netstats/netstats.go (about) 1 package netstats 2 3 import ( 4 "bufio" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strconv" 9 "strings" 10 ) 11 12 type InterfaceStats struct { 13 // The name of the interface. 14 Name string `json:"name"` 15 // Cumulative count of bytes received. 16 RxBytes uint64 `json:"rx_bytes"` 17 // Cumulative count of packets received. 18 RxPackets uint64 `json:"rx_packets"` 19 // Cumulative count of receive errors encountered. 20 RxErrors uint64 `json:"rx_errors"` 21 // Cumulative count of packets dropped while receiving. 22 RxDropped uint64 `json:"rx_dropped"` 23 // Cumulative count of bytes transmitted. 24 TxBytes uint64 `json:"tx_bytes"` 25 // Cumulative count of packets transmitted. 26 TxPackets uint64 `json:"tx_packets"` 27 // Cumulative count of transmit errors encountered. 28 TxErrors uint64 `json:"tx_errors"` 29 // Cumulative count of packets dropped while transmitting. 30 TxDropped uint64 `json:"tx_dropped"` 31 } 32 33 func NewReader(procDir string) *Reader { 34 return &Reader{procDir: procDir} 35 } 36 37 type Reader struct { 38 procDir string 39 } 40 41 func (r *Reader) Read(pid uint32) ([]InterfaceStats, error) { 42 netStatsFile := filepath.Join(r.procDir, strconv.Itoa(int(pid)), "/net/dev") 43 44 ifaceStats, err := scanInterfaceStats(netStatsFile) 45 if err != nil { 46 return []InterfaceStats{}, fmt.Errorf("reading network stats: %w", err) 47 } 48 49 return ifaceStats, nil 50 } 51 52 func scanInterfaceStats(netStatsFile string) ([]InterfaceStats, error) { 53 file, err := os.Open(netStatsFile) 54 if err != nil { 55 return nil, fmt.Errorf("opening %s: %w", netStatsFile, err) 56 } 57 defer file.Close() 58 59 scanner := bufio.NewScanner(file) 60 61 // Discard header lines 62 for i := 0; i < 2; i++ { 63 if b := scanner.Scan(); !b { 64 return nil, scanner.Err() 65 } 66 } 67 68 var stats []InterfaceStats 69 for scanner.Scan() { 70 line := scanner.Text() 71 line = strings.Replace(line, ":", "", -1) 72 73 fields := strings.Fields(line) 74 // If the format of the line is invalid then don't trust any of the stats 75 // in this file. 76 if len(fields) != 17 { 77 return nil, fmt.Errorf("invalid interface stats line: %s", line) 78 } 79 80 devName := fields[0] 81 if isIgnoredDevice(devName) { 82 continue 83 } 84 85 i := InterfaceStats{ 86 Name: devName, 87 } 88 89 statFields := append(fields[1:5], fields[9:13]...) 90 statPointers := []*uint64{ 91 &i.RxBytes, &i.RxPackets, &i.RxErrors, &i.RxDropped, 92 &i.TxBytes, &i.TxPackets, &i.TxErrors, &i.TxDropped, 93 } 94 95 err := setInterfaceStatValues(statFields, statPointers) 96 if err != nil { 97 return nil, fmt.Errorf("cannot parse interface stats (%w): %v", err, line) 98 } 99 100 stats = append(stats, i) 101 } 102 103 return stats, nil 104 } 105 106 var ignoredDevicePrefixes = []string{"lo", "veth", "docker"} 107 108 func isIgnoredDevice(ifName string) bool { 109 for _, prefix := range ignoredDevicePrefixes { 110 if strings.HasPrefix(strings.ToLower(ifName), prefix) { 111 return true 112 } 113 } 114 return false 115 } 116 117 func setInterfaceStatValues(fields []string, pointers []*uint64) error { 118 for i, v := range fields { 119 val, err := strconv.ParseUint(v, 10, 64) 120 if err != nil { 121 return err 122 } 123 *pointers[i] = val 124 } 125 return nil 126 }