No description
  • Python 91.4%
  • Shell 8.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-22 16:50:36 -04:00
bin Initial commit: ptop terminal system health monitor 2026-09-20 13:06:34 -04:00
ptop A-2-i 2026-09-22 16:50:36 -04:00
systemd A-2-i 2026-09-22 16:50:36 -04:00
tests A-2-i 2026-09-22 16:50:36 -04:00
.gitignore Initial commit: ptop terminal system health monitor 2026-09-20 13:06:34 -04:00
install.sh A-2-i 2026-09-22 16:50:36 -04:00
LICENSE Initial commit: ptop terminal system health monitor 2026-09-20 13:06:34 -04:00
pyproject.toml A-2-i 2026-09-22 16:50:36 -04:00
README.md A-2-i 2026-09-22 16:50:36 -04:00
task.txt Initial commit: ptop terminal system health monitor 2026-09-20 13:06:34 -04:00
uninstall.sh A-2-i 2026-09-22 16:50:36 -04:00

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_DIR if set (the systemd unit sets /var/lib/ptop/incidents with StateDirectory=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.