mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-14 17:24:51 +02:00
add zfs io counters for linux and freebsd (#1248)
* add zfs io counters for linux and freebsd * ci * freebsd clippy * code review: remove dead code for zfs feature gate of freebsd iocounters and squash if statement in zfs_io_counters
This commit is contained in:
parent
ccc7091529
commit
b1a39026fb
@ -347,6 +347,13 @@ impl DataCollection {
|
|||||||
None => device.name.split('/').last(),
|
None => device.name.split('/').last(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
#[cfg(feature = "zfs")]
|
||||||
|
if ! device.name.starts_with('/'){
|
||||||
|
Some(device.name.as_str()) // use the whole zfs dataset name
|
||||||
|
} else {
|
||||||
|
device.name.split('/').last()
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "zfs"))]
|
||||||
device.name.split('/').last()
|
device.name.split('/').last()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,20 @@ use crate::app::filter::Filter;
|
|||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(target_os = "freebsd")] {
|
if #[cfg(target_os = "freebsd")] {
|
||||||
mod freebsd;
|
mod freebsd;
|
||||||
|
#[cfg(feature = "zfs")]
|
||||||
|
mod io_counters;
|
||||||
|
#[cfg(feature = "zfs")]
|
||||||
|
mod zfs_io_counters;
|
||||||
|
#[cfg(feature = "zfs")]
|
||||||
|
pub use io_counters::IoCounters;
|
||||||
pub(crate) use self::freebsd::*;
|
pub(crate) use self::freebsd::*;
|
||||||
} else if #[cfg(target_os = "windows")] {
|
} else if #[cfg(target_os = "windows")] {
|
||||||
mod windows;
|
mod windows;
|
||||||
pub(crate) use self::windows::*;
|
pub(crate) use self::windows::*;
|
||||||
} else if #[cfg(target_os = "linux")] {
|
} else if #[cfg(target_os = "linux")] {
|
||||||
mod unix;
|
mod unix;
|
||||||
|
#[cfg(feature = "zfs")]
|
||||||
|
mod zfs_io_counters;
|
||||||
pub(crate) use self::unix::*;
|
pub(crate) use self::unix::*;
|
||||||
} else if #[cfg(target_os = "macos")] {
|
} else if #[cfg(target_os = "macos")] {
|
||||||
mod unix;
|
mod unix;
|
||||||
|
@ -6,7 +6,11 @@ use serde::Deserialize;
|
|||||||
|
|
||||||
use super::{keep_disk_entry, DiskHarvest, IoHarvest};
|
use super::{keep_disk_entry, DiskHarvest, IoHarvest};
|
||||||
|
|
||||||
use crate::{app::data_harvester::DataCollector, data_harvester::deserialize_xo, utils::error};
|
use crate::{
|
||||||
|
app::data_harvester::DataCollector, data_harvester::deserialize_xo,
|
||||||
|
data_harvester::disks::IoData, utils::error,
|
||||||
|
};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Default)]
|
#[derive(Deserialize, Debug, Default)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
@ -25,7 +29,9 @@ struct FileSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_io_usage() -> error::Result<IoHarvest> {
|
pub fn get_io_usage() -> error::Result<IoHarvest> {
|
||||||
let io_harvest = get_disk_info().map(|storage_system_information| {
|
#[allow(unused_mut)]
|
||||||
|
let mut io_harvest: HashMap<String, Option<IoData>> =
|
||||||
|
get_disk_info().map(|storage_system_information| {
|
||||||
storage_system_information
|
storage_system_information
|
||||||
.filesystem
|
.filesystem
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -33,6 +39,22 @@ pub fn get_io_usage() -> error::Result<IoHarvest> {
|
|||||||
.collect()
|
.collect()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
#[cfg(feature = "zfs")]
|
||||||
|
{
|
||||||
|
use crate::app::data_harvester::disks::zfs_io_counters;
|
||||||
|
if let Ok(zfs_io) = zfs_io_counters::zfs_io_stats() {
|
||||||
|
for io in zfs_io.into_iter().flatten() {
|
||||||
|
let mount_point = io.device_name().to_string_lossy();
|
||||||
|
io_harvest.insert(
|
||||||
|
mount_point.to_string(),
|
||||||
|
Some(IoData {
|
||||||
|
read_bytes: io.read_bytes(),
|
||||||
|
write_bytes: io.write_bytes(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(io_harvest)
|
Ok(io_harvest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,5 +81,13 @@ pub fn io_stats() -> anyhow::Result<Vec<anyhow::Result<IoCounters>>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "zfs")]
|
||||||
|
{
|
||||||
|
use crate::app::data_harvester::disks::zfs_io_counters;
|
||||||
|
if let Ok(mut zfs_io) = zfs_io_counters::zfs_io_stats() {
|
||||||
|
results.append(&mut zfs_io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
152
src/app/data_harvester/disks/zfs_io_counters.rs
Normal file
152
src/app/data_harvester/disks/zfs_io_counters.rs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
use crate::app::data_harvester::disks::IoCounters;
|
||||||
|
|
||||||
|
/// Returns zpool I/O stats. Pulls data from `sysctl kstat.zfs.{POOL}.dataset.{objset-*}`
|
||||||
|
#[cfg(target_os = "freebsd")]
|
||||||
|
pub fn zfs_io_stats() -> anyhow::Result<Vec<anyhow::Result<IoCounters>>> {
|
||||||
|
use sysctl::Sysctl;
|
||||||
|
let zfs_ctls: Vec<_> = sysctl::Ctl::new("kstat.zfs.")?
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| {
|
||||||
|
e.ok().and_then(|ctl| {
|
||||||
|
let name = ctl.name();
|
||||||
|
if let Ok(name) = name {
|
||||||
|
if name.contains("objset-")
|
||||||
|
&& (name.contains("dataset_name")
|
||||||
|
|| name.contains("nwritten")
|
||||||
|
|| name.contains("nread"))
|
||||||
|
{
|
||||||
|
Some(ctl)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
let results: Vec<anyhow::Result<IoCounters>> = zfs_ctls
|
||||||
|
.iter()
|
||||||
|
.chunks(3)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|chunk| {
|
||||||
|
let mut nread = 0;
|
||||||
|
let mut nwrite = 0;
|
||||||
|
let mut ds_name = String::new();
|
||||||
|
for ctl in chunk {
|
||||||
|
if let Ok(name) = ctl.name() {
|
||||||
|
if name.contains("dataset_name") {
|
||||||
|
ds_name = ctl.value_string().ok()?;
|
||||||
|
} else if name.contains("nread") {
|
||||||
|
if let Ok(sysctl::CtlValue::U64(val)) = ctl.value() {
|
||||||
|
nread = val;
|
||||||
|
}
|
||||||
|
} else if name.contains("nwritten") {
|
||||||
|
if let Ok(sysctl::CtlValue::U64(val)) = ctl.value() {
|
||||||
|
nwrite = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Ok(IoCounters::new(ds_name, nread, nwrite)))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns zpool I/O stats. Pulls data from `/proc/spl/kstat/zfs/*/objset-*`.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn zfs_io_stats() -> anyhow::Result<Vec<anyhow::Result<IoCounters>>> {
|
||||||
|
if let Ok(zpools) = std::fs::read_dir("/proc/spl/kstat/zfs") {
|
||||||
|
let zpools_vec: Vec<std::path::PathBuf> = zpools
|
||||||
|
.filter_map(|e| {
|
||||||
|
e.ok().and_then(|d| {
|
||||||
|
let p = d.path();
|
||||||
|
if p.is_dir() {
|
||||||
|
Some(p)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let results = zpools_vec
|
||||||
|
.iter()
|
||||||
|
.filter_map(|zpool| {
|
||||||
|
// go through each pool
|
||||||
|
if let Ok(datasets) = std::fs::read_dir(zpool) {
|
||||||
|
let datasets_vec: Vec<std::path::PathBuf> =
|
||||||
|
datasets // go through dataset
|
||||||
|
.filter_map(|e| {
|
||||||
|
e.ok().and_then(|d| {
|
||||||
|
let p = d.path();
|
||||||
|
if p.is_file() && p.to_str()?.contains("objset-") {
|
||||||
|
Some(p)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let io_counters: Vec<anyhow::Result<IoCounters>> = datasets_vec
|
||||||
|
.iter()
|
||||||
|
.filter_map(|ds| {
|
||||||
|
// get io-counter from each dataset
|
||||||
|
if let Ok(contents) = std::fs::read_to_string(ds) {
|
||||||
|
let mut read = 0;
|
||||||
|
let mut write = 0;
|
||||||
|
let mut name = "";
|
||||||
|
contents.lines().for_each(|line| {
|
||||||
|
if let Some((label, value)) = line.split_once(' ') {
|
||||||
|
match label {
|
||||||
|
"dataset_name" => {
|
||||||
|
if let Some((_type, val)) =
|
||||||
|
value.trim_start().rsplit_once(' ')
|
||||||
|
{
|
||||||
|
name = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"nwritten" => {
|
||||||
|
if let Some((_type, val)) =
|
||||||
|
value.trim_start().rsplit_once(' ')
|
||||||
|
{
|
||||||
|
if let Ok(number) = val.parse::<u64>() {
|
||||||
|
write = number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"nread" => {
|
||||||
|
if let Some((_type, val)) =
|
||||||
|
value.trim_start().rsplit_once(' ')
|
||||||
|
{
|
||||||
|
if let Ok(number) = val.parse::<u64>() {
|
||||||
|
read = number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let counter = IoCounters::new(name.to_owned(), read, write);
|
||||||
|
//log::debug!("adding io counter for zfs {:?}", counter);
|
||||||
|
Some(Ok(counter))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Some(io_counters)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect(); // combine io-counters
|
||||||
|
Ok(results)
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("Unable to open zfs proc directory"))
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user