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