perf: report top-5 processes by CPU and memory
build-windows / build-hello-agent-x64 (push) Successful in 6m35s
build-windows / sign-hello-agent-x64 (push) Successful in 5s
build-windows / validate-hello-agent-x64 (push) Successful in 9s

Collect every named process per sample, then send the top-5 by CPU and the
top-5 by memory as a `top_processes` object alongside the existing single
top-CPU/top-memory scalars (kept for backward compatibility). opsbase shows
these as 5-row lists in the device's Live performance card.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 12:21:48 +00:00
parent b5e8c00d92
commit 69ec496a62
+43 -15
View File
@@ -57,6 +57,9 @@ struct PendingSample {
top_cpu_pct: f64,
top_mem_name: String,
top_mem_mb: i64,
/// Top-5 processes by CPU (name, normalised %) and by memory (name, MB).
top_cpu_procs: Vec<(String, f64)>,
top_mem_procs: Vec<(String, i64)>,
attempts: u32,
}
@@ -156,33 +159,48 @@ fn collect_sample(sys: &mut hbb_common::sysinfo::System) -> Option<PendingSample
// CPU% reading (both 0-100 of the whole machine).
let cpu_count = sys.cpus().len().max(1) as f64;
let mut top_cpu: Option<(&str, f32)> = None;
let mut top_mem: Option<(&str, u64)> = None;
// Collect every (named) process once, then derive the top-5 by CPU and by
// memory. The single top_cpu_*/top_mem_* scalars are kept for backward
// compatibility (older servers ignore top_processes).
let mut procs: Vec<(&str, f64, i64)> = Vec::new();
let mut proc_count = 0i64;
for proc in sys.processes().values() {
proc_count += 1;
let name = proc.name();
// Some kernel-side rows show up with empty names on Windows;
// skip them so we don't ever render a top-CPU row with no
// label.
// skip them so we don't ever render a top row with no label.
if name.is_empty() {
continue;
}
let cu = proc.cpu_usage();
if cu.is_finite() && cu > top_cpu.map(|(_, v)| v).unwrap_or(0.0) {
top_cpu = Some((name, cu));
}
let mu = proc.memory();
if mu > top_mem.map(|(_, v)| v).unwrap_or(0) {
top_mem = Some((name, mu));
}
let cu = if cu.is_finite() { (cu as f64 / cpu_count).min(100.0) } else { 0.0 };
let mb = (proc.memory() / 1024 / 1024) as i64;
procs.push((name, cu, mb));
}
let (top_cpu_name, top_cpu_pct) = top_cpu
.map(|(n, v)| (n.to_string(), (v as f64 / cpu_count).min(100.0)))
let mut by_cpu = procs.clone();
by_cpu.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let top_cpu_procs: Vec<(String, f64)> = by_cpu
.iter()
.take(5)
.map(|(n, c, _)| (n.to_string(), *c))
.collect();
let mut by_mem = procs;
by_mem.sort_by(|a, b| b.2.cmp(&a.2));
let top_mem_procs: Vec<(String, i64)> = by_mem
.iter()
.take(5)
.map(|(n, _, m)| (n.to_string(), *m))
.collect();
let (top_cpu_name, top_cpu_pct) = top_cpu_procs
.first()
.cloned()
.unwrap_or_default();
let (top_mem_name, top_mem_mb) = top_mem
.map(|(n, v)| (n.to_string(), (v / 1024 / 1024) as i64))
let (top_mem_name, top_mem_mb) = top_mem_procs
.first()
.cloned()
.unwrap_or_default();
Some(PendingSample {
@@ -196,6 +214,8 @@ fn collect_sample(sys: &mut hbb_common::sysinfo::System) -> Option<PendingSample
top_cpu_pct,
top_mem_name,
top_mem_mb,
top_cpu_procs,
top_mem_procs,
attempts: 0,
})
}
@@ -277,6 +297,14 @@ async fn post_batch(batch: &[PendingSample]) -> Result<()> {
"top_cpu_pct": s.top_cpu_pct,
"top_mem_name": s.top_mem_name,
"top_mem_mb": s.top_mem_mb,
"top_processes": {
"cpu": s.top_cpu_procs.iter()
.map(|(n, p)| hbb_common::serde_json::json!({"name": n, "pct": p}))
.collect::<Vec<_>>(),
"mem": s.top_mem_procs.iter()
.map(|(n, m)| hbb_common::serde_json::json!({"name": n, "mb": m}))
.collect::<Vec<_>>(),
},
})
})
.collect();