blob: b12917588db25b4d4bc420ebf81afd8bff4fcf4a [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
19def get_test_summary(params):
20 rw = {"randread": "rr",
21 "randwrite": "rw",
22 "read": "sr",
23 "write": "sw"}[params["rw"]]
24
25 if params.get("direct") == '1':
26 sync_mode = 'd'
27 elif params.get("sync") == '1':
28 sync_mode = 's'
29 else:
30 sync_mode = 'a'
31
32 th_count = int(params.get('numjobs', '1'))
33
34 return "{0}{1}{2}th{3}".format(rw, sync_mode,
35 params['blocksize'], th_count)
36
37
38counter = [0]
39
40
41def process_section(name, vals, defaults, format_params):
42 vals = vals.copy()
43 params = format_params.copy()
44
45 if '*' in name:
46 name, repeat = name.split('*')
47 name = name.strip()
48 repeat = int(repeat.format(**params))
49 else:
50 repeat = 1
51
52 # this code can be optimized
koder aka kdanilovb896f692015-04-07 14:57:55 +030053 iterable_names = []
54 iterable_values = []
55 processed_vals = {}
koder aka kdanilovda45e882015-04-06 02:24:42 +030056
koder aka kdanilovb896f692015-04-07 14:57:55 +030057 for val_name, val in vals.items():
58 if val is None:
59 processed_vals[val_name] = val
60 # remove hardcode
61 elif val.startswith('{%'):
62 assert val.endswith("%}")
63 content = val[2:-2].format(**params)
64 iterable_names.append(val_name)
65 iterable_values.append(list(i.strip() for i in content.split(',')))
66 else:
67 processed_vals[val_name] = val.format(**params)
koder aka kdanilovda45e882015-04-06 02:24:42 +030068
koder aka kdanilov2e928022015-04-08 13:47:15 +030069 group_report_err_msg = "Group reporting should be set if numjobs != 1"
70
koder aka kdanilovb896f692015-04-07 14:57:55 +030071 if iterable_values == []:
72 params['UNIQ'] = 'UN{0}'.format(counter[0])
73 counter[0] += 1
74 params['TEST_SUMM'] = get_test_summary(processed_vals)
koder aka kdanilov2e928022015-04-08 13:47:15 +030075
76 if processed_vals.get('numjobs', '1') != '1':
77 assert 'group_reporting' in processed_vals, group_report_err_msg
78
koder aka kdanilov652cd802015-04-13 12:21:07 +030079 ramp_time = processed_vals.get('ramp_time')
koder aka kdanilovb896f692015-04-07 14:57:55 +030080 for i in range(repeat):
koder aka kdanilov2e928022015-04-08 13:47:15 +030081 yield name.format(**params), processed_vals.copy()
koder aka kdanilov652cd802015-04-13 12:21:07 +030082
83 if 'ramp_time' in processed_vals:
84 del processed_vals['ramp_time']
85
86 if ramp_time is not None:
87 processed_vals['ramp_time'] = ramp_time
koder aka kdanilovb896f692015-04-07 14:57:55 +030088 else:
89 for it_vals in itertools.product(*iterable_values):
90 processed_vals.update(dict(zip(iterable_names, it_vals)))
koder aka kdanilovda45e882015-04-06 02:24:42 +030091 params['UNIQ'] = 'UN{0}'.format(counter[0])
92 counter[0] += 1
93 params['TEST_SUMM'] = get_test_summary(processed_vals)
koder aka kdanilov2e928022015-04-08 13:47:15 +030094
95 if processed_vals.get('numjobs', '1') != '1':
96 assert 'group_reporting' in processed_vals,\
97 group_report_err_msg
98
koder aka kdanilov66839a92015-04-11 13:22:31 +030099 ramp_time = processed_vals.get('ramp_time')
100
koder aka kdanilovb896f692015-04-07 14:57:55 +0300101 for i in range(repeat):
102 yield name.format(**params), processed_vals.copy()
koder aka kdanilov66839a92015-04-11 13:22:31 +0300103 if 'ramp_time' in processed_vals:
104 del processed_vals['ramp_time']
105
106 if ramp_time is not None:
107 processed_vals['ramp_time'] = ramp_time
koder aka kdanilovda45e882015-04-06 02:24:42 +0300108
109
110def calculate_execution_time(combinations):
111 time = 0
112 for _, params in combinations:
113 time += int(params.get('ramp_time', 0))
114 time += int(params.get('runtime', 0))
115 return time
116
117
118def parse_fio_config_full(fio_cfg, params=None):
119 defaults = {}
120 format_params = {}
121
122 if params is None:
123 ext_params = {}
124 else:
125 ext_params = params.copy()
126
127 curr_section = None
128 curr_section_name = None
129
130 for tp, name, val in parse_fio_config_iter(fio_cfg):
131 if tp == SECTION:
132 non_def = curr_section_name != 'defaults'
133 if curr_section_name is not None and non_def:
134 format_params.update(ext_params)
135 for sec in process_section(curr_section_name,
136 curr_section,
137 defaults,
138 format_params):
139 yield sec
140
141 if name == 'defaults':
142 curr_section = defaults
143 else:
144 curr_section = OrderedDict()
145 curr_section.update(defaults)
146 curr_section_name = name
147
148 else:
149 assert tp == SETTING
150 assert curr_section_name is not None, "no section name"
151 if name == name.upper():
152 assert curr_section_name == 'defaults'
153 format_params[name] = val
154 else:
155 curr_section[name] = val
156
157 if curr_section_name is not None and curr_section_name != 'defaults':
158 format_params.update(ext_params)
159 for sec in process_section(curr_section_name,
160 curr_section,
161 defaults,
162 format_params):
163 yield sec
164
165
166def parse_fio_config_iter(fio_cfg):
167 for lineno, line in enumerate(fio_cfg.split("\n")):
168 try:
169 line = line.strip()
170
171 if line.startswith("#") or line.startswith(";"):
172 continue
173
174 if line == "":
175 continue
176
177 if line.startswith('['):
178 assert line.endswith(']'), "name should ends with ]"
179 yield SECTION, line[1:-1], None
180 elif '=' in line:
181 opt_name, opt_val = line.split('=', 1)
182 yield SETTING, opt_name.strip(), opt_val.strip()
183 else:
184 yield SETTING, line, None
185 except Exception as exc:
186 pref = "During parsing line number {0}\n".format(lineno)
187 raise ValueError(pref + exc.message)
188
189
190def format_fio_config(fio_cfg):
191 res = ""
192 for pos, (name, section) in enumerate(fio_cfg):
193 if pos != 0:
194 res += "\n"
195
196 res += "[{0}]\n".format(name)
197 for opt_name, opt_val in section.items():
198 if opt_val is None:
199 res += opt_name + "\n"
200 else:
201 res += "{0}={1}\n".format(opt_name, opt_val)
202 return res
203
204
koder aka kdanilovb896f692015-04-07 14:57:55 +0300205count = 0
206
207
208def to_bytes(sz):
209 sz = sz.lower()
210 try:
211 return int(sz)
212 except ValueError:
213 if sz[-1] == 'm':
214 return (1024 ** 2) * int(sz[:-1])
215 if sz[-1] == 'k':
216 return 1024 * int(sz[:-1])
217 raise
218
219
koder aka kdanilovb896f692015-04-07 14:57:55 +0300220def do_run_fio_fake(bconf):
koder aka kdanilov66839a92015-04-11 13:22:31 +0300221 def estimate_iops(sz, bw, lat):
222 return 1 / (lat + float(sz) / bw)
koder aka kdanilovb896f692015-04-07 14:57:55 +0300223 global count
224 count += 1
225 parsed_out = []
226
227 BW = 120.0 * (1024 ** 2)
228 LAT = 0.003
229
230 for name, cfg in bconf:
231 sz = to_bytes(cfg['blocksize'])
232 curr_lat = LAT * ((random.random() - 0.5) * 0.1 + 1)
233 curr_ulat = curr_lat * 1000000
234 curr_bw = BW * ((random.random() - 0.5) * 0.1 + 1)
235 iops = estimate_iops(sz, curr_bw, curr_lat)
236 bw = iops * sz
237
238 res = {'ctx': 10683,
239 'error': 0,
240 'groupid': 0,
241 'jobname': name,
242 'majf': 0,
243 'minf': 30,
244 'read': {'bw': 0,
245 'bw_agg': 0.0,
246 'bw_dev': 0.0,
247 'bw_max': 0,
248 'bw_mean': 0.0,
249 'bw_min': 0,
250 'clat': {'max': 0,
251 'mean': 0.0,
252 'min': 0,
253 'stddev': 0.0},
254 'io_bytes': 0,
255 'iops': 0,
256 'lat': {'max': 0, 'mean': 0.0,
257 'min': 0, 'stddev': 0.0},
258 'runtime': 0,
259 'slat': {'max': 0, 'mean': 0.0,
260 'min': 0, 'stddev': 0.0}
261 },
262 'sys_cpu': 0.64,
263 'trim': {'bw': 0,
264 'bw_agg': 0.0,
265 'bw_dev': 0.0,
266 'bw_max': 0,
267 'bw_mean': 0.0,
268 'bw_min': 0,
269 'clat': {'max': 0,
270 'mean': 0.0,
271 'min': 0,
272 'stddev': 0.0},
273 'io_bytes': 0,
274 'iops': 0,
275 'lat': {'max': 0, 'mean': 0.0,
276 'min': 0, 'stddev': 0.0},
277 'runtime': 0,
278 'slat': {'max': 0, 'mean': 0.0,
279 'min': 0, 'stddev': 0.0}
280 },
281 'usr_cpu': 0.23,
282 'write': {'bw': 0,
283 'bw_agg': 0,
284 'bw_dev': 0,
285 'bw_max': 0,
286 'bw_mean': 0,
287 'bw_min': 0,
288 'clat': {'max': 0, 'mean': 0,
289 'min': 0, 'stddev': 0},
290 'io_bytes': 0,
291 'iops': 0,
292 'lat': {'max': 0, 'mean': 0,
293 'min': 0, 'stddev': 0},
294 'runtime': 0,
295 'slat': {'max': 0, 'mean': 0.0,
296 'min': 0, 'stddev': 0.0}
297 }
298 }
299
300 if cfg['rw'] in ('read', 'randread'):
301 key = 'read'
302 elif cfg['rw'] in ('write', 'randwrite'):
303 key = 'write'
304 else:
305 raise ValueError("Uknown op type {0}".format(key))
306
307 res[key]['bw'] = bw
308 res[key]['iops'] = iops
309 res[key]['runtime'] = 30
310 res[key]['io_bytes'] = res[key]['runtime'] * bw
311 res[key]['bw_agg'] = bw
312 res[key]['bw_dev'] = bw / 30
313 res[key]['bw_max'] = bw * 1.5
314 res[key]['bw_min'] = bw / 1.5
315 res[key]['bw_mean'] = bw
316 res[key]['clat'] = {'max': curr_ulat * 10, 'mean': curr_ulat,
317 'min': curr_ulat / 2, 'stddev': curr_ulat}
318 res[key]['lat'] = res[key]['clat'].copy()
319 res[key]['slat'] = res[key]['clat'].copy()
320
321 parsed_out.append(res)
322
323 return zip(parsed_out, bconf)
324
325
koder aka kdanilovda45e882015-04-06 02:24:42 +0300326def do_run_fio(bconf):
327 benchmark_config = format_fio_config(bconf)
328 cmd = ["fio", "--output-format=json", "-"]
329 p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
330 stdout=subprocess.PIPE,
331 stderr=subprocess.STDOUT)
332
333 # set timeout
334 raw_out, _ = p.communicate(benchmark_config)
335
336 try:
337 parsed_out = json.loads(raw_out)["jobs"]
338 except Exception:
339 msg = "Can't parse fio output: {0!r}\nError: {1}"
340 raise ValueError(msg.format(raw_out, traceback.format_exc()))
341
342 return zip(parsed_out, bconf)
343
koder aka kdanilovda45e882015-04-06 02:24:42 +0300344# limited by fio
345MAX_JOBS = 1000
346
347
348def next_test_portion(whole_conf, runcycle):
349 jcount = 0
350 runtime = 0
351 bconf = []
352
353 for pos, (name, sec) in enumerate(whole_conf):
354 jc = int(sec.get('numjobs', '1'))
355
356 if runcycle is not None:
357 curr_task_time = calculate_execution_time([(name, sec)])
358 else:
359 curr_task_time = 0
360
361 if jc > MAX_JOBS:
362 err_templ = "Can't process job {0!r} - too large numjobs"
363 raise ValueError(err_templ.format(name))
364
365 if runcycle is not None and len(bconf) != 0:
366 rc_ok = curr_task_time + runtime <= runcycle
367 else:
368 rc_ok = True
369
370 if jc + jcount <= MAX_JOBS and rc_ok:
371 runtime += curr_task_time
372 jcount += jc
373 bconf.append((name, sec))
374 continue
375
376 assert len(bconf) != 0
377 yield bconf
378
379 runtime = curr_task_time
380 jcount = jc
381 bconf = [(name, sec)]
382
383 if bconf != []:
384 yield bconf
385
386
koder aka kdanilov652cd802015-04-13 12:21:07 +0300387def get_test_sync_mode(jconfig):
388 is_sync = jconfig.get("sync", "0") == "1"
389 is_direct = jconfig.get("direct_io", "0") == "1"
390
391 if is_sync and is_direct:
392 return 'sd'
393 elif is_sync:
394 return 's'
395 elif is_direct:
396 return 'd'
397 else:
398 return 'a'
399
400
koder aka kdanilovda45e882015-04-06 02:24:42 +0300401def add_job_results(jname, job_output, jconfig, res):
402 if job_output['write']['iops'] != 0:
403 raw_result = job_output['write']
404 else:
405 raw_result = job_output['read']
406
407 if jname not in res:
408 j_res = {}
409 j_res["action"] = jconfig["rw"]
koder aka kdanilov652cd802015-04-13 12:21:07 +0300410 j_res["sync_mode"] = get_test_sync_mode(jconfig)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300411 j_res["concurence"] = int(jconfig.get("numjobs", 1))
koder aka kdanilov2e928022015-04-08 13:47:15 +0300412 j_res["blocksize"] = jconfig["blocksize"]
koder aka kdanilovda45e882015-04-06 02:24:42 +0300413 j_res["jobname"] = job_output["jobname"]
koder aka kdanilov66839a92015-04-11 13:22:31 +0300414 j_res["timings"] = [int(jconfig.get("runtime", 0)),
415 int(jconfig.get("ramp_time", 0))]
koder aka kdanilovda45e882015-04-06 02:24:42 +0300416 else:
417 j_res = res[jname]
418 assert j_res["action"] == jconfig["rw"]
koder aka kdanilov652cd802015-04-13 12:21:07 +0300419 assert j_res["sync_mode"] == get_test_sync_mode(jconfig)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300420 assert j_res["concurence"] == int(jconfig.get("numjobs", 1))
koder aka kdanilov2e928022015-04-08 13:47:15 +0300421 assert j_res["blocksize"] == jconfig["blocksize"]
koder aka kdanilovda45e882015-04-06 02:24:42 +0300422 assert j_res["jobname"] == job_output["jobname"]
koder aka kdanilov652cd802015-04-13 12:21:07 +0300423
424 # ramp part is skipped for all tests, except first
425 # assert j_res["timings"] == (jconfig.get("runtime"),
426 # jconfig.get("ramp_time"))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300427
428 def j_app(name, x):
429 j_res.setdefault(name, []).append(x)
430
431 # 'bw_dev bw_mean bw_max bw_min'.split()
koder aka kdanilov652cd802015-04-13 12:21:07 +0300432 # probably fix fio bug - iops is scaled to joncount, but bw - isn't
433 j_app("bw_mean", raw_result["bw_mean"] * j_res["concurence"])
koder aka kdanilovda45e882015-04-06 02:24:42 +0300434 j_app("iops", raw_result["iops"])
435 j_app("lat", raw_result["lat"]["mean"])
436 j_app("clat", raw_result["clat"]["mean"])
437 j_app("slat", raw_result["slat"]["mean"])
438
439 res[jname] = j_res
440
441
442def run_fio(benchmark_config,
443 params,
444 runcycle=None,
445 raw_results_func=None,
koder aka kdanilovb896f692015-04-07 14:57:55 +0300446 skip_tests=0,
447 fake_fio=False):
koder aka kdanilovda45e882015-04-06 02:24:42 +0300448
449 whole_conf = list(parse_fio_config_full(benchmark_config, params))
450 whole_conf = whole_conf[skip_tests:]
451 res = {}
452 curr_test_num = skip_tests
koder aka kdanilov2e928022015-04-08 13:47:15 +0300453 executed_tests = 0
koder aka kdanilovda45e882015-04-06 02:24:42 +0300454 try:
455 for bconf in next_test_portion(whole_conf, runcycle):
koder aka kdanilovb896f692015-04-07 14:57:55 +0300456
457 if fake_fio:
458 res_cfg_it = do_run_fio_fake(bconf)
459 else:
460 res_cfg_it = do_run_fio(bconf)
461
koder aka kdanilovda45e882015-04-06 02:24:42 +0300462 res_cfg_it = enumerate(res_cfg_it, curr_test_num)
463
464 for curr_test_num, (job_output, (jname, jconfig)) in res_cfg_it:
koder aka kdanilov2e928022015-04-08 13:47:15 +0300465 executed_tests += 1
koder aka kdanilovda45e882015-04-06 02:24:42 +0300466 if raw_results_func is not None:
koder aka kdanilov2e928022015-04-08 13:47:15 +0300467 raw_results_func(executed_tests,
koder aka kdanilovda45e882015-04-06 02:24:42 +0300468 [job_output, jname, jconfig])
469
koder aka kdanilovb896f692015-04-07 14:57:55 +0300470 assert jname == job_output["jobname"], \
471 "{0} != {1}".format(jname, job_output["jobname"])
koder aka kdanilovda45e882015-04-06 02:24:42 +0300472
473 if jname.startswith('_'):
474 continue
475
476 add_job_results(jname, job_output, jconfig, res)
477
478 except (SystemExit, KeyboardInterrupt):
koder aka kdanilov652cd802015-04-13 12:21:07 +0300479 raise
koder aka kdanilovda45e882015-04-06 02:24:42 +0300480
481 except Exception:
482 traceback.print_exc()
483
koder aka kdanilov2e928022015-04-08 13:47:15 +0300484 return res, executed_tests
koder aka kdanilovda45e882015-04-06 02:24:42 +0300485
486
487def run_benchmark(binary_tp, *argv, **kwargs):
488 if 'fio' == binary_tp:
489 return run_fio(*argv, **kwargs)
490 raise ValueError("Unknown behcnmark {0}".format(binary_tp))
491
492
koder aka kdanilov652cd802015-04-13 12:21:07 +0300493def read_config(fd, timeout=10):
494 job_cfg = ""
495 etime = time.time() + timeout
496 while True:
497 wtime = etime - time.time()
498 if wtime <= 0:
499 raise IOError("No config provided")
500
501 r, w, x = select.select([fd], [], [], wtime)
502 if len(r) == 0:
503 raise IOError("No config provided")
504
505 char = fd.read(1)
506 if '' == char:
507 return job_cfg
508
509 job_cfg += char
510
511
512def estimate_cfg(job_cfg, params):
513 bconf = list(parse_fio_config_full(job_cfg, params))
514 return calculate_execution_time(bconf)
515
516
517def sec_to_str(seconds):
518 h = seconds // 3600
519 m = (seconds % 3600) // 60
520 s = seconds % 60
521 return "{0}:{1:02d}:{2:02d}".format(h, m, s)
522
523
koder aka kdanilovda45e882015-04-06 02:24:42 +0300524def parse_args(argv):
525 parser = argparse.ArgumentParser(
526 description="Run fio' and return result")
527 parser.add_argument("--type", metavar="BINARY_TYPE",
528 choices=['fio'], default='fio',
529 help=argparse.SUPPRESS)
530 parser.add_argument("--start-at", metavar="START_AT_UTC", type=int,
531 help="Start execution at START_AT_UTC")
532 parser.add_argument("--json", action="store_true", default=False,
533 help="Json output format")
534 parser.add_argument("--output", default='-', metavar="FILE_PATH",
535 help="Store results to FILE_PATH")
536 parser.add_argument("--estimate", action="store_true", default=False,
537 help="Only estimate task execution time")
538 parser.add_argument("--compile", action="store_true", default=False,
539 help="Compile config file to fio config")
540 parser.add_argument("--num-tests", action="store_true", default=False,
541 help="Show total number of tests")
542 parser.add_argument("--runcycle", type=int, default=None,
543 metavar="MAX_CYCLE_SECONDS",
544 help="Max cycle length in seconds")
545 parser.add_argument("--show-raw-results", action='store_true',
546 default=False, help="Output raw input and results")
547 parser.add_argument("--skip-tests", type=int, default=0, metavar="NUM",
548 help="Skip NUM tests")
koder aka kdanilovb896f692015-04-07 14:57:55 +0300549 parser.add_argument("--faked-fio", action='store_true',
550 default=False, help="Emulate fio with 0 test time")
koder aka kdanilovda45e882015-04-06 02:24:42 +0300551 parser.add_argument("--params", nargs="*", metavar="PARAM=VAL",
552 default=[],
553 help="Provide set of pairs PARAM=VAL to" +
554 "format into job description")
555 parser.add_argument("jobfile")
556 return parser.parse_args(argv)
557
558
koder aka kdanilovda45e882015-04-06 02:24:42 +0300559def main(argv):
560 argv_obj = parse_args(argv)
561
562 if argv_obj.jobfile == '-':
563 job_cfg = read_config(sys.stdin)
564 else:
565 job_cfg = open(argv_obj.jobfile).read()
566
567 if argv_obj.output == '-':
568 out_fd = sys.stdout
569 else:
570 out_fd = open(argv_obj.output, "w")
571
572 params = {}
573 for param_val in argv_obj.params:
574 assert '=' in param_val
575 name, val = param_val.split("=", 1)
576 params[name] = val
577
koder aka kdanilov652cd802015-04-13 12:21:07 +0300578 if argv_obj.estimate:
579 print sec_to_str(estimate_cfg(job_cfg, params))
580 return 0
581
582 if argv_obj.num_tests or argv_obj.compile:
koder aka kdanilovda45e882015-04-06 02:24:42 +0300583 bconf = list(parse_fio_config_full(job_cfg, params))
584 bconf = bconf[argv_obj.skip_tests:]
585
586 if argv_obj.compile:
587 out_fd.write(format_fio_config(bconf))
588 out_fd.write("\n")
589
590 if argv_obj.num_tests:
591 print len(bconf)
592
koder aka kdanilovda45e882015-04-06 02:24:42 +0300593 return 0
594
595 if argv_obj.start_at is not None:
596 ctime = time.time()
597 if argv_obj.start_at >= ctime:
598 time.sleep(ctime - argv_obj.start_at)
599
600 def raw_res_func(test_num, data):
601 pref = "========= RAW_RESULTS({0}) =========\n".format(test_num)
602 out_fd.write(pref)
603 out_fd.write(json.dumps(data))
604 out_fd.write("\n========= END OF RAW_RESULTS =========\n")
605 out_fd.flush()
606
607 rrfunc = raw_res_func if argv_obj.show_raw_results else None
608
609 stime = time.time()
610 job_res, num_tests = run_benchmark(argv_obj.type,
611 job_cfg,
612 params,
613 argv_obj.runcycle,
614 rrfunc,
koder aka kdanilovb896f692015-04-07 14:57:55 +0300615 argv_obj.skip_tests,
616 argv_obj.faked_fio)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300617 etime = time.time()
618
koder aka kdanilov652cd802015-04-13 12:21:07 +0300619 res = {'__meta__': {'raw_cfg': job_cfg, 'params': params}, 'res': job_res}
koder aka kdanilovda45e882015-04-06 02:24:42 +0300620
621 oformat = 'json' if argv_obj.json else 'eval'
koder aka kdanilov652cd802015-04-13 12:21:07 +0300622 out_fd.write("\nRun {0} tests in {1} seconds\n".format(num_tests,
623 int(etime - stime)))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300624 out_fd.write("========= RESULTS(format={0}) =========\n".format(oformat))
625 if argv_obj.json:
626 out_fd.write(json.dumps(res))
627 else:
628 out_fd.write(pprint.pformat(res) + "\n")
629 out_fd.write("\n========= END OF RESULTS =========\n".format(oformat))
630
631 return 0
632
633
634if __name__ == '__main__':
635 exit(main(sys.argv[1:]))