diff --git a/src/bin/pdata_tools.rs b/src/bin/pdata_tools.rs index b3e0676..da5d430 100644 --- a/src/bin/pdata_tools.rs +++ b/src/bin/pdata_tools.rs @@ -48,6 +48,8 @@ fn main_() -> Result<()> { thin_dump::run(&new_args); } else if name_eq(name, "thin_metadata_pack") { thin_metadata_pack::run(&new_args); + } else if name_eq(name, "thin_metadata_size") { + thin_metadata_size::run(&new_args); } else if name_eq(name, "thin_metadata_unpack") { thin_metadata_unpack::run(&new_args); } else if name_eq(name, "thin_repair") { diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 34377a6..74fc092 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -11,6 +11,7 @@ pub mod era_restore; pub mod thin_check; pub mod thin_dump; pub mod thin_metadata_pack; +pub mod thin_metadata_size; pub mod thin_metadata_unpack; pub mod thin_repair; pub mod thin_restore; diff --git a/src/commands/thin_metadata_size.rs b/src/commands/thin_metadata_size.rs new file mode 100644 index 0000000..1574d5a --- /dev/null +++ b/src/commands/thin_metadata_size.rs @@ -0,0 +1,100 @@ +extern crate clap; + +use clap::{value_t_or_exit, App, Arg}; +use std::ffi::OsString; +use std::process; + +use crate::thin::metadata_size::{metadata_size, ThinMetadataSizeOptions}; +use crate::units::*; + +//------------------------------------------ + +fn parse_args(args: I) -> (ThinMetadataSizeOptions, Units, bool) +where + I: IntoIterator, + T: Into + Clone, +{ + let parser = App::new("thin_metadata_size") + .version(crate::version::tools_version()) + .about("Estimate the size of the metadata device needed for a given configuration.") + // options + .arg( + Arg::with_name("BLOCK_SIZE") + .help("Specify the data block size") + .short("b") + .long("block-size") + .required(true) + .value_name("SECTORS"), + ) + .arg( + Arg::with_name("POOL_SIZE") + .help("Specify the size of pool device") + .short("s") + .long("pool-size") + .required(true) + .value_name("SECTORS"), + ) + .arg( + Arg::with_name("MAX_THINS") + .help("Maximum number of thin devices and snapshots") + .short("m") + .long("max-thins") + .required(true) + .value_name("NUM"), + ) + .arg( + Arg::with_name("UNIT") + .help("Specify the output unit") + .short("u") + .long("unit") + .value_name("UNIT") + .default_value("sector"), + ) + .arg( + Arg::with_name("NUMERIC_ONLY") + .help("Output numeric value only") + .short("n") + .long("numeric-only"), + ); + + let matches = parser.get_matches_from(args); + + // TODO: handle unit suffix + let pool_size = value_t_or_exit!(matches.value_of("POOL_SIZE"), u64); + let block_size = value_t_or_exit!(matches.value_of("BLOCK_SIZE"), u32); + let max_thins = value_t_or_exit!(matches.value_of("MAX_THINS"), u64); + let unit = value_t_or_exit!(matches.value_of("UNIT"), Units); + let numeric_only = matches.is_present("NUMERIC_ONLY"); + + ( + ThinMetadataSizeOptions { + nr_blocks: pool_size / block_size as u64, + max_thins, + }, + unit, + numeric_only, + ) +} + +pub fn run(args: &[std::ffi::OsString]) { + let (opts, unit, numeric_only) = parse_args(args); + + match metadata_size(&opts) { + Ok(size) => { + let size = to_units(size * 512, unit.clone()); + if numeric_only { + println!("{}", size); + } else { + let mut name = unit.to_string(); + name.push('s'); + println!("{} {}", size, name); + } + } + Err(reason) => { + eprintln!("{}", reason); + process::exit(1); + } + } +} + +//------------------------------------------ diff --git a/src/lib.rs b/src/lib.rs index 23fc9c3..ef9cef1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub mod pdata; pub mod report; pub mod shrink; pub mod thin; +pub mod units; pub mod version; pub mod write_batcher; pub mod xml; diff --git a/src/thin/metadata_size.rs b/src/thin/metadata_size.rs new file mode 100644 index 0000000..340ffd1 --- /dev/null +++ b/src/thin/metadata_size.rs @@ -0,0 +1,21 @@ +use anyhow::Result; + +use crate::math::div_up; + +pub struct ThinMetadataSizeOptions { + pub nr_blocks: u64, + pub max_thins: u64, +} + +pub fn metadata_size(opts: &ThinMetadataSizeOptions) -> Result { + const ENTRIES_PER_NODE: u64 = 126; // assumed the mapping leaves are half populated + const BLOCK_SIZE: u64 = 8; // sectors + + // size of all the leaf nodes for data mappings + let mapping_size = div_up(opts.nr_blocks, ENTRIES_PER_NODE) * BLOCK_SIZE; + + // space required by root nodes + let roots_overhead = opts.max_thins * BLOCK_SIZE; + + Ok(mapping_size + roots_overhead) +} diff --git a/src/thin/mod.rs b/src/thin/mod.rs index 197f2c2..71928e7 100644 --- a/src/thin/mod.rs +++ b/src/thin/mod.rs @@ -5,6 +5,7 @@ pub mod dump; pub mod ir; pub mod metadata; pub mod metadata_repair; +pub mod metadata_size; pub mod repair; pub mod restore; pub mod runs; diff --git a/src/units.rs b/src/units.rs new file mode 100644 index 0000000..11a4527 --- /dev/null +++ b/src/units.rs @@ -0,0 +1,104 @@ +use anyhow::anyhow; +use std::str::FromStr; + +//------------------------------------------ + +#[derive(Clone)] +pub enum Units { + Byte, + Sector, + Kilobyte, + Megabyte, + Gigabyte, + Terabyte, + Petabyte, + Exabyte, + Kibibyte, + Mebibyte, + Gibibyte, + Tebibyte, + Pebibyte, + Exbibyte, +} + +impl Units { + fn size_bytes(&self) -> u64 { + match self { + Units::Byte => 1, + Units::Sector => 512, + // base 2 + Units::Kibibyte => 1024, + Units::Mebibyte => 1048576, + Units::Gibibyte => 1073741824, + Units::Tebibyte => 1099511627776, + Units::Pebibyte => 1125899906842624, + Units::Exbibyte => 1152921504606846976, + // base 10 + Units::Kilobyte => 1000, + Units::Megabyte => 1000000, + Units::Gigabyte => 1000000000, + Units::Terabyte => 1000000000000, + Units::Petabyte => 1000000000000000, + Units::Exabyte => 1000000000000000000, + } + } +} + +impl FromStr for Units { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "byte" | "b" => Ok(Units::Byte), + "sector" | "s" => Ok(Units::Sector), + // base 2 + "kibibyte" | "k" => Ok(Units::Kibibyte), + "mibibyte" | "m" => Ok(Units::Mebibyte), + "gibibyte" | "g" => Ok(Units::Gibibyte), + "tebibyte" | "t" => Ok(Units::Tebibyte), + "pebibyte" | "p" => Ok(Units::Pebibyte), + "exbibyte" | "e" => Ok(Units::Exbibyte), + // base 10 + "kilobyte" | "K" => Ok(Units::Kilobyte), + "megabyte" | "M" => Ok(Units::Megabyte), + "gigabyte" | "G" => Ok(Units::Gigabyte), + "terabyte" | "T" => Ok(Units::Terabyte), + "petabyte" | "P" => Ok(Units::Petabyte), + "exabyte" | "E" => Ok(Units::Exabyte), + _ => Err(anyhow!("Invalid unit specifier")), + } + } +} + +impl ToString for Units { + fn to_string(&self) -> String { + String::from(match self { + Units::Byte => "byte", + Units::Sector => "sector", + // base 2 + Units::Kibibyte => "kibibyte", + Units::Mebibyte => "mibibyte", + Units::Gibibyte => "gibibyte", + Units::Tebibyte => "terabyte", + Units::Pebibyte => "pebibyte", + Units::Exbibyte => "exbibyte", + // base 10 + Units::Kilobyte => "kilobyte", + Units::Megabyte => "megabyte", + Units::Gigabyte => "gigabyte", + Units::Terabyte => "terabyte", + Units::Petabyte => "petabyte", + Units::Exabyte => "exabyte", + }) + } +} + +pub fn to_bytes(size: u64, unit: Units) -> u64 { + size * unit.size_bytes() +} + +pub fn to_units(bytes: u64, unit: Units) -> f64 { + bytes as f64 / unit.size_bytes() as f64 +} + +//------------------------------------------