feat(core): read the Windows memory figures from GlobalMemoryStatusEx

detect_profile fell back to MemoryProfile::from_total_kb(u64::MAX) -- the top
tier -- on any machine without /proc/meminfo, and read_memory_pressure
returned None forever. The prefetch and buffer tiers were therefore pinned at
the top on Windows and the memory watchdog was blind: it continues on None, so
evict_l1_audio_cache and the prefetch halt could never fire at any pressure.

Both halves had to land together. With the old u64::MAX total, a working
pressure probe would have computed roughly 0 per cent available and reported
critical permanently.

ullAvailPhys is the analogue of MemAvailable: what an allocation can take
without going to disk. The Normal-fallback contract is preserved, so a probe
that fails still never throttles the machine. windows-sys 0.61 is already in
the workspace lock via qbz-local-catalog and qbzd, so nothing new is pulled.

Measured on a 32 GB box: 57.9 per cent available, class Normal, which is the
same class the old fallback produced -- the change is the true total and a
pressure figure that exists at all.

cc
This commit is contained in:
vicrodh committed 2026-08-27 12:44:20 -06:00
1 parent 5ba49a7390
commit 1503b8236f
4 files changed
+69 -8

No files matched your search

+1
View File
@@ -4179,6 +4179,7 @@ dependencies = [
"serde_json",
"thiserror 2.0.18",
"tokio",
"windows-sys 0.61.2",
]
[[package]]
+2 -1
View File
@@ -51,7 +51,8 @@ pub fn spawn(player: Arc<Player>) -> tokio::task::JoinHandle<()> {
let mut interval = tokio::time::interval(Duration::from_secs(TICK_SECS));
loop {
interval.tick().await;
// None on platforms without /proc/meminfo — nothing to do.
// None where no memory probe exists (/proc/meminfo on unix,
// GlobalMemoryStatusEx on Windows) — nothing to do.
let Some(pressure) = qbz_core::system_capabilities::read_memory_pressure() else {
continue;
};
+7
View File
@@ -30,6 +30,13 @@ anyhow = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
[target.'cfg(windows)'.dependencies]
# W14: GlobalMemoryStatusEx stands in for /proc/meminfo. 0.61 is already in
# the workspace lock (qbz-local-catalog, qbzd), so this pulls nothing new.
windows-sys = { version = "0.61", features = [
"Win32_System_SystemInformation",
] }
[features]
default = []
# Tier 2 features (to be added later)
+59 -7
View File
@@ -141,14 +141,55 @@ pub fn pressure_from_figures(mem_available_kb: u64, mem_total_kb: u64) -> Memory
}
}
/// Read /proc/meminfo and return a pressure snapshot relative to the
/// detected MemoryProfile. Returns None on platforms without
/// /proc/meminfo or when the file is unreadable.
/// Windows' answer to `/proc/meminfo`: total and available physical memory,
/// both in KiB, from `GlobalMemoryStatusEx`.
///
/// W14. Without this the profile falls back to the top tier and
/// `read_memory_pressure` returns `None` forever, so the memory watchdog is
/// blind on Windows and the prefetch/buffer tiers never step down on a small
/// machine. `ullAvailPhys` is the closest analogue to `MemAvailable`: both
/// mean "what a new allocation can expect without swapping".
#[cfg(windows)]
fn global_memory_status_kb() -> Option<(u64, u64)> {
use windows_sys::Win32::System::SystemInformation::{
GlobalMemoryStatusEx, MEMORYSTATUSEX,
};
let mut status = MEMORYSTATUSEX {
dwLength: std::mem::size_of::<MEMORYSTATUSEX>() as u32,
..unsafe { std::mem::zeroed() }
};
// SAFETY: `status` is a live, correctly sized MEMORYSTATUSEX whose
// `dwLength` is set as the API requires; the call only writes into it.
let ok = unsafe { GlobalMemoryStatusEx(&mut status) };
if ok == 0 {
return None;
}
// The fields are bytes; the rest of this module speaks KiB.
Some((status.ullTotalPhys / 1024, status.ullAvailPhys / 1024))
}
/// Read the platform's memory figures and return a pressure snapshot relative
/// to the detected MemoryProfile. Returns None on platforms with neither
/// `/proc/meminfo` nor `GlobalMemoryStatusEx`, or when the source is
/// unreadable.
pub fn read_memory_pressure() -> Option<MemoryPressure> {
let content = std::fs::read_to_string("/proc/meminfo").ok()?;
let mem_available_kb = parse_meminfo_available_kb(&content)?;
let profile = memory_profile();
Some(pressure_from_figures(mem_available_kb, profile.mem_total_kb))
#[cfg(windows)]
{
let (_total_kb, avail_kb) = global_memory_status_kb()?;
let profile = memory_profile();
return Some(pressure_from_figures(avail_kb, profile.mem_total_kb));
}
#[cfg(not(windows))]
{
let content = std::fs::read_to_string("/proc/meminfo").ok()?;
let mem_available_kb = parse_meminfo_available_kb(&content)?;
let profile = memory_profile();
Some(pressure_from_figures(mem_available_kb, profile.mem_total_kb))
}
}
/// Pure detection given `/proc/meminfo` content. Falls back to Normal
@@ -164,6 +205,17 @@ pub fn detect_profile_from_meminfo(content: &str) -> MemoryProfile {
/// profile on platforms without `/proc/meminfo` (macOS, Windows) or when
/// the file is unreadable for any reason.
fn detect_profile() -> MemoryProfile {
#[cfg(windows)]
{
// Same Normal-fallback contract as the meminfo path: a probe we could
// not run must never throttle the machine.
return match global_memory_status_kb() {
Some((total_kb, _avail_kb)) => MemoryProfile::from_total_kb(total_kb),
None => MemoryProfile::from_total_kb(u64::MAX),
};
}
#[cfg(not(windows))]
match std::fs::read_to_string("/proc/meminfo") {
Ok(content) => detect_profile_from_meminfo(&content),
Err(_) => MemoryProfile::from_total_kb(u64::MAX),