Skip to main content

gander_core/
tree.rs

1//! Tree-view construction.
2//!
3//! Matches htop's behaviour: rows whose parent is missing (or is itself) become
4//! roots, and cycles are broken instead of hanging the program.
5
6use std::collections::HashMap;
7
8use crate::row::Row;
9use crate::Pid;
10
11/// Fill `depth`, `has_children`, `is_last_child` on every row, in tree order.
12///
13/// Returns the row indices in depth-first display order. The input slice is not
14/// reordered; only the tree metadata on each row is updated.
15///
16/// Thread rows are nested under their process rather than looked up through
17/// `ppid`. They carry their leader's pid in `tgid`, and several threads share it,
18/// so the generic `by_pid` map (which is keyed by `pid`) cannot express the
19/// relation: a thread's `pid` is its tid, not its leader's. Handling them
20/// separately is what keeps the two namespaces from colliding.
21pub fn build(rows: &mut [Row]) -> Vec<usize> {
22    // Only process rows take part in the parent map. A thread's `pid` is a tid,
23    // which can coincidentally equal an unrelated process's pid; letting it into
24    // `by_pid` would silently re-parent that process.
25    let mut by_pid: HashMap<Pid, usize> = HashMap::with_capacity(rows.len());
26    for (idx, row) in rows.iter().enumerate() {
27        if !row.is_thread {
28            by_pid.insert(row.pid, idx);
29        }
30    }
31
32    let mut children: Vec<Vec<usize>> = vec![Vec::new(); rows.len()];
33    let mut is_root = vec![true; rows.len()];
34
35    for idx in 0..rows.len() {
36        if rows[idx].is_thread {
37            // Attach to the leader if it is present; otherwise leave it a root so
38            // it still appears in the list instead of vanishing.
39            match by_pid.get(&rows[idx].tgid).copied() {
40                Some(p) if p != idx => {
41                    children[p].push(idx);
42                    is_root[idx] = false;
43                }
44                _ => is_root[idx] = true,
45            }
46            continue;
47        }
48
49        let ppid = rows[idx].ppid;
50        let parent = by_pid.get(&ppid).copied();
51        match parent {
52            Some(p) if p != idx => {
53                children[p].push(idx);
54                is_root[idx] = false;
55            }
56            // Missing parent, or the degenerate self-parent case: treat as root.
57            _ => is_root[idx] = true,
58        }
59    }
60
61    // Break cycles: anything not reachable from a root becomes a root itself.
62    let mut seen = vec![false; rows.len()];
63    let mut order = Vec::with_capacity(rows.len());
64
65    for root in 0..rows.len() {
66        if is_root[root] {
67            rows[root].is_last_child = false;
68            walk(root, 0, &children, rows, &mut order, &mut seen);
69        }
70    }
71    // Any remaining node sits on a cycle. Promote and walk it.
72    for idx in 0..rows.len() {
73        if !seen[idx] {
74            rows[idx].depth = 0;
75            rows[idx].is_last_child = false;
76            walk(idx, 0, &children, rows, &mut order, &mut seen);
77        }
78    }
79
80    order
81}
82
83fn walk(
84    idx: usize,
85    depth: u16,
86    children: &[Vec<usize>],
87    rows: &mut [Row],
88    order: &mut Vec<usize>,
89    seen: &mut [bool],
90) {
91    if seen[idx] {
92        return;
93    }
94    seen[idx] = true;
95
96    let kids = &children[idx];
97    rows[idx].depth = depth;
98    rows[idx].has_children = !kids.is_empty();
99    // `is_last_child` is assigned by the caller (the parent's loop), or left
100    // false for roots; assigning it here would erase the parent's decision.
101    order.push(idx);
102
103    let n = kids.len();
104    for (i, &child) in kids.iter().enumerate() {
105        if seen[child] {
106            continue;
107        }
108        rows[child].is_last_child = i + 1 == n;
109        walk(child, depth.saturating_add(1), children, rows, order, seen);
110    }
111}
112
113/// All pids in the subtree rooted at `pid`, including `pid` itself.
114pub fn subtree(rows: &[Row], pid: Pid) -> Vec<Pid> {
115    let mut by_pid: HashMap<Pid, usize> = HashMap::with_capacity(rows.len());
116    for (idx, row) in rows.iter().enumerate() {
117        by_pid.insert(row.pid, idx);
118    }
119    let mut children: HashMap<Pid, Vec<Pid>> = HashMap::new();
120    for row in rows {
121        if by_pid.contains_key(&row.ppid) {
122            children.entry(row.ppid).or_default().push(row.pid);
123        }
124    }
125
126    let mut out = Vec::new();
127    let mut stack = vec![pid];
128    let mut guard = 0usize;
129    while let Some(p) = stack.pop() {
130        out.push(p);
131        guard += 1;
132        if guard > rows.len() + 1 {
133            break; // defensive: cycle
134        }
135        if let Some(kids) = children.get(&p) {
136            stack.extend(kids.iter().copied());
137        }
138    }
139    out
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    fn row(pid: Pid, ppid: Pid) -> Row {
147        let mut r = Row::default_for_test(pid);
148        r.ppid = ppid;
149        r
150    }
151
152    #[test]
153    fn depth_first_order() {
154        // 1 -> (2 -> 4, 3)
155        let mut rows = vec![row(1, 0), row(2, 1), row(3, 1), row(4, 2)];
156        let order = build(&mut rows);
157        let pids: Vec<Pid> = order.iter().map(|&i| rows[i].pid).collect();
158        assert_eq!(pids, vec![1, 2, 4, 3]);
159        assert_eq!(rows[0].depth, 0, "pid 1 is the root");
160        assert_eq!(rows[1].depth, 1, "pid 2 is a child of 1");
161        assert_eq!(rows[3].depth, 2, "pid 4 is a child of 2");
162        assert!(rows[1].has_children);
163        assert!(!rows[0].is_last_child);
164        // Sibling order follows discovery order in the input slice, so pid 3 is
165        // not necessarily last; only the structure is asserted here.
166        assert!(!rows[2].has_children);
167        assert!(order.iter().any(|&i| rows[i].pid == 3));
168    }
169
170    #[test]
171    fn orphan_is_a_root() {
172        let mut rows = vec![row(10, 999)];
173        let order = build(&mut rows);
174        assert_eq!(order.len(), 1);
175        assert_eq!(rows[0].depth, 0);
176    }
177
178    #[test]
179    fn self_parent_does_not_loop() {
180        let mut rows = vec![row(7, 7)];
181        let order = build(&mut rows);
182        assert_eq!(order.len(), 1);
183    }
184
185    #[test]
186    fn cycle_does_not_hang() {
187        // 1 -> 2 -> 3 -> 1
188        let mut rows = vec![row(1, 3), row(2, 1), row(3, 2)];
189        let order = build(&mut rows);
190        assert_eq!(order.len(), 3, "every node must appear exactly once");
191    }
192
193    #[test]
194    fn subtree_walk() {
195        let rows = vec![row(1, 0), row(2, 1), row(3, 1), row(4, 2)];
196        let mut got = subtree(&rows, 1);
197        got.sort_unstable();
198        assert_eq!(got, vec![1, 2, 3, 4]);
199    }
200
201    fn thread_row(pid: Pid, tgid: Pid, id: u64) -> Row {
202        let mut r = Row::default_for_test(pid);
203        r.tgid = tgid;
204        r.ppid = tgid;
205        r.is_thread = true;
206        r.thread_id = Some(id);
207        r
208    }
209
210    /// A thread nests under its leader even though `by_pid` is keyed by `pid`.
211    #[test]
212    fn threads_nest_under_their_leader() {
213        let mut rows = vec![row(1, 0), thread_row(9001, 1, 1), row(2, 1)];
214        let order = build(&mut rows);
215        let pids: Vec<Pid> = order.iter().map(|&i| rows[i].pid).collect();
216        // 1, then its thread, then the child process 2.
217        assert_eq!(pids[0], 1);
218        assert_eq!(pids.len(), 3, "every row appears exactly once");
219        let thread_pos = order
220            .iter()
221            .position(|&i| rows[i].is_thread)
222            .expect("thread present");
223        assert_eq!(rows[order[thread_pos]].depth, 1, "one level under the leader");
224    }
225
226    /// A thread whose pid collides with an unrelated process must not re-parent it.
227    ///
228    /// Linux tids live in the same namespace as pids, so a tid can equal a
229    /// different process's pid. Before this was fixed, `by_pid` was built from
230    /// every row, so the thread silently became that process's tree node.
231    #[test]
232    fn a_thread_pid_does_not_reparent_an_unrelated_process() {
233        let mut rows = vec![
234            row(1, 0),
235            // pid 2 is a real child of 1
236            row(2, 1),
237            // a thread of pid 1 whose tid happens to be 3
238            thread_row(3, 1, 3),
239            // pid 3 is a real process whose parent is 2
240            row(3, 2),
241        ];
242        // The duplicate pid 3 means one of them cannot be in `by_pid`; the thread
243        // must be the one excluded.
244        let order = build(&mut rows);
245        assert_eq!(order.len(), 4);
246
247        let real_3 = order
248            .iter()
249            .position(|&i| rows[i].pid == 3 && !rows[i].is_thread)
250            .expect("the real process 3 must be in the tree");
251        assert_eq!(
252            rows[order[real_3]].depth, 2,
253            "process 3 must still be a child of 2, not of the thread"
254        );
255    }
256
257    /// A thread whose leader is missing still appears rather than vanishing.
258    #[test]
259    fn an_orphan_thread_is_a_root() {
260        let mut rows = vec![thread_row(9001, 424242, 1)];
261        let order = build(&mut rows);
262        assert_eq!(order.len(), 1);
263        assert_eq!(rows[0].depth, 0);
264    }
265}