From b1a39026fb9fb9480831eebff64f95cee6b6f35d Mon Sep 17 00:00:00 2001 From: Justin Martin Date: Sun, 9 Jul 2023 17:50:22 -0400 Subject: [PATCH] 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 --- src/app/data_farmer.rs | 7 + src/app/data_harvester/disks.rs | 8 + src/app/data_harvester/disks/freebsd.rs | 38 ++++- .../disks/unix/linux/counters.rs | 8 + .../data_harvester/disks/zfs_io_counters.rs | 152 ++++++++++++++++++ 5 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 src/app/data_harvester/disks/zfs_io_counters.rs diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index 37e58229..d2edb53a 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -347,6 +347,13 @@ impl DataCollection { None => device.name.split('/').last(), } } 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() } } diff --git a/src/app/data_harvester/disks.rs b/src/app/data_harvester/disks.rs index 3359fcc7..5075f335 100644 --- a/src/app/data_harvester/disks.rs +++ b/src/app/data_harvester/disks.rs @@ -8,12 +8,20 @@ use crate::app::filter::Filter; cfg_if! { if #[cfg(target_os = "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::*; } else if #[cfg(target_os = "windows")] { mod windows; pub(crate) use self::windows::*; } else if #[cfg(target_os = "linux")] { mod unix; + #[cfg(feature = "zfs")] + mod zfs_io_counters; pub(crate) use self::unix::*; } else if #[cfg(target_os = "macos")] { mod unix; diff --git a/src/app/data_harvester/disks/freebsd.rs b/src/app/data_harvester/disks/freebsd.rs index aa127ef6..a10fa642 100644 --- a/src/app/data_harvester/disks/freebsd.rs +++ b/src/app/data_harvester/disks/freebsd.rs @@ -6,7 +6,11 @@ use serde::Deserialize; 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)] #[serde(rename_all = "kebab-case")] @@ -25,14 +29,32 @@ struct FileSystem { } pub fn get_io_usage() -> error::Result { - let io_harvest = get_disk_info().map(|storage_system_information| { - storage_system_information - .filesystem - .into_iter() - .map(|disk| (disk.name, None)) - .collect() - })?; + #[allow(unused_mut)] + let mut io_harvest: HashMap> = + get_disk_info().map(|storage_system_information| { + storage_system_information + .filesystem + .into_iter() + .map(|disk| (disk.name, None)) + .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) } diff --git a/src/app/data_harvester/disks/unix/linux/counters.rs b/src/app/data_harvester/disks/unix/linux/counters.rs index f01d5b7d..d65863ba 100644 --- a/src/app/data_harvester/disks/unix/linux/counters.rs +++ b/src/app/data_harvester/disks/unix/linux/counters.rs @@ -81,5 +81,13 @@ pub fn io_stats() -> anyhow::Result>> { } } + #[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) } diff --git a/src/app/data_harvester/disks/zfs_io_counters.rs b/src/app/data_harvester/disks/zfs_io_counters.rs new file mode 100644 index 00000000..a53e8af3 --- /dev/null +++ b/src/app/data_harvester/disks/zfs_io_counters.rs @@ -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>> { + 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> = 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>> { + if let Ok(zpools) = std::fs::read_dir("/proc/spl/kstat/zfs") { + let zpools_vec: Vec = 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 = + 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> = 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::() { + write = number; + } + } + } + "nread" => { + if let Some((_type, val)) = + value.trim_start().rsplit_once(' ') + { + if let Ok(number) = val.parse::() { + 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")) + } +}