Skip to main content

nu_command/system/
ps.rs

1#[cfg(target_os = "macos")]
2use chrono::{Local, TimeZone};
3#[cfg(windows)]
4use itertools::Itertools;
5use nu_engine::command_prelude::*;
6
7#[cfg(target_os = "linux")]
8use procfs::WithCurrentSystemInfo;
9use std::time::Duration;
10
11#[derive(Clone)]
12pub struct Ps;
13
14impl Command for Ps {
15    fn name(&self) -> &str {
16        "ps"
17    }
18
19    fn signature(&self) -> Signature {
20        Signature::build("ps")
21            .input_output_types(vec![(Type::Nothing, Type::table())])
22            .switch(
23                "long",
24                "List all available columns for each entry.",
25                Some('l'),
26            )
27            .filter()
28            .category(Category::System)
29    }
30
31    fn description(&self) -> &str {
32        "View information about system processes."
33    }
34
35    fn search_terms(&self) -> Vec<&str> {
36        vec![
37            "procedures",
38            "operations",
39            "tasks",
40            "ops",
41            "top",
42            "tasklist",
43        ]
44    }
45
46    fn run(
47        &self,
48        engine_state: &EngineState,
49        stack: &mut Stack,
50        call: &Call,
51        _input: PipelineData,
52    ) -> Result<PipelineData, ShellError> {
53        run_ps(engine_state, stack, call)
54    }
55
56    fn examples(&self) -> Vec<Example<'_>> {
57        vec![
58            Example {
59                description: "List the system processes",
60                example: "ps",
61                result: None,
62            },
63            Example {
64                description: "List the top 5 system processes with the highest memory usage",
65                example: "ps | sort-by mem | last 5",
66                result: None,
67            },
68            Example {
69                description: "List the top 3 system processes with the highest CPU usage",
70                example: "ps | sort-by cpu | last 3",
71                result: None,
72            },
73            Example {
74                description: "List the system processes with 'nu' in their names",
75                example: "ps | where name =~ 'nu'",
76                result: None,
77            },
78            Example {
79                description: "Get the parent process id of the current nu process",
80                example: "ps | where pid == $nu.pid | get ppid",
81                result: None,
82            },
83        ]
84    }
85}
86
87fn run_ps(
88    engine_state: &EngineState,
89    stack: &mut Stack,
90    call: &Call,
91) -> Result<PipelineData, ShellError> {
92    let mut output = vec![];
93    let span = call.head;
94    let long = call.has_flag(engine_state, stack, "long")?;
95
96    for proc in nu_system::collect_proc(Duration::from_millis(100), false) {
97        let mut record = Record::new();
98
99        record.push("pid", Value::int(proc.pid() as i64, span));
100        record.push("ppid", Value::int(proc.ppid() as i64, span));
101        record.push("name", Value::string(proc.name(), span));
102
103        #[cfg(not(windows))]
104        {
105            // Hide status on Windows until we can find a good way to support it
106            record.push("status", Value::string(proc.status(), span));
107        }
108
109        record.push("cpu", Value::float(proc.cpu_usage(), span));
110        record.push("mem", Value::filesize(proc.mem_size() as i64, span));
111        record.push("virtual", Value::filesize(proc.virtual_size() as i64, span));
112
113        if long {
114            record.push("command", Value::string(proc.command(), span));
115            #[cfg(target_os = "linux")]
116            {
117                let proc_stat = proc.curr_proc.stat().map_err(|e| {
118                    ShellError::Generic(nu_protocol::shell_error::generic::GenericError::new(
119                        "Error getting process stat",
120                        e.to_string(),
121                        span,
122                    ))
123                })?;
124                record.push(
125                    "start_time",
126                    match proc_stat.starttime().get() {
127                        Ok(ts) => Value::date(ts.into(), span),
128                        Err(_) => Value::nothing(span),
129                    },
130                );
131                record.push("user_id", Value::int(proc.curr_proc.owner() as i64, span));
132                record.push("process_group_id", Value::int(proc_stat.pgrp as i64, span));
133                record.push("session_id", Value::int(proc_stat.session as i64, span));
134                // This may be helpful for ctrl+z type of checking, once we get there
135                // record.push("tpg_id", Value::int(proc_stat.tpgid as i64, span));
136                record.push("priority", Value::int(proc_stat.priority, span));
137                record.push("process_threads", Value::int(proc_stat.num_threads, span));
138                record.push("cwd", Value::string(proc.cwd(), span));
139            }
140            #[cfg(windows)]
141            {
142                //TODO: There's still more information we can cram in there if we want to
143                // see the ProcessInfo struct for more information
144                record.push(
145                    "start_time",
146                    Value::date(proc.start_time.fixed_offset(), span),
147                );
148                record.push(
149                    "user",
150                    Value::string(
151                        proc.user.clone().name.unwrap_or("unknown".to_string()),
152                        span,
153                    ),
154                );
155                record.push(
156                    "user_sid",
157                    Value::string(
158                        proc.user
159                            .clone()
160                            .sid
161                            .iter()
162                            .map(|r| r.to_string())
163                            .join("-"),
164                        span,
165                    ),
166                );
167                record.push("priority", Value::int(proc.priority as i64, span));
168                record.push("cwd", Value::string(proc.cwd(), span));
169                record.push(
170                    "environment",
171                    Value::list(
172                        proc.environ()
173                            .iter()
174                            .map(|x| Value::string(x.to_string(), span))
175                            .collect(),
176                        span,
177                    ),
178                );
179            }
180            #[cfg(target_os = "macos")]
181            {
182                let timestamp = Local
183                    .timestamp_nanos(proc.start_time * 1_000_000_000)
184                    .into();
185                record.push("start_time", Value::date(timestamp, span));
186                record.push("user_id", Value::int(proc.user_id, span));
187                record.push("priority", Value::int(proc.priority, span));
188                record.push("process_threads", Value::int(proc.task_thread_num, span));
189                record.push("cwd", Value::string(proc.cwd(), span));
190            }
191        }
192
193        output.push(Value::record(record, span));
194    }
195
196    Ok(output.into_pipeline_data(span, engine_state.signals().clone()))
197}