Skip to main content

btrfs_cli/device/
stats.rs

1use crate::{Format, Runnable};
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::{fs::File, 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 = File::open(&self.path).with_context(|| {
34            format!("failed to open '{}'", self.path.display())
35        })?;
36        let fd = file.as_fd();
37
38        let fs = filesystem_info(fd).with_context(|| {
39            format!(
40                "failed to get filesystem info for '{}'",
41                self.path.display()
42            )
43        })?;
44
45        let devices = device_info_all(fd, &fs).with_context(|| {
46            format!("failed to get device info for '{}'", self.path.display())
47        })?;
48
49        if devices.is_empty() {
50            anyhow::bail!("no devices found for '{}'", self.path.display());
51        }
52
53        let mut any_nonzero = false;
54
55        for dev in &devices {
56            let stats =
57                device_stats(fd, dev.devid, self.reset).with_context(|| {
58                    format!(
59                        "failed to get stats for device {} ({})",
60                        dev.devid, dev.path
61                    )
62                })?;
63
64            print_stats(&dev.path, &stats);
65
66            if !stats.is_clean() {
67                any_nonzero = true;
68            }
69        }
70
71        if self.check && any_nonzero {
72            anyhow::bail!("one or more devices have non-zero error counters");
73        }
74
75        Ok(())
76    }
77}
78
79/// Print the five counters for one device in the same layout as the C tool:
80/// `[/dev/path].counter_name   <value>`
81fn print_stats(path: &str, stats: &DeviceStats) {
82    let p = path;
83    println!("[{p}].{:<24} {}", "write_io_errs", stats.write_errs);
84    println!("[{p}].{:<24} {}", "read_io_errs", stats.read_errs);
85    println!("[{p}].{:<24} {}", "flush_io_errs", stats.flush_errs);
86    println!("[{p}].{:<24} {}", "corruption_errs", stats.corruption_errs);
87    println!("[{p}].{:<24} {}", "generation_errs", stats.generation_errs);
88}