cpulimit

Contributing

Bug fixes, tests and ports are welcome. A change should come with a test that fails without it.

Before you send a change

make check analyze verif-sim format-check
  • make check: unit, CLI and integration tests (SKIP_INTEGRATION=1 for the unit tests only).
  • make analyze: gcc -fanalyzer, which must be silent, and clang-tidy.
  • make verif-sim: the model-checking harnesses as random tests.
  • make format-check: .clang-format (4 spaces, attached braces, char *p, 120 columns). make format rewrites files. CI pins clang-format 18, because the output differs between major versions.
  • If you changed the controller, the process table or an iterator, also run sh tools/mutation.sh; every mutant must still be killed, and a new defect class deserves a new mutant in tools/mutants.txt.

Rules of the code base

  • Architecture first. The module rules and invariants in ARCHITECTURE.md are normative: a change that breaks one changes that document in the same commit. Only src/main.c calls exit(); only src/platform/*/iterator.c knows an operating system.
  • Compatibility. Options, exit statuses and log text are the public interface of 1.x. A change a script could notice is listed in Upgrading and the changelog.
  • Say what is not tested. Code that was written without a host to run it on is labelled that way in the source, the changelog and Platforms. A claim in the docs is a statement about a run that happened.
  • Original code. Contributions are GPL-2.0-or-later. New files carry the copyright and SPDX-License-Identifier: GPL-2.0-or-later header used in the tree; contributors are credited in AUTHORS.

Adding a platform

  1. Create src/platform/<os>/iterator.c with init_process_iterator, get_next_process and close_process_iterator, and add its branch to src/platform/iterator.c and the fields it needs to struct process_iterator in iterator.h.
  2. Fill struct process: pid, parent pid, a start time that is stable for the life of a process and only ever compared for equality, and CPU time in milliseconds. Document the units in iterator.h.
  3. Copy the processes into an array whose records start with struct snap_rec and call snapshot_select(recs, n, sizeof(*recs), filter->pid, filter->include_children).
  4. Skip zombies and kernel threads. Check what your kernel really reports for them: OpenBSD listed zombies, and a limiter that waits for them never finishes.
  5. Check how the CPU clock advances. A counter that only moves when a process is switched out (OpenBSD's p_rtime) makes the controller see steps, not a rate.
  6. Add a job to .github/workflows/vm.yml if a VM exists for it (tools/vm_build.sh builds and tests inside the guest and keeps the transcript), and update Platforms with what actually ran.

Sending a change

Open a pull request at GitHub, or send a patch from git format-patch. Keep commits focused, explain the reason and the evidence (which test fails before and passes after) in the message, and do not mix a reformat with a behaviour change.