Releases: jamesccupps/PingerPlot
Release list
PingerPlot 1.3.2
PingerPlot 1.3.2
Acts on an independent third-party audit of 1.3.1. All fifteen findings are
addressed. No new features; the theme is that several ways the app could stop
doing its job did so silently, and now say so.
Fixed
- One malformed geo reply killed the lookup worker permanently, and the Map
tab stopped resolving with no error and no retry. - A probe log that could not be opened was disabled silently: the error was
written to the status line, which the worker overwrites milliseconds later. - Headless never checked whether the ICMP backend works, so an unusable
backend read as "destination never answered" and pointed the operator at
the network instead of at a sysctl. - --baseline could crash a scheduled job after printing its report, and skip
--report-csv, by reading a live monitor's hop list without the lock. - On POSIX a source IP the machine does not hold raised a bare errno instead
of being rejected by name -- and inside a round it read as 100% packet loss
on a path that was fine. - Loading a session left a permanently dead row in the Targets grid, rewritten
on every save. - The UDP port walk could wrap to 0 and silently lose a hop from the round.
- socket.SIO_RCVALL is Windows-only and escaped every caller's except OSError.
- Alert clearing logged, beeped and fired the webhook while holding the lock.
- The geo response body is now read with a 64 KB bound.
Changed
- The release workflow no longer interpolates ${{ }} into a shell script, and
validates the tag by character set as well as shape. Actions are pinned by
commit SHA, with Dependabot to keep them current. - The Engine dialog explains that UDP traceroute walks the destination port.
Notes
- 370 -> 424 tests. gui.py is under test for the first time, via a real
gui.App under xvfb on Linux and a withdrawn root on Windows. - One test depended on what the LAN happens to do; it is now deterministic.
- Every fix was verified by reverting it and watching its test go red.
PingerPlot 1.3.1
PingerPlot 1.3.1
Adds downloadable Windows executables, and fixes a config-file bug found while
building them.
Added
- Windows executables attached to the release, built by CI from this commit:
PingerPlot.exe (GUI) and pingerplot-headless.exe (console), plus
SHA256SUMS.txt. Neither needs Python installed. Both are smoke-tested before
they are attached: the GUI binary's import graph is verified through a new
--version flag, and the headless binary runs a real ICMP probe. They are
UNSIGNED -- see the README for what SmartScreen and antivirus heuristics will
make of that, and why this binary is a worse-than-average case for the latter. - main.py --version.
Fixed
- A byte-order mark made a config unreadable. Writing monitor.json with
PowerShell's Set-Content -Encoding utf8 -- the shell that ships with Windows
-- produced a file the app refused outright with "Unexpected UTF-8 BOM", exit- Files a person may have authored are now read as utf-8-sig. The settings
case was the quietest of these: load() swallows the decode error and returns
{}, so a BOM there silently reset the theme, engine options, alert thresholds
and target list to defaults with no message at all.
- Files a person may have authored are now read as utf-8-sig. The settings
Changed
- CI opens net.ipv4.ping_group_range on the Linux runners. Without it the POSIX
backend's live tests skipped on every ubuntu leg, so the backend shipped in
1.3.0 having never executed a single socket operation anywhere. They now run
against real sockets on real Linux. A real multi-hop path remains untested.
PingerPlot 1.3.0
A full audit pass plus four features.
Two fixes change results you may have relied on — see Behaviour changes before comparing old exports with new ones.
Fixed
- A route longer than 16 hops took two or more reply timeouts per round. The probe pool was fixed at 16 workers while
max_hopsdefaults to 30 and may reach 64, so a round silently split into waves — measured 2.0× at 30 hops, 4.0× at 64. With a 2 s timeout a 30-hop round overran the 2.5 s interval entirely, and the configured interval was quietly replaced by the round duration. Now 1.0× at every length. - An alert never cleared once a reroute changed the path length. Alerts are keyed by TTL and only the destination's TTL was evaluated, so the old key was orphaned: the banner, the active-alert count and the summary row stayed wrong for the rest of the run — reporting loss on a hop that no longer existed while the real destination was healthy.
- A closed TCP port read as an unreachable host. Windows does not surface the RST until it has finished retransmitting the SYN (~2 s measured), and the default reply timeout was 1000 ms — so "the service is down but the box is fine" came back as "host unreachable". TCP mode now floors the reply timeout at 3000 ms and logs that it did.
settings.save()could raise on a valuejsoncannot encode, from the window-close handler.- Writing to the probe log during shutdown raised
ValueError, notOSError, so it escaped the handler and aborted the round as "Monitor error". - The Map tab never repainted when geo lookups returned — on a loaded session it stayed empty until the window was resized.
- The comparison table crashed a cp1252 console (a delta sign in the header).
- A router's identity could be discarded when its ICMP error and the probe socket's error arrived in the same
select()wakeup.
Added
- Baseline comparison — File → Compare with saved session…, or
--baselinein headless mode. A hop whose responding address changed is reported as rerouted with no latency delta, because subtracting one router's latency from another's is a meaningless number. - DSCP marking and source-interface binding — measure a QoS-marked path as itself rather than as best-effort, and pin the outgoing NIC on a multi-homed box. A source address the machine does not hold is rejected outright rather than quietly falling back.
- MOS alerting — on the score that folds latency, jitter and loss together, so it catches the combination that ruins a call while each ingredient sits under its own threshold.
- One-shot report mode —
--report Ncollects N rounds, prints an MTR-style table per target and exits. Exit status is 0 only if every target reached its destination, so a scheduled job can branch on it. - Linux and macOS ICMP backend — unprivileged
SOCK_DGRAM+IP_RECVERR/MSG_ERRQUEUE, themtrtechnique, with a separate path for macOS which has neither. Unit-tested on every platform in CI; not yet field-tested on real hardware — smoke-test withpython -m pingerplot.selftest 8.8.8.8before trusting a trace from it. - Automatic restart of a stopped target in headless mode, backing off 30 s → 5 min. A target whose name did not resolve at boot used to stay dead for the life of the service.
Behaviour changes
- Latency alone can now colour a hop row red. The
BAD_MSbranch was dead code, so a 900 ms hop looked no more alarming than a 130 ms one. - TCP reply timeouts below 3000 ms are raised, with a note in the Events tab. An unreachable TCP destination now costs 3 s a round rather than 1 s — the trade for being able to tell "service down, host fine" from "host unreachable".
Notes
Tests 95 → 343. Engine coverage 62% → 81%; tcpudp.py, which had no coverage of either probe round loop, went 25% → 78%. The TCP/UDP rounds are now exercised against a mock router feeding a stand-in capture socket, so select(), recvfrom() and the timeouts are the real code paths.
That the DSCP byte reaches the wire has not been verified — every code point is accepted without error, which is a weaker claim. Confirm with a capture before relying on a QoS result.
Full detail in CHANGELOG.md.
PingerPlot 1.2.0
PingerPlot 1.2.0 — headless mode + reliability.
The big one: PingerPlot now runs without a GUI, so you can monitor 24/7 from a server or a VLAN-probe host under Task Scheduler. Plus correctness/robustness fixes and the snappier UI from 1.1.1. Still pure Python standard library — zero third-party dependencies.
New
- Headless / service mode.
python -m pingerplot.headless --init monitor.jsonwrites a starter config;python -m pingerplot.headless monitor.jsonruns it (Ctrl-C to stop). A JSON config drives N targets (defaults + per-target overrides), each logging to its own crash-safe, rotating CSV, with periodic status lines to the console. Built for Windows Task Scheduler;pip install .also adds apingerplot-headlesscommand.
Reliability & correctness
- Fixed a TCP-traceroute bug: in elevated full-traceroute mode a hop that returned a hard unreachable could vanish from the round and be logged as a plain timeout. It now always records a definite result.
- Session saves are atomic (temp + replace) — a crash mid-write can't truncate the file.
- Session load caps hops and samples against a crafted/corrupt file.
- Faster UI (from 1.1.1): per-tick CPU ~30 ms → ~2–3 ms; the sidebar is ~97× cheaper with several targets, so it stays responsive as you add more.
Tested
95 tests across Windows + Linux × Python 3.10–3.13, including new coverage for the route grow/shrink logic and the geo-IP resolver.
Install / run (no Git needed)
- Download Source code (zip) below and extract it anywhere.
- GUI: double-click
PingerPlot.vbs. Headless:python -m pingerplot.headless --init monitor.json.
Requires Windows 10/11 and Python 3.10+. Full TCP/UDP traceroute needs an elevated session; ICMP works as a normal user. The headless engine runs anywhere Python does.
Full changelog: v1.1.1...v1.2.0
PingerPlot 1.1.1
PingerPlot 1.1.1 — performance.
A focused speedup of the live UI. The 700 ms refresh was doing ~30 ms of pure-Python stats work each tick — holding the GIL against the probe threads — which made the app feel sluggish, and worse the more targets you watched. Now ~2–3 ms.
- Per-hop stats (loss / avg / min / max / jitter) are computed in a single pass instead of ~5 —
snapshot()~4× faster, the sidebar grid ~97× faster with several targets. - The sidebar reads only the destination hop's stats instead of every hop of every monitor.
- Redundant canvas redraws are skipped on ticks where nothing changed (resize / tab-switch still redraw).
No feature or behavior changes. 75 tests green on Windows + Linux, Python 3.10–3.13.
Install / run (no Git needed)
Download Source code (zip) below, extract anywhere, double-click PingerPlot.vbs. No build, no pip install.
Full changelog: v1.1.0...v1.1.1
PingerPlot 1.1.0
PingerPlot 1.1.0 — the "leave it running" release.
Quality-of-life and operational features for monitoring you actually leave running, plus reliability fixes from a full code audit. Still pure Python standard library — zero third-party dependencies.
New
- Persists between launches — theme, engine options, alert thresholds, and your target list save to
%APPDATA%\PingerPlotand restore on start. View → Resume targets on launch re-arms the last session's monitors. - Pause / Resume — right-click a target to halt probing without losing history; resume continues where it left off.
- Per-target settings — each target keeps its own engine/alert config; right-click → Edit settings… to inspect or change just that one.
- Copy — right-click a hop to copy its IP or hostname.
- Webhook alerts — Engine → Webhook URL gets a JSON POST (http/https) when the destination alert raises or clears. Fired off-thread, so it never blocks probing.
- Custom app icon in place of the default Tk feather.
Reliability & hardening
- Probe log is size-capped with rollover (~25 MB, one backup) — unattended multi-week runs can't fill the disk.
- Fixed a monitor restart race (a slow worker from a previous run could write stale probes into a freshly restarted one) and made the engine reusable after shutdown.
- Session load clamps sample history so a crafted/corrupt file can't exhaust memory.
python -m pingerplotentry point; CI least-privilege (permissions: contents: read).
Install / run (no Git needed)
- Download Source code (zip) below and extract it anywhere.
- Double-click
PingerPlot.vbs— no console window, no build, nopip install.
Requires Windows 10/11 and Python 3.10+ (Tkinter ships with the python.org installer). Full TCP/UDP traceroute needs an elevated session; ICMP and TCP final-hop work as a normal user.
Full changelog: v1.0.0...v1.1.0
PingerPlot 1.0.0
PingerPlot 1.0.0 — first public release.
A continuous-traceroute network path monitor for Windows (MTR-style): it discovers every hop to a target, then re-probes the whole path on an interval, so you get per-hop latency, jitter, and packet loss over time — making it obvious where a problem lives (your LAN, your ISP, a peering point, or the destination).
Highlights
- Continuous per-hop monitoring with a live latency graph and an all-hops timeline
- Multi-target sidebar showing loss / latency / MOS per target at a glance
- Sustained-degradation alerts (loss or latency over a window) with banner + beep
- MOS (VoIP quality) score for the destination
- ICMP / TCP / UDP probe modes — ICMP needs no admin (Windows IP Helper API)
- Session save/load, crash-safe CSV logging, optional world-map view
- Pure Python standard library — zero third-party dependencies
Install / run (no Git needed)
- Download Source code (zip) below and extract it anywhere.
- Double-click
PingerPlot.vbs— launches with no console window. No build, nopip install.
Requires Windows 10/11 and Python 3.10+ (Tkinter ships with the python.org installer). Full TCP/UDP traceroute needs an elevated session; ICMP and TCP final-hop work as a normal user.
See the README for full details.