agentmux_srv\backend\blockcontroller/
process_tree.rs

1// Copyright 2025-2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Process tree traversal: BFS from a root PID to collect all descendant PIDs.
5//! Used by the sysinfo loop to aggregate CPU/memory across entire process trees.
6
7use std::collections::{HashMap, VecDeque};
8
9use sysinfo::{Pid, System};
10
11/// Maximum number of PIDs to track per block (safety cap against pathological trees).
12pub const MAX_PIDS_PER_BLOCK: usize = 64;
13
14/// Returns the root PID plus all descendant PIDs via BFS, capped at `max_pids`.
15///
16/// Requires `sys` to have been refreshed with at least a minimal `ProcessRefreshKind`
17/// (so that `Process::parent()` is populated for all processes).
18pub fn collect_descendants(sys: &System, root: Pid, max_pids: usize) -> Vec<Pid> {
19    // Build parent → children adjacency map in O(N) over all known processes.
20    let mut children: HashMap<Pid, Vec<Pid>> = HashMap::new();
21    for (pid, proc) in sys.processes() {
22        if let Some(ppid) = proc.parent() {
23            children.entry(ppid).or_default().push(*pid);
24        }
25    }
26
27    // BFS from root, stopping when we hit max_pids.
28    let mut result = Vec::with_capacity(max_pids.min(8));
29    result.push(root);
30    let mut queue = VecDeque::from([root]);
31
32    while let Some(pid) = queue.pop_front() {
33        if result.len() >= max_pids {
34            break;
35        }
36        if let Some(kids) = children.get(&pid) {
37            for &child in kids {
38                if result.len() >= max_pids {
39                    break;
40                }
41                result.push(child);
42                queue.push_back(child);
43            }
44        }
45    }
46
47    result
48}