Skip to content

Releases: jamesccupps/PingerPlot

PingerPlot 1.3.2

Choose a tag to compare

@github-actions github-actions released this 21 Aug 18:44

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

Choose a tag to compare

@github-actions github-actions released this 21 Aug 17:30

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
    1. 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.

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

Choose a tag to compare

@jamesccupps jamesccupps released this 21 Aug 16:59

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_hops defaults 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 value json cannot encode, from the window-close handler.
  • Writing to the probe log during shutdown raised ValueError, not OSError, 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 comparisonFile → Compare with saved session…, or --baseline in 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 N collects 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, the mtr technique, 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 with python -m pingerplot.selftest 8.8.8.8 before 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_MS branch 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

Choose a tag to compare

@jamesccupps jamesccupps released this 04 Jun 19:39

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.json writes a starter config; python -m pingerplot.headless monitor.json runs 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 a pingerplot-headless command.

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)

  1. Download Source code (zip) below and extract it anywhere.
  2. 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

Choose a tag to compare

@jamesccupps jamesccupps released this 04 Jun 18:04

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

Choose a tag to compare

@jamesccupps jamesccupps released this 04 Jun 17:45

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%\PingerPlot and 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 alertsEngine → 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 pingerplot entry point; CI least-privilege (permissions: contents: read).

Install / run (no Git needed)

  1. Download Source code (zip) below and extract it anywhere.
  2. Double-click PingerPlot.vbs — no console window, no build, no pip 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

Choose a tag to compare

@jamesccupps jamesccupps released this 04 Jun 16:29

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)

  1. Download Source code (zip) below and extract it anywhere.
  2. Double-click PingerPlot.vbs — launches with no console window. No build, no pip 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.