use std::collections::HashMap;
use crate::row::Row;
use crate::Pid;
pub fn build(rows: &mut [Row]) -> Vec<usize> {
let mut by_pid: HashMap<Pid, usize> = HashMap::with_capacity(rows.len());
for (idx, row) in rows.iter().enumerate() {
if !row.is_thread {
by_pid.insert(row.pid, idx);
}
}
let mut children: Vec<Vec<usize>> = vec![Vec::new(); rows.len()];
let mut is_root = vec![true; rows.len()];
for idx in 0..rows.len() {
if rows[idx].is_thread {
match by_pid.get(&rows[idx].tgid).copied() {
Some(p) if p != idx => {
children[p].push(idx);
is_root[idx] = false;
}
_ => is_root[idx] = true,
}
continue;
}
let ppid = rows[idx].ppid;
let parent = by_pid.get(&ppid).copied();
match parent {
Some(p) if p != idx => {
children[p].push(idx);
is_root[idx] = false;
}
_ => is_root[idx] = true,
}
}
let mut seen = vec![false; rows.len()];
let mut order = Vec::with_capacity(rows.len());
for root in 0..rows.len() {
if is_root[root] {
rows[root].is_last_child = false;
walk(root, 0, &children, rows, &mut order, &mut seen);
}
}
for idx in 0..rows.len() {
if !seen[idx] {
rows[idx].depth = 0;
rows[idx].is_last_child = false;
walk(idx, 0, &children, rows, &mut order, &mut seen);
}
}
order
}
fn walk(
idx: usize,
depth: u16,
children: &[Vec<usize>],
rows: &mut [Row],
order: &mut Vec<usize>,
seen: &mut [bool],
) {
if seen[idx] {
return;
}
seen[idx] = true;
let kids = &children[idx];
rows[idx].depth = depth;
rows[idx].has_children = !kids.is_empty();
order.push(idx);
let n = kids.len();
for (i, &child) in kids.iter().enumerate() {
if seen[child] {
continue;
}
rows[child].is_last_child = i + 1 == n;
walk(child, depth.saturating_add(1), children, rows, order, seen);
}
}
pub fn subtree(rows: &[Row], pid: Pid) -> Vec<Pid> {
let mut by_pid: HashMap<Pid, usize> = HashMap::with_capacity(rows.len());
for (idx, row) in rows.iter().enumerate() {
by_pid.insert(row.pid, idx);
}
let mut children: HashMap<Pid, Vec<Pid>> = HashMap::new();
for row in rows {
if by_pid.contains_key(&row.ppid) {
children.entry(row.ppid).or_default().push(row.pid);
}
}
let mut out = Vec::new();
let mut stack = vec![pid];
let mut guard = 0usize;
while let Some(p) = stack.pop() {
out.push(p);
guard += 1;
if guard > rows.len() + 1 {
break; }
if let Some(kids) = children.get(&p) {
stack.extend(kids.iter().copied());
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn row(pid: Pid, ppid: Pid) -> Row {
let mut r = Row::default_for_test(pid);
r.ppid = ppid;
r
}
#[test]
fn depth_first_order() {
let mut rows = vec![row(1, 0), row(2, 1), row(3, 1), row(4, 2)];
let order = build(&mut rows);
let pids: Vec<Pid> = order.iter().map(|&i| rows[i].pid).collect();
assert_eq!(pids, vec![1, 2, 4, 3]);
assert_eq!(rows[0].depth, 0, "pid 1 is the root");
assert_eq!(rows[1].depth, 1, "pid 2 is a child of 1");
assert_eq!(rows[3].depth, 2, "pid 4 is a child of 2");
assert!(rows[1].has_children);
assert!(!rows[0].is_last_child);
assert!(!rows[2].has_children);
assert!(order.iter().any(|&i| rows[i].pid == 3));
}
#[test]
fn orphan_is_a_root() {
let mut rows = vec![row(10, 999)];
let order = build(&mut rows);
assert_eq!(order.len(), 1);
assert_eq!(rows[0].depth, 0);
}
#[test]
fn self_parent_does_not_loop() {
let mut rows = vec![row(7, 7)];
let order = build(&mut rows);
assert_eq!(order.len(), 1);
}
#[test]
fn cycle_does_not_hang() {
let mut rows = vec![row(1, 3), row(2, 1), row(3, 2)];
let order = build(&mut rows);
assert_eq!(order.len(), 3, "every node must appear exactly once");
}
#[test]
fn subtree_walk() {
let rows = vec![row(1, 0), row(2, 1), row(3, 1), row(4, 2)];
let mut got = subtree(&rows, 1);
got.sort_unstable();
assert_eq!(got, vec![1, 2, 3, 4]);
}
fn thread_row(pid: Pid, tgid: Pid, id: u64) -> Row {
let mut r = Row::default_for_test(pid);
r.tgid = tgid;
r.ppid = tgid;
r.is_thread = true;
r.thread_id = Some(id);
r
}
#[test]
fn threads_nest_under_their_leader() {
let mut rows = vec![row(1, 0), thread_row(9001, 1, 1), row(2, 1)];
let order = build(&mut rows);
let pids: Vec<Pid> = order.iter().map(|&i| rows[i].pid).collect();
assert_eq!(pids[0], 1);
assert_eq!(pids.len(), 3, "every row appears exactly once");
let thread_pos = order
.iter()
.position(|&i| rows[i].is_thread)
.expect("thread present");
assert_eq!(rows[order[thread_pos]].depth, 1, "one level under the leader");
}
#[test]
fn a_thread_pid_does_not_reparent_an_unrelated_process() {
let mut rows = vec![
row(1, 0),
row(2, 1),
thread_row(3, 1, 3),
row(3, 2),
];
let order = build(&mut rows);
assert_eq!(order.len(), 4);
let real_3 = order
.iter()
.position(|&i| rows[i].pid == 3 && !rows[i].is_thread)
.expect("the real process 3 must be in the tree");
assert_eq!(
rows[order[real_3]].depth, 2,
"process 3 must still be a child of 2, not of the thread"
);
}
#[test]
fn an_orphan_thread_is_a_root() {
let mut rows = vec![thread_row(9001, 424242, 1)];
let order = build(&mut rows);
assert_eq!(order.len(), 1);
assert_eq!(rows[0].depth, 0);
}
}