Linx.Cgroup puts a Linux workload under a resource ceiling — memory, CPU, process count — and reads back what it actually consumed, using cgroup v2 and nothing but the filesystem the kernel already exposes.

cgroup v2 is how Linux accounts for and limits what a tree of processes may consume. Its entire interface is a directory under /sys/fs/cgroup: each cgroup is a folder, each knob (memory.max, cpu.max, pids.max) a file you write, each counter (memory.current, cpu.stat) a file you read. There is no syscall, no daemon, no NIF — Linx.Cgroup is plain File.read/1 and File.write/2 against that tree, wrapped in typed setters, structured errors, and a Stats reader that copes with counters newer kernels invent.

The path is the handle. create/1 returns the cgroup's path; every other verb takes it. There's no opaque struct or GenServer — cgroupfs already supplies the identity, and Linx declines to invent a second one on top.

Where it fits

Linx.Cgroup is one of the subsystems that reaches into a child while it is parked at the Linx.Process checkpoint. add_process/2 places the parked pid into a cgroup before proceed/1, so the workload's first instruction already runs inside its ceiling — the same window Linx.User, Linx.Netlink, and Linx.Seccomp use. Linx.Process knows nothing of cgroups; the checkpoint is the whole of the coupling.

It is equally useful with no clone in sight: any host pid — including BEAM processes — can be supervised. Linx supplies primitives, never policy: the caller picks the path, delegates controllers, and decides naming. A container engine built on Linx is the consumer that sequences create → delegate → limit → place around the checkpoint.

Flow

flowchart TD
    create["create/1<br/>mkdir under /sys/fs/cgroup"] --> dir
    subgraph dir["the cgroup directory (the handle)"]
        mem["memory.max ← set_memory_max/2"]
        cpu["cpu.max ← set_cpu_max/2"]
        pids["pids.max ← set_pids_max/2"]
        procs["cgroup.procs ← add_process/2"]
        freeze["cgroup.freeze ← freeze/1 · thaw/1"]
        stat["cpu.stat · memory.current → stats/1"]
    end
    dir --> proceed["Linx.Process.proceed/1<br/>workload execs already constrained"]

Learn more