use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
#[derive(Copy, Clone, Debug, Default)]
pub struct ProcessSnapshot {
pub memory_bytes: u64,
pub memory_allocated_bytes: u64,
pub memory_requested_bytes: u64,
pub memory_reserved_bytes: u64,
pub cpu_percent: f32,
}
static SYNC_MEMORY_BYTES: AtomicU64 = AtomicU64::new(0);
static SYNC_MEMORY_ALLOCATED_BYTES: AtomicU64 = AtomicU64::new(0);
static SYNC_MEMORY_REQUESTED_BYTES: AtomicU64 = AtomicU64::new(0);
static SYNC_MEMORY_RESERVED_BYTES: AtomicU64 = AtomicU64::new(0);
static SYNC_CPU_PERCENT_BITS: AtomicU32 = AtomicU32::new(0);
pub fn process_snapshot() -> ProcessSnapshot {
ProcessSnapshot {
memory_bytes: SYNC_MEMORY_BYTES.load(Ordering::Relaxed),
memory_allocated_bytes: SYNC_MEMORY_ALLOCATED_BYTES.load(Ordering::Relaxed),
memory_requested_bytes: SYNC_MEMORY_REQUESTED_BYTES.load(Ordering::Relaxed),
memory_reserved_bytes: SYNC_MEMORY_RESERVED_BYTES.load(Ordering::Relaxed),
cpu_percent: f32::from_bits(SYNC_CPU_PERCENT_BITS.load(Ordering::Relaxed)),
}
}
pub async fn refresh_process_snapshot() -> ProcessSnapshot {
crate::sys::refresh().await;
let info = crate::sys::INFORMATION.lock().await;
let snapshot = ProcessSnapshot {
memory_bytes: info.memory_usage,
memory_allocated_bytes: info.memory_allocated as u64,
memory_requested_bytes: info.memory_requested as u64,
memory_reserved_bytes: info.memory_reserved as u64,
cpu_percent: info.cpu_usage,
};
SYNC_MEMORY_BYTES.store(snapshot.memory_bytes, Ordering::Relaxed);
SYNC_MEMORY_ALLOCATED_BYTES.store(snapshot.memory_allocated_bytes, Ordering::Relaxed);
SYNC_MEMORY_REQUESTED_BYTES.store(snapshot.memory_requested_bytes, Ordering::Relaxed);
SYNC_MEMORY_RESERVED_BYTES.store(snapshot.memory_reserved_bytes, Ordering::Relaxed);
SYNC_CPU_PERCENT_BITS.store(snapshot.cpu_percent.to_bits(), Ordering::Relaxed);
snapshot
}
static PROCESS_REFRESH_CLAIMED: AtomicBool = AtomicBool::new(false);
#[derive(Clone)]
pub(crate) struct RefreshClaim(Arc<Holder>);
struct Holder {
slot: &'static AtomicBool,
held: AtomicBool,
}
impl Drop for Holder {
fn drop(&mut self) {
if *self.held.get_mut() {
self.slot.store(false, Ordering::Release);
}
}
}
impl RefreshClaim {
pub(crate) fn process() -> Self {
Self::on(&PROCESS_REFRESH_CLAIMED)
}
fn on(slot: &'static AtomicBool) -> Self {
Self(Arc::new(Holder {
slot,
held: AtomicBool::new(false),
}))
}
pub(crate) fn take(&self) -> bool {
if self.0.held.load(Ordering::Relaxed) {
return true;
}
let taken =
self.0.slot.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire).is_ok();
if taken {
self.0.held.store(true, Ordering::Relaxed);
}
taken
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn claim_admits_one_holder_and_hands_over_on_drop() {
static SLOT: AtomicBool = AtomicBool::new(false);
let first = RefreshClaim::on(&SLOT);
let second = RefreshClaim::on(&SLOT);
assert!(first.take(), "an unclaimed slot must admit the first holder");
assert!(!second.take(), "a second holder must be refused while the first holds the claim");
assert!(first.take(), "the holder keeps its claim across passes");
let clone = first.clone();
drop(first);
assert!(!second.take(), "the claim must outlive a clone of its holder");
drop(clone);
assert!(second.take(), "the claim must pass on once its holder is gone");
}
#[tokio::test]
async fn refresh_populates_sync_snapshot_cache() {
let refreshed = refresh_process_snapshot().await;
assert!(refreshed.memory_bytes > 0, "sysinfo failed to read RSS");
let after = process_snapshot();
assert_eq!(
after.memory_bytes, refreshed.memory_bytes,
"sync cache did not pick up the async refresh result",
);
assert_eq!(
after.memory_allocated_bytes, refreshed.memory_allocated_bytes,
"sync cache did not pick up the tracked-memory figure",
);
assert_eq!(
after.memory_requested_bytes, refreshed.memory_requested_bytes,
"sync cache did not pick up the requested-memory figure",
);
assert_eq!(
after.memory_reserved_bytes, refreshed.memory_reserved_bytes,
"sync cache did not pick up the reserved-memory figure",
);
}
}