summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fsbm.go145
-rw-r--r--go.mod3
2 files changed, 148 insertions, 0 deletions
diff --git a/fsbm.go b/fsbm.go
new file mode 100644
index 0000000..e26faf3
--- /dev/null
+++ b/fsbm.go
@@ -0,0 +1,145 @@
+package main
+
+import "fmt"
+import "bufio"
+import "flag"
+import "time"
+import "os"
+import "strings"
+import "strconv"
+
+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)
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..970b44e
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module fsbm-go
+
+go 1.19