diff --git a/crates/nu-command/src/debug/info.rs b/crates/nu-command/src/debug/info.rs new file mode 100644 index 000000000..1a921432c --- /dev/null +++ b/crates/nu-command/src/debug/info.rs @@ -0,0 +1,148 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + record, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span, + Type, Value, +}; +use sysinfo::{Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt}; +// Character used to separate directories in a Path Environment variable on windows is ";" +#[cfg(target_family = "windows")] +const ENV_PATH_SEPARATOR_CHAR: char = ';'; +// Character used to separate directories in a Path Environment variable on linux/mac/unix is ":" +#[cfg(not(target_family = "windows"))] +const ENV_PATH_SEPARATOR_CHAR: char = ':'; + +#[derive(Clone)] +pub struct DebugInfo; + +impl Command for DebugInfo { + fn name(&self) -> &str { + "debug info" + } + + fn usage(&self) -> &str { + "View process memory info." + } + + fn extra_usage(&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(vec![]))]) + .category(Category::Debug) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "View process information", + example: "debug info", + result: None, + }] + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + _call: &Call, + _input: PipelineData, + ) -> Result { + let span = Span::unknown(); + // get the nushell process id + let pid = Pid::from(std::process::id() as usize); + // only refresh the process and memory information + let rk = RefreshKind::new() + .with_processes( + ProcessRefreshKind::new() + .without_cpu() + .without_disk_usage() + .without_user(), + ) + .with_memory(); + // only get information requested + let system = System::new_with_specifics(rk); + // get the process information for the nushell pid + let pinfo = system.process(pid); + + if let Some(p) = pinfo { + Ok(Value::record( + record! { + "pid" => Value::int(p.pid().as_u32() as i64, span), + "ppid" => Value::int(p.parent().unwrap_or(0.into()).as_u32() as i64, span), + "process" => { + 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), + // This is a hack to get the "root" since p.root() doesn't work on macos + // Would probably puke if nu was on the root of a drive, maybe other ways too. + "root" => { + if let Some(filename) = p.exe().parent() { + Value::string(filename.to_string_lossy().to_string(), span) + } else { + Value::nothing(span) + } + }, + // "root" => Value::string(p.root().to_string_lossy().to_string(), span), + "cwd" => Value::string(p.cwd().to_string_lossy().to_string(), span), + "exe_path" => Value::string(p.exe().to_string_lossy().to_string(), span), + "command" => Value::string(p.cmd().join(" "), span), + "name" => Value::string(p.name().to_string(), span), + "environment" => { + let mut env_rec = Record::new(); + for val in p.environ() { + let (key, value) = val.split_once('=').unwrap_or(("", "")); + // Let's make some of the known list-variables into lists + if key == "PATH" || + key == "Path" || + key == "DYLD_FALLBACK_LIBRARY_PATH" || + key == "PATHEXT" || + key == "PSMODULEPATH" || + key == "PSModulePath" { + 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) + }, + }, + span, + ) + }, + "system" => { + Value::record( + record! { + "total_memory" => Value::filesize(system.total_memory() as i64, span), + "free_memory" => Value::filesize(system.free_memory() as i64, span), + "used_memory" => Value::filesize(system.used_memory() as i64, span), + "available_memory" => Value::filesize(system.available_memory() as i64, span), + }, + span, + ) + } + }, + span, + ).into_pipeline_data()) + } else { + // If we can't get the process information, just return the system information + Ok(Value::record( + record! { + "total_memory" => Value::filesize(system.total_memory() as i64, span), + "free_memory" => Value::filesize(system.free_memory() as i64, span), + "used_memory" => Value::filesize(system.used_memory() as i64, span), + "available_memory" => Value::filesize(system.available_memory() as i64, span), + }, + span, + ) + .into_pipeline_data()) + } + } +} diff --git a/crates/nu-command/src/debug/mod.rs b/crates/nu-command/src/debug/mod.rs index c92786170..32c09da46 100644 --- a/crates/nu-command/src/debug/mod.rs +++ b/crates/nu-command/src/debug/mod.rs @@ -1,6 +1,7 @@ mod ast; mod debug_; mod explain; +mod info; mod inspect; mod inspect_table; mod metadata; @@ -14,6 +15,7 @@ mod view_span; pub use ast::Ast; pub use debug_::Debug; pub use explain::Explain; +pub use info::DebugInfo; pub use inspect::Inspect; pub use inspect_table::build_table; pub use metadata::Metadata; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index e7902bed7..b319f4569 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -134,6 +134,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { bind_command! { Ast, Debug, + DebugInfo, Explain, Inspect, Metadata,