| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 1 | import os | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 2 | import sys | 
|  | 3 | import time | 
|  | 4 | import json | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 5 | import copy | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 6 | import select | 
|  | 7 | import pprint | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 8 | import os.path | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 9 | import argparse | 
|  | 10 | import traceback | 
|  | 11 | import subprocess | 
|  | 12 | import itertools | 
|  | 13 | from collections import OrderedDict | 
|  | 14 |  | 
|  | 15 |  | 
|  | 16 | SECTION = 0 | 
|  | 17 | SETTING = 1 | 
|  | 18 |  | 
|  | 19 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 20 | class FioJobSection(object): | 
|  | 21 | def __init__(self, name): | 
|  | 22 | self.name = name | 
|  | 23 | self.vals = OrderedDict() | 
|  | 24 | self.format_params = {} | 
|  | 25 |  | 
|  | 26 | def copy(self): | 
|  | 27 | return copy.deepcopy(self) | 
|  | 28 |  | 
|  | 29 |  | 
|  | 30 | def to_bytes(sz): | 
|  | 31 | sz = sz.lower() | 
|  | 32 | try: | 
|  | 33 | return int(sz) | 
|  | 34 | except ValueError: | 
|  | 35 | if sz[-1] == 'm': | 
|  | 36 | return (1024 ** 2) * int(sz[:-1]) | 
|  | 37 | if sz[-1] == 'k': | 
|  | 38 | return 1024 * int(sz[:-1]) | 
|  | 39 | if sz[-1] == 'g': | 
|  | 40 | return (1024 ** 3) * int(sz[:-1]) | 
|  | 41 | raise | 
|  | 42 |  | 
|  | 43 |  | 
|  | 44 | def fio_config_lexer(fio_cfg): | 
|  | 45 | for lineno, line in enumerate(fio_cfg.split("\n")): | 
|  | 46 | try: | 
|  | 47 | line = line.strip() | 
|  | 48 |  | 
|  | 49 | if line.startswith("#") or line.startswith(";"): | 
|  | 50 | continue | 
|  | 51 |  | 
|  | 52 | if line == "": | 
|  | 53 | continue | 
|  | 54 |  | 
|  | 55 | if line.startswith('['): | 
|  | 56 | assert line.endswith(']'), "name should ends with ]" | 
|  | 57 | yield lineno, SECTION, line[1:-1], None | 
|  | 58 | elif '=' in line: | 
|  | 59 | opt_name, opt_val = line.split('=', 1) | 
|  | 60 | yield lineno, SETTING, opt_name.strip(), opt_val.strip() | 
|  | 61 | else: | 
|  | 62 | yield lineno, SETTING, line, None | 
|  | 63 | except Exception as exc: | 
| koder aka kdanilov | ec1b973 | 2015-04-23 20:43:29 +0300 | [diff] [blame] | 64 | pref = "During parsing line number {0}\n{1!s}".format(lineno, exc) | 
|  | 65 | raise ValueError(pref) | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 66 |  | 
|  | 67 |  | 
|  | 68 | def fio_config_parse(lexer_iter, format_params): | 
|  | 69 | orig_format_params_keys = set(format_params) | 
|  | 70 | format_params = format_params.copy() | 
|  | 71 | in_defaults = False | 
|  | 72 | curr_section = None | 
|  | 73 | defaults = OrderedDict() | 
|  | 74 |  | 
|  | 75 | for lineno, tp, name, val in lexer_iter: | 
|  | 76 | if tp == SECTION: | 
|  | 77 | if curr_section is not None: | 
|  | 78 | yield curr_section | 
|  | 79 |  | 
|  | 80 | if name == 'defaults': | 
|  | 81 | in_defaults = True | 
|  | 82 | curr_section = None | 
|  | 83 | else: | 
|  | 84 | in_defaults = False | 
|  | 85 | curr_section = FioJobSection(name) | 
|  | 86 | curr_section.format_params = format_params.copy() | 
|  | 87 | curr_section.vals = defaults.copy() | 
|  | 88 | else: | 
|  | 89 | assert tp == SETTING | 
|  | 90 | if name == name.upper(): | 
|  | 91 | msg = "Param not in default section in line " + str(lineno) | 
|  | 92 | assert in_defaults, msg | 
|  | 93 | if name not in orig_format_params_keys: | 
|  | 94 | # don't make parse_value for PARAMS | 
|  | 95 | # they would be parsed later | 
|  | 96 | # or this would breakes arrays | 
|  | 97 | format_params[name] = val | 
|  | 98 | elif in_defaults: | 
|  | 99 | defaults[name] = parse_value(val) | 
|  | 100 | else: | 
|  | 101 | msg = "data outside section, line " + str(lineno) | 
|  | 102 | assert curr_section is not None, msg | 
|  | 103 | curr_section.vals[name] = parse_value(val) | 
|  | 104 |  | 
|  | 105 | if curr_section is not None: | 
|  | 106 | yield curr_section | 
|  | 107 |  | 
|  | 108 |  | 
|  | 109 | def parse_value(val): | 
|  | 110 | if val is None: | 
|  | 111 | return None | 
|  | 112 |  | 
|  | 113 | try: | 
|  | 114 | return int(val) | 
|  | 115 | except ValueError: | 
|  | 116 | pass | 
|  | 117 |  | 
|  | 118 | try: | 
|  | 119 | return float(val) | 
|  | 120 | except ValueError: | 
|  | 121 | pass | 
|  | 122 |  | 
|  | 123 | if val.startswith('{%'): | 
|  | 124 | assert val.endswith("%}") | 
|  | 125 | content = val[2:-2] | 
|  | 126 | vals = list(i.strip() for i in content.split(',')) | 
|  | 127 | return map(parse_value, vals) | 
|  | 128 | return val | 
|  | 129 |  | 
|  | 130 |  | 
|  | 131 | def process_repeats(sec_iter): | 
|  | 132 |  | 
|  | 133 | for sec in sec_iter: | 
|  | 134 | if '*' in sec.name: | 
|  | 135 | msg = "Only one '*' allowed in section name" | 
|  | 136 | assert sec.name.count('*') == 1, msg | 
|  | 137 |  | 
|  | 138 | name, count = sec.name.split("*") | 
|  | 139 | sec.name = name.strip() | 
|  | 140 | count = count.strip() | 
|  | 141 |  | 
|  | 142 | try: | 
|  | 143 | count = int(count.strip().format(**sec.format_params)) | 
|  | 144 | except KeyError: | 
|  | 145 | raise ValueError("No parameter {0} given".format(count[1:-1])) | 
|  | 146 | except ValueError: | 
|  | 147 | msg = "Parameter {0} nas non-int value {1!r}" | 
|  | 148 | raise ValueError(msg.format(count[1:-1], | 
|  | 149 | count.format(**sec.format_params))) | 
|  | 150 |  | 
|  | 151 | yield sec | 
|  | 152 |  | 
|  | 153 | if 'ramp_time' in sec.vals: | 
|  | 154 | sec = sec.copy() | 
|  | 155 | sec.vals['_ramp_time'] = sec.vals.pop('ramp_time') | 
|  | 156 |  | 
|  | 157 | for _ in range(count - 1): | 
|  | 158 | yield sec.copy() | 
|  | 159 | else: | 
|  | 160 | yield sec | 
|  | 161 |  | 
|  | 162 |  | 
|  | 163 | def process_cycles(sec_iter): | 
|  | 164 | # insert parametrized cycles | 
|  | 165 | sec_iter = try_format_params_into_section(sec_iter) | 
|  | 166 |  | 
|  | 167 | for sec in sec_iter: | 
|  | 168 |  | 
|  | 169 | cycles_var_names = [] | 
|  | 170 | cycles_var_values = [] | 
|  | 171 |  | 
|  | 172 | for name, val in sec.vals.items(): | 
|  | 173 | if isinstance(val, list): | 
|  | 174 | cycles_var_names.append(name) | 
|  | 175 | cycles_var_values.append(val) | 
|  | 176 |  | 
|  | 177 | if len(cycles_var_names) == 0: | 
|  | 178 | yield sec | 
|  | 179 | else: | 
|  | 180 | for combination in itertools.product(*cycles_var_values): | 
|  | 181 | new_sec = sec.copy() | 
|  | 182 | new_sec.vals.update(zip(cycles_var_names, combination)) | 
|  | 183 | yield new_sec | 
|  | 184 |  | 
|  | 185 |  | 
|  | 186 | def try_format_params_into_section(sec_iter): | 
|  | 187 | for sec in sec_iter: | 
|  | 188 | params = sec.format_params | 
|  | 189 | for name, val in sec.vals.items(): | 
|  | 190 | if isinstance(val, basestring): | 
|  | 191 | try: | 
|  | 192 | sec.vals[name] = parse_value(val.format(**params)) | 
|  | 193 | except: | 
|  | 194 | pass | 
|  | 195 |  | 
|  | 196 | yield sec | 
|  | 197 |  | 
|  | 198 |  | 
|  | 199 | def format_params_into_section_finall(sec_iter, counter=[0]): | 
|  | 200 | group_report_err_msg = "Group reporting should be set if numjobs != 1" | 
|  | 201 |  | 
|  | 202 | for sec in sec_iter: | 
|  | 203 |  | 
|  | 204 | num_jobs = int(sec.vals.get('numjobs', '1')) | 
|  | 205 | if num_jobs != 1: | 
|  | 206 | assert 'group_reporting' in sec.vals, group_report_err_msg | 
|  | 207 |  | 
|  | 208 | params = sec.format_params | 
|  | 209 |  | 
|  | 210 | fsize = to_bytes(sec.vals['size']) | 
|  | 211 | params['PER_TH_OFFSET'] = fsize // num_jobs | 
|  | 212 |  | 
|  | 213 | for name, val in sec.vals.items(): | 
|  | 214 | if isinstance(val, basestring): | 
|  | 215 | sec.vals[name] = parse_value(val.format(**params)) | 
|  | 216 | else: | 
|  | 217 | assert isinstance(val, (int, float)) or val is None | 
|  | 218 |  | 
|  | 219 | params['UNIQ'] = 'UN{0}'.format(counter[0]) | 
|  | 220 | counter[0] += 1 | 
|  | 221 | params['TEST_SUMM'] = get_test_summary(sec.vals) | 
|  | 222 | sec.name = sec.name.format(**params) | 
|  | 223 |  | 
|  | 224 | yield sec | 
|  | 225 |  | 
|  | 226 |  | 
|  | 227 | def fio_config_to_str(sec_iter): | 
|  | 228 | res = "" | 
|  | 229 |  | 
|  | 230 | for pos, sec in enumerate(sec_iter): | 
|  | 231 | if pos != 0: | 
|  | 232 | res += "\n" | 
|  | 233 |  | 
|  | 234 | res += "[{0}]\n".format(sec.name) | 
|  | 235 |  | 
|  | 236 | for name, val in sec.vals.items(): | 
|  | 237 | if name.startswith('_'): | 
|  | 238 | continue | 
|  | 239 |  | 
|  | 240 | if val is None: | 
|  | 241 | res += name + "\n" | 
|  | 242 | else: | 
|  | 243 | res += "{0}={1}\n".format(name, val) | 
|  | 244 |  | 
|  | 245 | return res | 
|  | 246 |  | 
|  | 247 |  | 
| koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 248 | def get_test_sync_mode(config): | 
| koder aka kdanilov | 4e9f3ed | 2015-04-14 11:26:12 +0300 | [diff] [blame] | 249 | try: | 
| koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 250 | return config['sync_mode'] | 
| koder aka kdanilov | 4e9f3ed | 2015-04-14 11:26:12 +0300 | [diff] [blame] | 251 | except KeyError: | 
|  | 252 | pass | 
|  | 253 |  | 
| koder aka kdanilov | ea22c3d | 2015-04-21 03:42:22 +0300 | [diff] [blame] | 254 | is_sync = str(config.get("sync", "0")) == "1" | 
|  | 255 | is_direct = str(config.get("direct", "0")) == "1" | 
| koder aka kdanilov | 4e9f3ed | 2015-04-14 11:26:12 +0300 | [diff] [blame] | 256 |  | 
|  | 257 | if is_sync and is_direct: | 
|  | 258 | return 'x' | 
|  | 259 | elif is_sync: | 
|  | 260 | return 's' | 
|  | 261 | elif is_direct: | 
|  | 262 | return 'd' | 
|  | 263 | else: | 
|  | 264 | return 'a' | 
|  | 265 |  | 
|  | 266 |  | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 267 | def get_test_summary(params): | 
|  | 268 | rw = {"randread": "rr", | 
|  | 269 | "randwrite": "rw", | 
|  | 270 | "read": "sr", | 
|  | 271 | "write": "sw"}[params["rw"]] | 
|  | 272 |  | 
| koder aka kdanilov | 4e9f3ed | 2015-04-14 11:26:12 +0300 | [diff] [blame] | 273 | sync_mode = get_test_sync_mode(params) | 
|  | 274 | th_count = params.get('numjobs') | 
| koder aka kdanilov | ea22c3d | 2015-04-21 03:42:22 +0300 | [diff] [blame] | 275 |  | 
| koder aka kdanilov | 4e9f3ed | 2015-04-14 11:26:12 +0300 | [diff] [blame] | 276 | if th_count is None: | 
| koder aka kdanilov | ea22c3d | 2015-04-21 03:42:22 +0300 | [diff] [blame] | 277 | th_count = params.get('concurence', 1) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 278 |  | 
| koder aka kdanilov | 4e9f3ed | 2015-04-14 11:26:12 +0300 | [diff] [blame] | 279 | return "{0}{1}{2}th{3}".format(rw, | 
|  | 280 | sync_mode, | 
|  | 281 | params['blocksize'], | 
|  | 282 | th_count) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 283 |  | 
|  | 284 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 285 | def calculate_execution_time(sec_iter): | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 286 | time = 0 | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 287 | for sec in sec_iter: | 
|  | 288 | time += sec.vals.get('ramp_time', 0) | 
|  | 289 | time += sec.vals.get('runtime', 0) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 290 | return time | 
|  | 291 |  | 
|  | 292 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 293 | def slice_config(sec_iter, runcycle=None, max_jobs=1000): | 
|  | 294 | jcount = 0 | 
|  | 295 | runtime = 0 | 
|  | 296 | curr_slice = [] | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 297 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 298 | for pos, sec in enumerate(sec_iter): | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 299 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 300 | jc = sec.vals.get('numjobs', 1) | 
|  | 301 | msg = "numjobs should be integer, not {0!r}".format(jc) | 
|  | 302 | assert isinstance(jc, int), msg | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 303 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 304 | curr_task_time = calculate_execution_time([sec]) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 305 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 306 | if jc > max_jobs: | 
|  | 307 | err_templ = "Can't process job {0!r} - too large numjobs" | 
|  | 308 | raise ValueError(err_templ.format(sec.name)) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 309 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 310 | if runcycle is not None and len(curr_slice) != 0: | 
|  | 311 | rc_ok = curr_task_time + runtime <= runcycle | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 312 | else: | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 313 | rc_ok = True | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 314 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 315 | if jc + jcount <= max_jobs and rc_ok: | 
|  | 316 | runtime += curr_task_time | 
|  | 317 | jcount += jc | 
|  | 318 | curr_slice.append(sec) | 
| koder aka kdanilov | 4500a5f | 2015-04-17 16:55:17 +0300 | [diff] [blame] | 319 | continue | 
|  | 320 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 321 | assert len(curr_slice) != 0 | 
|  | 322 | yield curr_slice | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 323 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 324 | if '_ramp_time' in sec.vals: | 
|  | 325 | sec.vals['ramp_time'] = sec.vals.pop('_ramp_time') | 
|  | 326 | curr_task_time = calculate_execution_time([sec]) | 
|  | 327 |  | 
|  | 328 | runtime = curr_task_time | 
|  | 329 | jcount = jc | 
|  | 330 | curr_slice = [sec] | 
|  | 331 |  | 
|  | 332 | if curr_slice != []: | 
|  | 333 | yield curr_slice | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 334 |  | 
|  | 335 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 336 | def parse_all_in_1(source, test_params): | 
|  | 337 | lexer_it = fio_config_lexer(source) | 
|  | 338 | sec_it = fio_config_parse(lexer_it, test_params) | 
|  | 339 | sec_it = process_cycles(sec_it) | 
|  | 340 | sec_it = process_repeats(sec_it) | 
|  | 341 | return format_params_into_section_finall(sec_it) | 
| koder aka kdanilov | b896f69 | 2015-04-07 14:57:55 +0300 | [diff] [blame] | 342 |  | 
|  | 343 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 344 | def parse_and_slice_all_in_1(source, test_params, **slice_params): | 
|  | 345 | sec_it = parse_all_in_1(source, test_params) | 
|  | 346 | return slice_config(sec_it, **slice_params) | 
| koder aka kdanilov | b896f69 | 2015-04-07 14:57:55 +0300 | [diff] [blame] | 347 |  | 
|  | 348 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 349 | def compile_all_in_1(source, test_params, **slice_params): | 
|  | 350 | slices_it = parse_and_slice_all_in_1(source, test_params, **slice_params) | 
|  | 351 | for slices in slices_it: | 
|  | 352 | yield fio_config_to_str(slices) | 
| koder aka kdanilov | b896f69 | 2015-04-07 14:57:55 +0300 | [diff] [blame] | 353 |  | 
|  | 354 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 355 | def do_run_fio(config_slice): | 
|  | 356 | benchmark_config = fio_config_to_str(config_slice) | 
| koder aka kdanilov | 4500a5f | 2015-04-17 16:55:17 +0300 | [diff] [blame] | 357 | cmd = ["fio", "--output-format=json", "--alloc-size=262144", "-"] | 
| koder aka kdanilov | e87ae65 | 2015-04-20 02:14:35 +0300 | [diff] [blame] | 358 | p = subprocess.Popen(cmd, | 
|  | 359 | stdin=subprocess.PIPE, | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 360 | stdout=subprocess.PIPE, | 
| koder aka kdanilov | e87ae65 | 2015-04-20 02:14:35 +0300 | [diff] [blame] | 361 | stderr=subprocess.PIPE) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 362 |  | 
|  | 363 | # set timeout | 
| koder aka kdanilov | e87ae65 | 2015-04-20 02:14:35 +0300 | [diff] [blame] | 364 | raw_out, raw_err = p.communicate(benchmark_config) | 
|  | 365 |  | 
| koder aka kdanilov | 6b1341a | 2015-04-21 22:44:21 +0300 | [diff] [blame] | 366 | # HACK | 
|  | 367 | raw_out = "{" + raw_out.split('{', 1)[1] | 
|  | 368 |  | 
| koder aka kdanilov | e87ae65 | 2015-04-20 02:14:35 +0300 | [diff] [blame] | 369 | if 0 != p.returncode: | 
|  | 370 | msg = "Fio failed with code: {0}\nOutput={1}" | 
|  | 371 | raise OSError(msg.format(p.returncode, raw_err)) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 372 |  | 
|  | 373 | try: | 
|  | 374 | parsed_out = json.loads(raw_out)["jobs"] | 
| koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 375 | except KeyError: | 
|  | 376 | msg = "Can't parse fio output {0!r}: no 'jobs' found" | 
|  | 377 | raw_out = raw_out[:100] | 
|  | 378 | raise ValueError(msg.format(raw_out)) | 
|  | 379 |  | 
|  | 380 | except Exception as exc: | 
| koder aka kdanilov | ec1b973 | 2015-04-23 20:43:29 +0300 | [diff] [blame] | 381 | msg = "Can't parse fio output: {0!r}\nError: {1!s}" | 
| koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 382 | raw_out = raw_out[:100] | 
| koder aka kdanilov | ec1b973 | 2015-04-23 20:43:29 +0300 | [diff] [blame] | 383 | raise ValueError(msg.format(raw_out, exc)) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 384 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 385 | return zip(parsed_out, config_slice) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 386 |  | 
|  | 387 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 388 | def add_job_results(section, job_output, res): | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 389 | if job_output['write']['iops'] != 0: | 
|  | 390 | raw_result = job_output['write'] | 
|  | 391 | else: | 
|  | 392 | raw_result = job_output['read'] | 
|  | 393 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 394 | vals = section.vals | 
|  | 395 | if section.name not in res: | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 396 | j_res = {} | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 397 | j_res["rw"] = vals["rw"] | 
|  | 398 | j_res["sync_mode"] = get_test_sync_mode(vals) | 
|  | 399 | j_res["concurence"] = int(vals.get("numjobs", 1)) | 
|  | 400 | j_res["blocksize"] = vals["blocksize"] | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 401 | j_res["jobname"] = job_output["jobname"] | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 402 | j_res["timings"] = [int(vals.get("runtime", 0)), | 
|  | 403 | int(vals.get("ramp_time", 0))] | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 404 | else: | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 405 | j_res = res[section.name] | 
|  | 406 | assert j_res["rw"] == vals["rw"] | 
|  | 407 | assert j_res["rw"] == vals["rw"] | 
|  | 408 | assert j_res["sync_mode"] == get_test_sync_mode(vals) | 
|  | 409 | assert j_res["concurence"] == int(vals.get("numjobs", 1)) | 
|  | 410 | assert j_res["blocksize"] == vals["blocksize"] | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 411 | assert j_res["jobname"] == job_output["jobname"] | 
| koder aka kdanilov | 652cd80 | 2015-04-13 12:21:07 +0300 | [diff] [blame] | 412 |  | 
|  | 413 | # ramp part is skipped for all tests, except first | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 414 | # assert j_res["timings"] == (vals.get("runtime"), | 
|  | 415 | #                             vals.get("ramp_time")) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 416 |  | 
|  | 417 | def j_app(name, x): | 
|  | 418 | j_res.setdefault(name, []).append(x) | 
|  | 419 |  | 
| koder aka kdanilov | 4e9f3ed | 2015-04-14 11:26:12 +0300 | [diff] [blame] | 420 | j_app("bw", raw_result["bw"]) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 421 | j_app("iops", raw_result["iops"]) | 
|  | 422 | j_app("lat", raw_result["lat"]["mean"]) | 
|  | 423 | j_app("clat", raw_result["clat"]["mean"]) | 
|  | 424 | j_app("slat", raw_result["slat"]["mean"]) | 
|  | 425 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 426 | res[section.name] = j_res | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 427 |  | 
|  | 428 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 429 | def run_fio(sliced_it, raw_results_func=None): | 
|  | 430 | sliced_list = list(sliced_it) | 
| koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 431 | ok = True | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 432 |  | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 433 | try: | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 434 | curr_test_num = 0 | 
|  | 435 | executed_tests = 0 | 
|  | 436 | result = {} | 
| koder aka kdanilov | b896f69 | 2015-04-07 14:57:55 +0300 | [diff] [blame] | 437 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 438 | for i, test_slice in enumerate(sliced_list): | 
|  | 439 | res_cfg_it = do_run_fio(test_slice) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 440 | res_cfg_it = enumerate(res_cfg_it, curr_test_num) | 
|  | 441 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 442 | for curr_test_num, (job_output, section) in res_cfg_it: | 
| koder aka kdanilov | 2e92802 | 2015-04-08 13:47:15 +0300 | [diff] [blame] | 443 | executed_tests += 1 | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 444 |  | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 445 | if raw_results_func is not None: | 
| koder aka kdanilov | 2e92802 | 2015-04-08 13:47:15 +0300 | [diff] [blame] | 446 | raw_results_func(executed_tests, | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 447 | [job_output, section]) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 448 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 449 | msg = "{0} != {1}".format(section.name, job_output["jobname"]) | 
|  | 450 | assert section.name == job_output["jobname"], msg | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 451 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 452 | if section.name.startswith('_'): | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 453 | continue | 
|  | 454 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 455 | add_job_results(section, job_output, result) | 
|  | 456 |  | 
| koder aka kdanilov | e87ae65 | 2015-04-20 02:14:35 +0300 | [diff] [blame] | 457 | curr_test_num += 1 | 
| koder aka kdanilov | 4500a5f | 2015-04-17 16:55:17 +0300 | [diff] [blame] | 458 | msg_template = "Done {0} tests from {1}. ETA: {2}" | 
| koder aka kdanilov | 4500a5f | 2015-04-17 16:55:17 +0300 | [diff] [blame] | 459 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 460 | rest = sliced_list[i:] | 
|  | 461 | time_eta = sum(map(calculate_execution_time, rest)) | 
|  | 462 | test_left = sum(map(len, rest)) | 
|  | 463 | print msg_template.format(curr_test_num, | 
|  | 464 | test_left, | 
|  | 465 | sec_to_str(time_eta)) | 
| koder aka kdanilov | 4500a5f | 2015-04-17 16:55:17 +0300 | [diff] [blame] | 466 |  | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 467 | except (SystemExit, KeyboardInterrupt): | 
| koder aka kdanilov | 652cd80 | 2015-04-13 12:21:07 +0300 | [diff] [blame] | 468 | raise | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 469 |  | 
|  | 470 | except Exception: | 
| koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 471 | print "=========== ERROR =============" | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 472 | traceback.print_exc() | 
| koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 473 | print "======== END OF ERROR =========" | 
|  | 474 | ok = False | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 475 |  | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 476 | return result, executed_tests, ok | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 477 |  | 
|  | 478 |  | 
|  | 479 | def run_benchmark(binary_tp, *argv, **kwargs): | 
|  | 480 | if 'fio' == binary_tp: | 
|  | 481 | return run_fio(*argv, **kwargs) | 
|  | 482 | raise ValueError("Unknown behcnmark {0}".format(binary_tp)) | 
|  | 483 |  | 
|  | 484 |  | 
| koder aka kdanilov | 652cd80 | 2015-04-13 12:21:07 +0300 | [diff] [blame] | 485 | def read_config(fd, timeout=10): | 
|  | 486 | job_cfg = "" | 
|  | 487 | etime = time.time() + timeout | 
|  | 488 | while True: | 
|  | 489 | wtime = etime - time.time() | 
|  | 490 | if wtime <= 0: | 
|  | 491 | raise IOError("No config provided") | 
|  | 492 |  | 
|  | 493 | r, w, x = select.select([fd], [], [], wtime) | 
|  | 494 | if len(r) == 0: | 
|  | 495 | raise IOError("No config provided") | 
|  | 496 |  | 
|  | 497 | char = fd.read(1) | 
|  | 498 | if '' == char: | 
|  | 499 | return job_cfg | 
|  | 500 |  | 
|  | 501 | job_cfg += char | 
|  | 502 |  | 
|  | 503 |  | 
| koder aka kdanilov | 652cd80 | 2015-04-13 12:21:07 +0300 | [diff] [blame] | 504 | def sec_to_str(seconds): | 
|  | 505 | h = seconds // 3600 | 
|  | 506 | m = (seconds % 3600) // 60 | 
|  | 507 | s = seconds % 60 | 
|  | 508 | return "{0}:{1:02d}:{2:02d}".format(h, m, s) | 
|  | 509 |  | 
|  | 510 |  | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 511 | def parse_args(argv): | 
|  | 512 | parser = argparse.ArgumentParser( | 
|  | 513 | description="Run fio' and return result") | 
|  | 514 | parser.add_argument("--type", metavar="BINARY_TYPE", | 
|  | 515 | choices=['fio'], default='fio', | 
|  | 516 | help=argparse.SUPPRESS) | 
|  | 517 | parser.add_argument("--start-at", metavar="START_AT_UTC", type=int, | 
|  | 518 | help="Start execution at START_AT_UTC") | 
|  | 519 | parser.add_argument("--json", action="store_true", default=False, | 
|  | 520 | help="Json output format") | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 521 | parser.add_argument("-o", "--output", default='-', metavar="FILE_PATH", | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 522 | help="Store results to FILE_PATH") | 
|  | 523 | parser.add_argument("--estimate", action="store_true", default=False, | 
|  | 524 | help="Only estimate task execution time") | 
|  | 525 | parser.add_argument("--compile", action="store_true", default=False, | 
|  | 526 | help="Compile config file to fio config") | 
|  | 527 | parser.add_argument("--num-tests", action="store_true", default=False, | 
|  | 528 | help="Show total number of tests") | 
|  | 529 | parser.add_argument("--runcycle", type=int, default=None, | 
|  | 530 | metavar="MAX_CYCLE_SECONDS", | 
|  | 531 | help="Max cycle length in seconds") | 
|  | 532 | parser.add_argument("--show-raw-results", action='store_true', | 
|  | 533 | default=False, help="Output raw input and results") | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 534 | parser.add_argument("--params", nargs="*", metavar="PARAM=VAL", | 
|  | 535 | default=[], | 
|  | 536 | help="Provide set of pairs PARAM=VAL to" + | 
|  | 537 | "format into job description") | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 538 | parser.add_argument("-p", "--pid-file", metavar="FILE_TO_STORE_PID", | 
|  | 539 | default=None, help="Store pid to FILE_TO_STORE_PID " + | 
|  | 540 | "and remove this file on exit") | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 541 | parser.add_argument("jobfile") | 
|  | 542 | return parser.parse_args(argv) | 
|  | 543 |  | 
|  | 544 |  | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 545 | def main(argv): | 
|  | 546 | argv_obj = parse_args(argv) | 
|  | 547 |  | 
|  | 548 | if argv_obj.jobfile == '-': | 
|  | 549 | job_cfg = read_config(sys.stdin) | 
|  | 550 | else: | 
|  | 551 | job_cfg = open(argv_obj.jobfile).read() | 
|  | 552 |  | 
|  | 553 | if argv_obj.output == '-': | 
|  | 554 | out_fd = sys.stdout | 
|  | 555 | else: | 
|  | 556 | out_fd = open(argv_obj.output, "w") | 
|  | 557 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 558 | if argv_obj.pid_file is not None: | 
|  | 559 | with open(argv_obj.pid_file, "w") as fd: | 
|  | 560 | fd.write(str(os.getpid())) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 561 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 562 | try: | 
|  | 563 | params = {} | 
|  | 564 | for param_val in argv_obj.params: | 
|  | 565 | assert '=' in param_val | 
|  | 566 | name, val = param_val.split("=", 1) | 
|  | 567 | params[name] = val | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 568 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 569 | slice_params = { | 
|  | 570 | 'runcycle': argv_obj.runcycle, | 
|  | 571 | } | 
| koder aka kdanilov | 0c598a1 | 2015-04-21 03:01:40 +0300 | [diff] [blame] | 572 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 573 | sliced_it = parse_and_slice_all_in_1(job_cfg, params, **slice_params) | 
| koder aka kdanilov | 652cd80 | 2015-04-13 12:21:07 +0300 | [diff] [blame] | 574 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 575 | if argv_obj.estimate: | 
|  | 576 | it = map(calculate_execution_time, sliced_it) | 
|  | 577 | print sec_to_str(sum(it)) | 
|  | 578 | return 0 | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 579 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 580 | if argv_obj.num_tests or argv_obj.compile: | 
|  | 581 | if argv_obj.compile: | 
|  | 582 | for test_slice in sliced_it: | 
|  | 583 | out_fd.write(fio_config_to_str(test_slice)) | 
|  | 584 | out_fd.write("\n#" + "-" * 70 + "\n\n") | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 585 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 586 | if argv_obj.num_tests: | 
|  | 587 | print len(list(sliced_it)) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 588 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 589 | return 0 | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 590 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 591 | if argv_obj.start_at is not None: | 
|  | 592 | ctime = time.time() | 
|  | 593 | if argv_obj.start_at >= ctime: | 
|  | 594 | time.sleep(ctime - argv_obj.start_at) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 595 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 596 | def raw_res_func(test_num, data): | 
|  | 597 | pref = "========= RAW_RESULTS({0}) =========\n".format(test_num) | 
|  | 598 | out_fd.write(pref) | 
|  | 599 | out_fd.write(json.dumps(data)) | 
|  | 600 | out_fd.write("\n========= END OF RAW_RESULTS =========\n") | 
|  | 601 | out_fd.flush() | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 602 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 603 | rrfunc = raw_res_func if argv_obj.show_raw_results else None | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 604 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 605 | stime = time.time() | 
|  | 606 | job_res, num_tests, ok = run_benchmark(argv_obj.type, | 
|  | 607 | sliced_it, rrfunc) | 
|  | 608 | etime = time.time() | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 609 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 610 | res = {'__meta__': {'raw_cfg': job_cfg, 'params': params}, | 
|  | 611 | 'res': job_res} | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 612 |  | 
| koder aka kdanilov | 4d4771c | 2015-04-23 01:32:02 +0300 | [diff] [blame] | 613 | oformat = 'json' if argv_obj.json else 'eval' | 
|  | 614 | msg = "\nRun {0} tests in {1} seconds\n" | 
|  | 615 | out_fd.write(msg.format(num_tests, int(etime - stime))) | 
|  | 616 |  | 
|  | 617 | msg = "========= RESULTS(format={0}) =========\n" | 
|  | 618 | out_fd.write(msg.format(oformat)) | 
|  | 619 | if argv_obj.json: | 
|  | 620 | out_fd.write(json.dumps(res)) | 
|  | 621 | else: | 
|  | 622 | out_fd.write(pprint.pformat(res) + "\n") | 
|  | 623 | out_fd.write("\n========= END OF RESULTS =========\n") | 
|  | 624 |  | 
|  | 625 | return 0 if ok else 1 | 
|  | 626 | finally: | 
|  | 627 | if argv_obj.pid_file is not None: | 
|  | 628 | if os.path.exists(argv_obj.pid_file): | 
|  | 629 | os.unlink(argv_obj.pid_file) | 
| koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 630 |  | 
|  | 631 |  | 
|  | 632 | def fake_main(x): | 
|  | 633 | import yaml | 
|  | 634 | time.sleep(60) | 
|  | 635 | out_fd = sys.stdout | 
|  | 636 | fname = "/tmp/perf_tests/metempirical_alisha/raw_results.yaml" | 
|  | 637 | res = yaml.load(open(fname).read())[0][1] | 
|  | 638 | out_fd.write("========= RESULTS(format=json) =========\n") | 
|  | 639 | out_fd.write(json.dumps(res)) | 
|  | 640 | out_fd.write("\n========= END OF RESULTS =========\n") | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 641 | return 0 | 
|  | 642 |  | 
|  | 643 |  | 
|  | 644 | if __name__ == '__main__': | 
| koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 645 | # exit(fake_main(sys.argv[1:])) | 
| koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 646 | exit(main(sys.argv[1:])) |