LCOV - differential code coverage report
Current view: top level - pageserver/src - statvfs.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 82.5 % 80 66 14 66
Current Date: 2023-10-19 02:04:12 Functions: 45.0 % 40 18 22 18
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

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

Generated by: LCOV version 2.1-beta