fix(agent): GPU worker thread, async journalctl, correctness + cache fixes
Lightweight: - GPU collection moves to a dedicated worker thread that owns the gfxinfo handle for the process lifetime. gfxinfo's active_gpu() runs a full NVML init/teardown (~20ms, blocking) and we were paying it on the async runtime for every collect — measured at ~80% of the agent's entire active CPU on a GPU machine. The handle holds Rc<Nvml> (not Send), so a thread + mpsc/oneshot channel pair confines it; a zero-total-VRAM reply is treated as a dead session (driver reload) and re-probed. - journalctl now runs via tokio::process instead of blocking one of the two runtime workers for the duration of the subprocess. - TtlCell (state.rs) replaces the four hand-rolled static TTL caches; a cached negative result now counts as fresh, so hosts with no matching temp sensor or GPU stop rescanning every request. Single lock+clone on the GPU cache hit path (was two). Correctness: - Process/child CPU times are now microseconds as documented; they were milliseconds, rendering 1000x too small next to (correct) thread times. - Non-Linux per-process CPU%% clamps AFTER dividing by core count; a 4-cores-busy process on an 8-core box reported 12.5% instead of 50%. - Journal timestamps are real RFC 3339 UTC plus an additive timestamp_us field (sorting is now numeric); the old strings were Debug-formatted SystemTime mangled by string replace. - Partition detection uses /sys/block on Linux: whole-disk filesystems on names like nvme0n1 or zram1 are no longer misclassified as partitions. One shared parent_disk_name() replaces two inline copies. - New sampled_at_ms on the metrics payload (additive) records when the snapshot was actually collected, so clients can compute exact rates across the agent's TTL cache. Security/robustness: - key.pem is created 0600 (was umask default 0644, world-readable) and pre-1.51 keys are tightened on startup. - Per-PID detail/journal caches now evict (60s max age, 64 entries max); they previously grew without bound under PID-walking clients. - The two per-PID ws handlers collapse into one generic helper. - /proc/<pid>/stat parsing unified in one comm-safe module. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -74,6 +74,55 @@ pub struct AppState {
|
||||
pub cache_journal_entries: Arc<Mutex<HashMap<u32, CacheEntry<crate::types::JournalResponse>>>>,
|
||||
}
|
||||
|
||||
/// TTL-gated value behind a std Mutex, for `static` caches on hot paths.
|
||||
/// Replaces the hand-rolled TempCache/GpuCache/refresh-timestamp statics
|
||||
/// that each reimplemented the same at/value pair.
|
||||
pub struct TtlCell<T> {
|
||||
inner: std::sync::Mutex<CacheEntry<T>>,
|
||||
}
|
||||
|
||||
impl<T: Clone> Default for TtlCell<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> TtlCell<T> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
inner: std::sync::Mutex::new(CacheEntry::new()),
|
||||
}
|
||||
}
|
||||
/// The stored value, only while fresh. Poisoned lock reads as a miss.
|
||||
pub fn get_fresh(&self, ttl: Duration) -> Option<T> {
|
||||
let g = self.inner.lock().ok()?;
|
||||
if g.is_fresh(ttl) {
|
||||
g.value.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn set(&self, v: T) {
|
||||
if let Ok(mut g) = self.inner.lock() {
|
||||
g.set(v);
|
||||
}
|
||||
}
|
||||
/// True exactly once per TTL window: restamps and tells the caller to do
|
||||
/// the refresh. Atomic check-and-stamp so concurrent callers don't both
|
||||
/// refresh.
|
||||
pub fn claim_stale(&self, ttl: Duration) -> bool {
|
||||
let Ok(mut g) = self.inner.lock() else {
|
||||
return false;
|
||||
};
|
||||
if g.at.is_none_or(|t| t.elapsed() >= ttl) {
|
||||
g.at = Some(Instant::now());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CacheEntry<T> {
|
||||
pub at: Option<Instant>,
|
||||
@@ -87,7 +136,7 @@ impl<T> Default for CacheEntry<T> {
|
||||
}
|
||||
|
||||
impl<T> CacheEntry<T> {
|
||||
pub fn new() -> Self {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
at: None,
|
||||
value: None,
|
||||
|
||||
Reference in New Issue
Block a user