nu_command/debug/
info.rs

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    // only get information requested
62    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                        // LS_COLORS is a special case, it's a colon separated list of key=value pairs
128                        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        // wasm doesn't have any threads accessible, so we return 0 as a fallback
183        0
184    }
185}