- layout: header, top menu (quicklook, cpu, mem, swap, load), left sidebar, process list and alert footer - decorations/colors, bars, per-plugin render implementations - hotkeys: quit, help, modes, sorting, cursor, nice, kill, filters, toggles - TestBackend layout test
237 lines
12 KiB
Rust
237 lines
12 KiB
Rust
//! 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
|
|
}
|
|
|
|
fn render(&mut self, ctx: &glances_core::RenderContext) -> Vec<glances_core::StatLine> {
|
|
let mut ret = Vec::new();
|
|
if self.model.stats.as_array().map(|a| a.is_empty()).unwrap_or(true) || ctx.max_width == 0 {
|
|
return ret;
|
|
}
|
|
let name_max_width = ctx.max_width.saturating_sub(13);
|
|
ret.push(self.model.curse_add_line(
|
|
format!("{:<width$}", "DISK I/O", width = name_max_width),
|
|
glances_core::Decoration::Title,
|
|
false,
|
|
false,
|
|
false,
|
|
));
|
|
if ctx.args.diskio_iops {
|
|
ret.push(self.model.curse_add_line(format!("{:>8}", "IOR/s"), glances_core::Decoration::Default, false, false, false));
|
|
ret.push(self.model.curse_add_line(format!("{:>7}", "IOW/s"), glances_core::Decoration::Default, false, false, false));
|
|
} else if ctx.args.diskio_latency {
|
|
ret.push(self.model.curse_add_line(format!("{:>8}", "ms/opR"), glances_core::Decoration::Default, false, false, false));
|
|
ret.push(self.model.curse_add_line(format!("{:>7}", "ms/opW"), glances_core::Decoration::Default, false, false, false));
|
|
} else {
|
|
ret.push(self.model.curse_add_line(format!("{:>8}", "R/s"), glances_core::Decoration::Default, false, false, false));
|
|
ret.push(self.model.curse_add_line(format!("{:>7}", "W/s"), glances_core::Decoration::Default, false, false, false));
|
|
}
|
|
let stats = self.model.sorted_stats();
|
|
let Some(items) = stats.as_array() else {
|
|
return ret;
|
|
};
|
|
for i in items {
|
|
let disk_real_name = i.get("disk_name").and_then(Value::as_str).unwrap_or("").to_string();
|
|
if self
|
|
.model
|
|
.hide_zero_fields
|
|
.iter()
|
|
.all(|field| self.model.view_bool(Some(&disk_real_name), field, "hidden"))
|
|
{
|
|
continue;
|
|
}
|
|
let mut disk_name = i
|
|
.get("alias")
|
|
.and_then(Value::as_str)
|
|
.map(|s| s.to_string())
|
|
.unwrap_or_else(|| disk_real_name.clone());
|
|
if disk_name.chars().count() > name_max_width {
|
|
disk_name = format!("{}_", disk_name.chars().take(name_max_width).collect::<String>());
|
|
}
|
|
ret.push(self.model.curse_new_line());
|
|
ret.push(self.model.curse_add_line(
|
|
format!("{:<width$}", disk_name, width = name_max_width + 1),
|
|
glances_core::Decoration::Default,
|
|
false,
|
|
false,
|
|
false,
|
|
));
|
|
if ctx.args.diskio_iops {
|
|
let rx = self.model.auto_unit(i.get("read_count_rate_per_sec").and_then(Value::as_f64).unwrap_or(0.0), false, "K");
|
|
let tx = self.model.auto_unit(i.get("write_count_rate_per_sec").and_then(Value::as_f64).unwrap_or(0.0), false, "K");
|
|
ret.push(self.model.curse_add_line(format!("{rx:>7}"), self.model.view_decoration(Some(&disk_real_name), "read_count"), false, false, false));
|
|
ret.push(self.model.curse_add_line(format!("{tx:>7}"), self.model.view_decoration(Some(&disk_real_name), "write_count"), false, false, false));
|
|
} else if ctx.args.diskio_latency {
|
|
let rx = self.model.auto_unit(i.get("read_latency").and_then(Value::as_f64).unwrap_or(0.0), true, "K");
|
|
let tx = self.model.auto_unit(i.get("write_latency").and_then(Value::as_f64).unwrap_or(0.0), true, "K");
|
|
ret.push(self.model.curse_add_line(format!("{rx:>7}"), self.model.view_decoration(Some(&disk_real_name), "read_latency"), false, false, false));
|
|
ret.push(self.model.curse_add_line(format!("{tx:>7}"), self.model.view_decoration(Some(&disk_real_name), "write_latency"), false, false, false));
|
|
} else {
|
|
let rx = self.model.auto_unit(i.get("read_bytes_rate_per_sec").and_then(Value::as_f64).unwrap_or(0.0), false, "K");
|
|
let tx = self.model.auto_unit(i.get("write_bytes_rate_per_sec").and_then(Value::as_f64).unwrap_or(0.0), false, "K");
|
|
ret.push(self.model.curse_add_line(format!("{rx:>7}"), self.model.view_decoration(Some(&disk_real_name), "read_bytes"), false, false, false));
|
|
ret.push(self.model.curse_add_line(format!("{tx:>7}"), self.model.view_decoration(Some(&disk_real_name), "write_bytes"), false, false, false));
|
|
}
|
|
}
|
|
ret
|
|
}
|
|
}
|