From 1f62024a1548351c3ba02ac0f98dd6c470572896 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 14 Oct 2023 12:28:48 -0500 Subject: [PATCH] add a `debug info` command to show memory info (#10711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR adds a new command called `debug info`. I'm not sure if the name is right but we can rename it if needed. The purpose of this command is to show a user how much memory nushell is using. This is what the output looks like. I feel like the further we go with nushell, the more we'll need to easily monitor the memory usage. With this command, we should easily be able to do that with scripts or just running the command. ```nushell ❯ debug info | table -e ╭─────────┬──────────────────────────────────────────────────────────────────────╮ │pid │31036 │ │ppid │29388 │ │ │╭─────────────────┬────────────────────────────────────────────────╮ │ │process ││memory │63.5 MB │ │ │ ││virtual_memory │5.6 GB │ │ │ ││status │Runnable │ │ │ ││root │C:\cartar\debug │ │ │ ││cwd │C:\Users\us991808\source\repos\forks\nushell\ │ │ │ ││exe_path │C:\cartar\debug\nu.exe │ │ │ ││command │c:\cartar\debug\nu.exe -l │ │ │ ││name │nu.exe │ │ │ ││environment │{record 110 fields} │ │ │ │╰─────────────────┴────────────────────────────────────────────────╯ │ │ │╭────────────────┬───────╮ │ │system ││total_memory │17.1 GB│ │ │ ││free_memory │5.9 GB │ │ │ ││used_memory │11.3 GB│ │ │ ││available_memory│5.9 GB │ │ │ │╰────────────────┴───────╯ │ ╰─────────┴──────────────────────────────────────────────────────────────────────╯ ``` > [!NOTE] The `process.environment` is not the nushell `$env` but the environment that the process was created with at launch time. # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-command/src/debug/info.rs | 148 +++++++++++++++++++++++ crates/nu-command/src/debug/mod.rs | 2 + crates/nu-command/src/default_context.rs | 1 + 3 files changed, 151 insertions(+) create mode 100644 crates/nu-command/src/debug/info.rs diff --git a/crates/nu-command/src/debug/info.rs b/crates/nu-command/src/debug/info.rs new file mode 100644 index 0000000000..1a921432cf --- /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 c927861709..32c09da464 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 e7902bed79..b319f45696 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,