Skip to content

Add filesystem stats in ContainerStatistics - #930

Merged
crosbymichael merged 7 commits into
apple:mainfrom
JaewonHur:add-filesystem-stats
Sep 21, 2026
Merged

crosbymichael merged 7 commits into
apple:mainfrom
JaewonHur:add-filesystem-stats

Conversation

@JaewonHur

@JaewonHur JaewonHur commented Sep 16, 2026

Copy link
Copy Markdown
Contributor
  • Add filesystem (block_size, blocks, free_blocks, inodes, free_inodes) field to protobuf ContainerStats.

    • Renamed files, ffree to inodes and free_inodes as those are POSIX's abstract.
  • Add ManagedContainer.filesystemStats which stats the file system using statfs syscall. This exposes all fields in statfs struct.

  • Update ContainerStatistics to include filesystem fields.

    • Caller can compute usedBytes = (blocks - freeBlocks) * blockSize, inodesUsed = inodes - freeInodes.
    • Other numbers can be computed if needed (e.g., FS capacity = blocks * blockSize).
  • Added CZ_statfs in LCShim/syscall.c instead of using musl or glibc because it fails to import statfs when compiling for x86_64 linux.

* Add `filesystem (usedBytes, inodesUsed)` field to protobuf
  `ContainerStats`
* Add `ManagedContainer.filesystemStats` which stats the file system
  using `statfs` syscall. `vminitd` in guest uses this to get the
  measurement on `containerStatistics` calls
* Update `ContainerStatistics` to include `filesystem` fields.
`statfs` isn't reliably importable from Swift via the Musl/Glibc system
modulemap across architectures.
* It should not do anything clever
* Expose blockSize, blocks, freeBlocks, inodes, freeInodes
* usedBytes and inodesUsed can be computed using those fields.

@adityaramani adityaramani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good. Will want another reviewer to also +1 though

Comment thread Sources/Containerization/SandboxContext/SandboxContext.pb.swift Outdated
Comment thread Sources/Containerization/SandboxContext/SandboxContext.proto Outdated
/// Memory event counters (OOM kills, pressure events, etc.).
public static let memoryEvents = StatCategory(rawValue: 1 << 5)
/// Filesystem occupancy statistics.
public static let filesystem = StatCategory(rawValue: 1 << 6)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this 1 << 6 mean

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Followed the convention. Internally, the guest side checks that bit to stat filesystem or not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though it'd be better using a named variable instead of a number..

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree variable is better than magic number. I was just trying other understand why these StatCategories are bit based type not an enum or something else

Comment on lines +108 to +109
// struct statfs itself isn't reliably importable from Swift across all
// target architectures via the Musl/Glibc modulemaps, so this wraps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this the case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glibc's struct statfs (bits/statfs.h) branches its field types based on a preprocessor macro:

struct statfs {
    __fsword_t f_type;
    __fsword_t f_bsize;
#ifndef __USE_FILE_OFFSET64
    __fsblkcnt_t f_blocks; ...      // 32-bit variant
#else
    __fsblkcnt64_t f_blocks; ...    // 64-bit variant
#endif
    ...
};

__USE_FILE_OFFSET64 is controlled by _FILE_OFFSET_BITS at compile time — the same header name struct statfs describes two different memory layouts depending on what macros the including translation unit had set. There's also a second name, struct statfs64, gated behind a different macro (__USE_LARGEFILE64).

musl's struct statfs has no such branching — musl never had a 32-bit legacy off_t/blkcnt_t story, so there's exactly one unconditional layout, and statfs64/fstatfs64 are just #define aliases to the same functions, not a different struct.

That's the discrepancy: a Clang module (which is what a modulemap builds) is precompiled once, with one fixed macro configuration baked into its PCM. A type whose layout depends on _FILE_OFFSET_BITS isn't safe to modularize that way — whichever macro state the module happened to be built with becomes "the" struct statfs for every importer, silently disagreeing with any C code compiled under the other setting. musl's single, macro-independent layout has no such hazard, so its modulemap can freely declare sys_statfs/sys_vfs as real submodules. glibc's toolchain-maintained glibc.modulemap most likely just never added sys/vfs.h/sys/statfs.h for exactly this reason (or nobody has patched it in, since these modulemaps are hand-curated, not auto-generated from every header).

Comment thread vminitd/Sources/LCShim/syscall.c
@JaewonHur
JaewonHur force-pushed the add-filesystem-stats branch 3 times, most recently from cfb6c9a to 7cc912d Compare September 21, 2026 20:31
@crosbymichael
crosbymichael merged commit ee881d6 into apple:main Sep 21, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants