package main import ( "bufio" "flag" "fmt" "os" "strconv" "strings" "time" ) type Iface struct { Name string RxBytes uint64 RxPackets uint64 RxErrs uint64 TxBytes uint64 TxPackets uint64 TxErrs uint64 } // human readable bits 'n bytes per second // assumes bytes input. func humanBps(bps float64, raw bool, bits bool) string { prefixes := []string{"", "k", "M", "G", "T", "P", "E", "Z", "Y"} unit := "B/s" if bits { bps *= 8 unit = "b/s" } if raw { return fmt.Sprintf("%.2f", bps) } prefix := 0 for prefix+1 < len(prefixes) && bps >= 1000 { bps /= 1000 prefix++ } return fmt.Sprintf("%.2f %s%s", bps, prefixes[prefix], unit) } func main() { bitFlag := flag.Bool("b", false, "render bits not bytes") intervalFlag := flag.String("d", "1s", "update interval (e.g. 500ms, 1s, 2s)") pathFlag := flag.String("f", "/proc/net/dev", "path") interfaceFlag := flag.String("i", "", "interface to report on") rawFlag := flag.Bool("r", false, "show raw values, not human") versionFlag := flag.Bool("V", false, "show version information") flag.Parse() if *versionFlag { fmt.Println("fsbm-go version 1.0") os.Exit(0) } interval, err := time.ParseDuration(*intervalFlag) if err != nil { fmt.Fprintf(os.Stderr, "invalid duration (%s): %v", *intervalFlag, err) os.Exit(1) } if _, err := os.Stat(*pathFlag); err != nil { fmt.Fprintf(os.Stderr, "cannot access %s: %v\n", *pathFlag, err) os.Exit(1) } limitIfaces := false requestedIfaces := make(map[string]struct{}) if *interfaceFlag != "" { for _, name := range strings.Split(*interfaceFlag, ",") { requestedIfaces[name] = struct{}{} } if len(requestedIfaces) > 0 { limitIfaces = true } } ifaces := make(map[string]Iface) var prevTime time.Time for { openTime := time.Now() file, err := os.Open(*pathFlag) // For read access. if err != nil { fmt.Fprintf(os.Stderr, "cannot access %s: %v\n", *pathFlag, err) os.Exit(1) } scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) name, rest, found := strings.Cut(line, ":") if !found { continue } if limitIfaces { _, ok := requestedIfaces[name] if !ok { continue } } fields := strings.Fields(rest) if len(fields) != 16 { fmt.Fprintf(os.Stderr, "cannot parse %s", line) os.Exit(1) } cur := Iface{ Name: name, } cur.RxBytes, _ = strconv.ParseUint(fields[0], 10, 64) // RxBytes uint64 cur.RxPackets, _ = strconv.ParseUint(fields[1], 10, 64) // RxPackets uint64 cur.RxErrs, _ = strconv.ParseUint(fields[2], 10, 64) // RxErrs uint64 cur.TxBytes, _ = strconv.ParseUint(fields[8], 10, 64) // TxBytes uint64 cur.TxPackets, _ = strconv.ParseUint(fields[9], 10, 64) // TxPackets uint64 cur.TxErrs, _ = strconv.ParseUint(fields[10], 10, 64) // TxErrs uint64 prev, exists := ifaces[name] if exists { // deltas timeDiff := openTime.Sub(prevTime).Seconds() rxRate := float64(cur.RxBytes-prev.RxBytes) / timeDiff txRate := float64(cur.TxBytes-prev.TxBytes) / timeDiff fmt.Printf("%s: (%s, %s) ", name, humanBps(rxRate, *rawFlag, *bitFlag), humanBps(txRate, *rawFlag, *bitFlag)) } ifaces[name] = cur } fmt.Printf("\n") file.Close() prevTime = openTime time.Sleep(interval) } }