{"id":50978740,"url":"https://github.com/jamesccupps/pingerplot","last_synced_at":"2026-06-19T12:02:09.018Z","repository":{"id":362538347,"uuid":"1259510272","full_name":"jamesccupps/PingerPlot","owner":"jamesccupps","description":"Continuous-traceroute network path monitor for Windows (MTR-style): per-hop latency, packet loss, jitter, MOS, and alerts with live graphs. Pure Python standard library — zero dependencies.","archived":false,"fork":false,"pushed_at":"2026-06-04T18:42:57.000Z","size":251,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-04T19:09:48.411Z","etag":null,"topics":["icmp","latency","mtr","network-diagnostics","network-monitoring","open-source","ping","pingplotter","python","tkinter","traceroute","traceroute-gui","windows"],"latest_commit_sha":null,"homepage":"https://jamesccupps.github.io/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesccupps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-04T15:23:01.000Z","updated_at":"2026-06-04T18:43:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesccupps/PingerPlot","commit_stats":null,"previous_names":["jamesccupps/pingerplot"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/jamesccupps/PingerPlot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesccupps%2FPingerPlot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesccupps%2FPingerPlot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesccupps%2FPingerPlot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesccupps%2FPingerPlot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesccupps","download_url":"https://codeload.github.com/jamesccupps/PingerPlot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesccupps%2FPingerPlot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34530302,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-19T02:00:06.005Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["icmp","latency","mtr","network-diagnostics","network-monitoring","open-source","ping","pingplotter","python","tkinter","traceroute","traceroute-gui","windows"],"created_at":"2026-06-19T12:02:07.966Z","updated_at":"2026-06-19T12:02:09.010Z","avatar_url":"https://github.com/jamesccupps.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PingerPlot\n\n[![CI](https://github.com/jamesccupps/PingerPlot/actions/workflows/ci.yml/badge.svg)](https://github.com/jamesccupps/PingerPlot/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n![Python](https://img.shields.io/badge/python-3.10%2B-blue)\n![Platform](https://img.shields.io/badge/platform-Windows-0078D6)\n\nA continuous-traceroute network path monitor for Windows (MTR-style): per-hop\nlatency and packet-loss tracking with a live latency graph, multi-target summary,\nMOS scoring, alerts, session save/load, a world map, and ICMP/TCP/UDP probe modes.\nPure Python, **zero third-party dependencies**, dark-themed, HiDPI-aware. Free and Open Source.\n\n![PingerPlot — dark theme, multi-target sidebar, hop table and latency graph](docs/screenshot.png)\n\n## What it does\n\nLike WinMTR or `mtr`, it discovers every router between you and a target,\nthen re-probes the whole path on an interval. Each round is effectively a full\ntraceroute, so you accumulate **min / avg / max / current latency, jitter, and\npacket loss for every hop over time** — which makes it obvious *where* along the\npath a problem lives (your LAN, your ISP's first hop, a peering point, or the\ndestination).\n\n- **Route discovery** by walking the IP TTL upward and reading the\n  \"TTL exceeded\" replies (standard traceroute primitive).\n- **Continuous monitoring** of all hops, fanned out across a small thread pool\n  so each round takes ~one timeout, not the sum.\n- **Live hop table** — IP, reverse-DNS hostname, loss %, sent count,\n  current/avg/min/max latency, jitter. Rows colour amber/red on loss or high\n  latency.\n- **Single-hop latency graph** (Overview tab) for the selected hop (click a\n  row), with red ticks marking lost probes and a filled area for the trend.\n- **All-hops timeline** (Timeline tab) — every hop drawn as a stacked strip on\n  a shared time + latency scale, so a latency step shows up as the row where the\n  bars get tall. This is the full MTR-style path-over-time view.\n- **Continuous auto-retrace + route-change detection** — because every round is\n  a full traceroute, reroutes (a hop's address changing) and route length\n  grow/shrink are detected live and written to the Events tab.\n- **Sustained-degradation alerts** — a red banner + Windows beep + Event-log\n  entry when the **destination** shows sustained packet loss or high latency\n  over the last *N* probes. Evaluated on the destination only (the one hop whose\n  numbers aren't muddied by ICMP-deprioritising routers), and gated so a target\n  that simply blocks ICMP never false-alarms.\n- **ICMP / TCP / UDP probe modes** — trace with ICMP (default, no admin), or TCP\n  SYN to a port / UDP, for paths that block ICMP or to test a specific service.\n  See [Probe modes](#probe-modes) for the admin tradeoffs.\n- **Multi-target** — monitor many targets at once; a left **summary grid** shows\n  each one's hop count, loss, latency, and MOS at a glance. Click one to drive\n  the detail tabs. Alerts on any target surface in the banner.\n- **MOS (VoIP quality) score** for the destination — one number (ITU-T E-model)\n  for \"is this path good enough for voice/video\", shown live in the status bar.\n- **Session save / load** to JSON for offline review, plus **crash-safe\n  CSV logging** of every probe (with a per-round index, so \"did every hop spike\n  in the same round?\" is a one-line filter) — size-capped with rollover, so\n  unattended multi-week runs can't fill the disk.\n- **Engine options dialog** — packet type \u0026 port, reply timeout, payload size,\n  send delay (rate throttle), max hops, name resolution, final-hop-only.\n- **Dark theme by default**, with a light/dark toggle (button, top-right).\n- **HiDPI-aware** — crisp on scaled (150 %/200 %) displays.\n- **CSV export** of the per-hop summary (with destination MOS), under a\n  self-documenting header that records the interval, timeout, probe mode, packet\n  size, sample count, and the exact time window — so two exports can actually be\n  compared instead of guessed at.\n- **Pause / Resume \u0026 copy** — right-click a target to pause probing without\n  losing its history (resume picks up where it left off); right-click a hop to\n  copy its IP or hostname.\n- **Per-target settings** — engine and alert options are remembered per target;\n  right-click a target → *Edit settings…* to inspect or change just that one.\n- **Webhook alerts** — point the Engine dialog's *Webhook URL* at an http(s)\n  endpoint to get a JSON POST when the destination alert raises or clears.\n- **Persists between launches** — theme, engine options, alert thresholds and\n  your target list save to `%APPDATA%\\PingerPlot` and restore on start;\n  *View → Resume targets on launch* re-arms the last session's monitors.\n\n### Tabs and layout\n\nA left **Targets** summary grid lists every monitored target; selecting one\ndrives the detail tabs on the right:\n\n| Tab | Shows |\n|---|---|\n| **Overview** | Hop table (top) over a latency graph for the selected hop (bottom). Click a row to graph it; until you do, it follows the destination. |\n| **Timeline** | All hops stacked on a shared time axis — the path-over-time view. Scrolls when there are many hops. |\n| **Events** | Timestamped log of route changes, alert raises, and alert clears. |\n| **Map** | Geo-located hops plotted on a world map with the path between them. See the privacy note below. |\n\n### Map view \u0026 privacy\n\nThe Map tab geo-locates each **public** hop via [ipwho.is](https://ipwho.is)\n(free, HTTPS, no key) and plots the path on a baked-in Natural Earth coastline\n(no runtime dependency). This is the **only** part of the app that contacts a\nthird party, and it's **opt-in**: nothing is sent until you open the Map tab, and\n**private/LAN hops are never looked up** (they have no public location anyway).\nLookups are rate-limited and cached. If you'd rather not send hop IPs anywhere,\njust don't open the Map tab.\n\n### Alerts\n\nSet the thresholds in the second toolbar row: **loss ≥ X%**, **latency ≥ Y ms**,\nevaluated **over the last N probes** (the window is what makes it \"sustained\" —\na single spike won't trip a 20-probe window). Set a threshold to `0` to disable\nthat check. **Sound** toggles the beep. A condition raises when it crosses the\nthreshold and clears automatically when it recovers; both are logged.\n\n## How it works (and why no admin / no Npcap)\n\nICMP is sent through the Windows IP Helper API (`IcmpSendEcho` in\n`iphlpapi.dll`) via `ctypes`. That API runs in user space, so unlike raw-socket\nping/traceroute tools it needs **no administrator rights and no packet-capture\ndriver**. The TTL is set per-probe through `IP_OPTION_INFORMATION`, and an\nintermediate router that drops the packet answers with status\n`IP_TTL_EXPIRED_TRANSIT` plus its own address — that is the whole trick.\n\nIPv4, and the probe **backend** is Windows-only today because it's the Win32 ICMP\nAPI — but that's the *only* platform-specific module (see [Platform](#platform)).\nAuto-retrace, the timeline, alerts, MOS, multi-target, and save/load are all built\non this same user-space primitive, so the whole app runs **without administrator\nrights** in ICMP mode.\n\n## Probe modes\n\nICMP mode is built on the Windows IP Helper API (`IcmpSendEcho`) — the same call\n`ping.exe` and `tracert.exe` use internally, which is why it needs no admin. The\nother two modes:\n\n| Mode | Needs admin? | Use it for |\n|---|---|---|\n| **ICMP** | No | Default. Full traceroute + monitoring. |\n| **TCP, final-hop-only** | No | \"Is this service up, and how fast?\" — a TCP handshake to a port (443, 3389, a device's web UI…). No per-hop trace, just the destination. |\n| **TCP / UDP, full traceroute** | **Yes** | Per-hop trace when ICMP is blocked/deprioritised. Sends via a normal socket with `IP_TTL`, captures routers' ICMP \"TTL exceeded\" via a raw `SIO_RCVALL` socket — which requires elevation. |\n\nWithout admin, the full TCP/UDP traceroute is refused with a clear message rather\nthan showing misleading all-timeout hops. (UDP note: an *open* UDP port stays\nsilent, so it reads as a timeout — prefer TCP for a definitive \"service is up\".)\n\n## Platform\n\nPingerPlot runs on **Windows 10/11** today. The only platform-specific piece is\nthe ICMP probe backend (`icmp.py`), which uses the Win32 IP Helper API so it needs\nno admin rights or capture driver. Everything else — the monitor engine, the\nTkinter GUI, the headless runner, settings, alerts, the map — is pure,\ncross-platform Python, and the test suite runs on both **Linux and Windows** in CI.\n\n**Linux and macOS** are a planned addition: a POSIX backend using unprivileged\n`SOCK_DGRAM`/`IPPROTO_ICMP` sockets (the same technique `mtr` uses) would let the\nsame app run on all three. The engine, GUI, and headless mode are already\nplatform-independent — only that one module is missing.\n\n## Requirements\n\n- Windows 10/11\n- Python 3.10+ (tested on 3.12). Tkinter ships with the python.org installer.\n- Nothing to `pip install`.\n- ICMP and TCP-final-hop work as a normal user. **Full TCP/UDP traceroute needs\n  an elevated (Run as administrator) session.**\n\n## Get it\n\n**Download (no Git needed)** — the easiest way:\n\n1. Grab the latest `Source code (zip)` from the\n   [**Releases**](https://github.com/jamesccupps/PingerPlot/releases/latest) page.\n2. Right-click the `.zip` → **Extract All…** to anywhere (e.g. your Desktop).\n3. Open the extracted folder and **double-click `PingerPlot.vbs`**. That's it —\n   no install, no build, no `pip`.\n\n**Or clone with Git:**\n\n```bash\ngit clone https://github.com/jamesccupps/PingerPlot\ncd PingerPlot\n```\n\nNo build step and nothing to `pip install` — it's pure standard library.\n(Optional: `pip install .` registers a `pingerplot` GUI entry point.)\n\n## Run\n\n**Double-click `PingerPlot.vbs`** — launches the GUI with no console window\n(uses `pyw`/`pythonw`). That's the everyday launcher.\n\nOther ways:\n\n| | |\n|---|---|\n| `PingerPlot.vbs` | Double-click launch, no console window (recommended). |\n| `Setup.cmd` | Menu: launch / launch-elevated / make shortcuts / enable-or-disable auto-start / status. |\n| `python main.py` | From a terminal (console stays open — handy for seeing errors). |\n| `run.bat` | Same as above, double-clickable. |\n\nEnter a hostname or IP (e.g. `8.8.8.8`, `1.1.1.1`, `cloudflare.com`, or an\ninternal address like your gateway), set the interval, and press **Add / Start**.\nAdd more targets the same way — they stack in the left grid. Click a hop row to\ngraph it; click a target to switch the detail view. **Engine…** opens the probe\ntype/port/timeout/logging options; **Save…/Load…** persist a session.\n\n### Shortcuts and auto-start\n\nRun `Setup.cmd` (or `launch.ps1` directly) for one-time setup:\n\n```powershell\n.\\launch.ps1 -CreateShortcuts    # Desktop + Start-menu icons (normal + an \"(Admin)\" one)\n.\\launch.ps1 -InstallAutostart   # auto-start at logon, elevated, with NO UAC prompt\n.\\launch.ps1 -UninstallAutostart # undo it\n.\\launch.ps1 -Status\n```\n\n`-InstallAutostart` registers a **Task Scheduler** logon task with *Run with\nhighest privileges*, so the app starts automatically every time you log in —\nalready elevated (so TCP/UDP traceroute works) and **without a UAC prompt**,\nwhich an on-demand elevated launch can't avoid. It's opt-in: nothing is\nregistered unless you run that command, and one command removes it.\n\n\u003e **Security note — where you install matters.** Because that task launches\n\u003e PingerPlot **elevated with no prompt**, only enable auto-start from a folder\n\u003e standard users can't modify — e.g. `C:\\Program Files\\PingerPlot`. If the\n\u003e program folder *or* your Python install sits somewhere you can write *without*\n\u003e elevation (your `Downloads`, your home folder, most data drives), then any\n\u003e non-elevated process running as you could replace that code and have it run as\n\u003e Administrator at your next logon. `-InstallAutostart` checks for this and warns\n\u003e you, but doesn't block you. If you don't need persistence, prefer the one-off\n\u003e **(Admin)** launch below.\n\nFor a one-off elevated launch instead, use the **PingerPlot (Admin)** shortcut,\n`Setup.cmd` → option 2, or `.\\launch.ps1 -Elevated` (UAC prompts once).\n\n## Headless / service mode (no GUI)\n\nFor unattended monitoring on a box with no desktop session, run the same engine\nwithout the GUI — each target logs to its own crash-safe, size-capped CSV.\n\n```bash\npython -m pingerplot.headless --init monitor.json   # write a starter config, then edit it\npython -m pingerplot.headless monitor.json          # run it (Ctrl-C to stop)\n```\n\nThe config is JSON: an optional `status_interval` (seconds between console\nstatus lines; `0` = silent), a `defaults` block, and a `targets` list where each\nentry may override any default:\n\n```json\n{\n  \"status_interval\": 60,\n  \"defaults\": { \"interval\": 2.5, \"alert_loss_pct\": 20, \"alert_latency_ms\": 250 },\n  \"targets\": [\n    { \"target\": \"8.8.8.8\", \"log_path\": \"logs/dns.csv\" },\n    { \"target\": \"10.0.0.1\", \"log_path\": \"logs/gateway.csv\", \"final_hop_only\": true,\n      \"webhook_url\": \"https://hooks.example/abc\" }\n  ]\n}\n```\n\nRelative `log_path`s resolve next to the config file (so it works regardless of\nthe working directory), and the log directory is created if missing. To run it\nat logon/boot under **Task Scheduler**, point a task at:\n\n```\npythonw.exe -m pingerplot.headless C:\\path\\to\\monitor.json\n```\n\n(or `python.exe` if you want the periodic status lines in a redirected log).\n`pip install .` also registers a `pingerplot-headless` console script.\n\n## Reading the results\n\n- **Loss at an intermediate hop while the final hop stays healthy is normal.**\n  Many routers rate-limit or ignore the ICMP \"TTL exceeded\" they're asked to\n  generate. Only loss that *carries through to the destination* (the last row)\n  indicates a real problem on the path.\n- A latency *step* that appears at one hop and persists through every hop after\n  it is where the delay is being introduced.\n- Sub-millisecond hops show as `\u003c1`; the Win32 RTT is integer-millisecond.\n\n## Tests\n\n```bash\npython -m pytest\n```\n\nThe pure logic (statistics, address encoding, status handling, MOS, alert state\nmachine, TCP/UDP ICMP correlation) is covered by a focused, platform-independent\nunit suite (the Win32 DLL access is guarded behind `sys.platform`), so\nthe suite runs on Linux/macOS too, and CI runs it on both Linux and Windows. The\nraw-socket/Tk layers need real hardware/a display and are exercised by running the\napp or `python -m pingerplot.selftest \u003chost\u003e`.\n\n## Project layout\n\n```\nmain.py            app entry point (python main.py)\nPingerPlot.vbs     no-console double-click launcher\nlaunch.ps1         launcher engine: launch / elevate / shortcuts / auto-start / status\nSetup.cmd          menu front-end for launch.ps1\nrun.bat            console launcher (for debugging)\npyproject.toml     packaging metadata + pytest config\npingerplot/\n  icmp.py          Windows ICMP via ctypes (IcmpSendEcho), TTL control\n  tcpudp.py        TCP/UDP probes via raw SIO_RCVALL capture (admin), parallel per round\n  model.py         Hop / Sample / stats / MOS (pure, tested)\n  monitor.py       trace + continuous monitor engine, alerts, logging, save/load\n  gui.py           Tkinter UI: summary grid, hop table, graphs, events, map\n  geoip.py         lazy IP geolocation via ipwho.is (Map tab only)\n  worldmap.py      baked Natural Earth coastlines (zero-dependency backdrop)\n  selftest.py      headless trace/monitor for the console\ntests/             pytest for the pure layers\n```\n\n## Possible extensions\n\n- **Linux \u0026 macOS** — a POSIX ICMP backend (unprivileged\n  `SOCK_DGRAM`/`IPPROTO_ICMP` + `IP_RECVERR`, the `mtr` technique). The engine,\n  GUI, and headless runner are already cross-platform; only the probe backend is\n  Windows-specific today.\n- IPv6 (`Icmp6SendEcho2` — different structs, needs a source address).\n- Sub-millisecond RTT (would require raw sockets + self-timing).\n- Email alert actions (webhook POSTs are already built in).\n\n## Contributing\n\nIssues and pull requests are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).\nThe hard rules: keep it dependency-free, and keep the Win32 access guarded so the\ntest suite keeps running on any OS.\n\n## License\n\n[MIT](LICENSE) © James Cupps.\n\nPingerPlot is an independent, hobby open-source project and is not affiliated with\nor endorsed by any commercial network-monitoring product. It is a **read-only\ndiagnostic**: it sends probes and reads replies; it never writes to or\nreconfigures anything on the network.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesccupps%2Fpingerplot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesccupps%2Fpingerplot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesccupps%2Fpingerplot/lists"}