mirror of
https://github.com/nushell/nushell.git
synced 2025-05-10 09:52:58 +00:00
# Description Bumps `sysinfo` to 0.30. * Changelog https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md#0304 # User-Facing Changes N/A
297 lines
9.3 KiB
Rust
297 lines
9.3 KiB
Rust
use chrono::prelude::DateTime;
|
|
use chrono::Local;
|
|
use nu_protocol::{
|
|
ast::Call,
|
|
engine::{Command, EngineState, Stack},
|
|
record, Category, Example, IntoPipelineData, LazyRecord, PipelineData, Record, ShellError,
|
|
Signature, Span, Type, Value,
|
|
};
|
|
use std::time::{Duration, UNIX_EPOCH};
|
|
use sysinfo::{
|
|
Components, CpuRefreshKind, Disks, Networks, System, Users, MINIMUM_CPU_UPDATE_INTERVAL,
|
|
};
|
|
|
|
#[derive(Clone)]
|
|
pub struct Sys;
|
|
|
|
impl Command for Sys {
|
|
fn name(&self) -> &str {
|
|
"sys"
|
|
}
|
|
|
|
fn signature(&self) -> Signature {
|
|
Signature::build("sys")
|
|
.filter()
|
|
.category(Category::System)
|
|
.input_output_types(vec![(Type::Nothing, Type::Record(vec![]))])
|
|
}
|
|
|
|
fn usage(&self) -> &str {
|
|
"View information about the system."
|
|
}
|
|
|
|
fn run(
|
|
&self,
|
|
_engine_state: &EngineState,
|
|
_stack: &mut Stack,
|
|
call: &Call,
|
|
_input: PipelineData,
|
|
) -> Result<PipelineData, ShellError> {
|
|
let span = call.span();
|
|
let ret = Value::lazy_record(Box::new(SysResult { span }), span);
|
|
|
|
Ok(ret.into_pipeline_data())
|
|
}
|
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
vec![
|
|
Example {
|
|
description: "Show info about the system",
|
|
example: "sys",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Show the os system name with get",
|
|
example: "(sys).host | get name",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Show the os system name",
|
|
example: "(sys).host.name",
|
|
result: None,
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct SysResult {
|
|
pub span: Span,
|
|
}
|
|
|
|
impl LazyRecord<'_> for SysResult {
|
|
fn column_names(&self) -> Vec<&'static str> {
|
|
vec!["host", "cpu", "disks", "mem", "temp", "net"]
|
|
}
|
|
|
|
fn get_column_value(&self, column: &str) -> Result<Value, ShellError> {
|
|
let span = self.span;
|
|
|
|
match column {
|
|
"host" => Ok(host(span)),
|
|
"cpu" => Ok(cpu(span)),
|
|
"disks" => Ok(disks(span)),
|
|
"mem" => Ok(mem(span)),
|
|
"temp" => Ok(temp(span)),
|
|
"net" => Ok(net(span)),
|
|
_ => Err(ShellError::LazyRecordAccessFailed {
|
|
message: format!("Could not find column '{column}'"),
|
|
column_name: column.to_string(),
|
|
span,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn span(&self) -> Span {
|
|
self.span
|
|
}
|
|
|
|
fn clone_value(&self, span: Span) -> Value {
|
|
Value::lazy_record(Box::new((*self).clone()), span)
|
|
}
|
|
}
|
|
|
|
pub fn trim_cstyle_null(s: String) -> String {
|
|
s.trim_matches(char::from(0)).to_string()
|
|
}
|
|
|
|
pub fn disks(span: Span) -> Value {
|
|
let disks = Disks::new_with_refreshed_list();
|
|
|
|
let mut output = vec![];
|
|
for disk in disks.list() {
|
|
let device = trim_cstyle_null(disk.name().to_string_lossy().to_string());
|
|
let typ = trim_cstyle_null(disk.file_system().to_string_lossy().to_string());
|
|
|
|
let record = record! {
|
|
"device" => Value::string(device, span),
|
|
"type" => Value::string(typ, span),
|
|
"mount" => Value::string(disk.mount_point().to_string_lossy(), span),
|
|
"total" => Value::filesize(disk.total_space() as i64, span),
|
|
"free" => Value::filesize(disk.available_space() as i64, span),
|
|
"removable" => Value::bool(disk.is_removable(), span),
|
|
"kind" => Value::string(format!("{:?}", disk.kind()), span),
|
|
};
|
|
|
|
output.push(Value::record(record, span));
|
|
}
|
|
Value::list(output, span)
|
|
}
|
|
|
|
pub fn net(span: Span) -> Value {
|
|
let networks = Networks::new_with_refreshed_list();
|
|
|
|
let mut output = vec![];
|
|
for (iface, data) in networks.list() {
|
|
let record = record! {
|
|
"name" => Value::string(trim_cstyle_null(iface.to_string()), span),
|
|
"sent" => Value::filesize(data.total_transmitted() as i64, span),
|
|
"recv" => Value::filesize(data.total_received() as i64, span),
|
|
};
|
|
|
|
output.push(Value::record(record, span));
|
|
}
|
|
Value::list(output, span)
|
|
}
|
|
|
|
pub fn cpu(span: Span) -> Value {
|
|
let mut sys = System::new();
|
|
sys.refresh_cpu_specifics(CpuRefreshKind::everything());
|
|
// We must refresh the CPU twice a while apart to get valid usage data.
|
|
// In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that
|
|
// that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily
|
|
std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2);
|
|
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
|
|
|
|
let mut output = vec![];
|
|
for cpu in sys.cpus() {
|
|
// sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes.
|
|
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
|
|
let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0;
|
|
|
|
let load_avg = System::load_average();
|
|
let load_avg = trim_cstyle_null(format!(
|
|
"{:.2}, {:.2}, {:.2}",
|
|
load_avg.one, load_avg.five, load_avg.fifteen
|
|
));
|
|
|
|
let record = record! {
|
|
"name" => Value::string(trim_cstyle_null(cpu.name().to_string()), span),
|
|
"brand" => Value::string(trim_cstyle_null(cpu.brand().to_string()), span),
|
|
"freq" => Value::int(cpu.frequency() as i64, span),
|
|
"cpu_usage" => Value::float(rounded_usage as f64, span),
|
|
"load_average" => Value::string(load_avg, span),
|
|
"vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id().to_string()), span),
|
|
};
|
|
|
|
output.push(Value::record(record, span));
|
|
}
|
|
|
|
Value::list(output, span)
|
|
}
|
|
|
|
pub fn mem(span: Span) -> Value {
|
|
let mut sys = System::new();
|
|
sys.refresh_memory();
|
|
|
|
let total_mem = sys.total_memory();
|
|
let free_mem = sys.free_memory();
|
|
let used_mem = sys.used_memory();
|
|
let avail_mem = sys.available_memory();
|
|
|
|
let total_swap = sys.total_swap();
|
|
let free_swap = sys.free_swap();
|
|
let used_swap = sys.used_swap();
|
|
|
|
let record = record! {
|
|
"total" => Value::filesize(total_mem as i64, span),
|
|
"free" => Value::filesize(free_mem as i64, span),
|
|
"used" => Value::filesize(used_mem as i64, span),
|
|
"available" => Value::filesize(avail_mem as i64, span),
|
|
"swap total" => Value::filesize(total_swap as i64, span),
|
|
"swap free" => Value::filesize(free_swap as i64, span),
|
|
"swap used" => Value::filesize(used_swap as i64, span),
|
|
};
|
|
|
|
Value::record(record, span)
|
|
}
|
|
|
|
pub fn host(span: Span) -> Value {
|
|
let mut record = Record::new();
|
|
|
|
if let Some(name) = System::name() {
|
|
record.push("name", Value::string(trim_cstyle_null(name), span));
|
|
}
|
|
if let Some(version) = System::os_version() {
|
|
record.push("os_version", Value::string(trim_cstyle_null(version), span));
|
|
}
|
|
|
|
if let Some(long_version) = System::long_os_version() {
|
|
record.push(
|
|
"long_os_version",
|
|
Value::string(trim_cstyle_null(long_version), span),
|
|
);
|
|
}
|
|
|
|
if let Some(version) = System::kernel_version() {
|
|
record.push(
|
|
"kernel_version",
|
|
Value::string(trim_cstyle_null(version), span),
|
|
);
|
|
}
|
|
if let Some(hostname) = System::host_name() {
|
|
record.push("hostname", Value::string(trim_cstyle_null(hostname), span));
|
|
}
|
|
|
|
record.push(
|
|
"uptime",
|
|
Value::duration(1000000000 * System::uptime() as i64, span),
|
|
);
|
|
|
|
// Creates a new SystemTime from the specified number of whole seconds
|
|
let d = UNIX_EPOCH + Duration::from_secs(System::boot_time());
|
|
// Create DateTime from SystemTime
|
|
let datetime = DateTime::<Local>::from(d);
|
|
// Convert to local time and then rfc3339
|
|
let timestamp_str = datetime.with_timezone(datetime.offset()).to_rfc3339();
|
|
|
|
record.push("boot_time", Value::string(timestamp_str, span));
|
|
|
|
let users = Users::new_with_refreshed_list();
|
|
|
|
let mut users_list = vec![];
|
|
for user in users.list() {
|
|
let mut groups = vec![];
|
|
for group in user.groups() {
|
|
groups.push(Value::string(
|
|
trim_cstyle_null(group.name().to_string()),
|
|
span,
|
|
));
|
|
}
|
|
|
|
let record = record! {
|
|
"name" => Value::string(trim_cstyle_null(user.name().to_string()), span),
|
|
"groups" => Value::list(groups, span),
|
|
};
|
|
|
|
users_list.push(Value::record(record, span));
|
|
}
|
|
|
|
if !users.is_empty() {
|
|
record.push("sessions", Value::list(users_list, span));
|
|
}
|
|
|
|
Value::record(record, span)
|
|
}
|
|
|
|
pub fn temp(span: Span) -> Value {
|
|
let components = Components::new_with_refreshed_list();
|
|
|
|
let mut output = vec![];
|
|
|
|
for component in components.list() {
|
|
let mut record = record! {
|
|
"unit" => Value::string(component.label(), span),
|
|
"temp" => Value::float(component.temperature() as f64, span),
|
|
"high" => Value::float(component.max() as f64, span),
|
|
};
|
|
|
|
if let Some(critical) = component.critical() {
|
|
record.push("critical", Value::float(critical as f64, span));
|
|
}
|
|
output.push(Value::record(record, span));
|
|
}
|
|
|
|
Value::list(output, span)
|
|
}
|