Skip to main content

proc_cli/commands/
tree.rs

1//! Tree command - Show process tree
2//!
3//! Usage:
4//!   proc tree              # Full process tree
5//!   proc tree node         # Tree for the processes named node
6//!   proc tree node --cmd   # ... or with node anywhere in their command line
7//!   proc tree :3000        # Tree for process on port 3000
8//!   proc tree 1234         # Tree for PID 1234
9//!   proc tree --min-cpu 10 # Only processes using >10% CPU
10//!   proc tree 1234 -a      # Show ancestry (path UP to root)
11
12use crate::core::{
13    classify, matches_by_filter, matches_in_filter, parse_target, resolve_in_dir, resolve_target,
14    Hit, NameMatch, Process, ProcessStatus, StatusFilter, TargetType,
15};
16use crate::error::Result;
17use crate::ui::{format_memory, plural, uptime_label, Printer};
18use clap::Args;
19use colored::*;
20use serde::Serialize;
21use std::collections::HashMap;
22
23/// Show process tree
24#[derive(Args, Debug)]
25pub struct TreeCommand {
26    /// Target: process name, :port, or PID (shows full tree if omitted)
27    #[arg(value_parser = crate::core::validate_target_arg)]
28    target: Option<String>,
29
30    /// Show ancestry (path UP to root) instead of descendants
31    #[arg(long, short)]
32    ancestors: bool,
33
34    /// Show verbose output
35    #[arg(long, short = 'v')]
36    verbose: bool,
37
38    /// Output as JSON
39    #[arg(long, short = 'j')]
40    json: bool,
41
42    /// Maximum depth to display
43    #[arg(long, short, default_value = "10")]
44    depth: usize,
45
46    /// Show PIDs only (compact view)
47    #[arg(long, short = 'C')]
48    compact: bool,
49
50    /// Only show processes using more than this CPU %
51    #[arg(long)]
52    min_cpu: Option<f32>,
53
54    /// Only show processes using more than this memory (MB)
55    #[arg(long)]
56    min_mem: Option<f64>,
57
58    /// Filter by status: running, sleeping, stopped, zombie
59    #[arg(long, value_enum, ignore_case = true)]
60    status: Option<StatusFilter>,
61
62    /// Only show processes running longer than this (seconds)
63    #[arg(long)]
64    min_uptime: Option<u64>,
65
66    /// Filter by directory; takes the next word as the directory unless it starts with '-'
67    /// (bare --in is the current directory, --in=DIR is explicit)
68    #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
69    pub in_dir: Option<String>,
70
71    /// Filter by process name
72    #[arg(long = "by", short = 'b')]
73    pub by_name: Option<String>,
74
75    /// Match name targets and --by as a substring of the process name or full command line (like pkill -f)
76    #[arg(long)]
77    pub cmd: bool,
78}
79
80impl TreeCommand {
81    /// Build the set of ancestor PIDs to exclude from name matching.
82    ///
83    /// Walks up the process tree from the current process to avoid matching
84    /// shells whose command lines contain proc's own arguments. `all_processes`
85    /// leaves proc itself out, so its parent is looked up separately.
86    fn ancestor_pids(all_processes: &[Process]) -> std::collections::HashSet<u32> {
87        let mut pids = std::collections::HashSet::new();
88        let self_pid = std::process::id();
89        pids.insert(self_pid);
90        let mut next = Process::find_by_pid(self_pid)
91            .ok()
92            .flatten()
93            .and_then(|me| me.parent_pid);
94        for _ in 0..10 {
95            let Some(parent_pid) = next else { break };
96            pids.insert(parent_pid);
97            next = all_processes
98                .iter()
99                .find(|p| p.pid == parent_pid)
100                .and_then(|p| p.parent_pid);
101        }
102        pids
103    }
104
105    /// The processes a name matches, leaving out `ancestors` (proc and the shells
106    /// above it), and how many others have the name only in their command line
107    /// (what `--cmd` would add): both come from the one pass over `all`.
108    fn named<'a>(
109        all: impl Iterator<Item = &'a Process>,
110        ancestors: &std::collections::HashSet<u32>,
111        pattern: &str,
112        mode: NameMatch,
113    ) -> (Vec<&'a Process>, usize) {
114        let mut others = 0;
115        let named = all
116            .filter(|p| !ancestors.contains(&p.pid))
117            .filter(|p| match classify(pattern, &p.names(), mode) {
118                Hit::Match => true,
119                Hit::CommandLineOnly => {
120                    others += 1;
121                    false
122                }
123                Hit::No => false,
124            })
125            .collect();
126        (named, others)
127    }
128
129    /// Executes the tree command, displaying the process hierarchy.
130    pub fn execute(&self) -> Result<()> {
131        let printer = Printer::from_flags(self.json, self.verbose);
132
133        // Get all processes, with real CPU readings for the nodes and --min-cpu
134        let mut all_processes = Process::find_all()?;
135        Process::measure_cpu(&mut all_processes);
136
137        // Build PID -> Process map for quick lookup
138        let pid_map: HashMap<u32, &Process> = all_processes.iter().map(|p| (p.pid, p)).collect();
139
140        // Build parent -> children map
141        let mut children_map: HashMap<u32, Vec<&Process>> = HashMap::new();
142
143        for proc in &all_processes {
144            if let Some(ppid) = proc.parent_pid {
145                children_map.entry(ppid).or_default().push(proc);
146            }
147        }
148
149        // Handle --ancestors mode
150        if self.ancestors {
151            return self.show_ancestors(&printer, &pid_map);
152        }
153
154        // Determine target processes; `in_command_line` counts the processes a name
155        // is only in the command line of, which `--cmd` would have matched
156        let mode = NameMatch::from_flag(self.cmd);
157        let mut in_command_line = 0;
158        let target_processes: Vec<&Process> = if let Some(ref target) = self.target {
159            // Use unified target resolution
160            match parse_target(target) {
161                TargetType::Port(_) | TargetType::Pid(_) => {
162                    // For port or PID, resolve to specific process(es)
163                    // Errors (not found) rather than returning nothing
164                    let resolved = resolve_target(target, mode)?;
165                    // Find matching processes in all_processes
166                    let pids: Vec<u32> = resolved.iter().map(|p| p.pid).collect();
167                    all_processes
168                        .iter()
169                        .filter(|p| pids.contains(&p.pid))
170                        .collect()
171                }
172                TargetType::Name(ref pattern) => {
173                    // For name, do pattern matching (exclude self and parent to avoid
174                    // false positives from proc's own args in the shell command line)
175                    let ancestor_pids = Self::ancestor_pids(&all_processes);
176                    let (named, others) =
177                        Self::named(all_processes.iter(), &ancestor_pids, pattern, mode);
178                    in_command_line = others;
179                    named
180                }
181            }
182        } else {
183            Vec::new() // Will show full tree
184        };
185
186        // Apply --in and --by filters (only for targeted mode)
187        let target_processes = if self.target.is_some() {
188            let in_dir_filter = resolve_in_dir(&self.in_dir);
189            target_processes
190                .into_iter()
191                .filter(|p| {
192                    if let Some(ref dir_path) = in_dir_filter {
193                        if !matches_in_filter(p, dir_path) {
194                            return false;
195                        }
196                    }
197                    if let Some(ref name) = self.by_name {
198                        if !matches_by_filter(p, name, mode) {
199                            return false;
200                        }
201                    }
202                    true
203                })
204                .collect()
205        } else {
206            target_processes
207        };
208
209        // Apply resource filters if specified
210        let matches_filters = |p: &Process| self.matches_filters(p);
211
212        // Apply filters to target processes or find filtered roots
213        let has_filters = self.min_cpu.is_some()
214            || self.min_mem.is_some()
215            || self.status.is_some()
216            || self.min_uptime.is_some();
217
218        if self.json {
219            let tree_nodes = if self.target.is_some() {
220                target_processes
221                    .iter()
222                    .filter(|p| matches_filters(p))
223                    .map(|p| self.build_tree_node(p, &children_map, 0))
224                    .collect()
225            } else if has_filters {
226                // Show only processes matching filters
227                all_processes
228                    .iter()
229                    .filter(|p| matches_filters(p))
230                    .map(|p| self.build_tree_node(p, &children_map, 0))
231                    .collect()
232            } else {
233                // Show full tree from roots
234                all_processes
235                    .iter()
236                    .filter(|p| p.parent_pid.is_none() || p.parent_pid == Some(0))
237                    .map(|p| self.build_tree_node(p, &children_map, 0))
238                    .collect()
239            };
240
241            printer.print_json(&TreeOutput {
242                action: "tree",
243                success: true,
244                tree: tree_nodes,
245            });
246        } else if self.target.is_some() {
247            let filtered: Vec<_> = target_processes
248                .into_iter()
249                .filter(|p| matches_filters(p))
250                .collect();
251            if filtered.is_empty() {
252                let target = self.target.as_ref().unwrap();
253                let mut warning = format!("No processes found for '{target}'");
254                if in_command_line > 0 {
255                    warning.push_str("\n  ");
256                    warning.push_str(&crate::error::command_line_hint(target, in_command_line));
257                }
258                printer.warning(&warning);
259                return Ok(());
260            }
261
262            println!(
263                "{} Process tree for '{}':\n",
264                "✓".green().bold(),
265                self.target.as_ref().unwrap().cyan()
266            );
267
268            for proc in &filtered {
269                self.print_tree(proc, &children_map, "", true, 0);
270                println!();
271            }
272        } else if has_filters {
273            let filtered: Vec<_> = all_processes
274                .iter()
275                .filter(|p| matches_filters(p))
276                .collect();
277            if filtered.is_empty() {
278                printer.warning("No processes match the specified filters");
279                return Ok(());
280            }
281
282            println!(
283                "{} {} process{} matching filters:\n",
284                "✓".green().bold(),
285                filtered.len().to_string().cyan().bold(),
286                plural(filtered.len())
287            );
288
289            for (i, proc) in filtered.iter().enumerate() {
290                let is_last = i == filtered.len() - 1;
291                self.print_tree(proc, &children_map, "", is_last, 0);
292            }
293        } else {
294            println!("{} Process tree:\n", "✓".green().bold());
295
296            // Find processes with PID 1 or no parent as roots
297            let display_roots: Vec<&Process> = all_processes
298                .iter()
299                .filter(|p| p.parent_pid.is_none() || p.parent_pid == Some(0))
300                .collect();
301
302            for (i, proc) in display_roots.iter().enumerate() {
303                let is_last = i == display_roots.len() - 1;
304                self.print_tree(proc, &children_map, "", is_last, 0);
305            }
306        }
307
308        Ok(())
309    }
310
311    /// Whether a process passes `--min-cpu`, `--min-mem`, `--status` and `--min-uptime`
312    fn matches_filters(&self, p: &Process) -> bool {
313        if let Some(min_cpu) = self.min_cpu {
314            if p.cpu_percent < min_cpu {
315                return false;
316            }
317        }
318        if let Some(min_mem) = self.min_mem {
319            if p.memory_mb < min_mem {
320                return false;
321            }
322        }
323        if let Some(status) = self.status {
324            if !status.matches(p.status) {
325                return false;
326            }
327        }
328        if let Some(min_uptime) = self.min_uptime {
329            if let Some(start_time) = p.start_time {
330                let now = std::time::SystemTime::now()
331                    .duration_since(std::time::UNIX_EPOCH)
332                    .map(|d| d.as_secs())
333                    .unwrap_or(0);
334                if now.saturating_sub(start_time) < min_uptime {
335                    return false;
336                }
337            } else {
338                return false;
339            }
340        }
341        true
342    }
343
344    fn print_tree(
345        &self,
346        proc: &Process,
347        children_map: &HashMap<u32, Vec<&Process>>,
348        prefix: &str,
349        is_last: bool,
350        depth: usize,
351    ) {
352        if depth > self.depth {
353            return;
354        }
355
356        let connector = if is_last { "└── " } else { "├── " };
357
358        if self.compact {
359            println!(
360                "{}{}{}",
361                prefix.bright_black(),
362                connector.bright_black(),
363                proc.pid.to_string().cyan()
364            );
365        } else {
366            let status_indicator = status_indicator(proc.status);
367
368            println!(
369                "{}{}{} {} [{}] {:.1}% {}{}",
370                prefix.bright_black(),
371                connector.bright_black(),
372                status_indicator,
373                proc.name.white().bold(),
374                proc.pid.to_string().cyan(),
375                proc.cpu_percent,
376                format_memory(proc.memory_mb),
377                uptime_label(proc.uptime_secs).bright_black()
378            );
379        }
380
381        let child_prefix = if is_last {
382            format!("{}    ", prefix)
383        } else {
384            format!("{}│   ", prefix)
385        };
386
387        if let Some(children) = children_map.get(&proc.pid) {
388            let mut sorted_children: Vec<&&Process> = children.iter().collect();
389            sorted_children.sort_by_key(|p| p.pid);
390
391            for (i, child) in sorted_children.iter().enumerate() {
392                let child_is_last = i == sorted_children.len() - 1;
393                self.print_tree(child, children_map, &child_prefix, child_is_last, depth + 1);
394            }
395        }
396    }
397
398    fn build_tree_node(
399        &self,
400        proc: &Process,
401        children_map: &HashMap<u32, Vec<&Process>>,
402        depth: usize,
403    ) -> TreeNode {
404        let children = if depth < self.depth {
405            children_map
406                .get(&proc.pid)
407                .map(|kids| {
408                    kids.iter()
409                        .map(|p| self.build_tree_node(p, children_map, depth + 1))
410                        .collect()
411                })
412                .unwrap_or_default()
413        } else {
414            Vec::new()
415        };
416
417        TreeNode {
418            pid: proc.pid,
419            name: proc.name.clone(),
420            cpu_percent: proc.cpu_percent,
421            memory_mb: proc.memory_mb,
422            status: proc.status,
423            uptime_secs: proc.uptime_secs,
424            children,
425        }
426    }
427
428    /// Show ancestry (path UP to root) for target processes
429    fn show_ancestors(&self, printer: &Printer, pid_map: &HashMap<u32, &Process>) -> Result<()> {
430        use crate::core::{parse_target, resolve_target, TargetType};
431
432        let mode = NameMatch::from_flag(self.cmd);
433        let mut in_command_line = 0;
434        let target = match &self.target {
435            Some(t) => t,
436            None => {
437                printer.warning("--ancestors requires a target (PID, :port, or name)");
438                return Ok(());
439            }
440        };
441
442        // Resolve target to processes
443        let target_processes = match parse_target(target) {
444            TargetType::Port(_) | TargetType::Pid(_) => resolve_target(target, mode)?,
445            TargetType::Name(ref pattern) => {
446                let all_procs: Vec<Process> = pid_map.values().map(|p| (*p).clone()).collect();
447                let ancestor_pids = Self::ancestor_pids(&all_procs);
448                let (named, others) =
449                    Self::named(pid_map.values().copied(), &ancestor_pids, pattern, mode);
450                in_command_line = others;
451                named.into_iter().cloned().collect()
452            }
453        };
454
455        if target_processes.is_empty() {
456            let mut warning = format!("No process found for '{}'", target);
457            if in_command_line > 0 {
458                warning.push_str("\n  ");
459                warning.push_str(&crate::error::command_line_hint(target, in_command_line));
460            }
461            printer.warning(&warning);
462            return Ok(());
463        }
464
465        if self.json {
466            let ancestry_output: Vec<AncestryNode> = target_processes
467                .iter()
468                .map(|proc| self.build_ancestry_node(proc, pid_map))
469                .collect();
470            printer.print_json(&AncestryOutput {
471                action: "tree",
472                success: true,
473                ancestry: ancestry_output,
474            });
475        } else {
476            println!("{} Ancestry for '{}':\n", "✓".green().bold(), target.cyan());
477
478            for proc in &target_processes {
479                self.print_ancestry(proc, pid_map);
480                println!();
481            }
482        }
483
484        Ok(())
485    }
486
487    /// Trace and print ancestry from root down to target
488    fn print_ancestry(&self, target: &Process, pid_map: &HashMap<u32, &Process>) {
489        // Root first, target last
490        let chain = ancestor_chain(target, pid_map);
491
492        // Print the chain
493        for (i, proc) in chain.iter().enumerate() {
494            let is_target = proc.pid == target.pid;
495            let indent = "    ".repeat(i);
496            let connector = if i == 0 { "" } else { "└── " };
497
498            let status_indicator = status_indicator(proc.status);
499
500            if is_target {
501                // Highlight the target
502                println!(
503                    "{}{}{} {} [{}] {:.1}% {}{}  {}",
504                    indent.bright_black(),
505                    connector.bright_black(),
506                    status_indicator,
507                    proc.name.cyan().bold(),
508                    proc.pid.to_string().cyan().bold(),
509                    proc.cpu_percent,
510                    format_memory(proc.memory_mb),
511                    uptime_label(proc.uptime_secs).bright_black(),
512                    "← target".yellow()
513                );
514            } else {
515                println!(
516                    "{}{}{} {} [{}] {:.1}% {}{}",
517                    indent.bright_black(),
518                    connector.bright_black(),
519                    status_indicator,
520                    proc.name.white(),
521                    proc.pid.to_string().cyan(),
522                    proc.cpu_percent,
523                    format_memory(proc.memory_mb),
524                    uptime_label(proc.uptime_secs).bright_black()
525                );
526            }
527        }
528    }
529
530    /// Build ancestry node for JSON output
531    fn build_ancestry_node(
532        &self,
533        target: &Process,
534        pid_map: &HashMap<u32, &Process>,
535    ) -> AncestryNode {
536        let chain: Vec<ProcessInfo> = ancestor_chain(target, pid_map)
537            .into_iter()
538            .map(|proc| ProcessInfo {
539                pid: proc.pid,
540                name: proc.name.clone(),
541                cpu_percent: proc.cpu_percent,
542                memory_mb: proc.memory_mb,
543                status: proc.status,
544                uptime_secs: proc.uptime_secs,
545            })
546            .collect();
547
548        AncestryNode {
549            target_pid: target.pid,
550            target_name: target.name.clone(),
551            depth: chain.len(),
552            chain,
553        }
554    }
555}
556
557/// The marker for a process status in the tree
558fn status_indicator(status: ProcessStatus) -> ColoredString {
559    match status {
560        ProcessStatus::Running => "●".green(),
561        ProcessStatus::Sleeping => "○".blue(),
562        ProcessStatus::Stopped => "◐".yellow(),
563        ProcessStatus::Zombie => "✗".red(),
564        _ => "?".white(),
565    }
566}
567
568/// The processes from the root down to `target`, found by following parent
569/// PIDs. The chain ends at a parent the process list doesn't hold (the root's
570/// parent is 0, or the parent is gone) and is cut at 100 so a loop in the
571/// parent links cannot run forever.
572fn ancestor_chain<'a>(
573    target: &'a Process,
574    pid_map: &HashMap<u32, &'a Process>,
575) -> Vec<&'a Process> {
576    let mut chain: Vec<&Process> = Vec::new();
577    let mut current_pid = Some(target.pid);
578
579    while let Some(pid) = current_pid {
580        let Some(proc) = pid_map.get(&pid) else { break };
581        chain.push(proc);
582        current_pid = proc.parent_pid;
583        if chain.len() > 100 {
584            break;
585        }
586    }
587
588    chain.reverse();
589    chain
590}
591
592#[derive(Serialize)]
593struct AncestryOutput {
594    action: &'static str,
595    success: bool,
596    ancestry: Vec<AncestryNode>,
597}
598
599#[derive(Serialize)]
600struct AncestryNode {
601    target_pid: u32,
602    target_name: String,
603    depth: usize,
604    chain: Vec<ProcessInfo>,
605}
606
607#[derive(Serialize)]
608struct ProcessInfo {
609    pid: u32,
610    name: String,
611    cpu_percent: f32,
612    memory_mb: f64,
613    status: ProcessStatus,
614    #[serde(skip_serializing_if = "Option::is_none")]
615    uptime_secs: Option<u64>,
616}
617
618#[derive(Serialize)]
619struct TreeOutput {
620    action: &'static str,
621    success: bool,
622    tree: Vec<TreeNode>,
623}
624
625#[derive(Serialize)]
626struct TreeNode {
627    pid: u32,
628    name: String,
629    cpu_percent: f32,
630    memory_mb: f64,
631    status: ProcessStatus,
632    #[serde(skip_serializing_if = "Option::is_none")]
633    uptime_secs: Option<u64>,
634    children: Vec<TreeNode>,
635}
636
637#[cfg(test)]
638mod tests {
639    use super::*;
640
641    fn command() -> TreeCommand {
642        TreeCommand {
643            target: None,
644            ancestors: false,
645            verbose: false,
646            json: false,
647            depth: 10,
648            compact: false,
649            min_cpu: None,
650            min_mem: None,
651            status: None,
652            min_uptime: None,
653            in_dir: None,
654            by_name: None,
655            cmd: false,
656        }
657    }
658
659    /// A process record with no start time, which some processes really are
660    fn process(pid: u32, parent_pid: Option<u32>) -> Process {
661        Process {
662            pid,
663            name: format!("p{pid}"),
664            exe_path: None,
665            cwd: None,
666            command: None,
667            argv0: None,
668            raw: None,
669            cpu_percent: 0.0,
670            memory_mb: 0.0,
671            status: ProcessStatus::Sleeping,
672            user: None,
673            parent_pid,
674            start_time: None,
675            uptime_secs: None,
676        }
677    }
678
679    #[test]
680    fn a_name_matches_whole_and_counts_the_processes_only_cmd_would_add() {
681        let mut node = process(1, None);
682        node.name = "node".to_string();
683        let mut editor = process(2, None);
684        editor.name = "vim".to_string();
685        editor.command = Some("vim node-notes.md".to_string());
686        let mut shell = process(3, None);
687        shell.name = "node".to_string();
688        let all = [node, editor, shell];
689        let skipped: std::collections::HashSet<u32> = [3].into();
690
691        let (named, others) = TreeCommand::named(all.iter(), &skipped, "node", NameMatch::Exact);
692        assert_eq!(named.iter().map(|p| p.pid).collect::<Vec<_>>(), vec![1]);
693        assert_eq!(others, 1);
694
695        let (named, others) = TreeCommand::named(all.iter(), &skipped, "node", NameMatch::Command);
696        assert_eq!(named.iter().map(|p| p.pid).collect::<Vec<_>>(), vec![1, 2]);
697        assert_eq!(others, 0);
698
699        let (named, others) = TreeCommand::named(all.iter(), &skipped, "python", NameMatch::Exact);
700        assert!(named.is_empty());
701        assert_eq!(others, 0);
702    }
703
704    #[test]
705    fn a_process_with_no_start_time_fails_min_uptime() {
706        let cmd = TreeCommand {
707            min_uptime: Some(0),
708            ..command()
709        };
710        assert!(!cmd.matches_filters(&process(1, None)));
711        assert!(command().matches_filters(&process(1, None)));
712    }
713
714    #[test]
715    fn every_status_has_its_own_marker() {
716        let color = |status| status_indicator(status).fgcolor;
717        let text = |status| status_indicator(status).trim().to_string();
718        assert_eq!(text(ProcessStatus::Running), "●");
719        assert_eq!(text(ProcessStatus::Sleeping), "○");
720        assert_eq!(text(ProcessStatus::Stopped), "◐");
721        assert_eq!(text(ProcessStatus::Zombie), "✗");
722        assert_eq!(text(ProcessStatus::Dead), "?");
723        assert_eq!(text(ProcessStatus::Unknown), "?");
724        assert_eq!(color(ProcessStatus::Running), Some(Color::Green));
725        assert_eq!(color(ProcessStatus::Sleeping), Some(Color::Blue));
726        assert_eq!(color(ProcessStatus::Stopped), Some(Color::Yellow));
727        assert_eq!(color(ProcessStatus::Zombie), Some(Color::Red));
728        assert_eq!(color(ProcessStatus::Unknown), Some(Color::White));
729    }
730
731    #[test]
732    fn the_chain_runs_from_the_root_down_to_the_target() {
733        let (root, middle, leaf) = (
734            process(1, Some(0)),
735            process(2, Some(1)),
736            process(3, Some(2)),
737        );
738        let map: HashMap<u32, &Process> = [&root, &middle, &leaf]
739            .iter()
740            .map(|p| (p.pid, *p))
741            .collect();
742        let chain: Vec<u32> = ancestor_chain(&leaf, &map).iter().map(|p| p.pid).collect();
743        assert_eq!(chain, vec![1, 2, 3]);
744    }
745
746    #[test]
747    fn a_loop_in_the_parent_links_is_cut() {
748        let (a, b) = (process(1, Some(2)), process(2, Some(1)));
749        let map: HashMap<u32, &Process> = [&a, &b].iter().map(|p| (p.pid, *p)).collect();
750        assert_eq!(ancestor_chain(&a, &map).len(), 101);
751    }
752
753    #[test]
754    fn a_process_that_passes_every_filter_is_kept() {
755        let mut p = process(1, None);
756        p.start_time = Some(0);
757        let cmd = TreeCommand {
758            min_cpu: Some(0.0),
759            min_mem: Some(0.0),
760            status: Some(StatusFilter::Sleeping),
761            min_uptime: Some(1),
762            ..command()
763        };
764        assert!(cmd.matches_filters(&p));
765        let other_status = TreeCommand {
766            status: Some(StatusFilter::Running),
767            ..command()
768        };
769        assert!(!other_status.matches_filters(&p));
770    }
771}