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)]
18pub struct DeviceStatsCommand {
19    /// Return a non-zero exit code if any error counter is greater than zero
20    #[clap(long, short)]
21    pub check: bool,
22
23    /// Print current values and then atomically reset all counters to zero
24    #[clap(long, short = 'z')]
25    pub reset: bool,
26
27    /// Path to a mounted btrfs filesystem or one of its devices
28    pub path: PathBuf,
29}
30
31impl Runnable for DeviceStatsCommand {
32    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
33        let file = open_path(&self.path)?;
34        let fd = file.as_fd();
35
36        let fs = filesystem_info(fd).with_context(|| {
37            format!(
38                "failed to get filesystem info for '{}'",
39                self.path.display()
40            )
41        })?;
42
43        let devices = device_info_all(fd, &fs).with_context(|| {
44            format!("failed to get device info for '{}'", self.path.display())
45        })?;
46
47        if devices.is_empty() {
48            anyhow::bail!("no devices found for '{}'", self.path.display());
49        }
50
51        let mut any_nonzero = false;
52
53        for dev in &devices {
54            let stats =
55                device_stats(fd, dev.devid, self.reset).with_context(|| {
56                    format!(
57                        "failed to get stats for device {} ({})",
58                        dev.devid, dev.path
59                    )
60                })?;
61
62            print_stats(&dev.path, &stats);
63
64            if !stats.is_clean() {
65                any_nonzero = true;
66            }
67        }
68
69        if self.check && any_nonzero {
70            anyhow::bail!("one or more devices have non-zero error counters");
71        }
72
73        Ok(())
74    }
75}
76
77/// Print the five counters for one device in the same layout as the C tool:
78/// `[/dev/path].counter_name   <value>`
79fn print_stats(path: &str, stats: &DeviceStats) {
80    let p = path;
81    println!("[{p}].{:<24} {}", "write_io_errs", stats.write_errs);
82    println!("[{p}].{:<24} {}", "read_io_errs", stats.read_errs);
83    println!("[{p}].{:<24} {}", "flush_io_errs", stats.flush_errs);
84    println!("[{p}].{:<24} {}", "corruption_errs", stats.corruption_errs);
85    println!("[{p}].{:<24} {}", "generation_errs", stats.generation_errs);
86}