Skip to main content

btrfs_cli/device/
stats.rs

1use crate::{Format, Runnable, util::open_path};
2use anyhow::{Context, Result};
3use btrfs_uapi::{
4    device::{DeviceStats, device_info_all, device_stats},
5    filesystem::filesystem_info,
6};
7use clap::Parser;
8use std::{os::unix::io::AsFd, path::PathBuf};
9
10/// Show device I/O error statistics for all devices of a filesystem
11///
12/// Reads per-device counters for write, read, flush, corruption, and
13/// generation errors. The path can be a mount point or a device belonging
14/// to the filesystem.
15///
16/// The operation requires CAP_SYS_ADMIN.
17#[derive(Parser, Debug)]
18#[allow(clippy::doc_markdown)]
19pub struct DeviceStatsCommand {
20    /// Return a non-zero exit code if any error counter is greater than zero
21    #[clap(long, short)]
22    pub check: bool,
23
24    /// Print current values and then atomically reset all counters to zero
25    #[clap(long, short = 'z')]
26    pub reset: bool,
27
28    /// Path to a mounted btrfs filesystem or one of its devices
29    pub path: PathBuf,
30}
31
32impl Runnable for DeviceStatsCommand {
33    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
34        let file = open_path(&self.path)?;
35        let fd = file.as_fd();
36
37        let fs = filesystem_info(fd).with_context(|| {
38            format!(
39                "failed to get filesystem info for '{}'",
40                self.path.display()
41            )
42        })?;
43
44        let devices = device_info_all(fd, &fs).with_context(|| {
45            format!("failed to get device info for '{}'", self.path.display())
46        })?;
47
48        if devices.is_empty() {
49            anyhow::bail!("no devices found for '{}'", self.path.display());
50        }
51
52        let mut any_nonzero = false;
53
54        for dev in &devices {
55            let stats =
56                device_stats(fd, dev.devid, self.reset).with_context(|| {
57                    format!(
58                        "failed to get stats for device {} ({})",
59                        dev.devid, dev.path
60                    )
61                })?;
62
63            print_stats(&dev.path, &stats);
64
65            if !stats.is_clean() {
66                any_nonzero = true;
67            }
68        }
69
70        if self.check && any_nonzero {
71            anyhow::bail!("one or more devices have non-zero error counters");
72        }
73
74        Ok(())
75    }
76}
77
78/// Print the five counters for one device in the same layout as the C tool:
79/// `[/dev/path].counter_name   <value>`
80fn print_stats(path: &str, stats: &DeviceStats) {
81    let p = path;
82    println!("[{p}].{:<24} {}", "write_io_errs", stats.write_errs);
83    println!("[{p}].{:<24} {}", "read_io_errs", stats.read_errs);
84    println!("[{p}].{:<24} {}", "flush_io_errs", stats.flush_errs);
85    println!("[{p}].{:<24} {}", "corruption_errs", stats.corruption_errs);
86    println!("[{p}].{:<24} {}", "generation_errs", stats.generation_errs);
87}