Procnet Live renders an interactive, live-updating map of processes and their TCP/UDP connections in your browser. It aims for Windows-first performance using the WinAPI, while providing working fallbacks for Linux and macOS.
- Windows fast collector: Uses
GetExtendedTcpTableviactypes(IPv4/IPv6). - Linux/macOS fallback: Linux via
ss -tanpi; macOS/others viapsutil. - Live server: Flask exposes
/(UI) and/api/graph(JSON). The frontend (vis-network) updates the graph by adding/updating/removing nodes/edges without flicker and keeps a stable layout. - Direction & pairing: Heuristics determine client→server using service-port classification; listener resolution for local peers; curved multi-edge layout.
- UDP optional: UDP edges are color-differentiated and tooltips include the protocol (TCP/UDP).
- Node typing: Rules (YAML/JSON) assign process types and labels (e.g.,
database,service,qt_desktop). - Icons: PNG per type from
--icons-dir. Smart fallback: type PNG →service.png→ font icon.
- Python 3.9+ recommended.
- Windows 10/11 (admin may be required to see all PIDs).
- Linux with
ss(iproute2). macOS viapsutilfallback.
Create a venv and install:
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux/macOS
source .venv/bin/activate
pip install -r requirements.txtrequirements.txt:
Flask>=3.0.0
psutil>=5.9.0
PyYAML>=6.0.0
orjson>=3.9.0
orjsonis optional; the app falls back to the stdlibjsonif not present.
python -m procnet_live.main --port 8765 --interval 1.0 --rules node_types.yaml --udp --svc-ports 80,443,5432 --icons-dir ./icons
# open http://localhost:8765| Option | Type | Default | Description |
|---|---|---|---|
--port |
int | 8765 |
HTTP port for Flask. |
--interval |
float (s) | 1.0 |
Collector sampling interval. |
--rules |
str (path) | None |
YAML/JSON rule file. Relative paths are resolved robustly. |
--p2p-only |
flag | False |
Render only PID↔PID edges. Suppresses external endpoints and listener edges. |
--udp |
flag | False |
Include UDP connections (with raddr) and color-differentiate edges. Tooltip shows protocol. |
--svc-ports |
CSV of int | '' |
Additional service ports for direction heuristics (e.g., 3000,5000). |
--icons-dir |
str (path) | None |
Directory with PNG icons. See Icons below. |
Assign process types and optional labels.
Fields:
match_pid(int, optional) – exact PID.match_name(str, optional) – exact name or regex (/pattern/or/pattern/i).match_cmd(str, optional) – substring or regex like above.type(str, required) –app,service,database,message_broker,cache,load_balancer,external,qt_desktop, ...label(str, optional) – custom display label.
Examples:
- match_pid: 1234
type: database
label: "Local Postgres"
- match_name: "redis-server.exe"
type: cache
label: "Redis"
- match_name: "/chrome/i"
type: app
label: "Chrome"
- match_cmd: "--listen 0.0.0.0:5432"
type: database
label: "DB (Public)"
- match_cmd: "/--port\s+(3000|5173)/i"
type: service
label: "Dev Server"
- match_name: "/qt|qtwebengine/i"
type: qt_desktop
label: "Qt App"First matching rule wins. If nothing matches:
type=app, label = process name.
Put PNGs in --icons-dir with exact names:
app.png
service.png
database.png
mq.png
cache.png
lb.png
external.png
qt_desktop.png
Server-side fallback: type PNG → service.png → font icon.
By default the UI loads vis-network from a CDN. For offline/air‑gapped environments or reproducible builds, you can vendor the JS locally and serve it with strong caching. This removes external dependencies, speeds up load times, and makes the app portable.
scripts/prepare_portable.py automates the following:
- (Optionally) runs
pip install -r requirements.txt(omit with--skip-pip). - Downloads the vis-network UMD bundle as a versioned filename:
procnet_live/web/vendor/vis-network-<VER>.standalone.min.js - Downloads the license (MIT or Apache-2.0) from the official repo/tarball.
- Writes a manifest with version and checksum:
{ "vis_network_version": "9.1.6", "js_file": "vis-network-9.1.6.standalone.min.js", "sha256_js": "…" }
The versioned filename enables immutable caching; changing the version yields a new filename (natural cache-busting).
# include requirements installation
python scripts/prepare_portable.py --version 9.1.6
# skip pip if your env is already set up
python scripts/prepare_portable.py --skip-pip --version 9.1.6Expected output:
procnet_live/web/vendor/
├─ vis-network-9.1.6.standalone.min.js
├─ LICENSE-MIT.vis-network-9.1.6.txt # or LICENSE-APACHE-2.0...
└─ manifest.json
- On page load, the HTML (in
ui.py) fetches /vendor/manifest.json (no‑cache). - It reads
vis_network_versionandjs_filefrom the manifest. - It tries to load local
"/vendor/<js_file>"first. - If that fails, it falls back to the CDN URL with the same version:
https://unpkg.com/vis-network@<VER>/standalone/umd/vis-network.min.js
This makes the version centrally controlled by re-running the script.
- Vendor JS (
/vendor/<file>.js): served withCache-Control: public, max-age=31536000, immutable - Manifest (
/vendor/manifest.json): served withCache-Control: no-store, no-cache, must-revalidate, max-age=0
So browsers cache the heavy JS aggressively, while manifest changes take effect immediately.
- Browser console prints what’s used:
using LOCAL: /vendor/vis-network-9.1.6.standalone.min.js- or:
local missing, using CDN: https://unpkg.com/...
- Flask logs (server) report:
- which vendor file was served
- whether the manifest was found
vis is not defined: Ensure your vis initialization lives insidewindow.startApp(). The loader callsstartApp()only after the library (local or CDN) has finished loading.- Manifest missing: The UI uses defaults and may hit the CDN. Run
scripts/prepare_portable.pyto createmanifest.json. - Upgrading vis-network: Re-run the script with
--version X.Y.Z. The manifest will point to the new file; thanks to the versioned filename, browsers fetch a fresh copy automatically.
- Project code: MIT (see
LICENSEif present). - Dependencies: Flask (BSD-3-Clause), psutil (BSD), PyYAML (MIT), orjson (Apache-2.0).
- Frontend: vis-network (MIT) via local vendor file or CDN (
standalone/umd).