rd-agent-intf/src/args.rs (260 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. use anyhow::{bail, Result}; use serde::{Deserialize, Serialize}; use std::fmt::Write; use std::sync::Mutex; use rd_util::*; lazy_static::lazy_static! { static ref ARGS_STR: String = format!( "-d, --dir=[TOPDIR] 'Top-level dir for operation and scratch files (default: {dfl_dir})' -s, --scratch=[DIR] 'Scratch dir for workloads to use (default: $TOPDIR/scratch)' -D, --dev=[NAME] 'Override storage device autodetection (e.g. sda, nvme0n1)' -r, --rep-retention=[SECS] '1s report retention in seconds (default: {dfl_rep_ret:.1}h)' -R, --rep-1min-retention=[SECS] '1m report retention in seconds (default: {dfl_rep_1m_ret:.1}h)' --systemd-timeout=[SECS] 'Systemd timeout (default: {dfl_systemd_timeout})' --passive=[SELS] 'Avoid system config changes (SELS=ALL/all/cpu/mem/io/fs/oomd/none)' -a, --args=[FILE] 'Load base command line arguments from FILE' --no-iolat 'Disable bpf-based io latency stat monitoring' --force 'Ignore startup check results and proceed' --force-running 'Ignore bench requirements and enter Running state' --prepare 'Prepare the files and directories and exit' --linux-tar=[FILE] 'Path to linux source tarball for compile sideload (__SKIP__ to skip)' --bench-file=[FILE] 'Bench file name override' --reset 'Reset all states except for bench results, linux.tar and testfiles' --keep-reports 'Don't delete expired report files, also affects --reset' --bypass 'Skip startup and periodic health checks' -v... 'Sets the level of verbosity'", dfl_dir = Args::default().dir, dfl_rep_ret = Args::default().rep_retention as f64 / 3600.0, dfl_rep_1m_ret = Args::default().rep_1min_retention as f64 / 3600.0, dfl_systemd_timeout = format_duration(Args::default().systemd_timeout), ); static ref BANDIT_MEM_HOG_USAGE: String = format!( "-w, --wbps=[BPS] 'Write BPS (memory growth rate, default 0)' -r, --rbps=[BPS] 'Read BPS (re-read rate, default 0)' -R, --readers=[NR] 'Number of readers (default: 1)' -d, --debt=[DUR] 'Maximum debt accumulation (default, 10s)' -c, --compressibility=[FRAC] 'Content compressibility (default: 0) -p, --report=[PATH] 'Report file path'" ); static ref HELP_BODY: Mutex<&'static str> = Mutex::new(""); } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EnforceConfig { pub crit_mem_prot: bool, pub cpu: bool, pub mem: bool, pub io: bool, pub fs: bool, pub oomd: bool, } impl Default for EnforceConfig { fn default() -> Self { Self { crit_mem_prot: true, cpu: true, mem: true, io: true, fs: true, oomd: true, } } } impl EnforceConfig { pub fn set_all_passive(&mut self) -> &mut Self { *self = Self { crit_mem_prot: false, cpu: false, mem: false, io: false, fs: false, oomd: false, }; self } pub fn set_crit_mem_prot_only(&mut self) -> &mut Self { self.set_all_passive().crit_mem_prot = true; self } pub fn parse_and_merge(&mut self, input: &str) -> Result<()> { for passive in input.split(&[',', '/'][..]) { match passive { "ALL" => { self.set_all_passive(); } "all" => { self.set_crit_mem_prot_only(); } "cpu" => self.cpu = false, "mem" => self.mem = false, "io" => self.io = false, "fs" => self.fs = false, "oomd" => self.oomd = false, "none" => *self = Default::default(), "" => {} v => bail!("Unknown --passive value {:?}", &v), } } Ok(()) } pub fn to_passive_string(&self) -> String { let mut buf = String::new(); if !self.crit_mem_prot { write!(buf, "ALL").unwrap(); } else if !self.cpu && !self.mem && !self.io && !self.fs && !self.oomd { write!(buf, "all").unwrap(); } else { if !self.cpu { write!(buf, "cpu/").unwrap(); } if !self.mem { write!(buf, "mem/").unwrap(); } if !self.io { write!(buf, "io/").unwrap(); } if !self.fs { write!(buf, "fs/").unwrap(); } if !self.oomd { write!(buf, "oomd/").unwrap(); } buf.pop(); } buf } pub fn all(&self) -> bool { self.crit_mem_prot && self.cpu && self.mem && self.io && self.fs && self.oomd } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BanditMemHogArgs { pub wbps: String, pub rbps: String, pub max_debt: f64, pub nr_readers: u32, pub comp: f64, pub report: Option<String>, } impl Default for BanditMemHogArgs { fn default() -> Self { Self { wbps: "0".to_owned(), rbps: "0".to_owned(), max_debt: 10.0, nr_readers: 1, comp: 0.0, report: None, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Bandit { MemHog(BanditMemHogArgs), } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct Args { pub dir: String, pub scratch: Option<String>, pub dev: Option<String>, pub rep_retention: u64, pub rep_1min_retention: u64, pub systemd_timeout: f64, pub enforce: EnforceConfig, #[serde(skip)] pub no_iolat: bool, #[serde(skip)] pub force: bool, #[serde(skip)] pub force_running: bool, #[serde(skip)] pub prepare: bool, #[serde(skip)] pub linux_tar: Option<String>, #[serde(skip)] pub bench_file: Option<String>, #[serde(skip)] pub reset: bool, #[serde(skip)] pub keep_reports: bool, #[serde(skip)] pub bypass: bool, #[serde(skip)] pub verbosity: u32, pub bandit: Option<Bandit>, } impl Default for Args { fn default() -> Self { Self { dir: "/var/lib/resctl-demo".into(), scratch: None, dev: None, rep_retention: 3600, rep_1min_retention: 24 * 3600, systemd_timeout: systemd::SYSTEMD_DFL_TIMEOUT, enforce: Default::default(), no_iolat: false, force: false, force_running: false, prepare: false, linux_tar: None, bench_file: None, reset: false, keep_reports: false, bypass: false, verbosity: 0, bandit: None, } } } impl JsonLoad for Args {} impl JsonSave for Args {} impl Args { pub fn set_help_body(help: &'static str) { *HELP_BODY.lock().unwrap() = help; } fn process_bandit(&mut self, bandit: &str, subm: &clap::ArgMatches) -> bool { let mut updated_base = false; match bandit { "bandit-mem-hog" => { let mut args = match self.bandit.as_ref() { Some(Bandit::MemHog(args)) => args.clone(), None => Default::default(), }; if let Some(v) = subm.value_of("wbps") { args.wbps = v.to_owned(); updated_base = true; } if let Some(v) = subm.value_of("rbps") { args.rbps = v.to_owned(); updated_base = true; } if let Some(v) = subm.value_of("readers") { args.nr_readers = v.parse::<u32>().expect("failed to parse \"readers\""); updated_base = true; } if let Some(v) = subm.value_of("debt") { args.max_debt = parse_duration(v).expect("failed to parse \"debt\""); updated_base = true; } if let Some(v) = subm.value_of("compressibility") { args.comp = parse_frac(v).unwrap(); updated_base = true; } if let Some(v) = subm.value_of("report") { args.report = if v.len() == 0 { None } else { Some(v.to_owned()) }; updated_base = true; } self.bandit = Some(Bandit::MemHog(args)); } _ => {} } updated_base } } impl JsonArgs for Args { fn match_cmdline() -> clap::ArgMatches<'static> { clap::App::new("rd-agent") .version((*super::FULL_VERSION).as_str()) .author(clap::crate_authors!("\n")) .about(*HELP_BODY.lock().unwrap()) .args_from_usage(&ARGS_STR) .subcommand( clap::SubCommand::with_name("bandit-mem-hog") .about("Bandit mode - keep bloating up memory") .args_from_usage(&BANDIT_MEM_HOG_USAGE), ) .setting(clap::AppSettings::UnifiedHelpMessage) .setting(clap::AppSettings::DeriveDisplayOrder) .get_matches() } fn verbosity(matches: &clap::ArgMatches) -> u32 { matches.occurrences_of("v") as u32 } fn process_cmdline(&mut self, matches: &clap::ArgMatches) -> bool { let dfl = Args::default(); let mut updated_base = false; if let Some(v) = matches.value_of("dir") { self.dir = if v.len() > 0 { v.to_string() } else { dfl.dir.clone() }; updated_base = true; } if let Some(v) = matches.value_of("scratch") { self.scratch = if v.len() > 0 { Some(v.to_string()) } else { None }; updated_base = true; } if let Some(v) = matches.value_of("dev") { self.dev = if v.len() > 0 { Some(v.to_string()) } else { None }; updated_base = true; } if let Some(v) = matches.value_of("rep-retention") { self.rep_retention = if v.len() > 0 { v.parse::<u64>().unwrap().max(0) } else { dfl.rep_retention }; updated_base = true; } if let Some(v) = matches.value_of("rep-1min-retention") { self.rep_1min_retention = if v.len() > 0 { v.parse::<u64>().unwrap().max(0) } else { dfl.rep_1min_retention }; updated_base = true; } if let Some(v) = matches.value_of("systemd-timeout") { self.systemd_timeout = if v.len() > 0 { parse_duration(v).unwrap().max(1.0) } else { dfl.systemd_timeout }; updated_base = true; } self.no_iolat = matches.is_present("no-iolat"); self.force = matches.is_present("force"); self.force_running = matches.is_present("force-running"); self.prepare = matches.is_present("prepare"); self.linux_tar = matches.value_of("linux-tar").map(|x| x.to_string()); self.bench_file = matches.value_of("bench-file").map(|x| x.to_string()); self.reset = matches.is_present("reset"); self.keep_reports = matches.is_present("keep-reports"); self.verbosity = Self::verbosity(&matches); self.bypass = matches.is_present("bypass"); match matches.value_of("passive") { Some(passives) => self.enforce.parse_and_merge(passives).unwrap(), None => self.enforce = Default::default(), } if let (bandit, Some(subm)) = matches.subcommand() { updated_base |= self.process_bandit(bandit, subm); } updated_base } }