Files
nushell/crates/nu-command/src/debug/info.rs
Darren Schroeder b23fe30530 fixes debug info not populating process information (#11909)
# Description

This PR fixes #11901. For some reason `debug info` stopped reporting
information. This hopefully fixes it. I think something changes in the
`sysinfo` crate that stopped it from working.

# 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.
-->
2024-02-20 13:34:11 -06:00

277 lines
11 KiB
Rust

use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, IntoPipelineData, LazyRecord, PipelineData, Record, ShellError,
Signature, Span, Type, Value,
};
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 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 run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
_call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = Span::unknown();
let record = LazySystemInfoRecord { span };
Ok(Value::lazy_record(Box::new(record), span).into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "View process information",
example: "debug info",
result: None,
}]
}
}
#[derive(Debug, Clone)]
struct LazySystemInfoRecord {
span: Span,
}
impl LazySystemInfoRecord {
fn get_column_value_with_system(
&self,
column: &str,
system_option: Option<&System>,
) -> Result<Value, ShellError> {
let pid = Pid::from(std::process::id() as usize);
match column {
"thread_id" => Ok(Value::int(get_thread_id(), self.span)),
"pid" => Ok(Value::int(pid.as_u32() as i64, self.span)),
"ppid" => {
// only get information requested
let system_opt = SystemOpt::from((system_option, || {
RefreshKind::new().with_processes(ProcessRefreshKind::everything())
}));
let system = system_opt.get_system();
// get the process information for the nushell pid
let pinfo = system.process(pid);
Ok(pinfo
.and_then(|p| p.parent())
.map(|p| Value::int(p.as_u32() as i64, self.span))
.unwrap_or(Value::nothing(self.span)))
}
"system" => {
// only get information requested
let system_opt = SystemOpt::from((system_option, || {
RefreshKind::new().with_memory(MemoryRefreshKind::everything())
}));
let system = system_opt.get_system();
Ok(Value::record(
record! {
"total_memory" => Value::filesize(system.total_memory() as i64, self.span),
"free_memory" => Value::filesize(system.free_memory() as i64, self.span),
"used_memory" => Value::filesize(system.used_memory() as i64, self.span),
"available_memory" => Value::filesize(system.available_memory() as i64, self.span),
},
self.span,
))
}
"process" => {
// only get information requested
let system_opt = SystemOpt::from((system_option, || {
RefreshKind::new().with_processes(ProcessRefreshKind::everything())
}));
let system = system_opt.get_system();
let pinfo = system.process(pid);
if let Some(p) = pinfo {
Ok(Value::record(
record! {
"memory" => Value::filesize(p.memory() as i64, self.span),
"virtual_memory" => Value::filesize(p.virtual_memory() as i64, self.span),
"status" => Value::string(p.status().to_string(), self.span),
"root" => {
if let Some(path) = p.exe().and_then(|p| p.parent()) {
Value::string(path.to_string_lossy().to_string(), self.span)
} else {
Value::nothing(self.span)
}
},
"cwd" => {
if let Some(path) = p.cwd() {
Value::string(path.to_string_lossy().to_string(), self.span)
}else{
Value::nothing(self.span)
}
},
"exe_path" => {
if let Some(path)= p.exe() {
Value::string(path.to_string_lossy().to_string(), self.span)
}else{
Value::nothing(self.span)
}
},
"command" => Value::string(p.cmd().join(" "), self.span),
"name" => Value::string(p.name().to_string(), self.span),
"environment" => {
let mut env_rec = Record::new();
for val in p.environ() {
if let Some((key, value)) = val.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(), self.span)).collect::<Vec<_>>();
env_rec.push(key.to_string(), Value::list(items, self.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(), self.span)).collect::<Vec<_>>();
env_rec.push(key.to_string(), Value::list(items, self.span));
} else {
env_rec.push(key.to_string(), Value::string(value.to_string(), self.span));
}
}
}
Value::record(env_rec, self.span)
},
},
self.span,
))
} else {
// If we can't get the process information, just return the system information
// only get information requested
let system_opt = SystemOpt::from((system_option, || {
RefreshKind::new().with_memory(MemoryRefreshKind::everything())
}));
let system = system_opt.get_system();
Ok(Value::record(
record! {
"total_memory" => Value::filesize(system.total_memory() as i64, self.span),
"free_memory" => Value::filesize(system.free_memory() as i64, self.span),
"used_memory" => Value::filesize(system.used_memory() as i64, self.span),
"available_memory" => Value::filesize(system.available_memory() as i64, self.span),
},
self.span,
))
}
}
_ => Err(ShellError::IncompatibleParametersSingle {
msg: format!("Unknown column: {}", column),
span: self.span,
}),
}
}
}
impl<'a> LazyRecord<'a> for LazySystemInfoRecord {
fn column_names(&'a self) -> Vec<&'a str> {
vec!["thread_id", "pid", "ppid", "process", "system"]
}
fn get_column_value(&self, column: &str) -> Result<Value, ShellError> {
self.get_column_value_with_system(column, None)
}
fn span(&self) -> Span {
self.span
}
fn clone_value(&self, span: Span) -> Value {
Value::lazy_record(Box::new(LazySystemInfoRecord { span }), span)
}
fn collect(&'a self) -> Result<Value, ShellError> {
let rk = RefreshKind::new()
.with_processes(ProcessRefreshKind::everything())
.with_memory(MemoryRefreshKind::everything());
// only get information requested
let system = System::new_with_specifics(rk);
self.column_names()
.into_iter()
.map(|col| {
let val = self.get_column_value_with_system(col, Some(&system))?;
Ok((col.to_owned(), val))
})
.collect::<Result<Record, _>>()
.map(|record| Value::record(record, self.span()))
}
}
enum SystemOpt<'a> {
Ptr(&'a System),
Owned(Box<System>),
}
impl<'a> SystemOpt<'a> {
fn get_system(&'a self) -> &'a System {
match self {
SystemOpt::Ptr(system) => system,
SystemOpt::Owned(system) => system,
}
}
}
impl<'a, F: Fn() -> RefreshKind> From<(Option<&'a System>, F)> for SystemOpt<'a> {
fn from((system_opt, refresh_kind_create): (Option<&'a System>, F)) -> Self {
match system_opt {
Some(system) => SystemOpt::<'a>::Ptr(system),
None => SystemOpt::Owned(Box::new(System::new_with_specifics(refresh_kind_create()))),
}
}
}
fn get_thread_id() -> i64 {
#[cfg(target_family = "windows")]
{
unsafe { windows::Win32::System::Threading::GetCurrentThreadId() as i64 }
}
#[cfg(not(target_family = "windows"))]
{
unsafe { libc::pthread_self() as i64 }
}
}