rd_hashd_intf/
args.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2use clap::{App, AppSettings, ArgMatches};
3use serde::{Deserialize, Serialize};
4use std::sync::Mutex;
5
6use super::Params;
7use rd_util::*;
8
9lazy_static::lazy_static! {
10    static ref ARGS_STR: String = {
11        let dfl_args = Args::default();
12        format!(
13            "-t, --testfiles=[DIR]         'Testfiles directory'
14             -s, --size=[SIZE]             'Max memory footprint, affects testfiles size (default: {dfl_size:.2}G)'
15             -f, --file-max=[FRAC]         'Max fraction of page cache, affects testfiles size (default: {dfl_file_max_frac:.2})'
16             -c, --compressibility=[FRAC]  'File and anon data compressibility (default: 0)
17             -p, --params=[FILE]           'Runtime updatable parameters, will be created if non-existent'
18             -r, --report=[FILE]           'Runtime report file, FILE.staging will be used for staging'
19             -l, --log-dir=[PATH]          'Record hash results to the files in PATH'
20             -L, --log-size=[SIZE]         'Maximum log retention (default: {dfl_log_size:.2}G)'
21             -i, --interval=[SECS]         'Summary report interval, 0 to disable (default: {dfl_intv}s)'
22             -R, --rotational=[BOOL]       'Force rotational detection to either true or false'
23             -a, --args=[FILE]             'Load base command line arguments from FILE'
24                 --keep-cache              'Don't drop page cache for testfiles on startup'
25                 --clear-testfiles         'Clear testfiles before preparing them'
26                 --prepare-config          'Prepare config files and exit'
27                 --prepare                 'Prepare config files and testfiles and exit'
28                 --bench                   'Benchmark and record results in args and params file'
29                 --bench-cpu-single        'Benchmark hash/chunk sizes instead of taking from params'
30                 --bench-cpu               'Benchmark cpu, implied by --bench'
31                 --bench-mem               'Benchmark memory, implied by --bench'
32                 --bench-test              'Use quick pseudo bench for testing'
33                 --bench-grain=[FACTOR]    'Adjust bench granularity'
34                 --bench-fake-cpu-load     'Fake CPU load while benchmarking memory'
35                 --bench-hash-size=[SIZE]  'Use the specified hash size'
36                 --bench-chunk-pages=[PAGES] 'Use the specified chunk pages'
37                 --bench-rps-max=[RPS]     'Use the specified RPS max'
38                 --bench-log-bps=[BPS]     'Log write bps at max rps (default: {dfl_log_bps:.2}M)'
39                 --bench-file-frac=[FRAC]  'Page cache ratio compared to anon memory (default: {dfl_file_frac:.2})'
40                 --bench-preload-cache=[SIZE] 'Prepopulate page cache with testfiles (default: {dfl_preload_cache:.2}G)'
41                 --total-memory=[SIZE]     'Override total memory detection'
42                 --total-swap=[SIZE]       'Override total swap space detection'
43                 --nr-cpus=[NR]            'Override cpu count detection'
44             -v...                         'Sets the level of verbosity'
45
46                 --logfile=[FILE]          'Specify file to dump trace logs'",
47            dfl_size=to_gb(dfl_args.size),
48            dfl_file_max_frac=dfl_args.file_max_frac,
49            dfl_log_size=to_gb(dfl_args.log_size),
50            dfl_log_bps=to_mb(dfl_args.bench_log_bps),
51            dfl_preload_cache=to_mb(dfl_args.bench_preload_cache_size()),
52            dfl_file_frac=Params::default().file_frac,
53            dfl_intv=dfl_args.interval)
54    };
55
56    static ref HELP_BODY: Mutex<&'static str> = Mutex::new("");
57}
58
59const ARGS_DOC: &str = "\
60//
61// rd-hashd command line arguments
62//
63// This file provides the base values for a subset of command line arguments.
64// They can be overridden from command line.
65//
66";
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(default)]
70pub struct Args {
71    pub testfiles: Option<String>,
72    pub size: u64,
73    pub file_max_frac: f64,
74    pub compressibility: f64,
75    pub params: Option<String>,
76    pub report: Option<String>,
77    pub log_dir: Option<String>,
78    pub log_size: u64,
79    pub interval: u32,
80    pub rotational: Option<bool>,
81
82    #[serde(skip)]
83    pub keep_cache: bool,
84    #[serde(skip)]
85    pub clear_testfiles: bool,
86    #[serde(skip)]
87    pub prepare_testfiles: bool,
88    #[serde(skip)]
89    pub prepare_and_exit: bool,
90    #[serde(skip)]
91    pub bench_cpu_single: bool,
92    #[serde(skip)]
93    pub bench_cpu: bool,
94    #[serde(skip)]
95    pub bench_mem: bool,
96    #[serde(skip)]
97    pub bench_test: bool,
98    #[serde(skip)]
99    pub bench_grain: f64,
100    #[serde(skip)]
101    pub bench_fake_cpu_load: bool,
102    #[serde(skip)]
103    pub bench_hash_size: Option<usize>,
104    #[serde(skip)]
105    pub bench_chunk_pages: Option<usize>,
106    #[serde(skip)]
107    pub bench_rps_max: Option<u32>,
108    #[serde(skip)]
109    pub bench_log_bps: u64,
110    #[serde(skip)]
111    pub bench_file_frac: Option<f64>,
112    #[serde(skip)]
113    bench_preload_cache: Option<usize>,
114    #[serde(skip)]
115    pub verbosity: u32,
116    #[serde(skip)]
117    pub logfile: Option<String>,
118}
119
120impl Args {
121    pub const DFL_SIZE_MULT: u64 = 4;
122    pub const DFL_FILE_MAX_FRAC: f64 = 0.25;
123
124    pub fn set_help_body(help: &'static str) {
125        *HELP_BODY.lock().unwrap() = help;
126    }
127
128    pub fn with_mem_size(mem_size: usize) -> Self {
129        let dfl_params = Params::default();
130        Self {
131            testfiles: None,
132            size: Self::DFL_SIZE_MULT * mem_size as u64,
133            file_max_frac: Self::DFL_FILE_MAX_FRAC,
134            compressibility: 0.0,
135            params: None,
136            report: None,
137            log_dir: None,
138            log_size: mem_size as u64 / 2,
139            interval: 10,
140            rotational: None,
141            clear_testfiles: false,
142            keep_cache: false,
143            bench_preload_cache: None,
144            prepare_testfiles: true,
145            prepare_and_exit: false,
146            bench_cpu_single: false,
147            bench_cpu: false,
148            bench_mem: false,
149            bench_test: false,
150            bench_grain: 1.0,
151            bench_fake_cpu_load: false,
152            bench_hash_size: None,
153            bench_chunk_pages: None,
154            bench_rps_max: None,
155            bench_log_bps: dfl_params.log_bps,
156            bench_file_frac: None,
157            verbosity: 0,
158            logfile: None,
159        }
160    }
161
162    pub fn bench_preload_cache_size(&self) -> usize {
163        match self.bench_preload_cache {
164            Some(v) => v,
165            None => {
166                let mem_size = self.size / Self::DFL_SIZE_MULT;
167                let file_frac = match self.bench_file_frac {
168                    Some(v) => v,
169                    None => Params::default().file_frac,
170                };
171                (mem_size as f64 * (file_frac * 2.0).min(1.0)) as usize
172            }
173        }
174    }
175
176    pub fn file_max_size(&self) -> u64 {
177        (self.size as f64 * self.file_max_frac).ceil() as u64
178    }
179}
180
181impl Default for Args {
182    fn default() -> Self {
183        Self::with_mem_size(total_memory())
184    }
185}
186
187impl JsonLoad for Args {}
188
189impl JsonSave for Args {
190    fn preamble() -> Option<String> {
191        Some(ARGS_DOC.to_string())
192    }
193}
194
195impl JsonArgs for Args {
196    fn match_cmdline() -> ArgMatches<'static> {
197        App::new("rd-hashd")
198            .version((*super::FULL_VERSION).as_str())
199            .author(clap::crate_authors!("\n"))
200            .about(*HELP_BODY.lock().unwrap())
201            .args_from_usage(&ARGS_STR)
202            .setting(AppSettings::UnifiedHelpMessage)
203            .setting(AppSettings::DeriveDisplayOrder)
204            .get_matches()
205    }
206
207    fn verbosity(matches: &ArgMatches) -> u32 {
208        matches.occurrences_of("v") as u32
209    }
210
211    fn log_file(matches: &clap::ArgMatches) -> String {
212        match matches.value_of("logfile") {
213            Some(v) => v.to_string(),
214            None => "".to_string(),
215        }
216    }
217
218    fn system_configuration_overrides(
219        matches: &ArgMatches,
220    ) -> (Option<usize>, Option<usize>, Option<usize>) {
221        (
222            matches
223                .value_of("total-memory")
224                .map(|x| x.parse::<usize>().unwrap()),
225            matches
226                .value_of("total-swap")
227                .map(|x| x.parse::<usize>().unwrap()),
228            matches
229                .value_of("nr-cpus")
230                .map(|x| x.parse::<usize>().unwrap()),
231        )
232    }
233
234    fn process_cmdline(&mut self, matches: &ArgMatches) -> bool {
235        let dfl: Args = Default::default();
236        let mut updated_base = false;
237
238        if let Some(v) = matches.value_of("testfiles") {
239            self.testfiles = if v.len() > 0 {
240                Some(v.to_string())
241            } else {
242                None
243            };
244            updated_base = true;
245        }
246        if let Some(v) = matches.value_of("size") {
247            self.size = if v.len() > 0 {
248                v.parse::<u64>().unwrap()
249            } else {
250                dfl.size
251            };
252            updated_base = true;
253        }
254        if let Some(v) = matches.value_of("file-max") {
255            self.file_max_frac = if v.len() > 0 {
256                v.parse::<f64>().unwrap().max(0.0).min(1.0)
257            } else {
258                dfl.file_max_frac
259            };
260            updated_base = true;
261        }
262        if let Some(v) = matches.value_of("compressibility") {
263            self.compressibility = if v.len() > 0 {
264                v.parse::<f64>().unwrap().max(0.0).min(1.0)
265            } else {
266                0.0
267            };
268            updated_base = true;
269        }
270        if let Some(v) = matches.value_of("params") {
271            self.params = if v.len() > 0 {
272                Some(v.to_string())
273            } else {
274                None
275            };
276            updated_base = true;
277        }
278        if let Some(v) = matches.value_of("report") {
279            self.report = if v.len() > 0 {
280                Some(v.to_string())
281            } else {
282                None
283            };
284            updated_base = true;
285        }
286        if let Some(v) = matches.value_of("log-dir") {
287            self.log_dir = if v.len() > 0 {
288                Some(v.to_string())
289            } else {
290                None
291            };
292            updated_base = true;
293        }
294        if let Some(v) = matches.value_of("log-size") {
295            self.log_size = if v.len() > 0 {
296                v.parse::<u64>().unwrap()
297            } else {
298                dfl.log_size
299            };
300            updated_base = true;
301        }
302        if let Some(v) = matches.value_of("interval") {
303            self.interval = if v.len() > 0 {
304                v.parse::<u32>().unwrap()
305            } else {
306                dfl.interval
307            };
308            updated_base = true;
309        }
310        if let Some(v) = matches.value_of("rotational") {
311            self.rotational = if v.len() > 0 {
312                Some(v.parse::<bool>().unwrap())
313            } else {
314                None
315            };
316            updated_base = true;
317        }
318
319        self.keep_cache = matches.is_present("keep-cache");
320        if let Some(v) = matches.value_of("bench-preload-cache") {
321            self.bench_preload_cache = match v.parse::<usize>().unwrap() {
322                0 => None,
323                v => Some(v),
324            };
325        }
326        self.clear_testfiles = matches.is_present("clear-testfiles");
327
328        let prep_cfg = matches.is_present("prepare-config");
329        let prep_all = matches.is_present("prepare");
330        if prep_cfg || prep_all {
331            self.prepare_testfiles = prep_all;
332            self.prepare_and_exit = true;
333        }
334
335        if !self.prepare_and_exit {
336            self.bench_cpu_single = matches.is_present("bench-cpu-single");
337            self.bench_cpu = matches.is_present("bench-cpu");
338            self.bench_mem = matches.is_present("bench-mem");
339            self.bench_test = matches.is_present("bench-test");
340
341            if matches.is_present("bench") {
342                self.bench_cpu = true;
343                self.bench_mem = true;
344            }
345
346            if self.bench_cpu || self.bench_mem {
347                self.prepare_testfiles = false;
348            }
349        }
350
351        if let Some(v) = matches.value_of("bench-grain") {
352            self.bench_grain = v.parse::<f64>().unwrap();
353            assert!(self.bench_grain > 0.0);
354        }
355
356        self.bench_fake_cpu_load = matches.is_present("bench-fake-cpu-load");
357
358        if let Some(v) = matches.value_of("bench-hash-size") {
359            self.bench_hash_size = match v.parse::<usize>().unwrap() {
360                0 => None,
361                v => Some(v),
362            };
363        }
364        if let Some(v) = matches.value_of("bench-chunk-pages") {
365            self.bench_chunk_pages = match v.parse::<usize>().unwrap() {
366                0 => None,
367                v => Some(v),
368            };
369        }
370        if let Some(v) = matches.value_of("bench-rps-max") {
371            self.bench_rps_max = match v.parse::<u32>().unwrap() {
372                0 => None,
373                v => Some(v),
374            };
375        }
376        if let Some(v) = matches.value_of("bench-log-bps") {
377            self.bench_log_bps = v.parse::<u64>().unwrap();
378        }
379        if let Some(v) = matches.value_of("bench-file-frac") {
380            self.bench_file_frac = {
381                let v = v.parse::<f64>().unwrap();
382                if v == 0.0 {
383                    None
384                } else if v > 0.0 {
385                    Some(v)
386                } else {
387                    panic!("negative bench-file-frac specified");
388                }
389            };
390        }
391
392        self.verbosity = Self::verbosity(matches);
393        self.logfile = matches.value_of("logfile").map(|x| x.to_string());
394
395        updated_base
396    }
397}