PROXMOX_BACKUP_TCP_KEEPALIVE_TIME,
disks::{
DiskManage,
- zfs_pool_stats,
- get_pool_from_dataset,
+ zfs_dataset_stats,
},
};
Ok(None) => {},
Ok(Some((fs_type, device, source))) => {
let mut device_stat = None;
- match fs_type.as_str() {
- "zfs" => {
- if let Some(source) = source {
- let pool = get_pool_from_dataset(&source).unwrap_or(&source);
- match zfs_pool_stats(pool) {
- Ok(stat) => device_stat = stat,
- Err(err) => eprintln!("zfs_pool_stats({:?}) failed - {}", pool, err),
- }
+ match (fs_type.as_str(), source) {
+ ("zfs", Some(source)) => match source.into_string() {
+ Ok(dataset) => match zfs_dataset_stats(&dataset) {
+ Ok(stat) => device_stat = Some(stat),
+ Err(err) => eprintln!("zfs_dataset_stats({:?}) failed - {}", dataset, err),
+ },
+ Err(source) => {
+ eprintln!("zfs_pool_stats({:?}) failed - invalid characters", source)
}
- }
+ },
_ => {
if let Ok(disk) = disk_manager.clone().disk_by_dev_num(device.into_dev_t()) {
match disk.read_stat() {
use std::path::PathBuf;
use std::collections::HashSet;
use std::os::unix::fs::MetadataExt;
+use std::sync::{Arc, Mutex};
use anyhow::{bail, Error};
use lazy_static::lazy_static;
+use proxmox_schema::const_regex;
+
use super::*;
lazy_static!{
};
}
-/// returns pool from dataset path of the form 'rpool/ROOT/pbs-1'
-pub fn get_pool_from_dataset(dataset: &OsStr) -> Option<&OsStr> {
- if let Some(dataset) = dataset.to_str() {
- if let Some(idx) = dataset.find('/') {
- return Some(dataset[0..idx].as_ref());
- }
+fn get_pool_from_dataset(dataset: &str) -> &str {
+ if let Some(idx) = dataset.find('/') {
+ &dataset[0..idx].as_ref()
+ } else {
+ dataset.as_ref()
}
-
- None
}
/// Returns kernel IO-stats for zfs pools
Ok(device_set)
}
+const ZFS_KSTAT_BASE_PATH: &str = "/proc/spl/kstat/zfs";
+const_regex! {
+ OBJSET_REGEX = r"^objset-0x[a-fA-F0-9]+$";
+}
+
+lazy_static::lazy_static! {
+ pub static ref ZFS_DATASET_OBJSET_MAP: Arc<Mutex<HashMap<String, (String, String)>>> =
+ Arc::new(Mutex::new(HashMap::new()));
+}
+
+// parses /proc/spl/kstat/zfs/POOL/objset-ID files
+// they have the following format:
+//
+// 0 0 0x00 0 0000 00000000000 000000000000000000
+// name type data
+// dataset_name 7 pool/dataset
+// writes 4 0
+// nwritten 4 0
+// reads 4 0
+// nread 4 0
+// nunlinks 4 0
+// nunlinked 4 0
+//
+// we are only interested in the dataset_name, writes, nwrites, reads and nread
+fn parse_objset_stat(pool: &str, objset_id: &str) -> Result<(String, BlockDevStat), Error> {
+ let path = PathBuf::from(format!("{}/{}/{}", ZFS_KSTAT_BASE_PATH, pool, objset_id));
+
+ let text = match proxmox_sys::fs::file_read_optional_string(&path)? {
+ Some(text) => text,
+ None => bail!("could not parse 'objset-{}' stat file", objset_id),
+ };
+
+ let mut dataset_name = String::new();
+ let mut stat = BlockDevStat {
+ read_sectors: 0,
+ write_sectors: 0,
+ read_ios: 0,
+ write_ios: 0,
+ io_ticks: 0,
+ };
+
+ for (i, line) in text.lines().enumerate() {
+ if i < 2 {
+ continue;
+ }
+
+ let mut parts = line.split_ascii_whitespace();
+ let name = parts.next();
+ parts.next(); // discard type
+ let value = parts.next().ok_or_else(|| format_err!("no value found"))?;
+ match name {
+ Some("dataset_name") => dataset_name = value.to_string(),
+ Some("writes") => stat.write_ios = u64::from_str_radix(value, 10).unwrap_or(0),
+ Some("nwritten") => {
+ stat.write_sectors = u64::from_str_radix(value, 10).unwrap_or(0) / 512
+ }
+ Some("reads") => stat.read_ios = u64::from_str_radix(value, 10).unwrap_or(0),
+ Some("nread") => stat.read_sectors = u64::from_str_radix(value, 10).unwrap_or(0) / 512,
+ _ => {}
+ }
+ }
+
+ Ok((dataset_name, stat))
+}
+
+
+fn get_mapping(dataset: &str) -> Option<(String, String)> {
+ ZFS_DATASET_OBJSET_MAP
+ .lock()
+ .unwrap()
+ .get(dataset)
+ .map(|c| c.to_owned())
+}
+
+/// Updates the dataset <-> objset_map
+pub(crate) fn update_zfs_objset_map(pool: &str) -> Result<(), Error> {
+ let mut map = ZFS_DATASET_OBJSET_MAP.lock().unwrap();
+ map.clear();
+ let path = PathBuf::from(format!("{}/{}", ZFS_KSTAT_BASE_PATH, pool));
+
+ proxmox_sys::fs::scandir(
+ libc::AT_FDCWD,
+ &path,
+ &OBJSET_REGEX,
+ |_l2_fd, filename, _type| {
+ let (name, _) = parse_objset_stat(pool, filename)?;
+ map.insert(name, (pool.to_string(), filename.to_string()));
+ Ok(())
+ },
+ )?;
+
+ Ok(())
+}
+
+/// Gets io stats for the dataset from /proc/spl/kstat/zfs/POOL/objset-ID
+pub fn zfs_dataset_stats(dataset: &str) -> Result<BlockDevStat, Error> {
+ let mut mapping = get_mapping(dataset);
+ if mapping.is_none() {
+ let pool = get_pool_from_dataset(dataset);
+ update_zfs_objset_map(pool)?;
+ mapping = get_mapping(dataset);
+ }
+ let (pool, objset_id) =
+ mapping.ok_or_else(|| format_err!("could not find objset id for dataset"))?;
+ let (_, stat) = parse_objset_stat(&pool, &objset_id)?;
+ Ok(stat)
+}