blob: d15d18ecd7b13a2568076f5dd44779750061fb44 [file] [log] [blame]
koder aka kdanilovda45e882015-04-06 02:24:42 +03001import sys
2import time
3import json
koder aka kdanilovb896f692015-04-07 14:57:55 +03004import random
koder aka kdanilovda45e882015-04-06 02:24:42 +03005import select
6import pprint
7import argparse
8import traceback
9import subprocess
10import itertools
11from collections import OrderedDict
12
13
14SECTION = 0
15SETTING = 1
16
17
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030018def get_test_sync_mode(config):
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030019 try:
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030020 return config['sync_mode']
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030021 except KeyError:
22 pass
23
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030024 is_sync = config.get("sync", "0") == "1"
25 is_direct = config.get("direct", "0") == "1"
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030026
27 if is_sync and is_direct:
28 return 'x'
29 elif is_sync:
30 return 's'
31 elif is_direct:
32 return 'd'
33 else:
34 return 'a'
35
36
koder aka kdanilovda45e882015-04-06 02:24:42 +030037def get_test_summary(params):
38 rw = {"randread": "rr",
39 "randwrite": "rw",
40 "read": "sr",
41 "write": "sw"}[params["rw"]]
42
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030043 sync_mode = get_test_sync_mode(params)
44 th_count = params.get('numjobs')
45 if th_count is None:
46 th_count = params.get('concurence', '1')
47 th_count = int(th_count)
koder aka kdanilovda45e882015-04-06 02:24:42 +030048
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030049 return "{0}{1}{2}th{3}".format(rw,
50 sync_mode,
51 params['blocksize'],
52 th_count)
koder aka kdanilovda45e882015-04-06 02:24:42 +030053
54
55counter = [0]
56
57
koder aka kdanilove87ae652015-04-20 02:14:35 +030058def extract_iterables(vals):
59 iterable_names = []
60 iterable_values = []
61 rest = {}
62
63 for val_name, val in vals.items():
64 if val is None or not val.startswith('{%'):
65 rest[val_name] = val
66 else:
67 assert val.endswith("%}")
68 content = val[2:-2]
69 iterable_names.append(val_name)
70 iterable_values.append(list(i.strip() for i in content.split(',')))
71
72 return iterable_names, iterable_values, rest
73
74
75def format_params_into_section(sec, params, final=True):
76 processed_vals = {}
77
78 for val_name, val in sec.items():
79 if val is None:
80 processed_vals[val_name] = val
81 else:
82 try:
83 processed_vals[val_name] = val.format(**params)
84 except KeyError:
85 if final:
86 raise
87 processed_vals[val_name] = val
88
89 return processed_vals
90
91
koder aka kdanilovda45e882015-04-06 02:24:42 +030092def process_section(name, vals, defaults, format_params):
93 vals = vals.copy()
94 params = format_params.copy()
95
96 if '*' in name:
97 name, repeat = name.split('*')
98 name = name.strip()
99 repeat = int(repeat.format(**params))
100 else:
101 repeat = 1
102
103 # this code can be optimized
koder aka kdanilove87ae652015-04-20 02:14:35 +0300104 iterable_names, iterable_values, processed_vals = extract_iterables(vals)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300105
koder aka kdanilov2e928022015-04-08 13:47:15 +0300106 group_report_err_msg = "Group reporting should be set if numjobs != 1"
107
koder aka kdanilovb896f692015-04-07 14:57:55 +0300108 if iterable_values == []:
koder aka kdanilove87ae652015-04-20 02:14:35 +0300109 processed_vals = format_params_into_section(processed_vals, params,
110 final=False)
koder aka kdanilovb896f692015-04-07 14:57:55 +0300111 params['UNIQ'] = 'UN{0}'.format(counter[0])
112 counter[0] += 1
113 params['TEST_SUMM'] = get_test_summary(processed_vals)
koder aka kdanilov2e928022015-04-08 13:47:15 +0300114
koder aka kdanilove87ae652015-04-20 02:14:35 +0300115 num_jobs = int(processed_vals.get('numjobs', '1'))
116 fsize = to_bytes(processed_vals['size'])
117 params['PER_TH_OFFSET'] = fsize // num_jobs
118
119 processed_vals = format_params_into_section(processed_vals, params,
120 final=True)
121
122 if num_jobs != 1:
koder aka kdanilov2e928022015-04-08 13:47:15 +0300123 assert 'group_reporting' in processed_vals, group_report_err_msg
124
koder aka kdanilov652cd802015-04-13 12:21:07 +0300125 ramp_time = processed_vals.get('ramp_time')
koder aka kdanilovb896f692015-04-07 14:57:55 +0300126 for i in range(repeat):
koder aka kdanilov2e928022015-04-08 13:47:15 +0300127 yield name.format(**params), processed_vals.copy()
koder aka kdanilov652cd802015-04-13 12:21:07 +0300128
129 if 'ramp_time' in processed_vals:
130 del processed_vals['ramp_time']
131
132 if ramp_time is not None:
133 processed_vals['ramp_time'] = ramp_time
koder aka kdanilovb896f692015-04-07 14:57:55 +0300134 else:
135 for it_vals in itertools.product(*iterable_values):
koder aka kdanilove87ae652015-04-20 02:14:35 +0300136 processed_vals = format_params_into_section(processed_vals, params,
137 final=False)
138
koder aka kdanilovb896f692015-04-07 14:57:55 +0300139 processed_vals.update(dict(zip(iterable_names, it_vals)))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300140 params['UNIQ'] = 'UN{0}'.format(counter[0])
141 counter[0] += 1
142 params['TEST_SUMM'] = get_test_summary(processed_vals)
koder aka kdanilov2e928022015-04-08 13:47:15 +0300143
koder aka kdanilove87ae652015-04-20 02:14:35 +0300144 num_jobs = int(processed_vals.get('numjobs', '1'))
145 fsize = to_bytes(processed_vals['size'])
146 params['PER_TH_OFFSET'] = fsize // num_jobs
147
148 processed_vals = format_params_into_section(processed_vals, params,
149 final=True)
150
koder aka kdanilov2e928022015-04-08 13:47:15 +0300151 if processed_vals.get('numjobs', '1') != '1':
152 assert 'group_reporting' in processed_vals,\
153 group_report_err_msg
154
koder aka kdanilov66839a92015-04-11 13:22:31 +0300155 ramp_time = processed_vals.get('ramp_time')
156
koder aka kdanilovb896f692015-04-07 14:57:55 +0300157 for i in range(repeat):
158 yield name.format(**params), processed_vals.copy()
koder aka kdanilov66839a92015-04-11 13:22:31 +0300159 if 'ramp_time' in processed_vals:
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300160 processed_vals['_ramp_time'] = ramp_time
161 processed_vals.pop('ramp_time')
koder aka kdanilov66839a92015-04-11 13:22:31 +0300162
163 if ramp_time is not None:
164 processed_vals['ramp_time'] = ramp_time
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300165 processed_vals.pop('_ramp_time')
koder aka kdanilovda45e882015-04-06 02:24:42 +0300166
167
168def calculate_execution_time(combinations):
169 time = 0
170 for _, params in combinations:
171 time += int(params.get('ramp_time', 0))
koder aka kdanilove87ae652015-04-20 02:14:35 +0300172 time += int(params.get('_ramp_time', 0))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300173 time += int(params.get('runtime', 0))
174 return time
175
176
177def parse_fio_config_full(fio_cfg, params=None):
178 defaults = {}
179 format_params = {}
180
181 if params is None:
182 ext_params = {}
183 else:
184 ext_params = params.copy()
185
186 curr_section = None
187 curr_section_name = None
188
189 for tp, name, val in parse_fio_config_iter(fio_cfg):
190 if tp == SECTION:
191 non_def = curr_section_name != 'defaults'
192 if curr_section_name is not None and non_def:
193 format_params.update(ext_params)
194 for sec in process_section(curr_section_name,
195 curr_section,
196 defaults,
197 format_params):
198 yield sec
199
200 if name == 'defaults':
201 curr_section = defaults
202 else:
203 curr_section = OrderedDict()
204 curr_section.update(defaults)
205 curr_section_name = name
206
207 else:
208 assert tp == SETTING
209 assert curr_section_name is not None, "no section name"
210 if name == name.upper():
211 assert curr_section_name == 'defaults'
212 format_params[name] = val
213 else:
214 curr_section[name] = val
215
216 if curr_section_name is not None and curr_section_name != 'defaults':
217 format_params.update(ext_params)
218 for sec in process_section(curr_section_name,
219 curr_section,
220 defaults,
221 format_params):
222 yield sec
223
224
225def parse_fio_config_iter(fio_cfg):
226 for lineno, line in enumerate(fio_cfg.split("\n")):
227 try:
228 line = line.strip()
229
230 if line.startswith("#") or line.startswith(";"):
231 continue
232
233 if line == "":
234 continue
235
236 if line.startswith('['):
237 assert line.endswith(']'), "name should ends with ]"
238 yield SECTION, line[1:-1], None
239 elif '=' in line:
240 opt_name, opt_val = line.split('=', 1)
241 yield SETTING, opt_name.strip(), opt_val.strip()
242 else:
243 yield SETTING, line, None
244 except Exception as exc:
245 pref = "During parsing line number {0}\n".format(lineno)
246 raise ValueError(pref + exc.message)
247
248
249def format_fio_config(fio_cfg):
250 res = ""
251 for pos, (name, section) in enumerate(fio_cfg):
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300252 if name.startswith('_'):
253 continue
254
koder aka kdanilovda45e882015-04-06 02:24:42 +0300255 if pos != 0:
256 res += "\n"
257
258 res += "[{0}]\n".format(name)
259 for opt_name, opt_val in section.items():
260 if opt_val is None:
261 res += opt_name + "\n"
262 else:
263 res += "{0}={1}\n".format(opt_name, opt_val)
264 return res
265
266
koder aka kdanilovb896f692015-04-07 14:57:55 +0300267count = 0
268
269
270def to_bytes(sz):
271 sz = sz.lower()
272 try:
273 return int(sz)
274 except ValueError:
275 if sz[-1] == 'm':
276 return (1024 ** 2) * int(sz[:-1])
277 if sz[-1] == 'k':
278 return 1024 * int(sz[:-1])
koder aka kdanilove87ae652015-04-20 02:14:35 +0300279 if sz[-1] == 'g':
280 return (1024 ** 3) * int(sz[:-1])
koder aka kdanilovb896f692015-04-07 14:57:55 +0300281 raise
282
283
koder aka kdanilovb896f692015-04-07 14:57:55 +0300284def do_run_fio_fake(bconf):
koder aka kdanilov66839a92015-04-11 13:22:31 +0300285 def estimate_iops(sz, bw, lat):
286 return 1 / (lat + float(sz) / bw)
koder aka kdanilovb896f692015-04-07 14:57:55 +0300287 global count
288 count += 1
289 parsed_out = []
290
291 BW = 120.0 * (1024 ** 2)
292 LAT = 0.003
293
294 for name, cfg in bconf:
295 sz = to_bytes(cfg['blocksize'])
296 curr_lat = LAT * ((random.random() - 0.5) * 0.1 + 1)
297 curr_ulat = curr_lat * 1000000
298 curr_bw = BW * ((random.random() - 0.5) * 0.1 + 1)
299 iops = estimate_iops(sz, curr_bw, curr_lat)
300 bw = iops * sz
301
302 res = {'ctx': 10683,
303 'error': 0,
304 'groupid': 0,
305 'jobname': name,
306 'majf': 0,
307 'minf': 30,
308 'read': {'bw': 0,
309 'bw_agg': 0.0,
310 'bw_dev': 0.0,
311 'bw_max': 0,
312 'bw_mean': 0.0,
313 'bw_min': 0,
314 'clat': {'max': 0,
315 'mean': 0.0,
316 'min': 0,
317 'stddev': 0.0},
318 'io_bytes': 0,
319 'iops': 0,
320 'lat': {'max': 0, 'mean': 0.0,
321 'min': 0, 'stddev': 0.0},
322 'runtime': 0,
323 'slat': {'max': 0, 'mean': 0.0,
324 'min': 0, 'stddev': 0.0}
325 },
326 'sys_cpu': 0.64,
327 'trim': {'bw': 0,
328 'bw_agg': 0.0,
329 'bw_dev': 0.0,
330 'bw_max': 0,
331 'bw_mean': 0.0,
332 'bw_min': 0,
333 'clat': {'max': 0,
334 'mean': 0.0,
335 'min': 0,
336 'stddev': 0.0},
337 'io_bytes': 0,
338 'iops': 0,
339 'lat': {'max': 0, 'mean': 0.0,
340 'min': 0, 'stddev': 0.0},
341 'runtime': 0,
342 'slat': {'max': 0, 'mean': 0.0,
343 'min': 0, 'stddev': 0.0}
344 },
345 'usr_cpu': 0.23,
346 'write': {'bw': 0,
347 'bw_agg': 0,
348 'bw_dev': 0,
349 'bw_max': 0,
350 'bw_mean': 0,
351 'bw_min': 0,
352 'clat': {'max': 0, 'mean': 0,
353 'min': 0, 'stddev': 0},
354 'io_bytes': 0,
355 'iops': 0,
356 'lat': {'max': 0, 'mean': 0,
357 'min': 0, 'stddev': 0},
358 'runtime': 0,
359 'slat': {'max': 0, 'mean': 0.0,
360 'min': 0, 'stddev': 0.0}
361 }
362 }
363
364 if cfg['rw'] in ('read', 'randread'):
365 key = 'read'
366 elif cfg['rw'] in ('write', 'randwrite'):
367 key = 'write'
368 else:
369 raise ValueError("Uknown op type {0}".format(key))
370
371 res[key]['bw'] = bw
372 res[key]['iops'] = iops
373 res[key]['runtime'] = 30
374 res[key]['io_bytes'] = res[key]['runtime'] * bw
375 res[key]['bw_agg'] = bw
376 res[key]['bw_dev'] = bw / 30
377 res[key]['bw_max'] = bw * 1.5
378 res[key]['bw_min'] = bw / 1.5
379 res[key]['bw_mean'] = bw
380 res[key]['clat'] = {'max': curr_ulat * 10, 'mean': curr_ulat,
381 'min': curr_ulat / 2, 'stddev': curr_ulat}
382 res[key]['lat'] = res[key]['clat'].copy()
383 res[key]['slat'] = res[key]['clat'].copy()
384
385 parsed_out.append(res)
386
387 return zip(parsed_out, bconf)
388
389
koder aka kdanilovda45e882015-04-06 02:24:42 +0300390def do_run_fio(bconf):
391 benchmark_config = format_fio_config(bconf)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300392 cmd = ["fio", "--output-format=json", "--alloc-size=262144", "-"]
koder aka kdanilove87ae652015-04-20 02:14:35 +0300393 p = subprocess.Popen(cmd,
394 stdin=subprocess.PIPE,
koder aka kdanilovda45e882015-04-06 02:24:42 +0300395 stdout=subprocess.PIPE,
koder aka kdanilove87ae652015-04-20 02:14:35 +0300396 stderr=subprocess.PIPE)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300397
398 # set timeout
koder aka kdanilove87ae652015-04-20 02:14:35 +0300399 raw_out, raw_err = p.communicate(benchmark_config)
400
401 if 0 != p.returncode:
402 msg = "Fio failed with code: {0}\nOutput={1}"
403 raise OSError(msg.format(p.returncode, raw_err))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300404
405 try:
406 parsed_out = json.loads(raw_out)["jobs"]
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300407 except KeyError:
408 msg = "Can't parse fio output {0!r}: no 'jobs' found"
409 raw_out = raw_out[:100]
410 raise ValueError(msg.format(raw_out))
411
412 except Exception as exc:
koder aka kdanilovda45e882015-04-06 02:24:42 +0300413 msg = "Can't parse fio output: {0!r}\nError: {1}"
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300414 raw_out = raw_out[:100]
415 raise ValueError(msg.format(raw_out, exc.message))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300416
417 return zip(parsed_out, bconf)
418
koder aka kdanilovda45e882015-04-06 02:24:42 +0300419# limited by fio
420MAX_JOBS = 1000
421
422
koder aka kdanilove87ae652015-04-20 02:14:35 +0300423def next_test_portion(whole_conf, runcycle, cluster=False):
424 if cluster:
425 for name, sec in whole_conf:
426 if '_ramp_time' in sec:
427 sec['ramp_time'] = sec.pop('_ramp_time')
428 yield [(name, sec)]
429 return
430
koder aka kdanilovda45e882015-04-06 02:24:42 +0300431 jcount = 0
432 runtime = 0
433 bconf = []
434
435 for pos, (name, sec) in enumerate(whole_conf):
436 jc = int(sec.get('numjobs', '1'))
437
438 if runcycle is not None:
439 curr_task_time = calculate_execution_time([(name, sec)])
440 else:
441 curr_task_time = 0
442
443 if jc > MAX_JOBS:
444 err_templ = "Can't process job {0!r} - too large numjobs"
445 raise ValueError(err_templ.format(name))
446
447 if runcycle is not None and len(bconf) != 0:
448 rc_ok = curr_task_time + runtime <= runcycle
449 else:
450 rc_ok = True
451
452 if jc + jcount <= MAX_JOBS and rc_ok:
453 runtime += curr_task_time
454 jcount += jc
455 bconf.append((name, sec))
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300456 if '_ramp_time' in sec:
457 del sec['_ramp_time']
koder aka kdanilovda45e882015-04-06 02:24:42 +0300458 continue
459
460 assert len(bconf) != 0
461 yield bconf
462
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300463 if '_ramp_time' in sec:
464 sec['ramp_time'] = sec.pop('_ramp_time')
465 curr_task_time = calculate_execution_time([(name, sec)])
466
koder aka kdanilovda45e882015-04-06 02:24:42 +0300467 runtime = curr_task_time
468 jcount = jc
469 bconf = [(name, sec)]
470
471 if bconf != []:
472 yield bconf
473
474
475def add_job_results(jname, job_output, jconfig, res):
476 if job_output['write']['iops'] != 0:
477 raw_result = job_output['write']
478 else:
479 raw_result = job_output['read']
480
481 if jname not in res:
482 j_res = {}
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300483 j_res["rw"] = jconfig["rw"]
koder aka kdanilov652cd802015-04-13 12:21:07 +0300484 j_res["sync_mode"] = get_test_sync_mode(jconfig)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300485 j_res["concurence"] = int(jconfig.get("numjobs", 1))
koder aka kdanilov2e928022015-04-08 13:47:15 +0300486 j_res["blocksize"] = jconfig["blocksize"]
koder aka kdanilovda45e882015-04-06 02:24:42 +0300487 j_res["jobname"] = job_output["jobname"]
koder aka kdanilov66839a92015-04-11 13:22:31 +0300488 j_res["timings"] = [int(jconfig.get("runtime", 0)),
489 int(jconfig.get("ramp_time", 0))]
koder aka kdanilovda45e882015-04-06 02:24:42 +0300490 else:
491 j_res = res[jname]
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300492 assert j_res["rw"] == jconfig["rw"]
493 assert j_res["rw"] == jconfig["rw"]
koder aka kdanilov652cd802015-04-13 12:21:07 +0300494 assert j_res["sync_mode"] == get_test_sync_mode(jconfig)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300495 assert j_res["concurence"] == int(jconfig.get("numjobs", 1))
koder aka kdanilov2e928022015-04-08 13:47:15 +0300496 assert j_res["blocksize"] == jconfig["blocksize"]
koder aka kdanilovda45e882015-04-06 02:24:42 +0300497 assert j_res["jobname"] == job_output["jobname"]
koder aka kdanilov652cd802015-04-13 12:21:07 +0300498
499 # ramp part is skipped for all tests, except first
500 # assert j_res["timings"] == (jconfig.get("runtime"),
501 # jconfig.get("ramp_time"))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300502
503 def j_app(name, x):
504 j_res.setdefault(name, []).append(x)
505
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300506 j_app("bw", raw_result["bw"])
koder aka kdanilovda45e882015-04-06 02:24:42 +0300507 j_app("iops", raw_result["iops"])
508 j_app("lat", raw_result["lat"]["mean"])
509 j_app("clat", raw_result["clat"]["mean"])
510 j_app("slat", raw_result["slat"]["mean"])
511
512 res[jname] = j_res
513
514
koder aka kdanilove87ae652015-04-20 02:14:35 +0300515def compile(benchmark_config, params, skip=0, runcycle=None, cluster=False):
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300516 whole_conf = list(parse_fio_config_full(benchmark_config, params))
koder aka kdanilove87ae652015-04-20 02:14:35 +0300517 whole_conf = whole_conf[skip:]
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300518 res = ""
519
koder aka kdanilove87ae652015-04-20 02:14:35 +0300520 for bconf in next_test_portion(whole_conf, runcycle, cluster=cluster):
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300521 res += format_fio_config(bconf)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300522 res += "\n#" + "-" * 50 + "\n\n"
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300523
524 return res
525
526
koder aka kdanilovda45e882015-04-06 02:24:42 +0300527def run_fio(benchmark_config,
528 params,
529 runcycle=None,
530 raw_results_func=None,
koder aka kdanilovb896f692015-04-07 14:57:55 +0300531 skip_tests=0,
koder aka kdanilove87ae652015-04-20 02:14:35 +0300532 fake_fio=False,
533 cluster=False):
koder aka kdanilovda45e882015-04-06 02:24:42 +0300534
535 whole_conf = list(parse_fio_config_full(benchmark_config, params))
536 whole_conf = whole_conf[skip_tests:]
537 res = {}
538 curr_test_num = skip_tests
koder aka kdanilov2e928022015-04-08 13:47:15 +0300539 executed_tests = 0
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300540 ok = True
koder aka kdanilovda45e882015-04-06 02:24:42 +0300541 try:
koder aka kdanilove87ae652015-04-20 02:14:35 +0300542 for bconf in next_test_portion(whole_conf, runcycle, cluster=cluster):
koder aka kdanilovb896f692015-04-07 14:57:55 +0300543
544 if fake_fio:
545 res_cfg_it = do_run_fio_fake(bconf)
546 else:
547 res_cfg_it = do_run_fio(bconf)
548
koder aka kdanilovda45e882015-04-06 02:24:42 +0300549 res_cfg_it = enumerate(res_cfg_it, curr_test_num)
550
551 for curr_test_num, (job_output, (jname, jconfig)) in res_cfg_it:
koder aka kdanilov2e928022015-04-08 13:47:15 +0300552 executed_tests += 1
koder aka kdanilovda45e882015-04-06 02:24:42 +0300553 if raw_results_func is not None:
koder aka kdanilov2e928022015-04-08 13:47:15 +0300554 raw_results_func(executed_tests,
koder aka kdanilovda45e882015-04-06 02:24:42 +0300555 [job_output, jname, jconfig])
556
koder aka kdanilovb896f692015-04-07 14:57:55 +0300557 assert jname == job_output["jobname"], \
558 "{0} != {1}".format(jname, job_output["jobname"])
koder aka kdanilovda45e882015-04-06 02:24:42 +0300559
560 if jname.startswith('_'):
561 continue
562
563 add_job_results(jname, job_output, jconfig, res)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300564 curr_test_num += 1
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300565 msg_template = "Done {0} tests from {1}. ETA: {2}"
566 exec_time = estimate_cfg(benchmark_config, params, curr_test_num)
567
568 print msg_template.format(curr_test_num - skip_tests,
569 len(whole_conf),
570 sec_to_str(exec_time))
571
koder aka kdanilovda45e882015-04-06 02:24:42 +0300572 except (SystemExit, KeyboardInterrupt):
koder aka kdanilov652cd802015-04-13 12:21:07 +0300573 raise
koder aka kdanilovda45e882015-04-06 02:24:42 +0300574
575 except Exception:
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300576 print "=========== ERROR ============="
koder aka kdanilovda45e882015-04-06 02:24:42 +0300577 traceback.print_exc()
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300578 print "======== END OF ERROR ========="
579 ok = False
koder aka kdanilovda45e882015-04-06 02:24:42 +0300580
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300581 return res, executed_tests, ok
koder aka kdanilovda45e882015-04-06 02:24:42 +0300582
583
584def run_benchmark(binary_tp, *argv, **kwargs):
585 if 'fio' == binary_tp:
586 return run_fio(*argv, **kwargs)
587 raise ValueError("Unknown behcnmark {0}".format(binary_tp))
588
589
koder aka kdanilov652cd802015-04-13 12:21:07 +0300590def read_config(fd, timeout=10):
591 job_cfg = ""
592 etime = time.time() + timeout
593 while True:
594 wtime = etime - time.time()
595 if wtime <= 0:
596 raise IOError("No config provided")
597
598 r, w, x = select.select([fd], [], [], wtime)
599 if len(r) == 0:
600 raise IOError("No config provided")
601
602 char = fd.read(1)
603 if '' == char:
604 return job_cfg
605
606 job_cfg += char
607
608
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300609def estimate_cfg(job_cfg, params, skip_tests=0):
610 bconf = list(parse_fio_config_full(job_cfg, params))[skip_tests:]
koder aka kdanilov652cd802015-04-13 12:21:07 +0300611 return calculate_execution_time(bconf)
612
613
614def sec_to_str(seconds):
615 h = seconds // 3600
616 m = (seconds % 3600) // 60
617 s = seconds % 60
618 return "{0}:{1:02d}:{2:02d}".format(h, m, s)
619
620
koder aka kdanilovda45e882015-04-06 02:24:42 +0300621def parse_args(argv):
622 parser = argparse.ArgumentParser(
623 description="Run fio' and return result")
624 parser.add_argument("--type", metavar="BINARY_TYPE",
625 choices=['fio'], default='fio',
626 help=argparse.SUPPRESS)
627 parser.add_argument("--start-at", metavar="START_AT_UTC", type=int,
628 help="Start execution at START_AT_UTC")
629 parser.add_argument("--json", action="store_true", default=False,
630 help="Json output format")
631 parser.add_argument("--output", default='-', metavar="FILE_PATH",
632 help="Store results to FILE_PATH")
633 parser.add_argument("--estimate", action="store_true", default=False,
634 help="Only estimate task execution time")
635 parser.add_argument("--compile", action="store_true", default=False,
636 help="Compile config file to fio config")
637 parser.add_argument("--num-tests", action="store_true", default=False,
638 help="Show total number of tests")
639 parser.add_argument("--runcycle", type=int, default=None,
640 metavar="MAX_CYCLE_SECONDS",
641 help="Max cycle length in seconds")
642 parser.add_argument("--show-raw-results", action='store_true',
643 default=False, help="Output raw input and results")
644 parser.add_argument("--skip-tests", type=int, default=0, metavar="NUM",
645 help="Skip NUM tests")
koder aka kdanilovb896f692015-04-07 14:57:55 +0300646 parser.add_argument("--faked-fio", action='store_true',
647 default=False, help="Emulate fio with 0 test time")
koder aka kdanilove87ae652015-04-20 02:14:35 +0300648 parser.add_argument("--cluster", action='store_true',
649 default=False, help="Apply cluster-test settings")
koder aka kdanilovda45e882015-04-06 02:24:42 +0300650 parser.add_argument("--params", nargs="*", metavar="PARAM=VAL",
651 default=[],
652 help="Provide set of pairs PARAM=VAL to" +
653 "format into job description")
654 parser.add_argument("jobfile")
655 return parser.parse_args(argv)
656
657
koder aka kdanilovda45e882015-04-06 02:24:42 +0300658def main(argv):
659 argv_obj = parse_args(argv)
660
661 if argv_obj.jobfile == '-':
662 job_cfg = read_config(sys.stdin)
663 else:
664 job_cfg = open(argv_obj.jobfile).read()
665
666 if argv_obj.output == '-':
667 out_fd = sys.stdout
668 else:
669 out_fd = open(argv_obj.output, "w")
670
671 params = {}
672 for param_val in argv_obj.params:
673 assert '=' in param_val
674 name, val = param_val.split("=", 1)
675 params[name] = val
676
koder aka kdanilov652cd802015-04-13 12:21:07 +0300677 if argv_obj.estimate:
678 print sec_to_str(estimate_cfg(job_cfg, params))
679 return 0
680
681 if argv_obj.num_tests or argv_obj.compile:
koder aka kdanilovda45e882015-04-06 02:24:42 +0300682 if argv_obj.compile:
koder aka kdanilove87ae652015-04-20 02:14:35 +0300683 data = compile(job_cfg, params, argv_obj.skip_tests,
684 cluster=argv_obj.cluster)
685 out_fd.write(data)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300686 out_fd.write("\n")
687
688 if argv_obj.num_tests:
koder aka kdanilove87ae652015-04-20 02:14:35 +0300689 bconf = list(parse_fio_config_full(job_cfg, params,
690 argv_obj.cluster))
691 bconf = bconf[argv_obj.skip_tests:]
koder aka kdanilovda45e882015-04-06 02:24:42 +0300692 print len(bconf)
693
koder aka kdanilovda45e882015-04-06 02:24:42 +0300694 return 0
695
696 if argv_obj.start_at is not None:
697 ctime = time.time()
698 if argv_obj.start_at >= ctime:
699 time.sleep(ctime - argv_obj.start_at)
700
701 def raw_res_func(test_num, data):
702 pref = "========= RAW_RESULTS({0}) =========\n".format(test_num)
703 out_fd.write(pref)
704 out_fd.write(json.dumps(data))
705 out_fd.write("\n========= END OF RAW_RESULTS =========\n")
706 out_fd.flush()
707
708 rrfunc = raw_res_func if argv_obj.show_raw_results else None
709
710 stime = time.time()
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300711 job_res, num_tests, ok = run_benchmark(argv_obj.type,
712 job_cfg,
713 params,
714 argv_obj.runcycle,
715 rrfunc,
716 argv_obj.skip_tests,
koder aka kdanilove87ae652015-04-20 02:14:35 +0300717 argv_obj.faked_fio,
718 cluster=argv_obj.cluster)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300719 etime = time.time()
720
koder aka kdanilov652cd802015-04-13 12:21:07 +0300721 res = {'__meta__': {'raw_cfg': job_cfg, 'params': params}, 'res': job_res}
koder aka kdanilovda45e882015-04-06 02:24:42 +0300722
723 oformat = 'json' if argv_obj.json else 'eval'
koder aka kdanilov652cd802015-04-13 12:21:07 +0300724 out_fd.write("\nRun {0} tests in {1} seconds\n".format(num_tests,
725 int(etime - stime)))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300726 out_fd.write("========= RESULTS(format={0}) =========\n".format(oformat))
727 if argv_obj.json:
728 out_fd.write(json.dumps(res))
729 else:
730 out_fd.write(pprint.pformat(res) + "\n")
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300731 out_fd.write("\n========= END OF RESULTS =========\n")
koder aka kdanilovda45e882015-04-06 02:24:42 +0300732
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300733 return 0 if ok else 1
734
735
736def fake_main(x):
737 import yaml
738 time.sleep(60)
739 out_fd = sys.stdout
740 fname = "/tmp/perf_tests/metempirical_alisha/raw_results.yaml"
741 res = yaml.load(open(fname).read())[0][1]
742 out_fd.write("========= RESULTS(format=json) =========\n")
743 out_fd.write(json.dumps(res))
744 out_fd.write("\n========= END OF RESULTS =========\n")
koder aka kdanilovda45e882015-04-06 02:24:42 +0300745 return 0
746
747
748if __name__ == '__main__':
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300749 # exit(fake_main(sys.argv[1:]))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300750 exit(main(sys.argv[1:]))