use nu_engine::command_prelude::*; use sysinfo::{MemoryRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System}; const ENV_PATH_SEPARATOR_CHAR: char = { #[cfg(target_family = "windows")] { ';' } #[cfg(not(target_family = "windows"))] { ':' } }; #[derive(Clone)] pub struct DebugInfo; impl Command for DebugInfo { fn name(&self) -> &str { "debug info" } fn description(&self) -> &str { "View process memory info." } fn extra_description(&self) -> &str { "This command is meant for debugging purposes.\nIt shows you the process information and system memory information." } fn signature(&self) -> nu_protocol::Signature { Signature::build("debug info") .input_output_types(vec![(Type::Nothing, Type::record())]) .category(Category::Debug) } fn run( &self, _engine_state: &EngineState, _stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(all_columns(call.head).into_pipeline_data()) } fn examples(&self) -> Vec { vec![Example { description: "View process information", example: "debug info", result: None, }] } } fn all_columns(span: Span) -> Value { let rk = RefreshKind::new() .with_processes(ProcessRefreshKind::everything()) .with_memory(MemoryRefreshKind::everything()); // only get information requested let sys = System::new_with_specifics(rk); let pid = Pid::from(std::process::id() as usize); let ppid = { sys.process(pid) .and_then(|p| p.parent()) .map(|p| Value::int(p.as_u32().into(), span)) .unwrap_or(Value::nothing(span)) }; let system = Value::record( record! { "total_memory" => Value::filesize(sys.total_memory() as i64, span), "free_memory" => Value::filesize(sys.free_memory() as i64, span), "used_memory" => Value::filesize(sys.used_memory() as i64, span), "available_memory" => Value::filesize(sys.available_memory() as i64, span), }, span, ); let process = if let Some(p) = sys.process(pid) { let root = if let Some(path) = p.exe().and_then(|p| p.parent()) { Value::string(path.to_string_lossy().to_string(), span) } else { Value::nothing(span) }; let cwd = if let Some(path) = p.cwd() { Value::string(path.to_string_lossy().to_string(), span) } else { Value::nothing(span) }; let exe_path = if let Some(path) = p.exe() { Value::string(path.to_string_lossy().to_string(), span) } else { Value::nothing(span) }; let environment = { let mut env_rec = Record::new(); for val in p.environ() { if let Some((key, value)) = val.to_string_lossy().split_once('=') { let is_env_var_a_list = { { #[cfg(target_family = "windows")] { key == "Path" || key == "PATHEXT" || key == "PSMODULEPATH" || key == "PSModulePath" } #[cfg(not(target_family = "windows"))] { key == "PATH" || key == "DYLD_FALLBACK_LIBRARY_PATH" } } }; if is_env_var_a_list { let items = value .split(ENV_PATH_SEPARATOR_CHAR) .map(|r| Value::string(r.to_string(), span)) .collect::>(); env_rec.push(key.to_string(), Value::list(items, span)); } else if key == "LS_COLORS" { // LS_COLORS is a special case, it's a colon separated list of key=value pairs let items = value .split(':') .map(|r| Value::string(r.to_string(), span)) .collect::>(); env_rec.push(key.to_string(), Value::list(items, span)); } else { env_rec.push(key.to_string(), Value::string(value.to_string(), span)); } } } Value::record(env_rec, span) }; Value::record( record! { "memory" => Value::filesize(p.memory() as i64, span), "virtual_memory" => Value::filesize(p.virtual_memory() as i64, span), "status" => Value::string(p.status().to_string(), span), "root" => root, "cwd" => cwd, "exe_path" => exe_path, "command" => Value::string(p.cmd().join(std::ffi::OsStr::new(" ")).to_string_lossy(), span), "name" => Value::string(p.name().to_string_lossy(), span), "environment" => environment, }, span, ) } else { Value::nothing(span) }; Value::record( record! { "thread_id" => Value::int(get_thread_id() as i64, span), "pid" => Value::int(pid.as_u32().into(), span), "ppid" => ppid, "system" => system, "process" => process, }, span, ) } fn get_thread_id() -> u64 { #[cfg(windows)] { unsafe { windows::Win32::System::Threading::GetCurrentThreadId().into() } } #[cfg(unix)] { nix::sys::pthread::pthread_self() as u64 } }