forked from extern/nushell
Make debug info lazy (#10728)
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> * Makes the `debug info` lazy which greatly improves performance. * Adds a `thread id` attribute   # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> `threadid` column added. --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
This commit is contained in:
parent
ff3a0a0de3
commit
0588a4fc19
@ -1,16 +1,20 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
record, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
|
record, Category, Example, IntoPipelineData, LazyRecord, PipelineData, Record, ShellError,
|
||||||
Type, Value,
|
Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use sysinfo::{Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt};
|
use sysinfo::{Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt};
|
||||||
// Character used to separate directories in a Path Environment variable on windows is ";"
|
const ENV_PATH_SEPARATOR_CHAR: char = {
|
||||||
#[cfg(target_family = "windows")]
|
#[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 = ':';
|
#[cfg(not(target_family = "windows"))]
|
||||||
|
{
|
||||||
|
':'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DebugInfo;
|
pub struct DebugInfo;
|
||||||
@ -34,14 +38,6 @@ impl Command for DebugInfo {
|
|||||||
.category(Category::Debug)
|
.category(Category::Debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "View process information",
|
|
||||||
example: "debug info",
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
@ -50,9 +46,181 @@ impl Command for DebugInfo {
|
|||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let span = Span::unknown();
|
let span = Span::unknown();
|
||||||
// get the nushell process id
|
|
||||||
|
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);
|
let pid = Pid::from(std::process::id() as usize);
|
||||||
// only refresh the process and memory information
|
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::new()
|
||||||
|
.without_cpu()
|
||||||
|
.without_disk_usage()
|
||||||
|
.without_user(),
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
|
||||||
|
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()));
|
||||||
|
|
||||||
|
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::new()
|
||||||
|
.without_cpu()
|
||||||
|
.without_disk_usage()
|
||||||
|
.without_user(),
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
|
||||||
|
let system = system_opt.get_system();
|
||||||
|
// get the process information for the nushell pid
|
||||||
|
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(filename) = p.exe().parent() {
|
||||||
|
Value::string(filename.to_string_lossy().to_string(), self.span)
|
||||||
|
} else {
|
||||||
|
Value::nothing(self.span)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cwd" => Value::string(p.cwd().to_string_lossy().to_string(), self.span),
|
||||||
|
"exe_path" => Value::string(p.exe().to_string_lossy().to_string(), 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()));
|
||||||
|
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()
|
let rk = RefreshKind::new()
|
||||||
.with_processes(
|
.with_processes(
|
||||||
ProcessRefreshKind::new()
|
ProcessRefreshKind::new()
|
||||||
@ -63,86 +231,48 @@ impl Command for DebugInfo {
|
|||||||
.with_memory();
|
.with_memory();
|
||||||
// only get information requested
|
// only get information requested
|
||||||
let system = System::new_with_specifics(rk);
|
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 {
|
self.column_names()
|
||||||
Ok(Value::record(
|
.into_iter()
|
||||||
record! {
|
.map(|col| {
|
||||||
"pid" => Value::int(p.pid().as_u32() as i64, span),
|
let val = self.get_column_value_with_system(col, Some(&system))?;
|
||||||
"ppid" => Value::int(p.parent().unwrap_or(0.into()).as_u32() as i64, span),
|
Ok((col.to_owned(), val))
|
||||||
"process" => {
|
})
|
||||||
Value::record(
|
.collect::<Result<Record, _>>()
|
||||||
record! {
|
.map(|record| Value::record(record, self.span()))
|
||||||
"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
|
enum SystemOpt<'a> {
|
||||||
// Would probably puke if nu was on the root of a drive, maybe other ways too.
|
Ptr(&'a System),
|
||||||
"root" => {
|
Owned(Box<System>),
|
||||||
if let Some(filename) = p.exe().parent() {
|
}
|
||||||
Value::string(filename.to_string_lossy().to_string(), span)
|
|
||||||
} else {
|
impl<'a> SystemOpt<'a> {
|
||||||
Value::nothing(span)
|
fn get_system(&'a self) -> &'a System {
|
||||||
}
|
match self {
|
||||||
},
|
SystemOpt::Ptr(system) => system,
|
||||||
// "root" => Value::string(p.root().to_string_lossy().to_string(), span),
|
SystemOpt::Owned(system) => system,
|
||||||
"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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
7
crates/nu-command/tests/commands/debug_info.rs
Normal file
7
crates/nu-command/tests/commands/debug_info.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use nu_test_support::nu;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn runs_successfully() {
|
||||||
|
let actual = nu!("debug info");
|
||||||
|
assert_eq!(actual.err, "");
|
||||||
|
}
|
@ -13,6 +13,7 @@ mod continue_;
|
|||||||
mod conversions;
|
mod conversions;
|
||||||
mod cp;
|
mod cp;
|
||||||
mod date;
|
mod date;
|
||||||
|
mod debug_info;
|
||||||
mod def;
|
mod def;
|
||||||
mod default;
|
mod default;
|
||||||
mod detect_columns;
|
mod detect_columns;
|
||||||
|
Loading…
Reference in New Issue
Block a user