A lightweight, single-host sideloader. Prefix it to a resource-hungry command and that command (the sideload) harvests the machine's leftover capacity, while everything else on the box (the main workload — e.g. a web server) stays responsive.
sudo process-scaler [flags] -- ./greedy-command [args...]It is built for individuals / homelabs / self-hosters running a single box: it is standalone, single-host, and needs no exotic setup. The main workload is protected by consequence of constraining the sideload — process-scaler only ever touches the sideload's own cgroup, never the rest of the system.
The sideload is launched inside its own systemd-run --scope, and a once-a-second
control loop adjusts that scope only:
- CPU headroom (
cpu.max). The cap tracks the machine's spare capacity:cap ≈ total − main_demand − headroom. It is self-correcting — the sideload's own usage cancels out, so the cap follows the main's demand, not the sideload's. The quota is computed correctly for multicore machines (quota = fraction × cores × period). - Memory (
memory.high). An optional soft cap that throttles and reclaims the sideload under memory pressure, without ever OOM-killing it. - Overload protection (PSI + freezer). Even a throttled sideload hurts the main
through shared-CPU contention (caches, memory bandwidth, turbo). When system
pressure (
/proc/pressure/{cpu,memory}) shows the main is starved, the sideload is frozen (cgroup.freeze); if it stays frozen past--kill-after, it is killed. On by default. - Thermal ceiling (the differentiator). A hard
cpu.maxceiling that holds even when the main is idle — something contention-based tools can't do. Above--thermal-max-tempthe sideload is clamped regardless of load (effective cap = min(headroom cap, thermal cap)).
See DESIGN.md for the rationale and docs/reference/
for the cgroup v2 / resctl notes this is based on.
Capping the sideload with cpu.max reserves CPU for the main and bounds the
sideload's cache / memory-bandwidth footprint — what protects a main that is
sensitive to that shared-resource contention. The cost: cpu.max bandwidth
throttling adds some tail latency to the main, and that cost grows the harder the
sideload is throttled (so a larger --cpu-headroom can, counter-intuitively,
worsen the main's p99). For a CPU-light, latency-sensitive main with spare cores, a
plain work-conserving cpu.weight often gives a better p99 than any hard cap.
Measure for your workload — the acceptance harness lets you compare the two.
- Linux with cgroup v2 (unified) and systemd
- PSI enabled (kernel ≥ 4.20,
CONFIG_PSI=y) - run as root (it manages the sideload's cgroup)
process-scaler checks all of these at startup and fails loudly if any is missing.
go build -o process-scaler .No external dependencies — pure Go standard library.
sudo process-scaler \
--cpu-headroom 15 \ # % of total CPU kept free for the main (default 15)
--memory-high 4G \ # soft memory cap on the sideload (default: unset)
--thermal-max-temp 85 \ # °C; hard cpu.max ceiling above this (default: off)
--kill-after 30s \ # kill if frozen longer than this (default 30s)
-- ./greedy-command argsPSI-driven freezing is on by default; pass --no-freeze-on-overload to disable it.
# Let a CPU-heavy build use spare capacity, keeping 20% free for the main:
sudo process-scaler --cpu-headroom 20 -- make -j$(nproc)
# Cap memory and add a thermal ceiling at 80 °C:
sudo process-scaler --memory-high 8G --thermal-max-temp 80 -- ./encode.shThe harness/ binary is the reproducible §7 acceptance test: it runs a
latency-sensitive HTTP main under a fixed request rate while a CPU-greedy sideload
runs three ways — unmanaged, naive cpu.weight=1, and under process-scaler — and
checks that process-scaler keeps the main's p99 within budget while the sideload
still harvests CPU. It needs root.
go build -o process-scaler .
go build -o harness ./harness
sudo ./harness --cpu-headroom 15 --tolerance 25 --scaler ./process-scalerv1: CPU + memory, cgroup v2 + systemd only.
Deferred to v2: IO control (io.latency/io.cost), a daemon / rules-by-name
mode, anything multi-host.
See LICENSE.