e4b0d9b9f6
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>
90 lines
3.0 KiB
Rust
90 lines
3.0 KiB
Rust
//! Tests for process detail collection functionality
|
|
|
|
use socktop_agent::metrics::{collect_journal_entries, collect_process_metrics};
|
|
use socktop_agent::state::AppState;
|
|
use std::process;
|
|
|
|
#[tokio::test]
|
|
async fn test_collect_process_metrics_self() {
|
|
// Test collecting metrics for our own process
|
|
let pid = process::id();
|
|
let state = AppState::new();
|
|
|
|
match collect_process_metrics(pid, &state).await {
|
|
Ok(response) => {
|
|
assert_eq!(response.process.pid, pid);
|
|
assert!(!response.process.name.is_empty());
|
|
// Command might be empty on some systems, so don't assert on it
|
|
assert!(response.cached_at > 0);
|
|
println!(
|
|
"✓ Process metrics collected for PID {}: {} ({})",
|
|
pid, response.process.name, response.process.command
|
|
);
|
|
}
|
|
Err(e) => {
|
|
// This might fail if sysinfo can't find the process, which is possible
|
|
println!("⚠ Warning: Failed to collect process metrics for self: {e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_collect_journal_entries_self() {
|
|
// Test collecting journal entries for our own process
|
|
let pid = process::id();
|
|
|
|
match collect_journal_entries(pid).await {
|
|
Ok(response) => {
|
|
assert!(response.cached_at > 0);
|
|
println!(
|
|
"✓ Journal entries collected for PID {}: {} entries",
|
|
pid, response.total_count
|
|
);
|
|
if !response.entries.is_empty() {
|
|
let entry = &response.entries[0];
|
|
println!(" Latest entry: {}", entry.message);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
// This might fail if journalctl is not available or restricted
|
|
println!("⚠ Warning: Failed to collect journal entries for self: {e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_collect_process_metrics_invalid_pid() {
|
|
// Test with an invalid PID
|
|
let invalid_pid = 999999;
|
|
let state = AppState::new();
|
|
|
|
match collect_process_metrics(invalid_pid, &state).await {
|
|
Ok(_) => {
|
|
println!("⚠ Warning: Unexpectedly found process for invalid PID {invalid_pid}");
|
|
}
|
|
Err(e) => {
|
|
println!("✓ Correctly failed for invalid PID {invalid_pid}: {e}");
|
|
assert!(e.contains("not found"));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_collect_journal_entries_invalid_pid() {
|
|
// Test with an invalid PID - journalctl might still return empty results
|
|
let invalid_pid = 999999;
|
|
|
|
match collect_journal_entries(invalid_pid).await {
|
|
Ok(response) => {
|
|
println!(
|
|
"✓ Journal query completed for invalid PID {} (empty result expected): {} entries",
|
|
invalid_pid, response.total_count
|
|
);
|
|
// Should be empty or very few entries
|
|
}
|
|
Err(e) => {
|
|
println!("✓ Journal query failed for invalid PID {invalid_pid}: {e}");
|
|
}
|
|
}
|
|
}
|