blob: 03702aecf92c1a9e532b27d1d1c1fed8b3518fef [file] [log] [blame]
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +03001#!/usr/bin/env python3
2
3import re
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +03004import os
5import sys
6import copy
7import os.path
8import argparse
9import itertools
koder aka kdanilov108ac362017-01-19 20:17:16 +020010from typing import Optional, Iterator, Union, Dict, Iterable, List, TypeVar, Callable, Tuple, NamedTuple, Any, cast
koder aka kdanilov70227062016-11-26 23:23:21 +020011from collections import OrderedDict
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030012
13
koder aka kdanilovf2865172016-12-30 03:35:11 +020014from ...result_classes import TestJobConfig
koder aka kdanilov108ac362017-01-19 20:17:16 +020015from ...utils import sec_to_str, ssize2b, b2ssize, flatmap
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030016
17
18SECTION = 0
19SETTING = 1
20INCLUDE = 2
21
22
koder aka kdanilov70227062016-11-26 23:23:21 +020023Var = NamedTuple('Var', [('name', str)])
24CfgLine = NamedTuple('CfgLine',
25 [('fname', str),
26 ('lineno', int),
27 ('oline', str),
28 ('tp', int),
29 ('name', str),
30 ('val', Any)])
koder aka kdanilov108ac362017-01-19 20:17:16 +020031FioTestSumm = NamedTuple("FioTestSumm",
32 [("oper", str),
33 ("sync_mode", str),
34 ("bsize", int),
35 ("qd", int),
36 ("thcount", int),
37 ("write_perc", Optional[int])])
koder aka kdanilov70227062016-11-26 23:23:21 +020038
koder aka kdanilov108ac362017-01-19 20:17:16 +020039
40def is_fio_opt_true(vl: Union[str, int]) -> bool:
41 return str(vl).lower() in ['1', 'true', 't', 'yes', 'y']
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030042
43
koder aka kdanilovf2865172016-12-30 03:35:11 +020044class FioJobConfig(TestJobConfig):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030045
koder aka kdanilov108ac362017-01-19 20:17:16 +020046 ds2mode = {(True, True): 'x',
47 (True, False): 's',
48 (False, True): 'd',
49 (False, False): 'a'}
50
51 sync2long = {'x': "sync direct",
52 's': "sync",
53 'd': "direct",
54 'a': "buffered"}
55
56 op_type2short = {"randread": "rr",
57 "randwrite": "rw",
58 "read": "sr",
59 "write": "sw",
60 "randrw": "rx"}
61
62 def __init__(self, name: str, idx: int) -> None:
63 TestJobConfig.__init__(self, idx)
64 self.name = name
65 self._sync_mode = None # type: Optional[str]
66 self._ctuple = None # type: Optional[FioTestSumm]
67 self._ctuple_no_qd = None # type: Optional[FioTestSumm]
68
69 # ------------- BASIC PROPERTIES -----------------------------------------------------------------------------------
70
71 @property
72 def write_perc(self) -> Optional[int]:
73 try:
74 return int(self.vals["rwmixwrite"])
75 except (KeyError, TypeError):
76 try:
77 return 100 - int(self.vals["rwmixread"])
78 except (KeyError, TypeError):
79 return None
80
81 @property
82 def qd(self) -> int:
83 return int(self.vals['iodepth'])
84
85 @property
86 def bsize(self) -> int:
87 return ssize2b(self.vals['blocksize']) // 1024
88
89 @property
90 def oper(self) -> str:
91 return self.vals['rw']
92
93 @property
94 def op_type_short(self) -> str:
95 return self.op_type2short[self.vals['rw']]
96
97 @property
98 def thcount(self) -> int:
99 return int(self.vals.get('numjobs', 1))
100
101 @property
102 def sync_mode(self) -> str:
103 if self._sync_mode is None:
104 direct = is_fio_opt_true(self.vals.get('direct', '0')) or \
105 not is_fio_opt_true(self.vals.get('buffered', '0'))
106 sync = is_fio_opt_true(self.vals.get('sync', '0'))
107 self._sync_mode = self.ds2mode[(sync, direct)]
108 return cast(str, self._sync_mode)
109
110 @property
111 def sync_mode_long(self) -> str:
112 return self.sync2long[self.sync_mode]
113
114 # ----------- COMPLEX PROPERTIES -----------------------------------------------------------------------------------
115
116 @property
117 def characterized_tuple(self) -> Tuple:
118 if self._ctuple is None:
119 self._ctuple = FioTestSumm(oper=self.oper,
120 sync_mode=self.sync_mode,
121 bsize=self.bsize,
122 qd=self.qd,
123 thcount=self.thcount,
124 write_perc=self.write_perc)
125
126 return cast(Tuple, self._ctuple)
127
128 @property
129 def characterized_tuple_no_qd(self) -> FioTestSumm:
130 if self._ctuple_no_qd is None:
131 self._ctuple_no_qd = FioTestSumm(oper=self.oper,
132 sync_mode=self.sync_mode,
133 bsize=self.bsize,
134 qd=None,
135 thcount=self.thcount,
136 write_perc=self.write_perc)
137
138 return cast(FioTestSumm, self._ctuple_no_qd)
139
140 @property
141 def long_summary(self) -> str:
142 res = "{0.sync_mode_long} {0.oper} {1} QD={0.qd}".format(self, b2ssize(self.bsize * 1024))
143 if self.thcount != 1:
144 res += " threads={}".format(self.thcount)
145 if self.write_perc is not None:
146 res += " write_perc={}%".format(self.write_perc)
147 return res
148
149 @property
150 def long_summary_no_qd(self) -> str:
151 res = "{0.sync_mode_long} {0.oper} {1}".format(self, b2ssize(self.bsize * 1024))
152 if self.thcount != 1:
153 res += " threads={}".format(self.thcount)
154 if self.write_perc is not None:
155 res += " write_perc={}%".format(self.write_perc)
156 return res
157
158 @property
159 def summary(self) -> str:
160 tpl = cast(FioTestSumm, self.characterized_tuple)
161 res = "{0.oper}{0.sync_mode}{0.bsize}_qd{0.qd}".format(tpl)
162
163 if tpl.thcount != 1:
164 res += "th" + str(tpl.thcount)
165 if tpl.write_perc != 1:
166 res += "wr" + str(tpl.write_perc)
167
168 return res
169
170 @property
171 def summary_no_qd(self) -> str:
172 tpl = cast(FioTestSumm, self.characterized_tuple)
173 res = "{0.oper}{0.sync_mode}{0.bsize}".format(tpl)
174
175 if tpl.thcount != 1:
176 res += "th" + str(tpl.thcount)
177 if tpl.write_perc != 1:
178 res += "wr" + str(tpl.write_perc)
179
180 return res
181 # ------------------------------------------------------------------------------------------------------------------
182
183 def __eq__(self, o: object) -> bool:
184 if not isinstance(o, FioJobConfig):
185 return False
186 return self.vals == cast(FioJobConfig, o).vals
koder aka kdanilovf2865172016-12-30 03:35:11 +0200187
188 def copy(self) -> 'FioJobConfig':
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300189 return copy.deepcopy(self)
190
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200191 def required_vars(self) -> Iterator[Tuple[str, Var]]:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300192 for name, val in self.vals.items():
193 if isinstance(val, Var):
194 yield name, val
195
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300196 def is_free(self) -> bool:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300197 return len(list(self.required_vars())) == 0
198
koder aka kdanilov70227062016-11-26 23:23:21 +0200199 def __str__(self) -> str:
koder aka kdanilovf2865172016-12-30 03:35:11 +0200200 res = "[{0}]\n".format(self.summary)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300201
202 for name, val in self.vals.items():
203 if name.startswith('_') or name == name.upper():
204 continue
205 if isinstance(val, Var):
206 res += "{0}={{{1}}}\n".format(name, val.name)
207 else:
208 res += "{0}={1}\n".format(name, val)
209
210 return res
211
koder aka kdanilovf2865172016-12-30 03:35:11 +0200212 def __repr__(self) -> str:
213 return str(self)
214
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200215 def raw(self) -> Dict[str, Any]:
koder aka kdanilov108ac362017-01-19 20:17:16 +0200216 res = self.__dict__.copy()
217 del res['_sync_mode']
218 res['vals'] = [[key, val] for key, val in self.vals.items()]
219 return res
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200220
221 @classmethod
koder aka kdanilovf2865172016-12-30 03:35:11 +0200222 def fromraw(cls, data: Dict[str, Any]) -> 'FioJobConfig':
koder aka kdanilov108ac362017-01-19 20:17:16 +0200223 obj = cls.__new__(cls)
224 data['vals'] = OrderedDict(data['vals'])
225 data['_sync_mode'] = None
226 obj.__dict__.update(data)
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200227 return obj
228
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300229
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300230class ParseError(ValueError):
koder aka kdanilov70227062016-11-26 23:23:21 +0200231 def __init__(self, msg: str, fname: str, lineno: int, line_cont:Optional[str] = "") -> None:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300232 ValueError.__init__(self, msg)
233 self.file_name = fname
234 self.lineno = lineno
235 self.line_cont = line_cont
236
koder aka kdanilov70227062016-11-26 23:23:21 +0200237 def __str__(self) -> str:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300238 msg = "In {0}:{1} ({2}) : {3}"
239 return msg.format(self.file_name,
240 self.lineno,
241 self.line_cont,
242 super(ParseError, self).__str__())
243
244
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300245def is_name(name: str) -> bool:
koder aka kdanilov70227062016-11-26 23:23:21 +0200246 return re.match("[a-zA-Z_][a-zA-Z_0-9]*", name) is not None
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300247
248
koder aka kdanilov70227062016-11-26 23:23:21 +0200249def parse_value(val: str) -> Union[int, str, float, List, Var]:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300250 try:
251 return int(val)
252 except ValueError:
253 pass
254
255 try:
256 return float(val)
257 except ValueError:
258 pass
259
260 if val.startswith('{%'):
261 assert val.endswith("%}")
262 content = val[2:-2]
263 vals = list(i.strip() for i in content.split(','))
koder aka kdanilov70227062016-11-26 23:23:21 +0200264 return list(map(parse_value, vals))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300265
266 if val.startswith('{'):
267 assert val.endswith("}")
268 assert is_name(val[1:-1])
269 return Var(val[1:-1])
koder aka kdanilov70227062016-11-26 23:23:21 +0200270
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300271 return val
272
273
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200274def fio_config_lexer(fio_cfg: str, fname: str) -> Iterator[CfgLine]:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300275 for lineno, oline in enumerate(fio_cfg.split("\n")):
276 try:
277 line = oline.strip()
278
279 if line.startswith("#") or line.startswith(";"):
280 continue
281
282 if line == "":
283 continue
284
285 if '#' in line:
286 raise ParseError("# isn't allowed inside line",
287 fname, lineno, oline)
288
289 if line.startswith('['):
290 yield CfgLine(fname, lineno, oline, SECTION,
291 line[1:-1].strip(), None)
292 elif '=' in line:
293 opt_name, opt_val = line.split('=', 1)
294 yield CfgLine(fname, lineno, oline, SETTING,
295 opt_name.strip(),
296 parse_value(opt_val.strip()))
297 elif line.startswith("include "):
298 yield CfgLine(fname, lineno, oline, INCLUDE,
299 line.split(" ", 1)[1], None)
300 else:
301 yield CfgLine(fname, lineno, oline, SETTING, line, '1')
302
303 except Exception as exc:
304 raise ParseError(str(exc), fname, lineno, oline)
305
306
koder aka kdanilovf2865172016-12-30 03:35:11 +0200307def fio_config_parse(lexer_iter: Iterable[CfgLine]) -> Iterator[FioJobConfig]:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300308 in_globals = False
309 curr_section = None
koder aka kdanilov70227062016-11-26 23:23:21 +0200310 glob_vals = OrderedDict() # type: Dict[str, Any]
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300311 sections_count = 0
312
koder aka kdanilov70227062016-11-26 23:23:21 +0200313 lexed_lines = list(lexer_iter) # type: List[CfgLine]
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300314 one_more = True
315 includes = {}
316
317 while one_more:
koder aka kdanilov70227062016-11-26 23:23:21 +0200318 new_lines = [] # type: List[CfgLine]
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300319 one_more = False
320 for line in lexed_lines:
321 fname, lineno, oline, tp, name, val = line
322
323 if INCLUDE == tp:
324 if not os.path.exists(fname):
325 dirname = '.'
326 else:
327 dirname = os.path.dirname(fname)
328
329 new_fname = os.path.join(dirname, name)
330 includes[new_fname] = (fname, lineno)
331
332 try:
333 cont = open(new_fname).read()
334 except IOError as err:
koder aka kdanilovf2865172016-12-30 03:35:11 +0200335 raise ParseError("Error while including file {}: {}".format(new_fname, err), fname, lineno, oline)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300336
337 new_lines.extend(fio_config_lexer(cont, new_fname))
338 one_more = True
339 else:
340 new_lines.append(line)
341
342 lexed_lines = new_lines
343
koder aka kdanilov108ac362017-01-19 20:17:16 +0200344 suite_section_idx = 0
345
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300346 for fname, lineno, oline, tp, name, val in lexed_lines:
347 if tp == SECTION:
348 if curr_section is not None:
349 yield curr_section
350 curr_section = None
351
352 if name == 'global':
353 if sections_count != 0:
koder aka kdanilovf2865172016-12-30 03:35:11 +0200354 raise ParseError("[global] section should be only one and first", fname, lineno, oline)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300355 in_globals = True
356 else:
357 in_globals = False
koder aka kdanilov108ac362017-01-19 20:17:16 +0200358 curr_section = FioJobConfig(name, idx=suite_section_idx)
359 suite_section_idx += 1
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300360 curr_section.vals = glob_vals.copy()
361 sections_count += 1
362 else:
363 assert tp == SETTING
364 if in_globals:
365 glob_vals[name] = val
366 elif name == name.upper():
koder aka kdanilovf2865172016-12-30 03:35:11 +0200367 raise ParseError("Param {!r} not in [global] section".format(name), fname, lineno, oline)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300368 elif curr_section is None:
koder aka kdanilovf2865172016-12-30 03:35:11 +0200369 raise ParseError("Data outside section", fname, lineno, oline)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300370 else:
371 curr_section.vals[name] = val
372
373 if curr_section is not None:
374 yield curr_section
375
376
koder aka kdanilovf2865172016-12-30 03:35:11 +0200377def process_cycles(sec: FioJobConfig) -> Iterator[FioJobConfig]:
koder aka kdanilov70227062016-11-26 23:23:21 +0200378 cycles = OrderedDict() # type: Dict[str, Any]
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300379
380 for name, val in sec.vals.items():
381 if isinstance(val, list) and name.upper() != name:
382 cycles[name] = val
383
384 if len(cycles) == 0:
385 yield sec
386 else:
koder aka kdanilov70227062016-11-26 23:23:21 +0200387 # iodepth should changes faster
388 numjobs = cycles.pop('iodepth', None)
389 items = list(cycles.items())
koder aka kdanilov170936a2015-06-27 22:51:17 +0300390
koder aka kdanilov70227062016-11-26 23:23:21 +0200391 if items:
koder aka kdanilov170936a2015-06-27 22:51:17 +0300392 keys, vals = zip(*items)
393 keys = list(keys)
394 vals = list(vals)
395 else:
396 keys = []
397 vals = []
398
399 if numjobs is not None:
400 vals.append(numjobs)
koder aka kdanilov70227062016-11-26 23:23:21 +0200401 keys.append('iodepth')
koder aka kdanilov170936a2015-06-27 22:51:17 +0300402
403 for combination in itertools.product(*vals):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300404 new_sec = sec.copy()
koder aka kdanilov170936a2015-06-27 22:51:17 +0300405 new_sec.vals.update(zip(keys, combination))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300406 yield new_sec
407
408
koder aka kdanilov70227062016-11-26 23:23:21 +0200409FioParamsVal = Union[str, Var]
410FioParams = Dict[str, FioParamsVal]
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300411
412
koder aka kdanilovf2865172016-12-30 03:35:11 +0200413def apply_params(sec: FioJobConfig, params: FioParams) -> FioJobConfig:
koder aka kdanilov70227062016-11-26 23:23:21 +0200414 processed_vals = OrderedDict() # type: Dict[str, Any]
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300415 processed_vals.update(params)
416 for name, val in sec.vals.items():
417 if name in params:
418 continue
419
420 if isinstance(val, Var):
421 if val.name in params:
422 val = params[val.name]
423 elif val.name in processed_vals:
424 val = processed_vals[val.name]
425 processed_vals[name] = val
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300426
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300427 sec = sec.copy()
428 sec.vals = processed_vals
429 return sec
430
431
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300432def abbv_name_to_full(name: str) -> str:
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300433 assert len(name) == 3
434
435 smode = {
436 'a': 'async',
437 's': 'sync',
438 'd': 'direct',
439 'x': 'sync direct'
440 }
441 off_mode = {'s': 'sequential', 'r': 'random'}
koder aka kdanilov7248c7b2015-05-31 22:53:03 +0300442 oper = {'r': 'read', 'w': 'write', 'm': 'mixed'}
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300443 return smode[name[2]] + " " + \
444 off_mode[name[0]] + " " + oper[name[1]]
445
446
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300447MAGIC_OFFSET = 0.1885
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300448
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300449
koder aka kdanilovf2865172016-12-30 03:35:11 +0200450def final_process(sec: FioJobConfig, counter: List[int] = [0]) -> FioJobConfig:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300451 sec = sec.copy()
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300452
453 sec.vals['unified_rw_reporting'] = '1'
454
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300455 if isinstance(sec.vals['size'], Var):
456 raise ValueError("Variable {0} isn't provided".format(
457 sec.vals['size'].name))
458
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300459 sz = ssize2b(sec.vals['size'])
460 offset = sz * ((MAGIC_OFFSET * counter[0]) % 1.0)
461 offset = int(offset) // 1024 ** 2
462 new_vars = {'UNIQ_OFFSET': str(offset) + "m"}
463
464 for name, val in sec.vals.items():
465 if isinstance(val, Var):
466 if val.name in new_vars:
467 sec.vals[name] = new_vars[val.name]
468
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300469 for vl in sec.vals.values():
470 if isinstance(vl, Var):
471 raise ValueError("Variable {0} isn't provided".format(vl.name))
472
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300473 params = sec.vals.copy()
474 params['UNIQ'] = 'UN{0}'.format(counter[0])
475 params['COUNTER'] = str(counter[0])
koder aka kdanilov108ac362017-01-19 20:17:16 +0200476 params['TEST_SUMM'] = sec.summary
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300477 sec.name = sec.name.format(**params)
478 counter[0] += 1
479
480 return sec
481
482
koder aka kdanilovf2865172016-12-30 03:35:11 +0200483def execution_time(sec: FioJobConfig) -> int:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300484 return sec.vals.get('ramp_time', 0) + sec.vals.get('runtime', 0)
485
486
koder aka kdanilovf2865172016-12-30 03:35:11 +0200487def parse_all_in_1(source:str, fname: str = None) -> Iterator[FioJobConfig]:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300488 return fio_config_parse(fio_config_lexer(source, fname))
489
490
koder aka kdanilov108ac362017-01-19 20:17:16 +0200491def get_log_files(sec: FioJobConfig, iops: bool = False) -> List[Tuple[str, str]]:
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +0200492 res = [] # type: List[Tuple[str, str]]
koder aka kdanilov108ac362017-01-19 20:17:16 +0200493
494 keys = [('write_bw_log', 'bw'), ('write_hist_log', 'lat')]
495 if iops:
496 keys.append(('write_iops_log', 'iops'))
497
498 for key, name in keys:
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +0200499 log = sec.vals.get(key)
500 if log is not None:
501 res.append((name, log))
koder aka kdanilov108ac362017-01-19 20:17:16 +0200502
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +0200503 return res
koder aka kdanilovbbbe1dc2016-12-20 01:19:56 +0200504
505
koder aka kdanilovf2865172016-12-30 03:35:11 +0200506def fio_cfg_compile(source: str, fname: str, test_params: FioParams) -> Iterator[FioJobConfig]:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300507 it = parse_all_in_1(source, fname)
508 it = (apply_params(sec, test_params) for sec in it)
509 it = flatmap(process_cycles, it)
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +0200510 for sec in map(final_process, it):
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +0200511 yield sec
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300512
513
514def parse_args(argv):
515 parser = argparse.ArgumentParser(
516 description="Run fio' and return result")
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300517 parser.add_argument("-p", "--params", nargs="*", metavar="PARAM=VAL",
518 default=[],
519 help="Provide set of pairs PARAM=VAL to" +
520 "format into job description")
521 parser.add_argument("action", choices=['estimate', 'compile', 'num_tests'])
522 parser.add_argument("jobfile")
523 return parser.parse_args(argv)
524
525
526def main(argv):
527 argv_obj = parse_args(argv)
528
529 if argv_obj.jobfile == '-':
530 job_cfg = sys.stdin.read()
531 else:
532 job_cfg = open(argv_obj.jobfile).read()
533
534 params = {}
535 for param_val in argv_obj.params:
536 assert '=' in param_val
537 name, val = param_val.split("=", 1)
538 params[name] = parse_value(val)
539
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300540 sec_it = fio_cfg_compile(job_cfg, argv_obj.jobfile, params)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300541
542 if argv_obj.action == 'estimate':
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300543 print(sec_to_str(sum(map(execution_time, sec_it))))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300544 elif argv_obj.action == 'num_tests':
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300545 print(sum(map(len, map(list, sec_it))))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300546 elif argv_obj.action == 'compile':
547 splitter = "\n#" + "-" * 70 + "\n\n"
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300548 print(splitter.join(map(str, sec_it)))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300549
550 return 0
551
552
553if __name__ == '__main__':
554 exit(main(sys.argv[1:]))