diff --git a/input.go b/input.go new file mode 100644 index 0000000..0a5cf8e --- /dev/null +++ b/input.go @@ -0,0 +1,48 @@ +package main + +import ( + "os" + + "golang.org/x/sys/unix" +) + +// Delivers a value every time space is pressed. Puts the terminal in +// non-canonical mode so the keypress arrives without waiting for a newline, +// and returns a function that restores the original settings. +func watchSpace() (<-chan struct{}, func()) { + ch := make(chan struct{}, 1) + restore := func() {} + fd := int(os.Stdin.Fd()) + + if orig, err := unix.IoctlGetTermios(fd, unix.TCGETS); err == nil { + saved := *orig + raw := *orig + raw.Lflag &^= unix.ICANON | unix.ECHO + raw.Cc[unix.VMIN] = 1 + raw.Cc[unix.VTIME] = 0 + if unix.IoctlSetTermios(fd, unix.TCSETS, &raw) == nil { + restore = func() { unix.IoctlSetTermios(fd, unix.TCSETS, &saved) } + } + } + + go func() { + buf := make([]byte, 64) + for { + n, err := os.Stdin.Read(buf) + if err != nil { + return + } + for _, b := range buf[:n] { + if b != ' ' { + continue + } + select { + case ch <- struct{}{}: + default: + } + } + } + }() + + return ch, restore +} diff --git a/main.go b/main.go index e66604d..196f557 100644 --- a/main.go +++ b/main.go @@ -45,10 +45,12 @@ type direction struct { rxFDs []int reports chan string - prev sample - drops uint64 - nicTX nicCounters - nicRX nicCounters + prev sample + drops uint64 + errBase sample + dropBase uint64 + nicTX nicCounters + nicRX nicCounters } type sample struct { @@ -121,6 +123,14 @@ func (d *direction) snapshot() sample { return s } +// Counters keep climbing in the workers, so resetting just moves the origin +// the display subtracts from. +func (d *direction) resetErrors() { + d.sampleDrops() + d.errBase = d.snapshot() + d.dropBase = d.drops +} + func (d *direction) sampleDrops() { for _, fd := range d.rxFDs { d.drops += packetDrops(fd) @@ -157,7 +167,13 @@ func (d *direction) intervalRow(elapsed time.Duration, secs, target float64) []s rxB := now.rxBytes - p.rxBytes d.sampleDrops() - total := now.lost + now.crcErr + now.badMagic + now.badLen + d.drops + b := d.errBase + lost := now.lost - b.lost + late := now.late - b.late + crc := now.crcErr - b.crcErr + badMagic := now.badMagic - b.badMagic + badLen := now.badLen - b.badLen + drops := d.drops - d.dropBase return []string{ uptime(elapsed), @@ -166,12 +182,12 @@ func (d *direction) intervalRow(elapsed time.Duration, secs, target float64) []s rateCell(gbps(txB, txF, secs), target), commas(uint64(float64(rxF) / secs)), rateCell(gbps(rxB, rxF, secs), target), - statusCell(now.lost), - statusCell(now.late), - statusCell(now.crcErr), - statusCell(now.badMagic), - statusCell(d.drops), - statusCell(total), + statusCell(lost), + statusCell(late), + statusCell(crc), + statusCell(badMagic), + statusCell(drops), + statusCell(lost + crc + badMagic + badLen + drops), } } @@ -405,7 +421,7 @@ func run(aName, bName, sizesArg, patArg string, humanBytes(uint64(sockBufSize(dirs[0].txFDs[0], unix.SO_SNDBUF))), humanBytes(uint64(sockBufSize(dirs[0].rxFDs[0], unix.SO_RCVBUF))))}, })) - fmt.Println(paint("rates are per interval; error counts are cumulative since start", cDim)) + fmt.Println(paint("rates are per interval; error counts are cumulative, press space to reset them", cDim)) fmt.Println() var doneTx, doneRx atomic.Bool @@ -423,6 +439,9 @@ func run(aName, bName, sizesArg, patArg string, sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + space, restoreTerm := watchSpace() + defer restoreTerm() + start := time.Now() close(startTx) tick := time.NewTicker(reportInterval) @@ -437,6 +456,12 @@ func run(aName, bName, sizesArg, patArg string, doneRx.Store(true) wg.Wait() return nil + case <-space: + for _, d := range dirs { + d.resetErrors() + } + stats.sinceHeader = 0 + fmt.Println(paint("error counts reset", cDim)) case now := <-tick.C: secs := now.Sub(last).Seconds() last = now