LCOV - code coverage report
Current view: top level - pageserver/src - statvfs.rs (source / functions) Coverage Total Hit
Test: 8ac049b474321fdc72ddcb56d7165153a1a900e8.info Lines: 84.6 % 78 66
Test Date: 2023-09-06 10:18:01 Functions: 45.0 % 40 18

            Line data    Source code
       1              : //! Wrapper around nix::sys::statvfs::Statvfs that allows for mocking.
       2              : 
       3              : use std::path::Path;
       4              : 
       5              : pub enum Statvfs {
       6              :     Real(nix::sys::statvfs::Statvfs),
       7              :     Mock(mock::Statvfs),
       8              : }
       9              : 
      10              : // NB: on macOS, the block count type of struct statvfs is u32.
      11              : // The workaround seems to be to use the non-standard statfs64 call.
      12              : // Sincce it should only be a problem on > 2TiB disks, let's ignore
      13              : // the problem for now and upcast to u64.
      14              : impl Statvfs {
      15              :     pub fn get(tenants_dir: &Path, mocked: Option<&mock::Behavior>) -> nix::Result<Self> {
      16            6 :         if let Some(mocked) = mocked {
      17            6 :             Ok(Statvfs::Mock(mock::get(tenants_dir, mocked)?))
      18              :         } else {
      19            0 :             Ok(Statvfs::Real(nix::sys::statvfs::statvfs(tenants_dir)?))
      20              :         }
      21            6 :     }
      22              : 
      23              :     // NB: allow() because the block count type is u32 on macOS.
      24              :     #[allow(clippy::useless_conversion)]
      25            4 :     pub fn blocks(&self) -> u64 {
      26            4 :         match self {
      27            0 :             Statvfs::Real(stat) => u64::try_from(stat.blocks()).unwrap(),
      28            4 :             Statvfs::Mock(stat) => stat.blocks,
      29              :         }
      30            4 :     }
      31              : 
      32              :     // NB: allow() because the block count type is u32 on macOS.
      33              :     #[allow(clippy::useless_conversion)]
      34            4 :     pub fn blocks_available(&self) -> u64 {
      35            4 :         match self {
      36            0 :             Statvfs::Real(stat) => u64::try_from(stat.blocks_available()).unwrap(),
      37            4 :             Statvfs::Mock(stat) => stat.blocks_available,
      38              :         }
      39            4 :     }
      40              : 
      41            8 :     pub fn fragment_size(&self) -> u64 {
      42            8 :         match self {
      43            0 :             Statvfs::Real(stat) => stat.fragment_size(),
      44            8 :             Statvfs::Mock(stat) => stat.fragment_size,
      45              :         }
      46            8 :     }
      47              : 
      48            0 :     pub fn block_size(&self) -> u64 {
      49            0 :         match self {
      50            0 :             Statvfs::Real(stat) => stat.block_size(),
      51            0 :             Statvfs::Mock(stat) => stat.block_size,
      52              :         }
      53            0 :     }
      54              : }
      55              : 
      56              : pub mod mock {
      57              :     use anyhow::Context;
      58              :     use regex::Regex;
      59              :     use std::path::Path;
      60              :     use tracing::log::info;
      61              : 
      62           23 :     #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
      63              :     #[serde(tag = "type")]
      64              :     pub enum Behavior {
      65              :         Success {
      66              :             blocksize: u64,
      67              :             total_blocks: u64,
      68              :             name_filter: Option<utils::serde_regex::Regex>,
      69              :         },
      70              :         Failure {
      71              :             mocked_error: MockedError,
      72              :         },
      73              :     }
      74              : 
      75            2 :     #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
      76              :     #[allow(clippy::upper_case_acronyms)]
      77              :     pub enum MockedError {
      78              :         EIO,
      79              :     }
      80              : 
      81              :     impl From<MockedError> for nix::Error {
      82            2 :         fn from(e: MockedError) -> Self {
      83            2 :             match e {
      84            2 :                 MockedError::EIO => nix::Error::EIO,
      85            2 :             }
      86            2 :         }
      87              :     }
      88              : 
      89            6 :     pub fn get(tenants_dir: &Path, behavior: &Behavior) -> nix::Result<Statvfs> {
      90            6 :         info!("running mocked statvfs");
      91              : 
      92            6 :         match behavior {
      93              :             Behavior::Success {
      94            4 :                 blocksize,
      95            4 :                 total_blocks,
      96            4 :                 ref name_filter,
      97            4 :             } => {
      98            4 :                 let used_bytes = walk_dir_disk_usage(tenants_dir, name_filter.as_deref()).unwrap();
      99            4 : 
     100            4 :                 // round it up to the nearest block multiple
     101            4 :                 let used_blocks = (used_bytes + (blocksize - 1)) / blocksize;
     102            4 : 
     103            4 :                 if used_blocks > *total_blocks {
     104            0 :                     panic!(
     105            0 :                         "mocking error: used_blocks > total_blocks: {used_blocks} > {total_blocks}"
     106            0 :                     );
     107            4 :                 }
     108            4 : 
     109            4 :                 let avail_blocks = total_blocks - used_blocks;
     110            4 : 
     111            4 :                 Ok(Statvfs {
     112            4 :                     blocks: *total_blocks,
     113            4 :                     blocks_available: avail_blocks,
     114            4 :                     fragment_size: *blocksize,
     115            4 :                     block_size: *blocksize,
     116            4 :                 })
     117              :             }
     118            2 :             Behavior::Failure { mocked_error } => Err((*mocked_error).into()),
     119              :         }
     120            6 :     }
     121              : 
     122            4 :     fn walk_dir_disk_usage(path: &Path, name_filter: Option<&Regex>) -> anyhow::Result<u64> {
     123            4 :         let mut total = 0;
     124          158 :         for entry in walkdir::WalkDir::new(path) {
     125          158 :             let entry = entry?;
     126          158 :             if !entry.file_type().is_file() {
     127           28 :                 continue;
     128          130 :             }
     129          130 :             if !name_filter
     130          130 :                 .as_ref()
     131          130 :                 .map(|filter| filter.is_match(entry.file_name().to_str().unwrap()))
     132          130 :                 .unwrap_or(true)
     133              :             {
     134           16 :                 continue;
     135          114 :             }
     136          114 :             total += entry
     137          114 :                 .metadata()
     138          114 :                 .with_context(|| format!("get metadata of {:?}", entry.path()))?
     139          114 :                 .len();
     140              :         }
     141            4 :         Ok(total)
     142            4 :     }
     143              : 
     144              :     pub struct Statvfs {
     145              :         pub blocks: u64,
     146              :         pub blocks_available: u64,
     147              :         pub fragment_size: u64,
     148              :         pub block_size: u64,
     149              :     }
     150              : }
        

Generated by: LCOV version 2.1-beta