Repository navigation
Conversation
…mance levels and peak RSS (issue #593) - os: page_bytes (a power of two of at least 4 KiB, else refused by name), host_memory (Linux: /proc/meminfo moved unchanged; macOS: hw.memsize and host_statistics64's free, inactive and purgeable pages), perf_levels (macOS sysctl hw.perflevel*; Linux refuses by name), max_rss_bytes and children_max_rss_bytes (bytes on both). - placement: row_room and row_table_tier take the host's base page; the crate reads no OS state, its callers pass the page. host_room takes the platform's reading; HostRead gains VmStats. - model: host_available_read reads os::host_memory and no longer refuses a 16 KiB page; a platform without posix_fadvise keeps its pages and says so once; the two CCD tests that read /proc are ignored on macOS by name. - threads: on macOS the topology is the performance level (its cores and L2); Linux's sysfs path is unchanged. - model-hf: the fixture's peak RSS reads go through os (bytes, not raw ru_maxrss), and the crate drops libc. Linux numbers are unchanged: the page is 4096 there. A unit test sizes the same rows at 4096 and 16384 and is red on a sizing that ignores the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Verdict: MUST_FIX: macOS memory is overcounted, RSS conversion hides invalid values, parser coverage is weakened, and sysctl reads are duplicated.
Must fix
1. Count inactive purgeable pages only once
crates/os/src/lib.rs:541 Purgeable pages also occupy the active/inactive queues. Inactive purgeable pages are added twice here. With no host-room override, the inflated value passes through host_available_read into placement; a need between the real and inflated room can keep tables on the host instead of the NVMe tier, causing memory pressure.
Correct the room before macOS plans depend on it. Count free and inactive pages conservatively, and update the descriptions that also list purgeable pages:
let pages = statistics
.free_count
.saturating_add(statistics.inactive_count);2. Reject invalid RSS instead of reporting zero
crates/os/src/lib.rs:653 and crates/os/src/lib.rs:662 clamp negative readings to zero. max_rss_of returns Ok(0), and the fixture prints valid-looking zero RSS. The Linux multiplication also saturates on overflow. These silent defaults reach a consumer (kind B).
Keep invalid telemetry distinguishable before more callers adopt this API. Keep the cfg attributes; replace the Linux definition, then the macOS definition:
fn rss_bytes(maxrss: libc::c_long) -> u64 {
u64::try_from(maxrss)
.expect("getrusage peak RSS must be nonnegative")
.checked_mul(1024)
.expect("getrusage peak RSS in bytes exceeds u64")
}fn rss_bytes(maxrss: libc::c_long) -> u64 {
u64::try_from(maxrss).expect("getrusage peak RSS must be nonnegative")
}3. Restore the malformed-unit test cases
crates/os/src/lib.rs:1239 omits MemTotal in every malformed-value case. Parsing stops at the missing total. Replacing production line 567's strip_suffix("kB") with split_whitespace().next() accepts MB as kB, but every assertion in this test still passes. The removed mem_available_reads_the_line_in_kb test rejected that mutant; its rejection coverage is lost without a dated reason (kind B).
Restore the previous rejection coverage in the same parser move. Replace the test definition:
fn meminfo_reads_its_lines_in_kb() {
let text = "MemTotal: 263741212 kB\nMemAvailable: 257300180 kB\n";
assert_eq!(
super::meminfo_bytes(text).expect("the lines read"),
(263_741_212 * 1024, 257_300_180 * 1024)
);
assert!(super::meminfo_bytes("MemTotal: 1 kB\n").is_err());
assert!(super::meminfo_bytes("MemAvailable: 1 kB\n").is_err());
for value in ["12x kB", "12 MB", "kB"] {
let text = format!("MemTotal: 1 kB\nMemAvailable: {value}\n");
assert!(super::meminfo_bytes(&text).is_err());
}
}4. Reuse the existing sysctl reader
crates/os/src/lib.rs:668 duplicates the CString conversion, syscall and error handling in crates/os/src/lib.rs:700. The latter already accepts four-byte values. R14 requires one owner; this removes a syscall implementation without adding a helper.
Consolidate these reads before their error handling diverges. Keep the existing cfg attribute and replace the count reader:
fn sysctl_u32(name: &str) -> Result<u32, OsError> {
let invalid = || {
OsError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("{name} is not a count"),
))
};
let value = i32::try_from(sysctl_u64(name)?).map_err(|_| invalid())?;
u32::try_from(value).map_err(|_| invalid())
}Non-blocking
⚠ Pin RSS units exactly: crates/os/src/lib.rs:1287 permits an extra factor of 1024: 8 MiB becomes 8 GiB and remains below a synthetic 32 GiB host's total [derived].
Catch this unit regression now with exact assertions inside the existing test:
#[cfg(target_os = "linux")] assert_eq!(super::rss_bytes(2048), 2_097_152);
#[cfg(target_os = "macos")] assert_eq!(super::rss_bytes(2048), 2048);
Checked
Duplication: both sysctl readers inspected; reuse removes a syscall body.
Silent failure: both RSS conversions followed to fixture output.
Tests: parser early return and surviving unit mutations checked statically.
Gates: parser rejection coverage lost; unsafe-count increase has a dated PIN.
Escape catalog: no C1–C4 patterns match these findings. No ledger lessons are in force.
Notes
Validation was static; no Rust tests or GPU gates were run.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
| .saturating_add(statistics.inactive_count) | ||
| .saturating_add(statistics.purgeable_count); |
There was a problem hiding this comment.
Inactive purgeable pages are already counted in inactive_count; adding purgeable_count again inflates placement room.
| .saturating_add(statistics.inactive_count) | |
| .saturating_add(statistics.purgeable_count); | |
| .saturating_add(statistics.inactive_count); |
…egative peak; one sysctl reader Purgeable pages also sit on the active and inactive queues, so adding them counted inactive purgeable pages twice. A negative ru_maxrss is now a named panic, not 0. sysctl_u32 reads through sysctl_u64. The meminfo parser test again rejects each malformed MemAvailable beside a valid MemTotal, and the RSS test pins the unit on each platform. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Since the last review:
- Memory double-counting: fixed. crates/os/src/lib.rs:543 counts free and inactive pages once.
- Invalid RSS defaults: fixed. crates/os/src/lib.rs:654 rejects negative values and Linux byte overflow with named panics.
- Malformed-unit coverage: fixed. crates/os/src/lib.rs:1223 supplies a valid total before testing each invalid available-memory value.
- Duplicate sysctl reads: fixed. crates/os/src/lib.rs:670 reuses the existing syscall owner and retains signed-count rejection.
- RSS-unit suggestion: fixed. crates/os/src/lib.rs:1276 asserts each platform's exact conversion.
Verdict: CLEAN: All earlier findings are resolved, and the new commits introduce no proven defect.
Checked
Duplicates: 0 new definitions; the duplicate syscall body was removed. Dryer reported 0 pairs.
Complexity: crapper flagged no changed function.
Silent failure: 4 changed paths followed to placement, topology and fixture consumers; invalid RSS panics and invalid sysctl counts return errors.
Tests: accepting MB at production line 568 now fails the malformed-unit assertion; dropping or adding a factor of 1024 at lines 657 or 665 fails the exact RSS assertions. Mutation checks were static.
Gates: the unsafe-block ceiling decreases from 31 to 30 with its dated PIN; no gate is relaxed.
Escape catalog: no C1–C4 pattern matches.
Notes
Validation was static; Rust tests and GPU gates were not run. The PR does not carry the complete GPU evidence list.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
What and why
M0b of #593: the host chain now runs on macOS, not just builds there.
The causes, and the fix for each:
os::page_bytestakes a power of two of at least 4 KiB and refuses anything else by name.placementstays free of OS calls; its callers pass the page in./proc/meminfo.os::host_memorykeeps that read on Linux. macOS readshw.memsize, plushost_statistics64's free, inactive and purgeable pages.HostReadgainsVmStats.os::perf_levels.posix_fadvise(DONTNEED)refusal was fatal inr8fileandhost_lock. A platform without the call now keeps its pages and says so once, as the populate fallback does.ru_maxrssis KiB on Linux and bytes on macOS.os::max_rss_bytesandos::children_max_rss_bytesreturn bytes on both. model-hf's fixture reads use them, and the crate dropslibc./proc/thread-self/stat. They are ignored on macOS by name.Linux numbers do not move, because the page is 4096 there.
Release notes: Under the hood — the host crates' tests pass on macOS (Apple Silicon).
Code shape
bloomery-os, each with a Linux body and a macOS body, or a namedUnsupported./proc/meminfoparsing moved there unchanged.qwen35moe/place.rs, which passes the page torow_table_tier.host_statistics64names itskern_return.tools/check-comments.shrc 0.PIN(2026-10-21):: os goes 24 → 31; model and model-hf come down by the 5 blocks that moved.cargo fmtis clean.cargo clippyon the touched crates for x86_64-unknown-linux-gnu: 0 warnings. On the Mac, one warning comes from main's qdot, not from this change.Duplication found or removed:
host_lock::page_bytes'ssysconfcall now goes throughos::page_bytes.What I ran
just narrow <main>:ptx-scan --no-jitof the scan set is identical on both sides (2016 entries). No kernel moved.Cargo.lockandCargo.tomlhave no gate-paths row.gate-gpu-e2e. The kernels are proven unchanged, and the changed host paths are the planner, host memory, host-lock, r8 and the pool.tools/gate-batch.sh --list … --tier fixture --ledgerover 41 gates →DONE total=41 red=0.gate-prepare-corpus, which needs an environment variable.gate-placementandgate-gpu-e2e→DONE total=2 red=0 tier=real.cargo test -p bloomery-model --lib: 421 passed, 0 failed, 7 ignored. That is 5 ignored before this change, plus the 2/procCPU-id tests, by name.cargo test -p bloomery-os -p bloomery-placement -p bloomery-threads: 204 passed.tools/check-*.sh: rc 0 (15 scripts).left: 536870912 right: 2147483648).sync_rangetest is red on a no-op body.fadviseandsync_rangenow assert an EBADF refusal on a closed descriptor, as asked on os: the host chain builds natively on macOS; docs/contrib/mac.md, the Mac port map (#593) #600.Hardware (if the change is for a machine)
Apple M1 Pro, macOS 27, for the native tests. The gates ran on the maintainers' workstation.
🤖 Generated with Claude Code