Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Performance

Reference measurements for tuxctl, how they are made, and how releases compare. These numbers come from one machine; they are a baseline for regressions, not a guarantee for other systems. The limits that CI enforces on every push are listed in scripts/README.md.

Reference machine

  • AMD Ryzen 5 7500F (6 cores, 12 threads), CachyOS, Linux 7.2.6
  • CPU governor and platform profile: performance; transparent huge pages: always
  • Rust 1.98.1
  • tuxctl runs in a 160×50 pseudo-terminal driven by scripts/ptyrun.py, at the default 1 s interval. The cost of a real terminal emulator is not included.

Method

cargo build --release --locked --features redraw-counter --target-dir target/counter
scripts/measure.py target/counter/release/tuxctl --json run1.json   # three runs, report the median
cargo build --release --locked
scripts/rss.py target/release/tuxctl 15 --json rss.json

Each measure.py scenario lasts 20 s. CPU is the percentage of one core (user + system time from /proc/<pid>/stat); RSS is VmRSS. The storm writes 200 journal messages per second.

Comparing two versions: run measure.py three times on each, on the same machine and power profile. The noise band of a metric is the spread (max − min) of the older version’s runs. A newer median is unchanged if it is within the larger of twice that band and a floor of 0.15 CPU percentage points, 0.3 redraws/s, or 256 KiB. After minute 5, RSS should grow by at most 128 KiB over a 15-minute run.

v0.3.4

Release binary: x86_64-unknown-linux-musl, statically linked, 1.77 MB (v0.3.3: 1.74 MB). Median of three runs, alternated with three runs of the v0.3.3 musl binary in the same session (in brackets), on Linux 7.2.8.

ScenarioCPU %Redraws/sRSS
Overview, idle0.70 (0.70)2.00 (2.00)2.7 MiB
Processes, idle0.70 (0.70)1.10 (1.25)2.8 MiB
Logs, idle0.65 (0.60)0.05 (0.05)2.9 MiB
Logs, 200 journal messages/s0.93 (1.00)3.87 (3.93)3.2 MiB
Mouse hover at 240 Hz1.50 (1.50)13.46 (13.53)3.1 MiB
  • CPU and redraws are unchanged: every difference is within the 0.15-point floor and the redraw noise band.
  • Startup RSS: 2.6 MiB (2.6 MiB). After 15 minutes on Overview: 2.7 MiB, 56 KiB more than at minute 5.
  • Time to the first frame: 2.1 ms (2.1 ms). Input latency during the log storm: 0.70 ms (0.73 ms).
  • The new glibc binary loads NVML for the NVIDIA GPU (an RTX 5070 Ti that cannot runtime-suspend): Overview idle 1.05 % and 31 MiB. NVML is now closed once the Overview has been hidden for 10 s, so the other screens run at 11 MiB instead of about 31 MiB (Processes idle 0.70 %, 11.0 MiB); returning to the Overview loads it again in about 40 ms on the worker.

v0.3.3

Release binary: x86_64-unknown-linux-musl, statically linked, 1.74 MB (v0.3.0: 1.45 MB). Median of three runs, alternated with three runs of the v0.3.0 musl binary in the same session (in brackets). Static binaries do not load NVML, so these figures leave it out; see below for a glibc build.

ScenarioCPU %Redraws/sRSS
Overview, idle0.60 (0.55)2.00 (1.10)2.7 MiB
Processes, idle0.60 (0.65)1.35 (2.00)2.8 MiB
Logs, idle0.55 (0.50)0.00 (0.15)2.9 MiB
Logs, 200 journal messages/s0.80 (0.80)3.87 (3.87)3.2 MiB
Mouse hover at 240 Hz1.39 (1.39)13.43 (13.52)3.2 MiB
  • CPU is unchanged: every difference is within the 0.15-point floor.
  • Idle redraws moved between tabs rather than grew: Overview +0.9/s, Processes −0.65/s. Bisected to the temperature work, whose longer metrics sample changes which collector updates land in the same frame (the main loop renders once for updates that arrive together). The redraw guards still pass.
  • Startup RSS: 2.7 MiB (v0.3.0: 2.4 MiB); the difference is about the binary’s growth. After 15 minutes on Overview: 2.7 MiB, 8 KiB more than at minute 5.
  • Time to the first frame: 1.8 ms. Input latency during the log storm: 0.7 ms (0.7 ms).
  • A glibc build on the same machine loads NVML for its NVIDIA GPU: startup RSS is about 31 MiB, Overview idle about 0.95 % and Processes idle about 0.60 % (hardware sensors are read only while the Overview is visible).
  • Another tuxctl was running on the machine during these runs; both versions ran alongside it.

v0.3.0

Release binary: x86_64-unknown-linux-musl, statically linked, 1.45 MB. Median of three runs, alternated with three runs of the v0.2.7 musl binary in the same session (in brackets).

ScenarioCPU %Redraws/sRSS
Overview, idle0.60 (0.60)1.10 (1.00)2.3 MiB
Processes, idle0.75 (0.70)2.00 (2.00)2.4 MiB
Logs, idle0.60 (0.55)0.00 (0.00)2.5 MiB
Logs, 200 journal messages/s0.87 (0.87)3.87 (3.87)2.8 MiB
Mouse hover at 240 Hz1.39 (1.29)13.54 (13.43)2.8 MiB
  • Every difference is within the noise band: the v0.2.7 hover runs alone spread by 0.20 points.
  • Startup RSS: 2.3 MiB (v0.2.7: 2.2–2.3 MiB). After 15 minutes on Overview: 2.3 MiB, unchanged since minute 5.
  • Time to the first frame: 2 ms. Input latency during the log storm: 0.9 ms (0.75 ms).
  • The v0.2.7 figures below came from an earlier session and read about 0.05–0.1 points lower on some scenarios than v0.2.7 measured again here; compare versions only within one session.

v0.2.7

Release binary: x86_64-unknown-linux-musl, statically linked, 1.39 MB.

ScenarioCPU %Redraws/sRSS
Overview, idle0.601.202.3 MiB
Processes, idle0.652.002.4 MiB
Logs, idle0.550.002.5 MiB
Logs, 200 journal messages/s0.803.872.8 MiB
Mouse hover at 240 Hz1.2913.412.7 MiB
  • Startup RSS: 2.2 MiB. After 15 minutes on Overview: 2.3 MiB, unchanged since minute 5.
  • Time to the first frame: 2 ms. Input latency during the log storm: 0.7 ms.
  • RSS change over two further 15 s storms with a full log buffer: within ±60 KiB.

A cargo install build (glibc, dynamically linked, 1.27 MB) measured 0.55 / 0.55 / 0.50 / 0.67 / 1.10 % CPU for the same scenarios, with the same redraw rates. Its RSS is about 4.8 MiB at startup and 5.2 MiB after 15 minutes (+4 KiB since minute 5). A dynamically linked process also counts the pages of the shared C library it maps, so RSS is higher than for the static build.

History

Same machine, glibc builds, measure.py as of v0.2.7, median of three runs.

Scenariov0.2.5 CPU %v0.2.7 CPU %v0.3.0 CPU %v0.2.5 redraws/sv0.2.7 redraws/sv0.3.0 redraws/s
Overview, idle0.550.550.551.401.251.10
Processes, idle0.600.550.602.002.002.00
Logs, idle0.550.500.550.000.050.10
Logs, 200 journal messages/s0.670.670.673.873.873.93
Mouse hover at 240 Hz1.201.101.1913.5613.5413.42
v0.2.5v0.2.7v0.3.0
Startup RSS5.1 MiB4.8 MiB4.9 MiB
RSS after 15 minutes5.4 MiB¹5.2 MiB5.1 MiB²
Binary size1.94 MB1.27 MB1.33 MB

¹ Measured before v0.2.7 with the same rss.py run: 5 496 kB, flat after minute 4. ² 5 264 kB, +4 KiB since minute 5; v0.2.7 measured 5 200 kB (+0 KiB) in the same session.

v0.3.0 was measured interleaved with three fresh v0.2.7 runs on the same day (v0.2.7: 0.55 / 0.60 / 0.50 / 0.67 / 1.09 % CPU, 1.10 / 2.00 / 0.00 / 3.93 / 13.48 redraws/s); every difference is within the noise band. Time to the first frame stayed at about 1.9 ms and storm input latency at about 0.5 ms. Single runs of each intermediate v0.3.0 commit stayed within the same band, so no item stands out. The sanitization pass over every frame does not show in the hover scenario, the most render-heavy one. The binary grew by 4.8 % (disk I/O, pinning, filters, menu, sparklines). The Logs idle redraws come from host journal traffic, as noted below.

Shortest interval (--interval 250ms)

One run each, glibc builds; the idle guard is 9.5 redraws/s at this interval.

Scenariov0.2.7 CPU %v0.3.0 CPU %v0.2.7 redraws/sv0.3.0 redraws/s
Overview, idle0.750.804.004.00
Processes, idle0.800.853.803.85
Logs, idle0.650.700.150.00
Logs, 200 journal messages/s0.800.803.873.87
Mouse hover at 240 Hz1.291.3913.4813.58

Four times as many metrics and network samples cost about 0.2 CPU percentage points on the idle screens and four redraws per second on Overview: the metrics and network updates usually arrive together and share one redraw.

v0.2.7 changes no collector cadence or rendering path; the CPU and redraw differences above are within the noise band. The smaller binary and the roughly 200 KiB lower RSS come from the release profile (LTO, one codegen unit, stripped symbols).

Overview idle redraws vary between runs (1.0 to 1.4 per second): the metrics, process, and network collectors each publish once per second, and their updates share a redraw only when they arrive close together.

Sources of noise

  • Tick resolution: CPU time is counted in 10 ms clock ticks, so a 20 s idle scenario at 0.5 % is only about 10 ticks.
  • Background load and power profile: keep them the same across compared runs.
  • Journal traffic: Logs idle redraws depend on how much the host writes to the journal.
  • Transparent huge pages: with THP set to always, a run can show about 2 MiB more RSS. measure.py and rss.py record AnonHugePages to identify such runs.
  • Warm-up: the command cache and hardware discovery fill during the first minutes, so judge memory growth from minute 5 on.
  • CI runners vary by CPU model and neighbours: idle CPU on GitHub-hosted runners ranged from 0.25 % to 0.62 % for the same scenario. CI therefore enforces only redraw limits, liveness, and catastrophe limits, and reports CPU and RSS.