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.