M1: core engine, config, plugin model and core plugins

- Cargo workspace (core, plugins, tui, cli) with drop-in compatible CLI
- Python-compatible config file handling, limits, views, rates, MMM, history
- process collection via sysinfo + procfs, filters, thresholds, events, actions
- 20 core plugins (cpu, percpu, mem, memswap, load, fs, network, diskio, ...)
- stdout, stdout-csv, stdout-json, --issue, --modules-list output modes
- differential test harness against the Python implementation
This commit is contained in:
glances-rs
2026-10-09 23:25:35 +00:00
commit 975385da5d
61 changed files with 12049 additions and 0 deletions
@@ -0,0 +1,163 @@
//! Disk I/O plugin.
use crate::collectors::{SECTOR_SIZE, read_diskstats};
use crate::fields;
use glances_core::plugin::{Field, HistoryItem, Plugin, PluginModel, UpdateContext};
use glances_core::{Args, Config, Decoration};
use serde_json::{Value, json};
pub struct DiskioPlugin {
pub model: PluginModel,
}
impl DiskioPlugin {
pub fn new(config: &Config, args: &Args) -> Self {
let fields = fields! {
"disk_name" => Field::new("Disk name."),
"read_count" => Field::new("Number of reads.").rate().unit("number"),
"write_count" => Field::new("Number of writes.").rate().unit("number"),
"read_bytes" => Field::new("Number of bytes read.").rate().unit("byte"),
"write_bytes" => Field::new("Number of bytes written.").rate().unit("byte"),
"read_time" => Field::new("Time spent reading.").rate().unit("millisecond"),
"write_time" => Field::new("Time spent writing.").rate().unit("millisecond"),
"read_latency" => Field::new("Mean time spent reading per operation.").unit("millisecond"),
"write_latency" => Field::new("Mean time spent writing per operation.").unit("millisecond"),
};
let items_history = vec![
HistoryItem::new("read_bytes_rate_per_sec", "Bytes read per second"),
HistoryItem::new("write_bytes_rate_per_sec", "Bytes write per second"),
];
let mut model = PluginModel::new(
"diskio",
fields,
items_history,
Value::Array(Vec::new()),
config,
args.time.unwrap_or(2.0),
args.disable_history,
args.disable_config_exec,
);
model.set_item_key("disk_name");
model.hide_zero = config.get_bool_value("diskio", "hide_zero", false);
model.hide_threshold_bytes = config.get_int_value("diskio", "hide_threshold_bytes", 0) as f64;
model.hide_zero_fields =
vec!["read_bytes_rate_per_sec".to_string(), "write_bytes_rate_per_sec".to_string()];
Self { model }
}
}
impl Plugin for DiskioPlugin {
fn name(&self) -> &str {
"diskio"
}
fn model(&self) -> &PluginModel {
&self.model
}
fn model_mut(&mut self) -> &mut PluginModel {
&mut self.model
}
fn get_key(&self) -> Option<&'static str> {
Some("disk_name")
}
fn collect(&mut self, ctx: &mut UpdateContext) -> anyhow::Result<Value> {
let mut stats = Vec::new();
for (disk_name, disk_stat) in read_diskstats() {
if !ctx.args.diskio_show_ramfs && disk_name.starts_with("ram") {
continue;
}
if !self.model.is_display(&disk_name, "") {
continue;
}
let mut item = serde_json::Map::new();
item.insert("read_count".to_string(), json!(disk_stat.read_count));
item.insert("write_count".to_string(), json!(disk_stat.write_count));
item.insert("read_bytes".to_string(), json!(disk_stat.sectors_read * SECTOR_SIZE));
item.insert("write_bytes".to_string(), json!(disk_stat.sectors_written * SECTOR_SIZE));
item.insert("read_time".to_string(), json!(disk_stat.read_time));
item.insert("write_time".to_string(), json!(disk_stat.write_time));
item.insert("key".to_string(), json!("disk_name"));
item.insert("disk_name".to_string(), json!(disk_name));
if let Some(alias) = self.model.has_alias(&disk_name) {
item.insert("alias".to_string(), json!(alias));
}
stats.push(Value::Object(item));
}
Ok(Value::Array(stats))
}
fn post_collect(&mut self, _ctx: &mut UpdateContext) -> anyhow::Result<()> {
if let Value::Array(items) = &mut self.model.stats {
for item in items.iter_mut() {
let Some(item) = item.as_object_mut() else {
continue;
};
let read_time = item.get("read_time_rate_per_sec").and_then(Value::as_f64).unwrap_or(0.0);
let read_count = item.get("read_count_rate_per_sec").and_then(Value::as_f64).unwrap_or(0.0);
let write_time = item.get("write_time_rate_per_sec").and_then(Value::as_f64).unwrap_or(0.0);
let write_count = item.get("write_count_rate_per_sec").and_then(Value::as_f64).unwrap_or(0.0);
let read_latency = if item.contains_key("read_time_rate_per_sec") && read_count > 0.0 {
(read_time / read_count).floor() as i64
} else {
0
};
let write_latency = if item.contains_key("write_time_rate_per_sec") && write_count > 0.0 {
(write_time / write_count).floor() as i64
} else {
0
};
item.insert("read_latency".to_string(), json!(read_latency));
item.insert("write_latency".to_string(), json!(write_latency));
}
}
Ok(())
}
fn update_views(&mut self) {
let key = self.get_key();
self.model.update_views(key);
let items: Vec<(String, f64, f64, f64, f64)> = match &self.model.stats {
Value::Array(list) => list
.iter()
.filter_map(|item| {
let name = item.get("disk_name")?.as_str()?.to_string();
let read_rate = item.get("read_bytes_rate_per_sec").and_then(Value::as_f64).unwrap_or(0.0);
let write_rate = item.get("write_bytes_rate_per_sec").and_then(Value::as_f64).unwrap_or(0.0);
let read_latency = item.get("read_latency").and_then(Value::as_f64).unwrap_or(0.0);
let write_latency = item.get("write_latency").and_then(Value::as_f64).unwrap_or(0.0);
Some((name, read_rate, write_rate, read_latency, write_latency))
})
.collect(),
_ => Vec::new(),
};
for (disk_name, read_rate, write_rate, read_latency, write_latency) in items {
let alert_rx = self.model.get_alert(read_rate, 0.0, 100.0, true, false, Some("rx"), Some(&disk_name), false);
let alert_tx = self.model.get_alert(write_rate, 0.0, 100.0, true, false, Some("tx"), Some(&disk_name), false);
for field in ["read_bytes", "read_bytes_rate_per_sec"] {
self.model.set_view_decoration(Some(&disk_name), field, alert_rx);
}
for field in ["write_bytes", "write_bytes_rate_per_sec"] {
self.model.set_view_decoration(Some(&disk_name), field, alert_tx);
}
let mut alert_latency_rx =
self.model.get_alert(read_latency, 0.0, 100.0, true, false, Some("rx_latency"), Some(&disk_name), false);
let mut alert_latency_tx =
self.model.get_alert(write_latency, 0.0, 100.0, true, false, Some("tx_latency"), Some(&disk_name), false);
if alert_latency_rx == Decoration::Default {
alert_latency_rx = self.model.get_alert(read_latency, 0.0, 100.0, true, false, Some("rx_latency"), None, false);
}
if alert_latency_tx == Decoration::Default {
alert_latency_tx = self.model.get_alert(write_latency, 0.0, 100.0, true, false, Some("tx_latency"), None, false);
}
self.model.set_view_decoration(Some(&disk_name), "read_latency", alert_latency_rx);
self.model.set_view_decoration(Some(&disk_name), "write_latency", alert_latency_tx);
}
}
fn display_curse(&self) -> bool {
true
}
}