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!["procedures", "operations", "tasks", "ops"]
37    }
38
39    fn run(
40        &self,
41        engine_state: &EngineState,
42        stack: &mut Stack,
43        call: &Call,
44        _input: PipelineData,
45    ) -> Result<PipelineData, ShellError> {
46        run_ps(engine_state, stack, call)
47    }
48
49    fn examples(&self) -> Vec<Example> {
50        vec![
51            Example {
52                description: "List the system processes",
53                example: "ps",
54                result: None,
55            },
56            Example {
57                description: "List the top 5 system processes with the highest memory usage",
58                example: "ps | sort-by mem | last 5",
59                result: None,
60            },
61            Example {
62                description: "List the top 3 system processes with the highest CPU usage",
63                example: "ps | sort-by cpu | last 3",
64                result: None,
65            },
66            Example {
67                description: "List the system processes with 'nu' in their names",
68                example: "ps | where name =~ 'nu'",
69                result: None,
70            },
71            Example {
72                description: "Get the parent process id of the current nu process",
73                example: "ps | where pid == $nu.pid | get ppid",
74                result: None,
75            },
76        ]
77    }
78}
79
80fn run_ps(
81    engine_state: &EngineState,
82    stack: &mut Stack,
83    call: &Call,
84) -> Result<PipelineData, ShellError> {
85    let mut output = vec![];
86    let span = call.head;
87    let long = call.has_flag(engine_state, stack, "long")?;
88
89    for proc in nu_system::collect_proc(Duration::from_millis(100), false) {
90        let mut record = Record::new();
91
92        record.push("pid", Value::int(proc.pid() as i64, span));
93        record.push("ppid", Value::int(proc.ppid() as i64, span));
94        record.push("name", Value::string(proc.name(), span));
95
96        #[cfg(not(windows))]
97        {
98            // Hide status on Windows until we can find a good way to support it
99            record.push("status", Value::string(proc.status(), span));
100        }
101
102        record.push("cpu", Value::float(proc.cpu_usage(), span));
103        record.push("mem", Value::filesize(proc.mem_size() as i64, span));
104        record.push("virtual", Value::filesize(proc.virtual_size() as i64, span));
105
106        if long {
107            record.push("command", Value::string(proc.command(), span));
108            #[cfg(target_os = "linux")]
109            {
110                let proc_stat = proc
111                    .curr_proc
112                    .stat()
113                    .map_err(|e| ShellError::GenericError {
114                        error: "Error getting process stat".into(),
115                        msg: e.to_string(),
116                        span: Some(Span::unknown()),
117                        help: None,
118                        inner: vec![],
119                    })?;
120                record.push(
121                    "start_time",
122                    match proc_stat.starttime().get() {
123                        Ok(ts) => Value::date(ts.into(), span),
124                        Err(_) => Value::nothing(span),
125                    },
126                );
127                record.push("user_id", Value::int(proc.curr_proc.owner() as i64, span));
128                // These work and may be helpful, but it just seemed crowded
129                // record.push("group_id", Value::int(proc_stat.pgrp as i64, span));
130                // record.push("session_id", Value::int(proc_stat.session as i64, span));
131                // This may be helpful for ctrl+z type of checking, once we get there
132                // record.push("tpg_id", Value::int(proc_stat.tpgid as i64, span));
133                record.push("priority", Value::int(proc_stat.priority, span));
134                record.push("process_threads", Value::int(proc_stat.num_threads, span));
135                record.push("cwd", Value::string(proc.cwd(), span));
136            }
137            #[cfg(windows)]
138            {
139                //TODO: There's still more information we can cram in there if we want to
140                // see the ProcessInfo struct for more information
141                record.push(
142                    "start_time",
143                    Value::date(proc.start_time.fixed_offset(), span),
144                );
145                record.push(
146                    "user",
147                    Value::string(
148                        proc.user.clone().name.unwrap_or("unknown".to_string()),
149                        span,
150                    ),
151                );
152                record.push(
153                    "user_sid",
154                    Value::string(
155                        proc.user
156                            .clone()
157                            .sid
158                            .iter()
159                            .map(|r| r.to_string())
160                            .join("-"),
161                        span,
162                    ),
163                );
164                record.push("priority", Value::int(proc.priority as i64, span));
165                record.push("cwd", Value::string(proc.cwd(), span));
166                record.push(
167                    "environment",
168                    Value::list(
169                        proc.environ()
170                            .iter()
171                            .map(|x| Value::string(x.to_string(), span))
172                            .collect(),
173                        span,
174                    ),
175                );
176            }
177            #[cfg(target_os = "macos")]
178            {
179                let timestamp = Local
180                    .timestamp_nanos(proc.start_time * 1_000_000_000)
181                    .into();
182                record.push("start_time", Value::date(timestamp, span));
183                record.push("user_id", Value::int(proc.user_id, span));
184                record.push("priority", Value::int(proc.priority, span));
185                record.push("process_threads", Value::int(proc.task_thread_num, span));
186                record.push("cwd", Value::string(proc.cwd(), span));
187            }
188        }
189
190        output.push(Value::record(record, span));
191    }
192
193    Ok(output.into_pipeline_data(span, engine_state.signals().clone()))
194}