Fix CPU usage info in sys (#8321)

Closes #8264. This PR does a few things to fix the `usage` column in
`sys.cpu`:

1. Sleep a while (~400ms) between calls to `sys.refresh_cpu()`, [as
required by
`sysinfo`](https://docs.rs/sysinfo/latest/sysinfo/trait.SystemExt.html#method.refresh_cpu)
2. Change `sys` to return a `LazyRecord` (so you can do things like `sys
| get host` instantly without waiting for CPU info)
3. Update our `sysinfo` dependency to [fix CPU usage calculations on
Linux](https://github.com/GuillaumeGomez/sysinfo/pull/946)

CPU usage is no longer always reported as zero:


![image](https://user-images.githubusercontent.com/26268125/222929775-5e9cbe18-95d9-4ecb-baf8-1e843f5c7086.png)
This commit is contained in:
Reilly Wood 2023-03-04 14:48:34 -08:00 committed by GitHub
parent 33fb17776a
commit f93033c20b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 76 deletions

10
Cargo.lock generated
View file

@ -2700,7 +2700,7 @@ dependencies = [
"percent-encoding",
"reedline",
"rstest",
"sysinfo 0.28.0",
"sysinfo 0.28.2",
"thiserror",
]
@ -2819,7 +2819,7 @@ dependencies = [
"serde_yaml",
"sha2",
"sqlparser",
"sysinfo 0.28.0",
"sysinfo 0.28.2",
"tabled",
"terminal_size 0.2.1",
"thiserror",
@ -2848,7 +2848,7 @@ dependencies = [
"nu-protocol",
"nu-utils",
"serde",
"sysinfo 0.28.0",
"sysinfo 0.28.2",
]
[[package]]
@ -5134,9 +5134,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.28.0"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "727220a596b4ca0af040a07091e49f5c105ec8f2592674339a5bf35be592f76e"
checksum = "d3e847e2de7a137c8c2cede5095872dbb00f4f9bf34d061347e36b43322acd56"
dependencies = [
"cfg-if",
"core-foundation-sys",

View file

@ -36,7 +36,7 @@ once_cell = "1.17.0"
log = "0.4"
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] }
percent-encoding = "2"
sysinfo = "0.28.0"
sysinfo = "0.28.2"
thiserror = "1.0.31"
[features]

View file

@ -85,7 +85,7 @@ percent-encoding = "2.2.0"
reedline = { version = "0.16.0", features = ["bashisms", "sqlite"] }
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
sqlparser = { version = "0.30.0", features = ["serde"], optional = true }
sysinfo = "0.28.0"
sysinfo = "0.28.2"
tabled = "0.10.0"
terminal_size = "0.2.1"
thiserror = "1.0.31"

View file

@ -3,8 +3,10 @@ use chrono::Local;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
Category, Example, IntoPipelineData, LazyRecord, PipelineData, ShellError, Signature, Span,
Type, Value,
};
use serde::{Deserialize, Serialize};
use std::time::{Duration, UNIX_EPOCH};
use sysinfo::{
ComponentExt, CpuExt, CpuRefreshKind, DiskExt, NetworkExt, System, SystemExt, UserExt,
@ -36,7 +38,13 @@ impl Command for Sys {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
run_sys(call)
let span = call.span();
let ret = Value::LazyRecord {
val: Box::new(SysResult { span }),
span,
};
Ok(ret.into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
@ -60,51 +68,53 @@ impl Command for Sys {
}
}
fn run_sys(call: &Call) -> Result<PipelineData, ShellError> {
let span = call.head;
let mut sys = System::new();
#[derive(Debug, Serialize, Deserialize)]
pub struct SysResult {
pub span: Span,
}
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);
impl LazyRecord for SysResult {
fn column_names(&self) -> Vec<&'static str> {
vec!["host", "cpu", "disks", "mem", "temp", "net"]
}
Ok(Value::Record {
cols: headers,
vals: values,
span,
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 typetag_name(&self) -> &'static str {
"sys"
}
fn typetag_deserialize(&self) {
unimplemented!("typetag_deserialize")
}
.into_pipeline_data())
}
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<Value> {
pub fn disks(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_disks();
sys.refresh_disks_list();
@ -157,14 +167,11 @@ pub fn disks(sys: &mut System, span: Span) -> Option<Value> {
output.push(Value::Record { cols, vals, span });
}
if !output.is_empty() {
Some(Value::List { vals: output, span })
} else {
None
}
Value::List { vals: output, span }
}
pub fn net(sys: &mut System, span: Span) -> Option<Value> {
pub fn net(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_networks();
sys.refresh_networks_list();
@ -193,18 +200,17 @@ pub fn net(sys: &mut System, span: Span) -> Option<Value> {
output.push(Value::Record { cols, vals, span });
}
if !output.is_empty() {
Some(Value::List { vals: output, span })
} else {
None
}
Value::List { vals: output, span }
}
pub fn cpu(sys: &mut System, span: Span) -> Option<Value> {
// FIXME: we must refresh the CPU *twice* System::MINIMUM_CPU_UPDATE_INTERVAL apart to
// get valid usage data, we're currently only doing it once. Consider using a LazyRecord
// to avoid slowing down all calls to `sys`
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(System::MINIMUM_CPU_UPDATE_INTERVAL * 2);
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
let mut output = vec![];
for cpu in sys.cpus() {
@ -230,8 +236,12 @@ pub fn cpu(sys: &mut System, span: Span) -> Option<Value> {
});
cols.push("cpu_usage".into());
// 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;
vals.push(Value::Float {
val: cpu.cpu_usage() as f64,
val: rounded_usage as f64,
span,
});
@ -254,14 +264,11 @@ pub fn cpu(sys: &mut System, span: Span) -> Option<Value> {
output.push(Value::Record { cols, vals, span });
}
if !output.is_empty() {
Some(Value::List { vals: output, span })
} else {
None
}
Value::List { vals: output, span }
}
pub fn mem(sys: &mut System, span: Span) -> Option<Value> {
pub fn mem(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_memory();
let mut cols = vec![];
@ -318,10 +325,11 @@ pub fn mem(sys: &mut System, span: Span) -> Option<Value> {
span,
});
Some(Value::Record { cols, vals, span })
Value::Record { cols, vals, span }
}
pub fn host(sys: &mut System, span: Span) -> Option<Value> {
pub fn host(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_users_list();
let mut cols = vec![];
@ -414,10 +422,11 @@ pub fn host(sys: &mut System, span: Span) -> Option<Value> {
vals.push(Value::List { vals: users, span });
}
Some(Value::Record { cols, vals, span })
Value::Record { cols, vals, span }
}
pub fn temp(sys: &mut System, span: Span) -> Option<Value> {
pub fn temp(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_components();
sys.refresh_components_list();
@ -455,9 +464,5 @@ pub fn temp(sys: &mut System, span: Span) -> Option<Value> {
output.push(Value::Record { cols, vals, span });
}
if !output.is_empty() {
Some(Value::List { vals: output, span })
} else {
None
}
Value::List { vals: output, span }
}

View file

@ -18,7 +18,7 @@ nu-utils = { path = "../nu-utils", version = "0.76.1" }
chrono = { version="0.4.23", features = ["std"], default-features = false }
serde = {version = "1.0.143", default-features = false }
sysinfo ="0.28.0"
sysinfo ="0.28.2"
[features]
plugin = []