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
5 changed files with 81 additions and 76 deletions

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 }
}