Statelessness 11 — Build tooling (vendored helpers)
The runnable slice of compendium Doc 11's build-tooling appendix: the cgroup_helper and psi_reader helpers built as static libraries, unit-tested with GoogleTest over their pure parsers, and exercised by a binary you run under different cgroup caps to watch cpu_limit_cores() track --cpus while hardware_concurrency() does not.
The full source for this example lives in
examples/statelessness/11-build-tooling/— clone the repo,cdin, and./demo.sh.
Compendium reference: Doc 11 — Build tooling appendix
Tutorial sections: §13 Reproducibility & ABI + §12 Static Analysis & Debugging
Doc 11 is a build-tooling reference — Conan profiles, the library/version
inventory, the C++23 toolchain matrix, the multi-stage Containerfile pattern,
and full source for three small vendored helpers that earlier docs referenced
but never shipped runnable. This example is the runnable slice: it takes the
two self-contained helpers — cgroup_helper (the cgroup limit reader) and
psi_reader (the PSI parser) — and makes them real, built as static
libraries, unit-tested with GoogleTest, and exercised under real cgroup caps.
Why this matters
The build is the part of a service that’s easy to treat as an afterthought, until an unreproducible build or a wrong runtime reading costs a day:
- The cgroup helper is the punchline of the whole threading story. The
threading section showed that
hardware_concurrency()lies under a--cpuscap; this is the ~30-line helper that reads the truth fromcpu.max. Seeing it track the cap whilehardware_concurrency()doesn’t is the concrete payoff of every “size to the cgroup” claim in the compendium. - A test gate in the build keeps the helpers honest. Parsing
cpu.max,memory.max, and PSI strings has edge cases (unconstrained sentinels, trailing newlines, thefullline that CPU pressure omits). Wiring the GoogleTest suite as a build gate means a regression in those parsers fails the image, not production. - Reproducibility is a build-tooling discipline. Pinned Conan versions, a lockfile, dev/release profiles, and a multi-stage Containerfile are what make “it built on my machine” mean “it builds anywhere.” This example is a small, complete instance of that setup.
What this demo shows
The cgroup reader (vendor/cgroup_helper/). cpu_limit_cores() reads
cgroup v2 cpu.max (falling back to v1, then to scheduler affinity);
memory_limit_bytes() reads memory.max. This is the helper the threading
section uses to size thread pools against the container budget instead of
hardware_concurrency(), which lies under a --cpus cap.
The PSI parser (vendor/psi_reader/). Parses the some/full pressure
lines from /proc/pressure/* or a cgroup’s *.pressure. Best-effort at
runtime, since PSI must be enabled in the kernel (CONFIG_PSI=y) and exposed
to the container.
Testable parsers, live readers. Each helper separates parsing from file
I/O: parse_cpu_max, parse_mem_max, and parse_line are pure functions
tested deterministically against fixture strings, while the live readers wrap
them around the real /sys/fs/cgroup and /proc/pressure files. The gtest
suite needs no live cgroup; the demo binary proves the live path under real
caps.
The cap sweep. The demo runs the same image with no caps, then
--cpus=1.5, then --cpus=0.5 --memory=256m, and you watch
cpu_limit_cores() report each limit while hardware_concurrency() keeps
reporting the full host count — the gap a container-aware service closes.
How to run
cd examples/statelessness/11-build-tooling
podman compose -f compose.yml build # build + run the gtest gate
./demo.sh # tests + the cap sweep
./demo.sh --clean # remove the image
The first build compiles GoogleTest (~30 s); the helpers and demo are tiny. GoogleTest is the only Conan dependency, and it’s test-only.
What you’ll see
Actual output from a host run on Fedora 44 (gcc-toolset-14, 22 logical cores, Podman 5.x) — the gtest gate, then the same binary under three cap settings:
==> Act 1: unit tests (gtest over the pure parsers, in-container)
[==========] Running 15 tests from 4 test suites.
[ PASSED ] 15 tests.
==> Act 2: unconstrained (no caps)
in_container() : false
cpu_limit_cores() : 22 cores (host nproc fallback)
hardware_concurrency(): 22
memory_limit_bytes() : unbounded
==> Act 3: podman run --cpus=1.5
cpu_limit_cores() : 1.5 cores <- read from cpu.max "150000 100000"
hardware_concurrency(): 22 (still the host count)
==> Act 4: podman run --cpus=0.5 --memory=256m
cpu_limit_cores() : 0.5 cores
hardware_concurrency(): 22 (the gap a naive pool would oversubscribe)
memory_limit_bytes() : 268435456 bytes (256.0 MiB)
How to read the output
- Act 1’s 15/15 is a build gate, not just a demo step. The same suite runs at image-build time; a failing parser test fails the build. Seeing it green in-container is the gate confirming the parsers are correct on this toolchain.
cpu_limit_cores()tracks the cap;hardware_concurrency()never moves. This is the entire lesson in two numbers. Under--cpus=1.5the helper reads1.5; under--cpus=0.5it reads0.5;hardware_concurrency()reports22the whole time. A pool sized to the latter would oversubscribe 44:1 against half a core.memory_limit_bytes()reads the exact cap.268435456is precisely 256 MiB — the helper readmemory.maxdirectly, so a cache sized from it respects the cgroup rather than the host’s RAM.in_container()readingfalseis expected under rootless podman. See the caveat below — it’s a known limitation of cgroup-path sniffing, not a defect, and it does not affect the limit readers, which are the load-bearing path.
Files
vendor/cgroup_helper/— cgroup v2/v1 CPU + memory limit reader (the threading section’s helper), split into pure parsers + live readersvendor/psi_reader/— PSIsome/fullparser, same parser/reader splitsrc/main.cpp— prints the live readings (the demo binary)tests/test_helpers.cpp— the GoogleTest suite over the pure parsersCMakeLists.txt— two static libs + demo + test suiteconanfile.py— GoogleTest (test-only, the single Conan dependency)Containerfile— builder runs the test gate;ubi-minimalruntimecompose.yml— build + default unconstrained rundemo.sh— the gtest gate + the cgroup-cap sweep
Caveats and gotchas
in_container()readsfalseunder rootless podman — and that’s not a bug. A rootless container’s/proc/1/cgroupoften reads0::/with none of the docker/libpod/.scopemarkers the heuristic sniffs for, so the detection underreports. It does not affect the limit readers:cpu_limit_cores()andmemory_limit_bytes()readcpu.max/memory.maxdirectly and track the caps exactly (as the sweep shows). Treatin_container()as a best-effort hint, not an authority.- PSI is best-effort. It requires
CONFIG_PSI=yin the kernel and the pressure files to be exposed to the container. The reader degrades gracefully when they’re absent — don’t treat a missing PSI reading as a failure. - The parsers are pure on purpose. Parsing is separated from file I/O so the tests need no live cgroup. When you extend a parser, add a fixture-string test alongside it; that’s what keeps the build gate meaningful.
- The third helper,
otel_propagator, is omitted. Doc 11 lists three vendored helpers; the OpenTelemetry propagator needs opentelemetry-cpp, which this tutorial deliberately doesn’t pull in. The full Conan profile, library inventory, and toolchain matrix from Doc 11 stay as reference prose on the compendium page.
Source materials
This example deepens material from the project’s bibliography:
- Andrist & Sehr, C++ High Performance 2e, ch. 1 — the build, the toolchain, and measuring against the real environment; the cgroup reading is the “measure reality” discipline applied to resource limits
- Iglberger, C++ Software Design, ch. 2 — separating the pure parser from the I/O-bound reader as a testability and single-responsibility design
- Enberg, Latency, ch. 3 — why the cgroup CPU budget (not the host count) is the number that governs scheduling latency, the reason this helper exists
Linked tutorial sections
- §13 Reproducibility & ABI — Conan lockfiles, CMake presets, hermetic builds, and the multi-stage image; this example is a small complete instance of that build discipline, with the gtest gate baked into the build.
- §12 Static Analysis & Debugging — the dev/release profile split and the build-as-quality-gate idea; running the test suite as a build stage is the same principle as the static-analysis pipeline there.
The build-tooling angle
The example doubles as a small worked instance of Doc 11’s build setup: the
helpers are Conan-free static libraries, GoogleTest comes via Conan as a
test-only dependency, and the multi-stage Containerfile runs the gtest suite
as a build gate so a failing parser test fails the image. The third helper the
doc lists, otel_propagator, needs opentelemetry-cpp, which this tutorial
deliberately doesn’t pull in; the full Conan/CMake/toolchain reference material
stays as prose on the compendium page.
Where it sits in the compendium
This is the build that produces every other example in the arc. The
cgroup_helper here is the helper the
threading example
relies on to size pools, and the one the capstone
(Doc 10)
uses to size its gRPC server thread pool from the CPU budget. The multi-stage,
pinned, test-gated build shown here is the pattern every runnable example in
the compendium is built with.