pseudofs #
Parsers for the text Linux exports through its pseudo-filesystems.
proc(5) and sysfs(5) both begin with the same sentence: the filesystem is "a
pseudo-filesystem which provides an interface to kernel data structures".
That interface is text in the kernel's own formats, which change with the
kernel and are documented by it. This library parses those formats into
typed records and cites the kernel documentation for each one. A caller then
reads /proc/<pid>/stat correctly in one place, where it would otherwise
split it on spaces in five, and learn at 3am that a process called
a) b (c) d reports somebody else's CPU time.
Each parser is a pure string -> (t, [> Msg of string]) resultover the bytes of one file. Nothing in the package opens a file, so the tests replay bytes captured from a real kernel and run on any machine. Reading the files is left to the separatepseudofs-eio` package.
Installation #
$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam install pseudofs pseudofs-eio
Usage #
The resident memory of a process and the CPU time it has used come from two files:
# let status = "Name:\tcat\nState:\tR (running)\nVmRSS:\t 932 kB\n"
val status : string =
"Name:\tcat\nState:\tR (running)\nVmRSS:\t 932 kB\n"
# Result.map Pseudofs.Proc.Pid.Status.vm_rss
(Pseudofs.Proc.Pid.Status.of_string status)
- : (int64 option, [> `Msg of string ]) result = Ok (Some 954368L)
The process controls its own executable name in /proc/<pid>/stat, and the
name can contain spaces and parentheses. The fields after it therefore start
at the last closing parenthesis of the line, not the first:
# let stat = "18 (a) b (c) d) S 1 1 1 0 -1 4194304 77 0 1 0 3 1 0 0 20 0 1 0 8798194 2293760 221 18446744073709551615 0 0 0 0 0 0 0 6 0 1 0 0 17 11 0 0 0 0 0 0 0 0 0 0 0 0 0" in
Result.map
(fun t ->
(t.Pseudofs.Proc.Pid.Stat.comm, Pseudofs.Proc.Pid.Stat.cpu_seconds t))
(Pseudofs.Proc.Pid.Stat.of_string stat)
- : (string * float, [> `Msg of string ]) result = Ok ("a) b (c) d", 0.04)
Malformed input gives an error that says what was wrong, never a value that looks like a healthy zero:
# Pseudofs.Cgroup2.Cpu_stat.of_string "usage_usec\n"
- : (Pseudofs.Cgroup2.Cpu_stat.t, [> `Msg of string ]) result =
Error (`Msg "not a flat-keyed line: \"usage_usec\"")
pseudofs-eio runs the same parsers on a real filesystem. Each reader takes
the root to read under, so a caller resolves /proc once and passes it
down:
let resident_bytes env pid =
let proc = Eio.Path.(Eio.Stdenv.fs env / "proc") in
match Pseudofs_eio.Proc.status proc pid with
| Ok status -> Pseudofs.Proc.Pid.Status.vm_rss status
| Error (`Msg m) -> failwith m
API #
Pseudofs.Attrparses a file holding one value, which is most of sysfs.Pseudofs.Procparsescmdline,meminfo,loadavg,uptime, and per processstat,status,net/devandcgroup.Pseudofs.Cgroup2parsescgroup.controllers, the flat-keyed files (cpu.stat,memory.stat,memory.events), the nested-keyedio.statand the pressure-stall files.Pseudofs.Sys_blockparses the block device attributes, counted in the 512-byte sectors the block layer uses whatever the logical block size of the device.Pseudofs.Kmsgparses the kernel log records/dev/kmsghands out.Pseudofs_eioreads the same files from a filesystem. It also lists the devices in/sys/block, since that needs code that can read a directory.
Testing #
test/fixtures holds bytes returned by a real Linux kernel, committed and
replayed by the tests; test/fixtures/README.md records which kernel they
came from and how they were captured. No test reads the machine it runs on.
The parsers are what is under test, and a test that read /proc would check
nothing on macOS and something different on every Linux host.
Related work #
janestreet/procfs parses /proc in
OCaml and owns the procfs name on opam. It is built on Core and Async. The
core of this library depends only on fmt and does no I/O, so that it can
link into a unikernel or an init process, and that is why it is separate
and not a contribution to procfs. It also covers more than /proc: sysfs
and /dev/kmsg too, which the kernel documentation also calls
pseudo-filesystems.
The usual alternative is to read /proc by hand, which keeps going wrong in
the same four places: the comm field in parentheses in /proc/<pid>/stat,
the tabs in /proc/<pid>/status, the unsigned 64-bit fields that no signed
decimal reader accepts, and a sysfs size that counts 512-byte sectors, not
blocks.
Licence #
ISC, see LICENSE.md.