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:
4 files changed
+69
-8
No files matched your search
Generated
+1
@@ -4179,6 +4179,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in new issue
Block a user