1use nu_engine::command_prelude::*;
2use sysinfo::{MemoryRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
3
4const ENV_PATH_SEPARATOR_CHAR: char = {
5 #[cfg(target_family = "windows")]
6 {
7 ';'
8 }
9 #[cfg(not(target_family = "windows"))]
10 {
11 ':'
12 }
13};
14
15#[derive(Clone)]
16pub struct DebugInfo;
17
18impl Command for DebugInfo {
19 fn name(&self) -> &str {
20 "debug info"
21 }
22
23 fn description(&self) -> &str {
24 "View process memory info."
25 }
26
27 fn extra_description(&self) -> &str {
28 "This command is meant for debugging purposes.\nIt shows you the process information and system memory information."
29 }
30
31 fn signature(&self) -> nu_protocol::Signature {
32 Signature::build("debug info")
33 .input_output_types(vec![(Type::Nothing, Type::record())])
34 .category(Category::Debug)
35 }
36
37 fn run(
38 &self,
39 _engine_state: &EngineState,
40 _stack: &mut Stack,
41 call: &Call,
42 _input: PipelineData,
43 ) -> Result<PipelineData, ShellError> {
44 Ok(all_columns(call.head).into_pipeline_data())
45 }
46
47 fn examples(&self) -> Vec<Example<'_>> {
48 vec![Example {
49 description: "View process information",
50 example: "debug info",
51 result: None,
52 }]
53 }
54}
55
56fn all_columns(span: Span) -> Value {
57 let rk = RefreshKind::nothing()
58 .with_processes(ProcessRefreshKind::everything())
59 .with_memory(MemoryRefreshKind::everything());
60
61 let sys = System::new_with_specifics(rk);
63
64 let pid = Pid::from(std::process::id() as usize);
65 let ppid = {
66 sys.process(pid)
67 .and_then(|p| p.parent())
68 .map(|p| Value::int(p.as_u32().into(), span))
69 .unwrap_or(Value::nothing(span))
70 };
71
72 let system = Value::record(
73 record! {
74 "total_memory" => Value::filesize(sys.total_memory() as i64, span),
75 "free_memory" => Value::filesize(sys.free_memory() as i64, span),
76 "used_memory" => Value::filesize(sys.used_memory() as i64, span),
77 "available_memory" => Value::filesize(sys.available_memory() as i64, span),
78 },
79 span,
80 );
81
82 let process = if let Some(p) = sys.process(pid) {
83 let root = if let Some(path) = p.exe().and_then(|p| p.parent()) {
84 Value::string(path.to_string_lossy().to_string(), span)
85 } else {
86 Value::nothing(span)
87 };
88
89 let cwd = if let Some(path) = p.cwd() {
90 Value::string(path.to_string_lossy().to_string(), span)
91 } else {
92 Value::nothing(span)
93 };
94
95 let exe_path = if let Some(path) = p.exe() {
96 Value::string(path.to_string_lossy().to_string(), span)
97 } else {
98 Value::nothing(span)
99 };
100
101 let environment = {
102 let mut env_rec = Record::new();
103 for val in p.environ() {
104 if let Some((key, value)) = val.to_string_lossy().split_once('=') {
105 let is_env_var_a_list = {
106 {
107 #[cfg(target_family = "windows")]
108 {
109 key == "Path"
110 || key == "PATHEXT"
111 || key == "PSMODULEPATH"
112 || key == "PSModulePath"
113 }
114 #[cfg(not(target_family = "windows"))]
115 {
116 key == "PATH" || key == "DYLD_FALLBACK_LIBRARY_PATH"
117 }
118 }
119 };
120 if is_env_var_a_list {
121 let items = value
122 .split(ENV_PATH_SEPARATOR_CHAR)
123 .map(|r| Value::string(r.to_string(), span))
124 .collect::<Vec<_>>();
125 env_rec.push(key.to_string(), Value::list(items, span));
126 } else if key == "LS_COLORS" {
127 let items = value
129 .split(':')
130 .map(|r| Value::string(r.to_string(), span))
131 .collect::<Vec<_>>();
132 env_rec.push(key.to_string(), Value::list(items, span));
133 } else {
134 env_rec.push(key.to_string(), Value::string(value.to_string(), span));
135 }
136 }
137 }
138 Value::record(env_rec, span)
139 };
140
141 Value::record(
142 record! {
143 "memory" => Value::filesize(p.memory() as i64, span),
144 "virtual_memory" => Value::filesize(p.virtual_memory() as i64, span),
145 "status" => Value::string(p.status().to_string(), span),
146 "root" => root,
147 "cwd" => cwd,
148 "exe_path" => exe_path,
149 "command" => Value::string(p.cmd().join(std::ffi::OsStr::new(" ")).to_string_lossy(), span),
150 "name" => Value::string(p.name().to_string_lossy(), span),
151 "environment" => environment,
152 },
153 span,
154 )
155 } else {
156 Value::nothing(span)
157 };
158
159 Value::record(
160 record! {
161 "thread_id" => Value::int(get_thread_id() as i64, span),
162 "pid" => Value::int(pid.as_u32().into(), span),
163 "ppid" => ppid,
164 "system" => system,
165 "process" => process,
166 },
167 span,
168 )
169}
170
171fn get_thread_id() -> u64 {
172 #[cfg(windows)]
173 {
174 unsafe { windows::Win32::System::Threading::GetCurrentThreadId().into() }
175 }
176 #[cfg(unix)]
177 {
178 nix::sys::pthread::pthread_self() as u64
179 }
180 #[cfg(target_arch = "wasm32")]
181 {
182 0
184 }
185}