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 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 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 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
208fn 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}