blob: e0af6e7e3b9a84da6577c4a9a85c980848465c7c [file] [log] [blame]
koder aka kdanilov2e928022015-04-08 13:47:15 +03001import re
koder aka kdanilovda45e882015-04-06 02:24:42 +03002import sys
3import time
4import json
koder aka kdanilovb896f692015-04-07 14:57:55 +03005import random
koder aka kdanilovda45e882015-04-06 02:24:42 +03006import select
7import pprint
8import argparse
9import traceback
10import subprocess
11import itertools
12from collections import OrderedDict
13
14
15SECTION = 0
16SETTING = 1
17
18
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030019def get_test_sync_mode(jconfig):
20 try:
21 return jconfig['sync_mode']
22 except KeyError:
23 pass
24
25 is_sync = jconfig.get("sync", "0") == "1"
26 is_direct = jconfig.get("direct", "0") == "1"
27
28 if is_sync and is_direct:
29 return 'x'
30 elif is_sync:
31 return 's'
32 elif is_direct:
33 return 'd'
34 else:
35 return 'a'
36
37
koder aka kdanilovda45e882015-04-06 02:24:42 +030038def get_test_summary(params):
39 rw = {"randread": "rr",
40 "randwrite": "rw",
41 "read": "sr",
42 "write": "sw"}[params["rw"]]
43
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030044 sync_mode = get_test_sync_mode(params)
45 th_count = params.get('numjobs')
46 if th_count is None:
47 th_count = params.get('concurence', '1')
48 th_count = int(th_count)
koder aka kdanilovda45e882015-04-06 02:24:42 +030049
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030050 return "{0}{1}{2}th{3}".format(rw,
51 sync_mode,
52 params['blocksize'],
53 th_count)
koder aka kdanilovda45e882015-04-06 02:24:42 +030054
55
56counter = [0]
57
58
59def process_section(name, vals, defaults, format_params):
60 vals = vals.copy()
61 params = format_params.copy()
62
63 if '*' in name:
64 name, repeat = name.split('*')
65 name = name.strip()
66 repeat = int(repeat.format(**params))
67 else:
68 repeat = 1
69
70 # this code can be optimized
koder aka kdanilovb896f692015-04-07 14:57:55 +030071 iterable_names = []
72 iterable_values = []
73 processed_vals = {}
koder aka kdanilovda45e882015-04-06 02:24:42 +030074
koder aka kdanilovb896f692015-04-07 14:57:55 +030075 for val_name, val in vals.items():
76 if val is None:
77 processed_vals[val_name] = val
78 # remove hardcode
79 elif val.startswith('{%'):
80 assert val.endswith("%}")
81 content = val[2:-2].format(**params)
82 iterable_names.append(val_name)
83 iterable_values.append(list(i.strip() for i in content.split(',')))
84 else:
85 processed_vals[val_name] = val.format(**params)
koder aka kdanilovda45e882015-04-06 02:24:42 +030086
koder aka kdanilov2e928022015-04-08 13:47:15 +030087 group_report_err_msg = "Group reporting should be set if numjobs != 1"
88
koder aka kdanilovb896f692015-04-07 14:57:55 +030089 if iterable_values == []:
90 params['UNIQ'] = 'UN{0}'.format(counter[0])
91 counter[0] += 1
92 params['TEST_SUMM'] = get_test_summary(processed_vals)
koder aka kdanilov2e928022015-04-08 13:47:15 +030093
94 if processed_vals.get('numjobs', '1') != '1':
95 assert 'group_reporting' in processed_vals, group_report_err_msg
96
koder aka kdanilov652cd802015-04-13 12:21:07 +030097 ramp_time = processed_vals.get('ramp_time')
koder aka kdanilovb896f692015-04-07 14:57:55 +030098 for i in range(repeat):
koder aka kdanilov2e928022015-04-08 13:47:15 +030099 yield name.format(**params), processed_vals.copy()
koder aka kdanilov652cd802015-04-13 12:21:07 +0300100
101 if 'ramp_time' in processed_vals:
102 del processed_vals['ramp_time']
103
104 if ramp_time is not None:
105 processed_vals['ramp_time'] = ramp_time
koder aka kdanilovb896f692015-04-07 14:57:55 +0300106 else:
107 for it_vals in itertools.product(*iterable_values):
108 processed_vals.update(dict(zip(iterable_names, it_vals)))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300109 params['UNIQ'] = 'UN{0}'.format(counter[0])
110 counter[0] += 1
111 params['TEST_SUMM'] = get_test_summary(processed_vals)
koder aka kdanilov2e928022015-04-08 13:47:15 +0300112
113 if processed_vals.get('numjobs', '1') != '1':
114 assert 'group_reporting' in processed_vals,\
115 group_report_err_msg
116
koder aka kdanilov66839a92015-04-11 13:22:31 +0300117 ramp_time = processed_vals.get('ramp_time')
118
koder aka kdanilovb896f692015-04-07 14:57:55 +0300119 for i in range(repeat):
120 yield name.format(**params), processed_vals.copy()
koder aka kdanilov66839a92015-04-11 13:22:31 +0300121 if 'ramp_time' in processed_vals:
122 del processed_vals['ramp_time']
123
124 if ramp_time is not None:
125 processed_vals['ramp_time'] = ramp_time
koder aka kdanilovda45e882015-04-06 02:24:42 +0300126
127
128def calculate_execution_time(combinations):
129 time = 0
130 for _, params in combinations:
131 time += int(params.get('ramp_time', 0))
132 time += int(params.get('runtime', 0))
133 return time
134
135
136def parse_fio_config_full(fio_cfg, params=None):
137 defaults = {}
138 format_params = {}
139
140 if params is None:
141 ext_params = {}
142 else:
143 ext_params = params.copy()
144
145 curr_section = None
146 curr_section_name = None
147
148 for tp, name, val in parse_fio_config_iter(fio_cfg):
149 if tp == SECTION:
150 non_def = curr_section_name != 'defaults'
151 if curr_section_name is not None and non_def:
152 format_params.update(ext_params)
153 for sec in process_section(curr_section_name,
154 curr_section,
155 defaults,
156 format_params):
157 yield sec
158
159 if name == 'defaults':
160 curr_section = defaults
161 else:
162 curr_section = OrderedDict()
163 curr_section.update(defaults)
164 curr_section_name = name
165
166 else:
167 assert tp == SETTING
168 assert curr_section_name is not None, "no section name"
169 if name == name.upper():
170 assert curr_section_name == 'defaults'
171 format_params[name] = val
172 else:
173 curr_section[name] = val
174
175 if curr_section_name is not None and curr_section_name != 'defaults':
176 format_params.update(ext_params)
177 for sec in process_section(curr_section_name,
178 curr_section,
179 defaults,
180 format_params):
181 yield sec
182
183
184def parse_fio_config_iter(fio_cfg):
185 for lineno, line in enumerate(fio_cfg.split("\n")):
186 try:
187 line = line.strip()
188
189 if line.startswith("#") or line.startswith(";"):
190 continue
191
192 if line == "":
193 continue
194
195 if line.startswith('['):
196 assert line.endswith(']'), "name should ends with ]"
197 yield SECTION, line[1:-1], None
198 elif '=' in line:
199 opt_name, opt_val = line.split('=', 1)
200 yield SETTING, opt_name.strip(), opt_val.strip()
201 else:
202 yield SETTING, line, None
203 except Exception as exc:
204 pref = "During parsing line number {0}\n".format(lineno)
205 raise ValueError(pref + exc.message)
206
207
208def format_fio_config(fio_cfg):
209 res = ""
210 for pos, (name, section) in enumerate(fio_cfg):
211 if pos != 0:
212 res += "\n"
213
214 res += "[{0}]\n".format(name)
215 for opt_name, opt_val in section.items():
216 if opt_val is None:
217 res += opt_name + "\n"
218 else:
219 res += "{0}={1}\n".format(opt_name, opt_val)
220 return res
221
222
koder aka kdanilovb896f692015-04-07 14:57:55 +0300223count = 0
224
225
226def to_bytes(sz):
227 sz = sz.lower()
228 try:
229 return int(sz)
230 except ValueError:
231 if sz[-1] == 'm':
232 return (1024 ** 2) * int(sz[:-1])
233 if sz[-1] == 'k':
234 return 1024 * int(sz[:-1])
235 raise
236
237
koder aka kdanilovb896f692015-04-07 14:57:55 +0300238def do_run_fio_fake(bconf):
koder aka kdanilov66839a92015-04-11 13:22:31 +0300239 def estimate_iops(sz, bw, lat):
240 return 1 / (lat + float(sz) / bw)
koder aka kdanilovb896f692015-04-07 14:57:55 +0300241 global count
242 count += 1
243 parsed_out = []
244
245 BW = 120.0 * (1024 ** 2)
246 LAT = 0.003
247
248 for name, cfg in bconf:
249 sz = to_bytes(cfg['blocksize'])
250 curr_lat = LAT * ((random.random() - 0.5) * 0.1 + 1)
251 curr_ulat = curr_lat * 1000000
252 curr_bw = BW * ((random.random() - 0.5) * 0.1 + 1)
253 iops = estimate_iops(sz, curr_bw, curr_lat)
254 bw = iops * sz
255
256 res = {'ctx': 10683,
257 'error': 0,
258 'groupid': 0,
259 'jobname': name,
260 'majf': 0,
261 'minf': 30,
262 'read': {'bw': 0,
263 'bw_agg': 0.0,
264 'bw_dev': 0.0,
265 'bw_max': 0,
266 'bw_mean': 0.0,
267 'bw_min': 0,
268 'clat': {'max': 0,
269 'mean': 0.0,
270 'min': 0,
271 'stddev': 0.0},
272 'io_bytes': 0,
273 'iops': 0,
274 'lat': {'max': 0, 'mean': 0.0,
275 'min': 0, 'stddev': 0.0},
276 'runtime': 0,
277 'slat': {'max': 0, 'mean': 0.0,
278 'min': 0, 'stddev': 0.0}
279 },
280 'sys_cpu': 0.64,
281 'trim': {'bw': 0,
282 'bw_agg': 0.0,
283 'bw_dev': 0.0,
284 'bw_max': 0,
285 'bw_mean': 0.0,
286 'bw_min': 0,
287 'clat': {'max': 0,
288 'mean': 0.0,
289 'min': 0,
290 'stddev': 0.0},
291 'io_bytes': 0,
292 'iops': 0,
293 'lat': {'max': 0, 'mean': 0.0,
294 'min': 0, 'stddev': 0.0},
295 'runtime': 0,
296 'slat': {'max': 0, 'mean': 0.0,
297 'min': 0, 'stddev': 0.0}
298 },
299 'usr_cpu': 0.23,
300 'write': {'bw': 0,
301 'bw_agg': 0,
302 'bw_dev': 0,
303 'bw_max': 0,
304 'bw_mean': 0,
305 'bw_min': 0,
306 'clat': {'max': 0, 'mean': 0,
307 'min': 0, 'stddev': 0},
308 'io_bytes': 0,
309 'iops': 0,
310 'lat': {'max': 0, 'mean': 0,
311 'min': 0, 'stddev': 0},
312 'runtime': 0,
313 'slat': {'max': 0, 'mean': 0.0,
314 'min': 0, 'stddev': 0.0}
315 }
316 }
317
318 if cfg['rw'] in ('read', 'randread'):
319 key = 'read'
320 elif cfg['rw'] in ('write', 'randwrite'):
321 key = 'write'
322 else:
323 raise ValueError("Uknown op type {0}".format(key))
324
325 res[key]['bw'] = bw
326 res[key]['iops'] = iops
327 res[key]['runtime'] = 30
328 res[key]['io_bytes'] = res[key]['runtime'] * bw
329 res[key]['bw_agg'] = bw
330 res[key]['bw_dev'] = bw / 30
331 res[key]['bw_max'] = bw * 1.5
332 res[key]['bw_min'] = bw / 1.5
333 res[key]['bw_mean'] = bw
334 res[key]['clat'] = {'max': curr_ulat * 10, 'mean': curr_ulat,
335 'min': curr_ulat / 2, 'stddev': curr_ulat}
336 res[key]['lat'] = res[key]['clat'].copy()
337 res[key]['slat'] = res[key]['clat'].copy()
338
339 parsed_out.append(res)
340
341 return zip(parsed_out, bconf)
342
343
koder aka kdanilovda45e882015-04-06 02:24:42 +0300344def do_run_fio(bconf):
345 benchmark_config = format_fio_config(bconf)
346 cmd = ["fio", "--output-format=json", "-"]
347 p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
348 stdout=subprocess.PIPE,
349 stderr=subprocess.STDOUT)
350
351 # set timeout
352 raw_out, _ = p.communicate(benchmark_config)
353
354 try:
355 parsed_out = json.loads(raw_out)["jobs"]
356 except Exception:
357 msg = "Can't parse fio output: {0!r}\nError: {1}"
358 raise ValueError(msg.format(raw_out, traceback.format_exc()))
359
360 return zip(parsed_out, bconf)
361
koder aka kdanilovda45e882015-04-06 02:24:42 +0300362# limited by fio
363MAX_JOBS = 1000
364
365
366def next_test_portion(whole_conf, runcycle):
367 jcount = 0
368 runtime = 0
369 bconf = []
370
371 for pos, (name, sec) in enumerate(whole_conf):
372 jc = int(sec.get('numjobs', '1'))
373
374 if runcycle is not None:
375 curr_task_time = calculate_execution_time([(name, sec)])
376 else:
377 curr_task_time = 0
378
379 if jc > MAX_JOBS:
380 err_templ = "Can't process job {0!r} - too large numjobs"
381 raise ValueError(err_templ.format(name))
382
383 if runcycle is not None and len(bconf) != 0:
384 rc_ok = curr_task_time + runtime <= runcycle
385 else:
386 rc_ok = True
387
388 if jc + jcount <= MAX_JOBS and rc_ok:
389 runtime += curr_task_time
390 jcount += jc
391 bconf.append((name, sec))
392 continue
393
394 assert len(bconf) != 0
395 yield bconf
396
397 runtime = curr_task_time
398 jcount = jc
399 bconf = [(name, sec)]
400
401 if bconf != []:
402 yield bconf
403
404
405def add_job_results(jname, job_output, jconfig, res):
406 if job_output['write']['iops'] != 0:
407 raw_result = job_output['write']
408 else:
409 raw_result = job_output['read']
410
411 if jname not in res:
412 j_res = {}
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300413 j_res["rw"] = jconfig["rw"]
koder aka kdanilov652cd802015-04-13 12:21:07 +0300414 j_res["sync_mode"] = get_test_sync_mode(jconfig)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300415 j_res["concurence"] = int(jconfig.get("numjobs", 1))
koder aka kdanilov2e928022015-04-08 13:47:15 +0300416 j_res["blocksize"] = jconfig["blocksize"]
koder aka kdanilovda45e882015-04-06 02:24:42 +0300417 j_res["jobname"] = job_output["jobname"]
koder aka kdanilov66839a92015-04-11 13:22:31 +0300418 j_res["timings"] = [int(jconfig.get("runtime", 0)),
419 int(jconfig.get("ramp_time", 0))]
koder aka kdanilovda45e882015-04-06 02:24:42 +0300420 else:
421 j_res = res[jname]
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300422 assert j_res["rw"] == jconfig["rw"]
423 assert j_res["rw"] == jconfig["rw"]
koder aka kdanilov652cd802015-04-13 12:21:07 +0300424 assert j_res["sync_mode"] == get_test_sync_mode(jconfig)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300425 assert j_res["concurence"] == int(jconfig.get("numjobs", 1))
koder aka kdanilov2e928022015-04-08 13:47:15 +0300426 assert j_res["blocksize"] == jconfig["blocksize"]
koder aka kdanilovda45e882015-04-06 02:24:42 +0300427 assert j_res["jobname"] == job_output["jobname"]
koder aka kdanilov652cd802015-04-13 12:21:07 +0300428
429 # ramp part is skipped for all tests, except first
430 # assert j_res["timings"] == (jconfig.get("runtime"),
431 # jconfig.get("ramp_time"))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300432
433 def j_app(name, x):
434 j_res.setdefault(name, []).append(x)
435
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300436 j_app("bw", raw_result["bw"])
koder aka kdanilovda45e882015-04-06 02:24:42 +0300437 j_app("iops", raw_result["iops"])
438 j_app("lat", raw_result["lat"]["mean"])
439 j_app("clat", raw_result["clat"]["mean"])
440 j_app("slat", raw_result["slat"]["mean"])
441
442 res[jname] = j_res
443
444
445def run_fio(benchmark_config,
446 params,
447 runcycle=None,
448 raw_results_func=None,
koder aka kdanilovb896f692015-04-07 14:57:55 +0300449 skip_tests=0,
450 fake_fio=False):
koder aka kdanilovda45e882015-04-06 02:24:42 +0300451
452 whole_conf = list(parse_fio_config_full(benchmark_config, params))
453 whole_conf = whole_conf[skip_tests:]
454 res = {}
455 curr_test_num = skip_tests
koder aka kdanilov2e928022015-04-08 13:47:15 +0300456 executed_tests = 0
koder aka kdanilovda45e882015-04-06 02:24:42 +0300457 try:
458 for bconf in next_test_portion(whole_conf, runcycle):
koder aka kdanilovb896f692015-04-07 14:57:55 +0300459
460 if fake_fio:
461 res_cfg_it = do_run_fio_fake(bconf)
462 else:
463 res_cfg_it = do_run_fio(bconf)
464
koder aka kdanilovda45e882015-04-06 02:24:42 +0300465 res_cfg_it = enumerate(res_cfg_it, curr_test_num)
466
467 for curr_test_num, (job_output, (jname, jconfig)) in res_cfg_it:
koder aka kdanilov2e928022015-04-08 13:47:15 +0300468 executed_tests += 1
koder aka kdanilovda45e882015-04-06 02:24:42 +0300469 if raw_results_func is not None:
koder aka kdanilov2e928022015-04-08 13:47:15 +0300470 raw_results_func(executed_tests,
koder aka kdanilovda45e882015-04-06 02:24:42 +0300471 [job_output, jname, jconfig])
472
koder aka kdanilovb896f692015-04-07 14:57:55 +0300473 assert jname == job_output["jobname"], \
474 "{0} != {1}".format(jname, job_output["jobname"])
koder aka kdanilovda45e882015-04-06 02:24:42 +0300475
476 if jname.startswith('_'):
477 continue
478
479 add_job_results(jname, job_output, jconfig, res)
480
481 except (SystemExit, KeyboardInterrupt):
koder aka kdanilov652cd802015-04-13 12:21:07 +0300482 raise
koder aka kdanilovda45e882015-04-06 02:24:42 +0300483
484 except Exception:
485 traceback.print_exc()
486
koder aka kdanilov2e928022015-04-08 13:47:15 +0300487 return res, executed_tests
koder aka kdanilovda45e882015-04-06 02:24:42 +0300488
489
490def run_benchmark(binary_tp, *argv, **kwargs):
491 if 'fio' == binary_tp:
492 return run_fio(*argv, **kwargs)
493 raise ValueError("Unknown behcnmark {0}".format(binary_tp))
494
495
koder aka kdanilov652cd802015-04-13 12:21:07 +0300496def read_config(fd, timeout=10):
497 job_cfg = ""
498 etime = time.time() + timeout
499 while True:
500 wtime = etime - time.time()
501 if wtime <= 0:
502 raise IOError("No config provided")
503
504 r, w, x = select.select([fd], [], [], wtime)
505 if len(r) == 0:
506 raise IOError("No config provided")
507
508 char = fd.read(1)
509 if '' == char:
510 return job_cfg
511
512 job_cfg += char
513
514
515def estimate_cfg(job_cfg, params):
516 bconf = list(parse_fio_config_full(job_cfg, params))
517 return calculate_execution_time(bconf)
518
519
520def sec_to_str(seconds):
521 h = seconds // 3600
522 m = (seconds % 3600) // 60
523 s = seconds % 60
524 return "{0}:{1:02d}:{2:02d}".format(h, m, s)
525
526
koder aka kdanilovda45e882015-04-06 02:24:42 +0300527def parse_args(argv):
528 parser = argparse.ArgumentParser(
529 description="Run fio' and return result")
530 parser.add_argument("--type", metavar="BINARY_TYPE",
531 choices=['fio'], default='fio',
532 help=argparse.SUPPRESS)
533 parser.add_argument("--start-at", metavar="START_AT_UTC", type=int,
534 help="Start execution at START_AT_UTC")
535 parser.add_argument("--json", action="store_true", default=False,
536 help="Json output format")
537 parser.add_argument("--output", default='-', metavar="FILE_PATH",
538 help="Store results to FILE_PATH")
539 parser.add_argument("--estimate", action="store_true", default=False,
540 help="Only estimate task execution time")
541 parser.add_argument("--compile", action="store_true", default=False,
542 help="Compile config file to fio config")
543 parser.add_argument("--num-tests", action="store_true", default=False,
544 help="Show total number of tests")
545 parser.add_argument("--runcycle", type=int, default=None,
546 metavar="MAX_CYCLE_SECONDS",
547 help="Max cycle length in seconds")
548 parser.add_argument("--show-raw-results", action='store_true',
549 default=False, help="Output raw input and results")
550 parser.add_argument("--skip-tests", type=int, default=0, metavar="NUM",
551 help="Skip NUM tests")
koder aka kdanilovb896f692015-04-07 14:57:55 +0300552 parser.add_argument("--faked-fio", action='store_true',
553 default=False, help="Emulate fio with 0 test time")
koder aka kdanilovda45e882015-04-06 02:24:42 +0300554 parser.add_argument("--params", nargs="*", metavar="PARAM=VAL",
555 default=[],
556 help="Provide set of pairs PARAM=VAL to" +
557 "format into job description")
558 parser.add_argument("jobfile")
559 return parser.parse_args(argv)
560
561
koder aka kdanilovda45e882015-04-06 02:24:42 +0300562def main(argv):
563 argv_obj = parse_args(argv)
564
565 if argv_obj.jobfile == '-':
566 job_cfg = read_config(sys.stdin)
567 else:
568 job_cfg = open(argv_obj.jobfile).read()
569
570 if argv_obj.output == '-':
571 out_fd = sys.stdout
572 else:
573 out_fd = open(argv_obj.output, "w")
574
575 params = {}
576 for param_val in argv_obj.params:
577 assert '=' in param_val
578 name, val = param_val.split("=", 1)
579 params[name] = val
580
koder aka kdanilov652cd802015-04-13 12:21:07 +0300581 if argv_obj.estimate:
582 print sec_to_str(estimate_cfg(job_cfg, params))
583 return 0
584
585 if argv_obj.num_tests or argv_obj.compile:
koder aka kdanilovda45e882015-04-06 02:24:42 +0300586 bconf = list(parse_fio_config_full(job_cfg, params))
587 bconf = bconf[argv_obj.skip_tests:]
588
589 if argv_obj.compile:
590 out_fd.write(format_fio_config(bconf))
591 out_fd.write("\n")
592
593 if argv_obj.num_tests:
594 print len(bconf)
595
koder aka kdanilovda45e882015-04-06 02:24:42 +0300596 return 0
597
598 if argv_obj.start_at is not None:
599 ctime = time.time()
600 if argv_obj.start_at >= ctime:
601 time.sleep(ctime - argv_obj.start_at)
602
603 def raw_res_func(test_num, data):
604 pref = "========= RAW_RESULTS({0}) =========\n".format(test_num)
605 out_fd.write(pref)
606 out_fd.write(json.dumps(data))
607 out_fd.write("\n========= END OF RAW_RESULTS =========\n")
608 out_fd.flush()
609
610 rrfunc = raw_res_func if argv_obj.show_raw_results else None
611
612 stime = time.time()
613 job_res, num_tests = run_benchmark(argv_obj.type,
614 job_cfg,
615 params,
616 argv_obj.runcycle,
617 rrfunc,
koder aka kdanilovb896f692015-04-07 14:57:55 +0300618 argv_obj.skip_tests,
619 argv_obj.faked_fio)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300620 etime = time.time()
621
koder aka kdanilov652cd802015-04-13 12:21:07 +0300622 res = {'__meta__': {'raw_cfg': job_cfg, 'params': params}, 'res': job_res}
koder aka kdanilovda45e882015-04-06 02:24:42 +0300623
624 oformat = 'json' if argv_obj.json else 'eval'
koder aka kdanilov652cd802015-04-13 12:21:07 +0300625 out_fd.write("\nRun {0} tests in {1} seconds\n".format(num_tests,
626 int(etime - stime)))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300627 out_fd.write("========= RESULTS(format={0}) =========\n".format(oformat))
628 if argv_obj.json:
629 out_fd.write(json.dumps(res))
630 else:
631 out_fd.write(pprint.pformat(res) + "\n")
632 out_fd.write("\n========= END OF RESULTS =========\n".format(oformat))
633
634 return 0
635
636
637if __name__ == '__main__':
638 exit(main(sys.argv[1:]))