rd_agent_intf/
args.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2use anyhow::{bail, Result};
3use serde::{Deserialize, Serialize};
4use std::fmt::Write;
5use std::sync::Mutex;
6
7use rd_util::*;
8
9lazy_static::lazy_static! {
10    static ref ARGS_STR: String = format!(
11        "-d, --dir=[TOPDIR]     'Top-level dir for operation and scratch files (default: {dfl_dir})'
12         -s, --scratch=[DIR]    'Scratch dir for workloads to use (default: $TOPDIR/scratch)'
13         -D, --dev=[NAME]       'Override storage device autodetection (e.g. sda, nvme0n1)'
14         -r, --rep-retention=[SECS]      '1s report retention in seconds (default: {dfl_rep_ret:.1}h)'
15         -R, --rep-1min-retention=[SECS] '1m report retention in seconds (default: {dfl_rep_1m_ret:.1}h)'
16             --systemd-timeout=[SECS] 'Systemd timeout (default: {dfl_systemd_timeout})'
17             --passive=[SELS]   'Avoid system config changes (SELS=ALL/all/cpu/mem/io/fs/oomd/none)'
18         -a, --args=[FILE]      'Load base command line arguments from FILE'
19             --no-iolat         'Disable bpf-based io latency stat monitoring'
20             --force            'Ignore startup check results and proceed'
21             --force-running    'Ignore bench requirements and enter Running state'
22             --prepare          'Prepare the files and directories and exit'
23             --linux-tar=[FILE] 'Path to linux source tarball for compile sideload (__SKIP__ to skip)'
24             --bench-file=[FILE] 'Bench file name override'
25             --reset            'Reset all states except for bench results, linux.tar and testfiles'
26             --keep-reports     'Don't delete expired report files, also affects --reset'
27             --bypass           'Skip startup and periodic health checks'
28         -v...                  'Sets the level of verbosity'
29             --logfile=[FILE]   'Specify file to dump logs'",
30        dfl_dir = Args::default().dir,
31        dfl_rep_ret = Args::default().rep_retention as f64 / 3600.0,
32        dfl_rep_1m_ret = Args::default().rep_1min_retention as f64 / 3600.0,
33        dfl_systemd_timeout = format_duration(Args::default().systemd_timeout),
34    );
35
36    static ref BANDIT_MEM_HOG_USAGE: String = format!(
37        "-w, --wbps=[BPS]             'Write BPS (memory growth rate, default 0)'
38         -r, --rbps=[BPS]             'Read BPS (re-read rate, default 0)'
39         -R, --readers=[NR]           'Number of readers (default: 1)'
40         -d, --debt=[DUR]             'Maximum debt accumulation (default, 10s)'
41         -c, --compressibility=[FRAC] 'Content compressibility (default: 0)
42         -p, --report=[PATH]          'Report file path'"
43    );
44
45    static ref HELP_BODY: Mutex<&'static str> = Mutex::new("");
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct EnforceConfig {
50    pub crit_mem_prot: bool,
51    pub cpu: bool,
52    pub mem: bool,
53    pub io: bool,
54    pub fs: bool,
55    pub oomd: bool,
56}
57
58impl Default for EnforceConfig {
59    fn default() -> Self {
60        Self {
61            crit_mem_prot: true,
62            cpu: true,
63            mem: true,
64            io: true,
65            fs: true,
66            oomd: true,
67        }
68    }
69}
70
71impl EnforceConfig {
72    pub fn set_all_passive(&mut self) -> &mut Self {
73        *self = Self {
74            crit_mem_prot: false,
75            cpu: false,
76            mem: false,
77            io: false,
78            fs: false,
79            oomd: false,
80        };
81        self
82    }
83
84    pub fn set_crit_mem_prot_only(&mut self) -> &mut Self {
85        self.set_all_passive().crit_mem_prot = true;
86        self
87    }
88
89    pub fn parse_and_merge(&mut self, input: &str) -> Result<()> {
90        for passive in input.split(&[',', '/'][..]) {
91            match passive {
92                "ALL" => {
93                    self.set_all_passive();
94                }
95                "all" => {
96                    self.set_crit_mem_prot_only();
97                }
98                "cpu" => self.cpu = false,
99                "mem" => self.mem = false,
100                "io" => self.io = false,
101                "fs" => self.fs = false,
102                "oomd" => self.oomd = false,
103                "none" => *self = Default::default(),
104                "" => {}
105                v => bail!("Unknown --passive value {:?}", &v),
106            }
107        }
108        Ok(())
109    }
110
111    pub fn to_passive_string(&self) -> String {
112        let mut buf = String::new();
113        if !self.crit_mem_prot {
114            write!(buf, "ALL").unwrap();
115        } else if !self.cpu && !self.mem && !self.io && !self.fs && !self.oomd {
116            write!(buf, "all").unwrap();
117        } else {
118            if !self.cpu {
119                write!(buf, "cpu/").unwrap();
120            }
121            if !self.mem {
122                write!(buf, "mem/").unwrap();
123            }
124            if !self.io {
125                write!(buf, "io/").unwrap();
126            }
127            if !self.fs {
128                write!(buf, "fs/").unwrap();
129            }
130            if !self.oomd {
131                write!(buf, "oomd/").unwrap();
132            }
133            buf.pop();
134        }
135        buf
136    }
137
138    pub fn all(&self) -> bool {
139        self.crit_mem_prot && self.cpu && self.mem && self.io && self.fs && self.oomd
140    }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct BanditMemHogArgs {
145    pub wbps: String,
146    pub rbps: String,
147    pub max_debt: f64,
148    pub nr_readers: u32,
149    pub comp: f64,
150    pub report: Option<String>,
151}
152
153impl Default for BanditMemHogArgs {
154    fn default() -> Self {
155        Self {
156            wbps: "0".to_owned(),
157            rbps: "0".to_owned(),
158            max_debt: 10.0,
159            nr_readers: 1,
160            comp: 0.0,
161            report: None,
162        }
163    }
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub enum Bandit {
168    MemHog(BanditMemHogArgs),
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
172#[serde(default)]
173pub struct Args {
174    pub dir: String,
175    pub scratch: Option<String>,
176    pub dev: Option<String>,
177    pub rep_retention: u64,
178    pub rep_1min_retention: u64,
179    pub systemd_timeout: f64,
180    pub enforce: EnforceConfig,
181
182    #[serde(skip)]
183    pub no_iolat: bool,
184    #[serde(skip)]
185    pub force: bool,
186    #[serde(skip)]
187    pub force_running: bool,
188    #[serde(skip)]
189    pub prepare: bool,
190    #[serde(skip)]
191    pub linux_tar: Option<String>,
192    #[serde(skip)]
193    pub bench_file: Option<String>,
194    #[serde(skip)]
195    pub reset: bool,
196    #[serde(skip)]
197    pub keep_reports: bool,
198    #[serde(skip)]
199    pub bypass: bool,
200    #[serde(skip)]
201    pub verbosity: u32,
202    #[serde(skip)]
203    pub logfile: Option<String>,
204
205    pub bandit: Option<Bandit>,
206}
207
208impl Default for Args {
209    fn default() -> Self {
210        Self {
211            dir: "/var/lib/resctl-demo".into(),
212            scratch: None,
213            dev: None,
214            rep_retention: 3600,
215            rep_1min_retention: 24 * 3600,
216            systemd_timeout: systemd::SYSTEMD_DFL_TIMEOUT,
217            enforce: Default::default(),
218            no_iolat: false,
219            force: false,
220            force_running: false,
221            prepare: false,
222            linux_tar: None,
223            bench_file: None,
224            reset: false,
225            keep_reports: false,
226            bypass: false,
227            verbosity: 0,
228            logfile: None,
229            bandit: None,
230        }
231    }
232}
233
234impl JsonLoad for Args {}
235impl JsonSave for Args {}
236
237impl Args {
238    pub fn set_help_body(help: &'static str) {
239        *HELP_BODY.lock().unwrap() = help;
240    }
241
242    fn process_bandit(&mut self, bandit: &str, subm: &clap::ArgMatches) -> bool {
243        let mut updated_base = false;
244        match bandit {
245            "bandit-mem-hog" => {
246                let mut args = match self.bandit.as_ref() {
247                    Some(Bandit::MemHog(args)) => args.clone(),
248                    None => Default::default(),
249                };
250                if let Some(v) = subm.value_of("wbps") {
251                    args.wbps = v.to_owned();
252                    updated_base = true;
253                }
254                if let Some(v) = subm.value_of("rbps") {
255                    args.rbps = v.to_owned();
256                    updated_base = true;
257                }
258                if let Some(v) = subm.value_of("readers") {
259                    args.nr_readers = v.parse::<u32>().expect("failed to parse \"readers\"");
260                    updated_base = true;
261                }
262                if let Some(v) = subm.value_of("debt") {
263                    args.max_debt = parse_duration(v).expect("failed to parse \"debt\"");
264                    updated_base = true;
265                }
266                if let Some(v) = subm.value_of("compressibility") {
267                    args.comp = parse_frac(v).unwrap();
268                    updated_base = true;
269                }
270                if let Some(v) = subm.value_of("report") {
271                    args.report = if v.len() == 0 {
272                        None
273                    } else {
274                        Some(v.to_owned())
275                    };
276                    updated_base = true;
277                }
278                self.bandit = Some(Bandit::MemHog(args));
279            }
280            _ => {}
281        }
282        updated_base
283    }
284}
285
286impl JsonArgs for Args {
287    fn match_cmdline() -> clap::ArgMatches<'static> {
288        clap::App::new("rd-agent")
289            .version((*super::FULL_VERSION).as_str())
290            .author(clap::crate_authors!("\n"))
291            .about(*HELP_BODY.lock().unwrap())
292            .args_from_usage(&ARGS_STR)
293            .subcommand(
294                clap::SubCommand::with_name("bandit-mem-hog")
295                    .about("Bandit mode - keep bloating up memory")
296                    .args_from_usage(&BANDIT_MEM_HOG_USAGE),
297            )
298            .setting(clap::AppSettings::UnifiedHelpMessage)
299            .setting(clap::AppSettings::DeriveDisplayOrder)
300            .get_matches()
301    }
302
303    fn verbosity(matches: &clap::ArgMatches) -> u32 {
304        matches.occurrences_of("v") as u32
305    }
306
307    fn log_file(matches: &clap::ArgMatches) -> String {
308        match matches.value_of("logfile") {
309            Some(v) => v.to_string(),
310            None => "".to_string(),
311        }
312    }
313
314    fn process_cmdline(&mut self, matches: &clap::ArgMatches) -> bool {
315        let dfl = Args::default();
316        let mut updated_base = false;
317
318        if let Some(v) = matches.value_of("dir") {
319            self.dir = if v.len() > 0 {
320                v.to_string()
321            } else {
322                dfl.dir.clone()
323            };
324            updated_base = true;
325        }
326        if let Some(v) = matches.value_of("scratch") {
327            self.scratch = if v.len() > 0 {
328                Some(v.to_string())
329            } else {
330                None
331            };
332            updated_base = true;
333        }
334        if let Some(v) = matches.value_of("dev") {
335            self.dev = if v.len() > 0 {
336                Some(v.to_string())
337            } else {
338                None
339            };
340            updated_base = true;
341        }
342
343        if let Some(v) = matches.value_of("rep-retention") {
344            self.rep_retention = if v.len() > 0 {
345                v.parse::<u64>().unwrap().max(0)
346            } else {
347                dfl.rep_retention
348            };
349            updated_base = true;
350        }
351
352        if let Some(v) = matches.value_of("rep-1min-retention") {
353            self.rep_1min_retention = if v.len() > 0 {
354                v.parse::<u64>().unwrap().max(0)
355            } else {
356                dfl.rep_1min_retention
357            };
358            updated_base = true;
359        }
360
361        if let Some(v) = matches.value_of("systemd-timeout") {
362            self.systemd_timeout = if v.len() > 0 {
363                parse_duration(v).unwrap().max(1.0)
364            } else {
365                dfl.systemd_timeout
366            };
367            updated_base = true;
368        }
369
370        self.no_iolat = matches.is_present("no-iolat");
371        self.force = matches.is_present("force");
372        self.force_running = matches.is_present("force-running");
373        self.prepare = matches.is_present("prepare");
374        self.linux_tar = matches.value_of("linux-tar").map(|x| x.to_string());
375        self.bench_file = matches.value_of("bench-file").map(|x| x.to_string());
376        self.reset = matches.is_present("reset");
377        self.keep_reports = matches.is_present("keep-reports");
378        self.verbosity = Self::verbosity(&matches);
379        self.logfile = matches.value_of("logfile").map(|x| x.to_string());
380        self.bypass = matches.is_present("bypass");
381
382        match matches.value_of("passive") {
383            Some(passives) => self.enforce.parse_and_merge(passives).unwrap(),
384            None => self.enforce = Default::default(),
385        }
386
387        if let (bandit, Some(subm)) = matches.subcommand() {
388            updated_base |= self.process_bandit(bandit, subm);
389        }
390
391        updated_base
392    }
393}