- Python 91.4%
- Shell 8.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| bin | ||
| ptop | ||
| systemd | ||
| tests | ||
| .gitignore | ||
| install.sh | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| task.txt | ||
| uninstall.sh | ||
ptop
A lightweight, modern, colorful terminal system-health monitor.
ptop
launches an interactive curses dashboard showing CPU, RAM, temperature and
root-filesystem usage, with a scrolling historical graph that keeps the last
200 samples in RAM (16h40m at the default 5-minute interval).
Metric history is never written to disk: no databases, no CSV, no log files. When ptop exits, the history disappears. That is intentional and keeps the program boringly reliable on low-resource machines such as a Raspberry Pi Zero W.
The one deliberate exception is an incident log. The headless service
groups abnormal observations (CPU/RAM/disk/process spikes) into incidents and
persists each incident as a small JSON file - once when it starts, every five
minutes while it is active, and once when it ends. Press i in the UI to
browse them. Normal metric samples never touch disk.
Install
The whole program is Python standard library only — no third-party dependencies.
Quick setup on a new machine (copies ptop, no pip/network needed):
./install.sh # user install: ~/.local/bin/ptop
./install.sh --systemd # + install & enable the systemd service (root)
Remove it again:
./uninstall.sh # remove the user install
./uninstall.sh --systemd # also stop/remove the systemd service (root)
./uninstall.sh --config # also delete ~/.config/ptop
Or install it the usual way:
pip install .
ptop
Or run uninstalled from the repository:
./bin/ptop
No root privileges are required for normal operation.
Quick start
ptop # interactive dashboard
ptop --interval 30 # sample every 30 seconds
ptop --service # headless background sampler (for systemd)
ptop --once # print a single sample and exit
ptop --help # all options
Keys
| Key | Action |
|---|---|
q |
quit |
r |
collect a new sample now |
h / ? |
toggle help |
c |
toggle colour |
a |
toggle ASCII / Unicode markers |
i |
incident log |
Incident log
Press i for the incident list. ↑/↓ (or j/k) selects, Enter opens the
selected incident's JSON in $VISUAL (falling back to $EDITOR, then vi),
and Esc (or i) returns to the dashboard. The editor is launched with the
terminal cleanly suspended and restored afterwards, so the UI is never left in
a broken state.
Incidents are produced by the headless service (ptop --service, used by
the systemd unit). The engine is additive: the dashboard, the history service,
the socket bridge and the metric collectors are untouched.
Configuration
Optional configuration file:
~/.config/ptop/config.toml
(or $XDG_CONFIG_HOME/ptop/config.toml). The file is a simple key = value
list; no config file is required for defaults to work.
interval = 300 # seconds between samples (default 300)
history_size = 200 # max in-memory samples (default 200)
graph_char = "auto" # "auto" | "unicode" | "ascii" | a single character
color = true # false disables colour
show_system = true # show the SYSTEM panel
show_resources = true # show the RESOURCES panel
show_optional = true # show swap / cores / processes / network
# per-series and status colours
color_cpu = blue
color_ram = green
color_temp = yellow
color_disk = magenta
color_title = cyan
color_label = white
color_dim = blue
color_warning = red
color_good = green
# Incident detection (used by the headless service).
# Durations are seconds, or accept a suffix: "30m", "5m", "1h30m".
incidents_enabled = true
incident_window = "30m" # grouping window for abnormal samples
incident_checkpoint_interval = "5m" # disk checkpoint while incident is active
incident_cpu_threshold = 90 # % -> "cpu_high"
incident_ram_threshold = 90 # % -> "ram_high"
incident_disk_threshold = 90 # % -> "disk_high"
incident_processes_threshold = 1000 # count -> "processes_high"
# incident_dir = "/var/lib/ptop/incidents" # see "How incidents work"
Systemd service
ptop can run as a headless sampling service:
sudo useradd --system --shell /usr/sbin/nologin ptop # first time only
sudo cp systemd/ptop.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now ptop
The service runs unprivileged, restarts on failure, writes nothing to disk for its metric history and keeps that history in RAM only (so the history is gone when it stops — by design). Abnormal observations are batched into incidents and written as JSON as described below.
Interactive UI attaches to the running service. When the service is active
its accumulated in-memory history is served over a local Unix socket
(/run/ptop/ptop.sock, managed by RuntimeDirectory). Any interactive ptop
instance probes that socket first and, if found, renders the service's existing
samples — so you can check hours of history at any time rather than starting
from scratch. Nothing is written to disk; samples flow through kernel memory
only. If the socket is unavailable the interactive UI falls back to its own
local sampler automatically.
How incidents work
The always-on service is the central collector. Each sample is also evaluated
against the incident thresholds; a sample that crosses a threshold is an
abnormal observation, and abnormal observations within incident_window
of each other are grouped into a single incident (never one file per
spike):
ONE INCIDENT
10:00 → 10:20
5 spikes
CPU + RAM
An incident is not closed by the first healthy sample: it stays open until the grouping window elapses with no new abnormal observation, and is then written one final time. Disk writes are deliberately minimal:
incident starts -> 1 write
every checkpoint -> 1 write / 5 minutes (while active)
incident ends -> 1 write
no incident -> 0 writes
Each incident is one JSON file named after its start time, e.g.
2026-09-22_10-42-13.json, holding start/end timestamps, duration, spike
count, conditions, and bounded snapshots (full system state — CPU, RAM, disk,
process count and the top five processes — with the most recent kept for long
incidents). Writes are atomic (write temp → fsync → rename), so a crash or a
concurrent UI read can never see a half-written file.
When the service restarts it inspects the persisted incidents, closes any left "active" that have gone stale, and resumes the most recent one so nothing is duplicated.
Incidents are stored under:
$PTOP_INCIDENT_DIRif set (the systemd unit sets/var/lib/ptop/incidentswithStateDirectory=ptop),- otherwise
/var/lib/ptop/incidents, - otherwise
$XDG_STATE_HOME/ptop/incidents(default~/.local/state/ptop/incidents).
The interactive UI discovers the same directory, so i shows the service's
incidents regardless of which user you run ptop as. Incidents are never
deleted automatically.
Metrics
| Metric | Source | Graph scale |
|---|---|---|
| CPU | /proc/stat delta |
0–100% |
| RAM | /proc/meminfo (MemAvailable) |
used/total × 100 |
| Temp | /sys/class/thermal + /sys/class/hwmon |
0–100 °C (clamped) |
| Disk | statvfs("/") |
0–100% |
All four metrics are normalized to a single 0–100 vertical scale, clamped, and
drawn with their own colour. Missing sensors are reported as N/A rather than
crashing.
Layout
+ ptop 1.1.0 — mipha ---------------------------+
| +- SYSTEM --------++- RESOURCES --------------+
| | hostname mipha || CPU 12% |
| | kernel 6.18.x || RAM 5.8 GiB / 7.7 GiB |
| | arch aarch64 || TEMP 41°C |
| | uptime 17d 4h || DISK 37% |
| +-----------------++--------------------------+
| +- HEALTH HISTORY --------------------------+ |
| | 100 ┤ ● | |
| | 75 ┤ ● ● | |
| | 50 ┤ ● | |
| | 25 ┤ ● | |
| | 0 ┼──────────────────────────────────── │ |
| | -16h now | |
| +-------------------------------------------+ |
| ● CPU ● RAM ● TEMP ● DISK |
| sampling 300s | history 200/200 | q quit | i incidents ... |
+-----------------------------------------------+
Terminal width and height drive the layout; the graph resizes automatically and always shows the newest samples once history exceeds the visible width.
Development
python3 -m unittest discover -s tests -v # run the test suite
The code is split into independently testable modules:
ptop/metrics.py metric collection (no curses) — pure parsers + collectors
ptop/history.py bounded in-memory ring buffer (thread-safe)
ptop/graph.py normalization + plot grid (pure)
ptop/config.py config file handling
ptop/incidents.py abnormal-observation grouping engine (pure, headless)
ptop/incident_store.py JSON persistence + atomic writes + dir discovery
ptop/service.py background Sampler thread + headless service
ptop/ui.py curses dashboard (incl. incident log screen)
ptop/main.py CLI entry point
Design principles
- Small, fast, pretty, boringly reliable.
- No busy loops: the sampler blocks on an event between samples; the UI polls briefly and only redraws when data or geometry changes.
- Minimal CPU/memory/disk footprint — fits comfortably on a Pi Zero W.