Something went wrong. Try again.
A lightweight home network monitor with a live dashboard for latency, packet loss, jitter, DNS & bandwidth. Runs on Pi, Docker, or any mach…
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127package monitor
import ( "math" "testing")
const pingMacOS = `PING 1.1.1.1 (1.1.1.1): 56 data bytes64 bytes from 1.1.1.1: icmp_seq=0 ttl=58 time=13.190 ms64 bytes from 1.1.1.1: icmp_seq=1 ttl=58 time=15.017 ms64 bytes from 1.1.1.1: icmp_seq=2 ttl=58 time=13.597 ms64 bytes from 1.1.1.1: icmp_seq=3 ttl=58 time=13.194 ms64 bytes from 1.1.1.1: icmp_seq=4 ttl=58 time=13.801 ms
--- 1.1.1.1 ping statistics ---5 packets transmitted, 5 packets received, 0.0% packet lossround-trip min/avg/max/stddev = 13.190/13.760/15.017/0.671 ms`
const pingLinux = `PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.64 bytes from 1.1.1.1: icmp_seq=1 ttl=57 time=10.0 ms64 bytes from 1.1.1.1: icmp_seq=2 ttl=57 time=14.0 ms64 bytes from 1.1.1.1: icmp_seq=3 ttl=57 time=12.0 ms
--- 1.1.1.1 ping statistics ---3 packets transmitted, 3 received, 0% packet loss, time 402msrtt min/avg/max/mdev = 10.000/12.000/14.000/1.633 ms`
const pingBusyBox = `PING 1.1.1.1 (1.1.1.1): 56 data bytes64 bytes from 1.1.1.1: seq=0 ttl=63 time=15.662 ms64 bytes from 1.1.1.1: seq=1 ttl=63 time=14.958 ms64 bytes from 1.1.1.1: seq=2 ttl=63 time=23.532 ms64 bytes from 1.1.1.1: seq=3 ttl=63 time=25.083 ms64 bytes from 1.1.1.1: seq=4 ttl=63 time=23.205 ms
--- 1.1.1.1 ping statistics ---5 packets transmitted, 5 packets received, 0% packet lossround-trip min/avg/max = 14.958/20.488/25.083 ms`
const pingMacOSAllLost = `PING 10.255.255.1 (10.255.255.1): 56 data bytesRequest timeout for icmp_seq 0Request timeout for icmp_seq 1
--- 10.255.255.1 ping statistics ---2 packets transmitted, 0 packets received, 100.0% packet loss`
const pingWindows = "Pinging 192.168.1.1 with 32 bytes of data:\r\n" + "Reply from 192.168.1.1: bytes=32 time<1ms TTL=64\r\n" + "Reply from 192.168.1.1: bytes=32 time=3ms TTL=64\r\n" + "Request timed out.\r\n" + "Reply from 192.168.1.1: bytes=32 time=2ms TTL=64\r\n\r\n" + "Ping statistics for 192.168.1.1:\r\n" + " Packets: Sent = 4, Received = 3, Lost = 1 (25% loss),\r\n" + "Approximate round trip times in milli-seconds:\r\n" + " Minimum = 0ms, Maximum = 3ms, Average = 2ms\r\n"
func TestParsePingUnix(t *testing.T) { tests := []struct { name string text string avg, jitter, loss float64 wantErr bool }{ {"macOS", pingMacOS, 13.76, 1.064, 0, false}, {"linux", pingLinux, 12, 3, 0, false}, {"busybox", pingBusyBox, 20.488, 3.177, 0, false}, {"all lost", pingMacOSAllLost, 0, 0, 100, false}, {"unknown host macOS", "ping: cannot resolve no-such-host.invalid: Unknown host\n", 0, 0, 0, true}, {"unknown host busybox", "ping: bad address 'no-such-host.invalid'\n", 0, 0, 0, true}, {"empty", "", 0, 0, 0, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { avg, jitter, loss, err := parsePingUnix(tt.text) if (err != nil) != tt.wantErr { t.Fatalf("err = %v, wantErr %v", err, tt.wantErr) } assertNear(t, "avg", avg, tt.avg) assertNear(t, "jitter", jitter, tt.jitter) assertNear(t, "loss", loss, tt.loss) }) }}
func TestParsePingWindows(t *testing.T) { avg, jitter, loss, err := parsePingWindows(pingWindows) if err != nil { t.Fatal(err) } assertNear(t, "avg", avg, 2) assertNear(t, "jitter", jitter, 1.5) assertNear(t, "loss", loss, 25)
if _, _, _, err := parsePingWindows("Ping request could not find host foo. Please check the name and try again.\r\n"); err == nil { t.Error("expected error for unknown host") }}
func TestReplyJitter(t *testing.T) { tests := []struct { name string text string want float64 }{ {"no replies", "Request timeout for icmp_seq 0\n", 0}, {"single reply", "64 bytes from 1.1.1.1: icmp_seq=0 ttl=58 time=13.1 ms\n", 0}, {"summary line ignored", "3 packets transmitted, 3 received, 0% packet loss, time 402ms\n", 0}, {"constant", "time=10 ms\ntime=10 ms\ntime=10 ms\n", 0}, {"alternating", "time=10 ms\ntime=20 ms\ntime=10 ms\n", 10}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assertNear(t, "jitter", replyJitter(tt.text), tt.want) }) }}
func assertNear(t *testing.T, name string, got, want float64) { t.Helper() if math.Abs(got-want) > 0.01 { t.Errorf("%s = %.3f, want %.3f", name, got, want) }}