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 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 record.push("priority", Value::int(proc_stat.priority, span));
137 record.push("process_threads", Value::int(proc_stat.num_threads, span));
138 record.push("working", Value::filesize(proc.working_size() as i64, span));
139 record.push("paged", Value::filesize(proc.paged_size() as i64, span));
140 record.push("cwd", Value::string(proc.cwd(), span));
141 }
142 #[cfg(windows)]
143 {
144 record.push(
147 "start_time",
148 Value::date(proc.start_time.fixed_offset(), span),
149 );
150 record.push(
151 "user",
152 Value::string(
153 proc.user.clone().name.unwrap_or("unknown".to_string()),
154 span,
155 ),
156 );
157 record.push(
158 "user_sid",
159 Value::string(
160 proc.user
161 .clone()
162 .sid
163 .iter()
164 .map(|r| r.to_string())
165 .join("-"),
166 span,
167 ),
168 );
169 record.push("priority", Value::int(proc.priority as i64, span));
170 record.push("working", Value::filesize(proc.working_size() as i64, span));
171 record.push("paged", Value::filesize(proc.paged_size() as i64, span));
172 record.push("cwd", Value::string(proc.cwd(), span));
173 record.push(
174 "environment",
175 Value::list(
176 proc.environ()
177 .iter()
178 .map(|x| Value::string(x.to_string(), span))
179 .collect(),
180 span,
181 ),
182 );
183 }
184 #[cfg(target_os = "macos")]
185 {
186 let timestamp = Local
187 .timestamp_nanos(proc.start_time * 1_000_000_000)
188 .into();
189 record.push("start_time", Value::date(timestamp, span));
190 record.push("user_id", Value::int(proc.user_id, span));
191 record.push("priority", Value::int(proc.priority, span));
192 record.push("process_threads", Value::int(proc.task_thread_num, span));
193 record.push("cwd", Value::string(proc.cwd(), span));
194 }
195 }
196
197 output.push(Value::record(record, span));
198 }
199
200 Ok(output.into_pipeline_data(span, engine_state.signals().clone()))
201}