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",
|
"serde_json",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[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));
|
let mut interval = tokio::time::interval(Duration::from_secs(TICK_SECS));
|
||||||
loop {
|
loop {
|
||||||
interval.tick().await;
|
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 {
|
let Some(pressure) = qbz_core::system_capabilities::read_memory_pressure() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ anyhow = { workspace = true }
|
|||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { 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]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
# Tier 2 features (to be added later)
|
# 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
|
/// Windows' answer to `/proc/meminfo`: total and available physical memory,
|
||||||
/// detected MemoryProfile. Returns None on platforms without
|
/// both in KiB, from `GlobalMemoryStatusEx`.
|
||||||
/// /proc/meminfo or when the file is unreadable.
|
///
|
||||||
|
/// 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> {
|
pub fn read_memory_pressure() -> Option<MemoryPressure> {
|
||||||
let content = std::fs::read_to_string("/proc/meminfo").ok()?;
|
#[cfg(windows)]
|
||||||
let mem_available_kb = parse_meminfo_available_kb(&content)?;
|
{
|
||||||
let profile = memory_profile();
|
let (_total_kb, avail_kb) = global_memory_status_kb()?;
|
||||||
Some(pressure_from_figures(mem_available_kb, profile.mem_total_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
|
/// 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
|
/// profile on platforms without `/proc/meminfo` (macOS, Windows) or when
|
||||||
/// the file is unreadable for any reason.
|
/// the file is unreadable for any reason.
|
||||||
fn detect_profile() -> MemoryProfile {
|
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") {
|
match std::fs::read_to_string("/proc/meminfo") {
|
||||||
Ok(content) => detect_profile_from_meminfo(&content),
|
Ok(content) => detect_profile_from_meminfo(&content),
|
||||||
Err(_) => MemoryProfile::from_total_kb(u64::MAX),
|
Err(_) => MemoryProfile::from_total_kb(u64::MAX),
|
||||||
|
|||||||
Reference in new issue
Block a user