1use 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}