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.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091package monitor
import ( "net" "os/exec" "regexp" "runtime" "strconv" "time")
type pingResult struct { Host string IP string AvgMs float64 JitterMs float64 PacketLoss float64 Err error}
// runPing pings target count times and returns avg RTT, jitter, and packet loss.// Works on Linux, macOS, and Windows.func runPing(target string, count int) (avg, jitter, loss float64, err error) { var cmd *exec.Cmd if runtime.GOOS == "windows" { cmd = exec.Command("ping", "-n", strconv.Itoa(count), target) } else { cmd = exec.Command("ping", "-c", strconv.Itoa(count), "-i", "0.2", target) } out, _ := cmd.CombinedOutput() text := string(out)
if runtime.GOOS == "windows" { return parsePingWindows(text) } return parsePingUnix(text)}
// parsePingUnix handles Linux (mdev) and macOS (stddev) ping output.func parsePingUnix(text string) (avg, jitter, loss float64, err error) { lossRe := regexp.MustCompile(`([\d.]+)% packet loss`) if m := lossRe.FindStringSubmatch(text); m != nil { loss, _ = strconv.ParseFloat(m[1], 64) } // macOS: min/avg/max/stddev — Linux: min/avg/max/mdev rttRe := regexp.MustCompile(`min/avg/max/\w+ = [\d.]+/([\d.]+)/[\d.]+/([\d.]+)`) if m := rttRe.FindStringSubmatch(text); m != nil { avg, _ = strconv.ParseFloat(m[1], 64) jitter, _ = strconv.ParseFloat(m[2], 64) } return}
// parsePingWindows handles Windows ping output.// Windows doesn't report stddev; jitter is approximated as (max-min)/2.func parsePingWindows(text string) (avg, jitter, loss float64, err error) { lossRe := regexp.MustCompile(`\((\d+)% loss\)`) if m := lossRe.FindStringSubmatch(text); m != nil { loss, _ = strconv.ParseFloat(m[1], 64) } avgRe := regexp.MustCompile(`Average = (\d+)ms`) minRe := regexp.MustCompile(`Minimum = (\d+)ms`) maxRe := regexp.MustCompile(`Maximum = (\d+)ms`) if m := avgRe.FindStringSubmatch(text); m != nil { avg, _ = strconv.ParseFloat(m[1], 64) } var minMs, maxMs float64 if m := minRe.FindStringSubmatch(text); m != nil { minMs, _ = strconv.ParseFloat(m[1], 64) } if m := maxRe.FindStringSubmatch(text); m != nil { maxMs, _ = strconv.ParseFloat(m[1], 64) } jitter = (maxMs - minMs) / 2 return}
func resolveIP(host string) string { ips, err := net.LookupIP(host) if err != nil || len(ips) == 0 { return "" } return ips[0].String()}
func measureDNS(host string) (time.Duration, error) { start := time.Now() _, err := net.LookupHost(host) return time.Since(start), err}