add a debug info command to show memory info (#10711)

# 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
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
This commit is contained in:
Darren Schroeder 2023-10-14 12:28:48 -05:00 committed by GitHub
parent 6181ea5fc1
commit 1f62024a15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 151 additions and 0 deletions

View File

@ -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<Example> {
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<PipelineData, ShellError> {
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::<Vec<_>>();
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::<Vec<_>>();
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())
}
}
}

View File

@ -1,6 +1,7 @@
mod ast; mod ast;
mod debug_; mod debug_;
mod explain; mod explain;
mod info;
mod inspect; mod inspect;
mod inspect_table; mod inspect_table;
mod metadata; mod metadata;
@ -14,6 +15,7 @@ mod view_span;
pub use ast::Ast; pub use ast::Ast;
pub use debug_::Debug; pub use debug_::Debug;
pub use explain::Explain; pub use explain::Explain;
pub use info::DebugInfo;
pub use inspect::Inspect; pub use inspect::Inspect;
pub use inspect_table::build_table; pub use inspect_table::build_table;
pub use metadata::Metadata; pub use metadata::Metadata;

View File

@ -134,6 +134,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
bind_command! { bind_command! {
Ast, Ast,
Debug, Debug,
DebugInfo,
Explain, Explain,
Inspect, Inspect,
Metadata, Metadata,