diff --git a/Cargo.lock b/Cargo.lock index d18a075ac2..5e9478b028 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,6 +123,56 @@ dependencies = [ "winapi", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + [[package]] name = "crossterm" version = "0.21.0" @@ -299,9 +349,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.102" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" [[package]] name = "linked-hash-map" @@ -337,6 +387,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + [[package]] name = "miette" version = "3.0.0" @@ -441,6 +500,7 @@ dependencies = [ "nu-json", "nu-protocol", "nu-table", + "sysinfo", "thiserror", ] @@ -519,6 +579,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.26.2" @@ -683,6 +753,31 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.10" @@ -886,6 +981,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "sysinfo" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffff4a02fa61eee51f95210fc9c98ea6eeb46bb071adeafd61e1a0b9b22c6a6d" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "tempfile" version = "3.2.0" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 892f1aef8e..2c8ddfee89 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -13,4 +13,5 @@ nu-table = { path = "../nu-table" } # Potential dependencies for extras glob = "0.3.0" -thiserror = "1.0.29" \ No newline at end of file +thiserror = "1.0.29" +sysinfo = "0.20.4" \ No newline at end of file diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index fa2e17bdca..609d0cc683 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -7,7 +7,7 @@ use nu_protocol::{ use crate::{ Alias, Benchmark, BuildString, Def, Do, Each, External, For, From, FromJson, Git, GitCheckout, - If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Table, Use, Where, + If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Sys, Table, Use, Where, }; pub fn create_default_context() -> Rc> { @@ -37,6 +37,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Lines)); working_set.add_decl(Box::new(Ls)); working_set.add_decl(Box::new(Module)); + working_set.add_decl(Box::new(Sys)); working_set.add_decl(Box::new(Table)); working_set.add_decl(Box::new(Use)); working_set.add_decl(Box::new(Where)); diff --git a/crates/nu-command/src/system/mod.rs b/crates/nu-command/src/system/mod.rs index b1d9f28e1a..a37368d526 100644 --- a/crates/nu-command/src/system/mod.rs +++ b/crates/nu-command/src/system/mod.rs @@ -1,5 +1,7 @@ mod benchmark; mod run_external; +mod sys; pub use benchmark::Benchmark; pub use run_external::{External, ExternalCommand}; +pub use sys::Sys; diff --git a/crates/nu-command/src/system/sys.rs b/crates/nu-command/src/system/sys.rs new file mode 100644 index 0000000000..5a965700cc --- /dev/null +++ b/crates/nu-command/src/system/sys.rs @@ -0,0 +1,356 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EvaluationContext}, + ShellError, Signature, Span, Value, +}; +use sysinfo::{ComponentExt, DiskExt, NetworkExt, ProcessorExt, System, SystemExt, UserExt}; + +pub struct Sys; + +impl Command for Sys { + fn name(&self) -> &str { + "sys" + } + + fn signature(&self) -> Signature { + Signature::build("sys") + .desc("View information about the current system.") + .filter() + } + + fn usage(&self) -> &str { + "View information about the system." + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + run_sys(call) + } + + // fn examples(&self) -> Vec { + // vec![Example { + // description: "Show info about the system", + // example: "sys", + // result: None, + // }] + // } +} + +fn run_sys(call: &Call) -> Result { + let span = call.head; + let mut sys = System::new(); + + let mut headers = vec![]; + let mut values = vec![]; + + if let Some(value) = host(&mut sys, span) { + headers.push("host".into()); + values.push(value); + } + if let Some(value) = cpu(&mut sys, span) { + headers.push("cpu".into()); + values.push(value); + } + if let Some(value) = disks(&mut sys, span) { + headers.push("disks".into()); + values.push(value); + } + if let Some(value) = mem(&mut sys, span) { + headers.push("mem".into()); + values.push(value); + } + if let Some(value) = temp(&mut sys, span) { + headers.push("temp".into()); + values.push(value); + } + if let Some(value) = net(&mut sys, span) { + headers.push("net".into()); + values.push(value); + } + + Ok(Value::Record { + cols: headers, + vals: values, + span, + }) +} + +pub fn trim_cstyle_null(s: String) -> String { + s.trim_matches(char::from(0)).to_string() +} + +pub fn disks(sys: &mut System, span: Span) -> Option { + sys.refresh_disks(); + sys.refresh_disks_list(); + + let mut output = vec![]; + for disk in sys.disks() { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("device".into()); + vals.push(Value::String { + val: trim_cstyle_null(disk.name().to_string_lossy().to_string()), + span, + }); + + cols.push("type".into()); + vals.push(Value::String { + val: trim_cstyle_null(String::from_utf8_lossy(disk.file_system()).to_string()), + span, + }); + + cols.push("mount".into()); + vals.push(Value::String { + val: disk.mount_point().to_string_lossy().to_string(), + span, + }); + + cols.push("total".into()); + vals.push(Value::Filesize { + val: disk.total_space(), + span, + }); + + cols.push("free".into()); + vals.push(Value::Filesize { + val: disk.available_space(), + span, + }); + + output.push(Value::Record { cols, vals, span }); + } + if !output.is_empty() { + Some(Value::List { vals: output, span }) + } else { + None + } +} + +pub fn net(sys: &mut System, span: Span) -> Option { + sys.refresh_networks(); + sys.refresh_networks_list(); + + let mut output = vec![]; + for (iface, data) in sys.networks() { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("name".into()); + vals.push(Value::String { + val: trim_cstyle_null(iface.to_string()), + span, + }); + + cols.push("sent".into()); + vals.push(Value::Filesize { + val: data.total_transmitted(), + span, + }); + + cols.push("recv".into()); + vals.push(Value::Filesize { + val: data.total_received(), + span, + }); + + output.push(Value::Record { cols, vals, span }); + } + if !output.is_empty() { + Some(Value::List { vals: output, span }) + } else { + None + } +} + +pub fn cpu(sys: &mut System, span: Span) -> Option { + sys.refresh_cpu(); + + let mut output = vec![]; + for cpu in sys.processors() { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("name".into()); + vals.push(Value::String { + val: trim_cstyle_null(cpu.name().to_string()), + span, + }); + + cols.push("brand".into()); + vals.push(Value::String { + val: trim_cstyle_null(cpu.brand().to_string()), + span, + }); + + cols.push("freq".into()); + vals.push(Value::Int { + val: cpu.frequency() as i64, + span, + }); + + output.push(Value::Record { cols, vals, span }); + } + if !output.is_empty() { + Some(Value::List { vals: output, span }) + } else { + None + } +} + +pub fn mem(sys: &mut System, span: Span) -> Option { + sys.refresh_memory(); + + let mut cols = vec![]; + let mut vals = vec![]; + + let total_mem = sys.total_memory(); + let free_mem = sys.free_memory(); + let total_swap = sys.total_swap(); + let free_swap = sys.free_swap(); + + cols.push("total".into()); + vals.push(Value::Filesize { + val: total_mem * 1000, + span, + }); + + cols.push("free".into()); + vals.push(Value::Filesize { + val: free_mem * 1000, + span, + }); + + cols.push("swap total".into()); + vals.push(Value::Filesize { + val: total_swap * 1000, + span, + }); + + cols.push("swap free".into()); + vals.push(Value::Filesize { + val: free_swap * 1000, + span, + }); + + Some(Value::Record { cols, vals, span }) +} + +pub fn host(sys: &mut System, span: Span) -> Option { + sys.refresh_users_list(); + + let mut cols = vec![]; + let mut vals = vec![]; + + if let Some(name) = sys.name() { + cols.push("name".into()); + vals.push(Value::String { + val: trim_cstyle_null(name), + span, + }); + } + if let Some(version) = sys.os_version() { + cols.push("os version".into()); + vals.push(Value::String { + val: trim_cstyle_null(version), + span, + }); + } + if let Some(version) = sys.kernel_version() { + cols.push("kernel version".into()); + vals.push(Value::String { + val: trim_cstyle_null(version), + span, + }); + } + if let Some(hostname) = sys.host_name() { + cols.push("hostname".into()); + vals.push(Value::String { + val: trim_cstyle_null(hostname), + span, + }); + } + // dict.insert_untagged( + // "uptime", + // UntaggedValue::duration(1000000000 * sys.uptime() as i64), + // ); + + let mut users = vec![]; + for user in sys.users() { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("name".into()); + vals.push(Value::String { + val: trim_cstyle_null(user.name().to_string()), + span, + }); + + let mut groups = vec![]; + for group in user.groups() { + groups.push(Value::String { + val: trim_cstyle_null(group.to_string()), + span, + }); + } + + cols.push("groups".into()); + vals.push(Value::List { vals: groups, span }); + + users.push(Value::Record { cols, vals, span }); + } + if !users.is_empty() { + cols.push("sessions".into()); + vals.push(Value::List { vals: users, span }); + } + + Some(Value::Record { cols, vals, span }) +} + +pub fn temp(sys: &mut System, span: Span) -> Option { + sys.refresh_components(); + sys.refresh_components_list(); + + let mut output = vec![]; + + for component in sys.components() { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("unit".into()); + vals.push(Value::String { + val: component.label().to_string(), + span, + }); + + cols.push("temp".into()); + vals.push(Value::Float { + val: component.temperature() as f64, + span, + }); + + cols.push("high".into()); + vals.push(Value::Float { + val: component.max() as f64, + span, + }); + + if let Some(critical) = component.critical() { + cols.push("critical".into()); + vals.push(Value::Float { + val: critical as f64, + span, + }); + } + output.push(Value::Record { cols, vals, span }); + } + if !output.is_empty() { + Some(Value::List { vals: output, span }) + } else { + None + } +} diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 5a3c0bdb0f..f91cd65ec1 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -25,6 +25,10 @@ pub enum Value { val: i64, span: Span, }, + Filesize { + val: u64, + span: Span, + }, Range { val: Box, span: Span, @@ -81,6 +85,7 @@ impl Value { Value::Bool { span, .. } => *span, Value::Int { span, .. } => *span, Value::Float { span, .. } => *span, + Value::Filesize { span, .. } => *span, Value::Range { span, .. } => *span, Value::String { span, .. } => *span, Value::Record { span, .. } => *span, @@ -98,6 +103,7 @@ impl Value { Value::Bool { span, .. } => *span = new_span, Value::Int { span, .. } => *span = new_span, Value::Float { span, .. } => *span = new_span, + Value::Filesize { span, .. } => *span = new_span, Value::Range { span, .. } => *span = new_span, Value::String { span, .. } => *span = new_span, Value::Record { span, .. } => *span = new_span, @@ -118,6 +124,7 @@ impl Value { Value::Bool { .. } => Type::Bool, Value::Int { .. } => Type::Int, Value::Float { .. } => Type::Float, + Value::Filesize { .. } => Type::Filesize, Value::Range { .. } => Type::Range, Value::String { .. } => Type::String, Value::Record { cols, vals, .. } => { @@ -138,6 +145,7 @@ impl Value { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), + Value::Filesize { val, .. } => format!("{} bytes", val), Value::Range { val, .. } => { format!( "range: [{}]", @@ -176,6 +184,7 @@ impl Value { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), + Value::Filesize { val, .. } => format!("{} bytes", val), Value::Range { val, .. } => val .into_iter() .map(|x| x.into_string())