fix #3743: extract zfs dataset io stats from /proc/spl/kstat/zfs/POOL/objset-*
Recently, ZFS removed the pool global io stats from /proc/spl/kstat/zfs/POOL/io with no replacement. To gather stats about the datastores, access now the objset specific entries there. To be able to make that efficient, cache a map of dataset <-> obset ids, so that we do not have to parse all files each time. We update the cache each time we try to get the info for a dataset where we do not have a mapping. We cannot update it on datastore add/remove since that happens in the proxmox-backup daemon, while we need the info here in proxmox-backup-proxy. Sadly with this we lose the io wait metric, but it seems that this is no longer tracked in zfs at all, so nothing we can do for that. Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
parent
b44483a853
commit
7c069e82d1
@ -62,8 +62,7 @@ use proxmox_backup::tools::{
|
|||||||
PROXMOX_BACKUP_TCP_KEEPALIVE_TIME,
|
PROXMOX_BACKUP_TCP_KEEPALIVE_TIME,
|
||||||
disks::{
|
disks::{
|
||||||
DiskManage,
|
DiskManage,
|
||||||
zfs_pool_stats,
|
zfs_dataset_stats,
|
||||||
get_pool_from_dataset,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1072,16 +1071,16 @@ fn gather_disk_stats(disk_manager: Arc<DiskManage>, path: &Path, rrd_prefix: &st
|
|||||||
Ok(None) => {},
|
Ok(None) => {},
|
||||||
Ok(Some((fs_type, device, source))) => {
|
Ok(Some((fs_type, device, source))) => {
|
||||||
let mut device_stat = None;
|
let mut device_stat = None;
|
||||||
match fs_type.as_str() {
|
match (fs_type.as_str(), source) {
|
||||||
"zfs" => {
|
("zfs", Some(source)) => match source.into_string() {
|
||||||
if let Some(source) = source {
|
Ok(dataset) => match zfs_dataset_stats(&dataset) {
|
||||||
let pool = get_pool_from_dataset(&source).unwrap_or(&source);
|
Ok(stat) => device_stat = Some(stat),
|
||||||
match zfs_pool_stats(pool) {
|
Err(err) => eprintln!("zfs_dataset_stats({:?}) failed - {}", dataset, err),
|
||||||
Ok(stat) => device_stat = stat,
|
},
|
||||||
Err(err) => eprintln!("zfs_pool_stats({:?}) failed - {}", pool, 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()) {
|
if let Ok(disk) = disk_manager.clone().disk_by_dev_num(device.into_dev_t()) {
|
||||||
match disk.read_stat() {
|
match disk.read_stat() {
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, Error};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use proxmox_schema::const_regex;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
lazy_static!{
|
lazy_static!{
|
||||||
@ -16,15 +19,12 @@ lazy_static!{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns pool from dataset path of the form 'rpool/ROOT/pbs-1'
|
fn get_pool_from_dataset(dataset: &str) -> &str {
|
||||||
pub fn get_pool_from_dataset(dataset: &OsStr) -> Option<&OsStr> {
|
if let Some(idx) = dataset.find('/') {
|
||||||
if let Some(dataset) = dataset.to_str() {
|
&dataset[0..idx].as_ref()
|
||||||
if let Some(idx) = dataset.find('/') {
|
} else {
|
||||||
return Some(dataset[0..idx].as_ref());
|
dataset.as_ref()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns kernel IO-stats for zfs pools
|
/// Returns kernel IO-stats for zfs pools
|
||||||
@ -97,3 +97,110 @@ pub fn zfs_devices(
|
|||||||
Ok(device_set)
|
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)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user