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