| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 1 | import re | 
|  | 2 | import os | 
|  | 3 | import sys | 
|  | 4 | import stat | 
|  | 5 | import time | 
|  | 6 | import json | 
| koder aka kdanilov | e21d747 | 2015-02-14 19:02:04 -0800 | [diff] [blame^] | 7 | import Queue | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 8 | import os.path | 
|  | 9 | import argparse | 
|  | 10 | import warnings | 
| koder aka kdanilov | e21d747 | 2015-02-14 19:02:04 -0800 | [diff] [blame^] | 11 | import threading | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 12 | import subprocess | 
|  | 13 |  | 
|  | 14 |  | 
|  | 15 | class BenchmarkOption(object): | 
|  | 16 | def __init__(self, concurence, iodepth, action, blocksize, size): | 
|  | 17 | self.iodepth = iodepth | 
|  | 18 | self.action = action | 
|  | 19 | self.blocksize = blocksize | 
|  | 20 | self.concurence = concurence | 
|  | 21 | self.size = size | 
|  | 22 | self.direct_io = False | 
|  | 23 | self.use_hight_io_priority = True | 
|  | 24 | self.sync = False | 
|  | 25 |  | 
|  | 26 |  | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 27 | def which(program): | 
|  | 28 | def is_exe(fpath): | 
|  | 29 | return os.path.isfile(fpath) and os.access(fpath, os.X_OK) | 
|  | 30 |  | 
|  | 31 | fpath, fname = os.path.split(program) | 
|  | 32 | if fpath: | 
|  | 33 | if is_exe(program): | 
|  | 34 | return program | 
|  | 35 | else: | 
|  | 36 | for path in os.environ["PATH"].split(os.pathsep): | 
|  | 37 | path = path.strip('"') | 
|  | 38 | exe_file = os.path.join(path, program) | 
|  | 39 | if is_exe(exe_file): | 
|  | 40 | return exe_file | 
|  | 41 |  | 
|  | 42 | return None | 
|  | 43 |  | 
|  | 44 |  | 
|  | 45 | # ------------------------------ IOZONE SUPPORT ------------------------------ | 
|  | 46 |  | 
|  | 47 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 48 | class IOZoneParser(object): | 
|  | 49 | "class to parse iozone results" | 
|  | 50 |  | 
|  | 51 | start_tests = re.compile(r"^\s+KB\s+reclen\s+") | 
|  | 52 | resuts = re.compile(r"[\s0-9]+") | 
|  | 53 | mt_iozone_re = re.compile(r"\s+Children see throughput " + | 
|  | 54 | r"for\s+\d+\s+(?P<cmd>.*?)\s+=\s+" + | 
|  | 55 | r"(?P<perf>[\d.]+)\s+KB/sec") | 
|  | 56 |  | 
|  | 57 | cmap = {'initial writers': 'write', | 
|  | 58 | 'rewriters': 'rewrite', | 
|  | 59 | 'initial readers': 'read', | 
|  | 60 | 're-readers': 'reread', | 
|  | 61 | 'random readers': 'random read', | 
|  | 62 | 'random writers': 'random write'} | 
|  | 63 |  | 
|  | 64 | string1 = "                           " + \ | 
|  | 65 | "                   random  random    " + \ | 
|  | 66 | "bkwd   record   stride                                   " | 
|  | 67 |  | 
|  | 68 | string2 = "KB  reclen   write rewrite    " + \ | 
|  | 69 | "read    reread    read   write    " + \ | 
|  | 70 | "read  rewrite     read   fwrite frewrite   fread  freread" | 
|  | 71 |  | 
|  | 72 | @classmethod | 
|  | 73 | def apply_parts(cls, parts, string, sep=' \t\n'): | 
|  | 74 | add_offset = 0 | 
|  | 75 | for part in parts: | 
|  | 76 | _, start, stop = part | 
|  | 77 | start += add_offset | 
|  | 78 | add_offset = 0 | 
|  | 79 |  | 
|  | 80 | # condition splited to make pylint happy | 
|  | 81 | while stop + add_offset < len(string): | 
|  | 82 |  | 
|  | 83 | # condition splited to make pylint happy | 
|  | 84 | if not (string[stop + add_offset] not in sep): | 
|  | 85 | break | 
|  | 86 |  | 
|  | 87 | add_offset += 1 | 
|  | 88 |  | 
|  | 89 | yield part, string[start:stop + add_offset] | 
|  | 90 |  | 
|  | 91 | @classmethod | 
|  | 92 | def make_positions(cls): | 
|  | 93 | items = [i for i in cls.string2.split() if i] | 
|  | 94 |  | 
|  | 95 | pos = 0 | 
|  | 96 | cls.positions = [] | 
|  | 97 |  | 
|  | 98 | for item in items: | 
|  | 99 | npos = cls.string2.index(item, 0 if pos == 0 else pos + 1) | 
|  | 100 | cls.positions.append([item, pos, npos + len(item)]) | 
|  | 101 | pos = npos + len(item) | 
|  | 102 |  | 
|  | 103 | for itm, val in cls.apply_parts(cls.positions, cls.string1): | 
|  | 104 | if val.strip(): | 
|  | 105 | itm[0] = val.strip() + " " + itm[0] | 
|  | 106 |  | 
|  | 107 | @classmethod | 
|  | 108 | def parse_iozone_res(cls, res, mthreads=False): | 
|  | 109 | parsed_res = None | 
|  | 110 |  | 
|  | 111 | sres = res.split('\n') | 
|  | 112 |  | 
|  | 113 | if not mthreads: | 
|  | 114 | for pos, line in enumerate(sres[1:]): | 
|  | 115 | if line.strip() == cls.string2 and \ | 
|  | 116 | sres[pos].strip() == cls.string1.strip(): | 
|  | 117 | add_pos = line.index(cls.string2) | 
|  | 118 | parsed_res = {} | 
|  | 119 |  | 
|  | 120 | npos = [(name, start + add_pos, stop + add_pos) | 
|  | 121 | for name, start, stop in cls.positions] | 
|  | 122 |  | 
|  | 123 | for itm, res in cls.apply_parts(npos, sres[pos + 2]): | 
|  | 124 | if res.strip() != '': | 
|  | 125 | parsed_res[itm[0]] = int(res.strip()) | 
|  | 126 |  | 
|  | 127 | del parsed_res['KB'] | 
|  | 128 | del parsed_res['reclen'] | 
|  | 129 | else: | 
|  | 130 | parsed_res = {} | 
|  | 131 | for line in sres: | 
|  | 132 | rr = cls.mt_iozone_re.match(line) | 
|  | 133 | if rr is not None: | 
|  | 134 | cmd = rr.group('cmd') | 
|  | 135 | key = cls.cmap.get(cmd, cmd) | 
|  | 136 | perf = int(float(rr.group('perf'))) | 
|  | 137 | parsed_res[key] = perf | 
|  | 138 | return parsed_res | 
|  | 139 |  | 
|  | 140 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 141 | IOZoneParser.make_positions() | 
|  | 142 |  | 
|  | 143 |  | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 144 | def iozone_do_prepare(params, filename, pattern_base): | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 145 | all_files = [] | 
|  | 146 | threads = int(params.concurence) | 
|  | 147 | if 1 != threads: | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 148 | filename = filename + "_{0}" | 
|  | 149 | all_files.extend(filename .format(i) for i in range(threads)) | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 150 | else: | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 151 | all_files.append(filename) | 
|  | 152 |  | 
|  | 153 | bsz = 1024 if params.size > 1024 else params.size | 
|  | 154 | if params.size % bsz != 0: | 
|  | 155 | fsz = (params.size // bsz + 1) * bsz | 
|  | 156 | else: | 
|  | 157 | fsz = params.size | 
|  | 158 |  | 
|  | 159 | for fname in all_files: | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 160 | with open(fname, "wb") as fd: | 
|  | 161 | if fsz > 1024: | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 162 | pattern = pattern_base * 1024 * 1024 | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 163 | for _ in range(int(fsz / 1024) + 1): | 
|  | 164 | fd.write(pattern) | 
|  | 165 | else: | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 166 | fd.write(pattern_base * 1024 * fsz) | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 167 | fd.flush() | 
| koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 168 | return all_files | 
|  | 169 |  | 
|  | 170 |  | 
|  | 171 | VERIFY_PATTERN = "\x6d" | 
|  | 172 |  | 
|  | 173 |  | 
|  | 174 | def do_run_iozone(params, filename, timeout, iozone_path='iozone', | 
|  | 175 | microsecond_mode=False, | 
|  | 176 | prepare_only=False): | 
|  | 177 |  | 
|  | 178 | cmd = [iozone_path, "-V", str(ord(VERIFY_PATTERN))] | 
|  | 179 |  | 
|  | 180 | if params.sync: | 
|  | 181 | cmd.append('-o') | 
|  | 182 |  | 
|  | 183 | if params.direct_io: | 
|  | 184 | cmd.append('-I') | 
|  | 185 |  | 
|  | 186 | if microsecond_mode: | 
|  | 187 | cmd.append('-N') | 
|  | 188 |  | 
|  | 189 | all_files = iozone_do_prepare(params, filename, VERIFY_PATTERN) | 
|  | 190 |  | 
|  | 191 | threads = int(params.concurence) | 
|  | 192 | if 1 != threads: | 
|  | 193 | cmd.extend(('-t', str(threads), '-F')) | 
|  | 194 | cmd.extend(all_files) | 
|  | 195 | else: | 
|  | 196 | cmd.extend(('-f', all_files[0])) | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 197 |  | 
|  | 198 | cmd.append('-i') | 
|  | 199 |  | 
|  | 200 | if params.action == 'write': | 
|  | 201 | cmd.append("0") | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 202 | elif params.action == 'read': | 
|  | 203 | cmd.append("1") | 
|  | 204 | elif params.action == 'randwrite' or params.action == 'randread': | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 205 | cmd.append("2") | 
|  | 206 | else: | 
|  | 207 | raise ValueError("Unknown action {0!r}".format(params.action)) | 
|  | 208 |  | 
|  | 209 | cmd.extend(('-s', str(params.size))) | 
|  | 210 | cmd.extend(('-r', str(params.blocksize))) | 
|  | 211 |  | 
| koder aka kdanilov | 78ba895 | 2015-02-03 01:11:23 +0200 | [diff] [blame] | 212 | # no retest | 
|  | 213 | cmd.append('-+n') | 
|  | 214 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 215 | raw_res = subprocess.check_output(cmd) | 
|  | 216 |  | 
|  | 217 | try: | 
|  | 218 | parsed_res = IOZoneParser.parse_iozone_res(raw_res, threads > 1) | 
|  | 219 |  | 
|  | 220 | res = {} | 
|  | 221 |  | 
|  | 222 | if params.action == 'write': | 
|  | 223 | res['bw_mean'] = parsed_res['write'] | 
|  | 224 | elif params.action == 'randwrite': | 
|  | 225 | res['bw_mean'] = parsed_res['random write'] | 
|  | 226 | elif params.action == 'read': | 
|  | 227 | res['bw_mean'] = parsed_res['read'] | 
|  | 228 | elif params.action == 'randread': | 
|  | 229 | res['bw_mean'] = parsed_res['random read'] | 
|  | 230 | except: | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 231 | raise | 
|  | 232 |  | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 233 | return res, " ".join(cmd) | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 234 |  | 
|  | 235 |  | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 236 | def run_iozone(benchmark, iozone_path, tmpname, | 
|  | 237 | prepare_only=False, | 
|  | 238 | timeout=None): | 
|  | 239 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 240 | if timeout is not None: | 
|  | 241 | benchmark.size = benchmark.blocksize * 50 | 
|  | 242 | res_time = do_run_iozone(benchmark, tmpname, timeout, | 
|  | 243 | iozone_path=iozone_path, | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 244 | microsecond_mode=True)[0] | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 245 |  | 
|  | 246 | size = (benchmark.blocksize * timeout * 1000000) | 
|  | 247 | size /= res_time["bw_mean"] | 
|  | 248 | size = (size // benchmark.blocksize + 1) * benchmark.blocksize | 
|  | 249 | benchmark.size = size | 
|  | 250 |  | 
|  | 251 | return do_run_iozone(benchmark, tmpname, timeout, | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 252 | iozone_path=iozone_path, prepare_only=prepare_only) | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 253 |  | 
|  | 254 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 255 | def install_iozone_package(): | 
|  | 256 | if which('iozone'): | 
|  | 257 | return | 
|  | 258 |  | 
|  | 259 | is_redhat = os.path.exists('/etc/centos-release') | 
|  | 260 | is_redhat = is_redhat or os.path.exists('/etc/fedora-release') | 
|  | 261 | is_redhat = is_redhat or os.path.exists('/etc/redhat-release') | 
|  | 262 |  | 
|  | 263 | if is_redhat: | 
|  | 264 | subprocess.check_output(["yum", "install", 'iozone3']) | 
|  | 265 | return | 
|  | 266 |  | 
|  | 267 | try: | 
|  | 268 | os_release_cont = open('/etc/os-release').read() | 
|  | 269 |  | 
|  | 270 | is_ubuntu = "Ubuntu" in os_release_cont | 
|  | 271 |  | 
|  | 272 | if is_ubuntu or "Debian GNU/Linux" in os_release_cont: | 
|  | 273 | subprocess.check_output(["apt-get", "install", "iozone3"]) | 
|  | 274 | return | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 275 | except (IOError, OSError): | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 276 | pass | 
|  | 277 |  | 
|  | 278 | raise RuntimeError("Unknown host OS.") | 
|  | 279 |  | 
|  | 280 |  | 
|  | 281 | def install_iozone_static(iozone_url, dst): | 
|  | 282 | if not os.path.isfile(dst): | 
|  | 283 | import urllib | 
|  | 284 | urllib.urlretrieve(iozone_url, dst) | 
|  | 285 |  | 
|  | 286 | st = os.stat(dst) | 
|  | 287 | os.chmod(dst, st.st_mode | stat.S_IEXEC) | 
|  | 288 |  | 
|  | 289 |  | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 290 | def locate_iozone(): | 
|  | 291 | binary_path = which('iozone') | 
|  | 292 |  | 
|  | 293 | if binary_path is None: | 
|  | 294 | binary_path = which('iozone3') | 
|  | 295 |  | 
|  | 296 | if binary_path is None: | 
|  | 297 | sys.stderr.write("Can't found neither iozone not iozone3 binary" | 
| Kostiantyn Danylov aka koder | 02adc1d | 2015-02-02 01:10:04 +0200 | [diff] [blame] | 298 | "Provide --bonary-path or --binary-url option") | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 299 | return False, None | 
|  | 300 |  | 
|  | 301 | return False, binary_path | 
|  | 302 |  | 
|  | 303 | # ------------------------------ FIO SUPPORT --------------------------------- | 
|  | 304 |  | 
|  | 305 |  | 
|  | 306 | def run_fio_once(benchmark, fio_path, tmpname, timeout=None): | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 307 | if benchmark.size is None: | 
|  | 308 | raise ValueError("You need to specify file size for fio") | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 309 |  | 
|  | 310 | cmd_line = [fio_path, | 
|  | 311 | "--name=%s" % benchmark.action, | 
|  | 312 | "--rw=%s" % benchmark.action, | 
|  | 313 | "--blocksize=%sk" % benchmark.blocksize, | 
|  | 314 | "--iodepth=%d" % benchmark.iodepth, | 
|  | 315 | "--filename=%s" % tmpname, | 
|  | 316 | "--size={0}k".format(benchmark.size), | 
|  | 317 | "--numjobs={0}".format(benchmark.concurence), | 
|  | 318 | "--output-format=json", | 
|  | 319 | "--sync=" + ('1' if benchmark.sync else '0')] | 
|  | 320 |  | 
|  | 321 | if timeout is not None: | 
|  | 322 | cmd_line.append("--timeout=%d" % timeout) | 
|  | 323 | cmd_line.append("--runtime=%d" % timeout) | 
|  | 324 |  | 
|  | 325 | if benchmark.direct_io: | 
|  | 326 | cmd_line.append("--direct=1") | 
|  | 327 |  | 
|  | 328 | if benchmark.use_hight_io_priority: | 
|  | 329 | cmd_line.append("--prio=0") | 
|  | 330 |  | 
|  | 331 | raw_out = subprocess.check_output(cmd_line) | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 332 | return json.loads(raw_out)["jobs"][0], " ".join(cmd_line) | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 333 |  | 
|  | 334 |  | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 335 | def run_fio(benchmark, fio_path, tmpname, prepare_only=False, timeout=None): | 
| koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 336 | if prepare_only: | 
|  | 337 | return {}, "" | 
|  | 338 |  | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 339 | job_output, cmd_line = run_fio_once(benchmark, fio_path, tmpname, timeout) | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 340 |  | 
|  | 341 | if benchmark.action in ('write', 'randwrite'): | 
|  | 342 | raw_result = job_output['write'] | 
|  | 343 | else: | 
|  | 344 | raw_result = job_output['read'] | 
|  | 345 |  | 
|  | 346 | res = {} | 
| koder aka kdanilov | 78ba895 | 2015-02-03 01:11:23 +0200 | [diff] [blame] | 347 |  | 
|  | 348 | # 'bw_dev bw_mean bw_max bw_min'.split() | 
|  | 349 | for field in ["bw_mean"]: | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 350 | res[field] = raw_result[field] | 
|  | 351 |  | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 352 | return res, cmd_line | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 353 |  | 
|  | 354 |  | 
|  | 355 | def locate_fio(): | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 356 | return False, which('fio') | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 357 |  | 
|  | 358 |  | 
|  | 359 | # ---------------------------------------------------------------------------- | 
|  | 360 |  | 
|  | 361 |  | 
|  | 362 | def locate_binary(binary_tp, binary_url, binary_path): | 
|  | 363 | remove_binary = False | 
|  | 364 |  | 
|  | 365 | if binary_url is not None: | 
|  | 366 | if binary_path is not None: | 
|  | 367 | sys.stderr.write("At most one option from --binary-path and " | 
|  | 368 | "--binary-url should be provided") | 
|  | 369 | return False, None | 
|  | 370 |  | 
|  | 371 | binary_path = os.tmpnam() | 
|  | 372 | install_iozone_static(binary_url, binary_path) | 
|  | 373 | remove_binary = True | 
|  | 374 |  | 
|  | 375 | elif binary_path is not None: | 
|  | 376 | if os.path.isfile(binary_path): | 
|  | 377 | if not os.access(binary_path, os.X_OK): | 
|  | 378 | st = os.stat(binary_path) | 
|  | 379 | os.chmod(binary_path, st.st_mode | stat.S_IEXEC) | 
|  | 380 | else: | 
|  | 381 | binary_path = None | 
|  | 382 |  | 
|  | 383 | if binary_path is not None: | 
|  | 384 | return remove_binary, binary_path | 
|  | 385 |  | 
|  | 386 | if 'iozone' == binary_tp: | 
|  | 387 | return locate_iozone() | 
|  | 388 | else: | 
|  | 389 | return locate_fio() | 
|  | 390 |  | 
|  | 391 |  | 
|  | 392 | def run_benchmark(binary_tp, *argv, **kwargs): | 
|  | 393 | if 'iozone' == binary_tp: | 
|  | 394 | return run_iozone(*argv, **kwargs) | 
|  | 395 | else: | 
|  | 396 | return run_fio(*argv, **kwargs) | 
|  | 397 |  | 
|  | 398 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 399 | def type_size(string): | 
|  | 400 | try: | 
|  | 401 | return re.match("\d+[KGBM]?", string, re.I).group(0) | 
|  | 402 | except: | 
|  | 403 | msg = "{0!r} don't looks like size-description string".format(string) | 
|  | 404 | raise ValueError(msg) | 
|  | 405 |  | 
|  | 406 |  | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 407 | def type_size_ext(string): | 
|  | 408 | if string.startswith("x"): | 
|  | 409 | int(string[1:]) | 
|  | 410 | return string | 
|  | 411 |  | 
|  | 412 | if string.startswith("r"): | 
|  | 413 | int(string[1:]) | 
|  | 414 | return string | 
|  | 415 |  | 
|  | 416 | try: | 
|  | 417 | return re.match("\d+[KGBM]?", string, re.I).group(0) | 
|  | 418 | except: | 
|  | 419 | msg = "{0!r} don't looks like size-description string".format(string) | 
|  | 420 | raise ValueError(msg) | 
|  | 421 |  | 
|  | 422 |  | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 423 | def ssize_to_kb(ssize): | 
|  | 424 | try: | 
|  | 425 | smap = dict(k=1, K=1, M=1024, m=1024, G=1024**2, g=1024**2) | 
|  | 426 | for ext, coef in smap.items(): | 
|  | 427 | if ssize.endswith(ext): | 
|  | 428 | return int(ssize[:-1]) * coef | 
|  | 429 |  | 
|  | 430 | if int(ssize) % 1024 != 0: | 
|  | 431 | raise ValueError() | 
|  | 432 |  | 
|  | 433 | return int(ssize) / 1024 | 
|  | 434 |  | 
|  | 435 | except (ValueError, TypeError, AttributeError): | 
|  | 436 | tmpl = "Unknow size format {0!r} (or size not multiples 1024)" | 
|  | 437 | raise ValueError(tmpl.format(ssize)) | 
|  | 438 |  | 
|  | 439 |  | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 440 | def get_ram_size(): | 
|  | 441 | try: | 
|  | 442 | with open("/proc/meminfo") as fd: | 
|  | 443 | for ln in fd: | 
|  | 444 | if "MemTotal:" in ln: | 
|  | 445 | sz, kb = ln.split(':')[1].strip().split(" ") | 
|  | 446 | assert kb == 'kB' | 
|  | 447 | return int(sz) | 
|  | 448 | except (ValueError, TypeError, AssertionError): | 
|  | 449 | raise | 
|  | 450 | # return None | 
|  | 451 |  | 
|  | 452 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 453 | def parse_args(argv): | 
|  | 454 | parser = argparse.ArgumentParser( | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 455 | description="Run 'iozone' or 'fio' and return result") | 
|  | 456 | parser.add_argument( | 
|  | 457 | "--type", metavar="BINARY_TYPE", | 
|  | 458 | choices=['iozone', 'fio'], required=True) | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 459 | parser.add_argument( | 
|  | 460 | "--iodepth", metavar="IODEPTH", type=int, | 
|  | 461 | help="I/O depths to test in kb", required=True) | 
|  | 462 | parser.add_argument( | 
|  | 463 | '-a', "--action", metavar="ACTION", type=str, | 
|  | 464 | help="actions to run", required=True, | 
|  | 465 | choices=["read", "write", "randread", "randwrite"]) | 
|  | 466 | parser.add_argument( | 
|  | 467 | "--blocksize", metavar="BLOCKSIZE", type=type_size, | 
|  | 468 | help="single operation block size", required=True) | 
|  | 469 | parser.add_argument( | 
|  | 470 | "--timeout", metavar="TIMEOUT", type=int, | 
|  | 471 | help="runtime of a single run", default=None) | 
|  | 472 | parser.add_argument( | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 473 | "--iosize", metavar="SIZE", type=type_size_ext, | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 474 | help="file size", default=None) | 
|  | 475 | parser.add_argument( | 
|  | 476 | "-s", "--sync", default=False, action="store_true", | 
|  | 477 | help="exec sync after each write") | 
|  | 478 | parser.add_argument( | 
|  | 479 | "-d", "--direct-io", default=False, action="store_true", | 
|  | 480 | help="use O_DIRECT", dest='directio') | 
|  | 481 | parser.add_argument( | 
|  | 482 | "-t", "--sync-time", default=None, type=int, | 
|  | 483 | help="sleep till sime utc time", dest='sync_time') | 
|  | 484 | parser.add_argument( | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 485 | "--binary-url", help="static binary url", | 
|  | 486 | dest="binary_url", default=None) | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 487 | parser.add_argument( | 
|  | 488 | "--test-file", help="file path to run test on", | 
|  | 489 | default=None, dest='test_file') | 
|  | 490 | parser.add_argument( | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 491 | "--binary-path", help="binary path", | 
|  | 492 | default=None, dest='binary_path') | 
| koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 493 | parser.add_argument( | 
|  | 494 | "--prepare-only", default=False, dest='prepare_only', | 
|  | 495 | action="store_true") | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 496 | parser.add_argument("--concurrency", default=1, type=int) | 
| koder aka kdanilov | e21d747 | 2015-02-14 19:02:04 -0800 | [diff] [blame^] | 497 |  | 
|  | 498 | parser.add_argument("--with-sensors", default="", | 
|  | 499 | dest="with_sensors") | 
|  | 500 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 501 | return parser.parse_args(argv) | 
|  | 502 |  | 
|  | 503 |  | 
| koder aka kdanilov | e21d747 | 2015-02-14 19:02:04 -0800 | [diff] [blame^] | 504 | def sensor_thread(sensor_list, cmd_q, data_q): | 
|  | 505 | while True: | 
|  | 506 | try: | 
|  | 507 | cmd_q.get(timeout=0.5) | 
|  | 508 | data_q.put([]) | 
|  | 509 | return | 
|  | 510 | except Queue.Empty: | 
|  | 511 | pass | 
|  | 512 |  | 
|  | 513 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 514 | def main(argv): | 
|  | 515 | argv_obj = parse_args(argv) | 
|  | 516 | argv_obj.blocksize = ssize_to_kb(argv_obj.blocksize) | 
|  | 517 |  | 
|  | 518 | if argv_obj.iosize is not None: | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 519 | if argv_obj.iosize.startswith('x'): | 
|  | 520 | argv_obj.iosize = argv_obj.blocksize * int(argv_obj.iosize[1:]) | 
|  | 521 | elif argv_obj.iosize.startswith('r'): | 
|  | 522 | rs = get_ram_size() | 
|  | 523 | if rs is None: | 
|  | 524 | sys.stderr.write("Can't determine ram size\n") | 
|  | 525 | exit(1) | 
|  | 526 | argv_obj.iosize = rs * int(argv_obj.iosize[1:]) | 
|  | 527 | else: | 
|  | 528 | argv_obj.iosize = ssize_to_kb(argv_obj.iosize) | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 529 |  | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 530 | benchmark = BenchmarkOption(argv_obj.concurrency, | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 531 | argv_obj.iodepth, | 
|  | 532 | argv_obj.action, | 
|  | 533 | argv_obj.blocksize, | 
|  | 534 | argv_obj.iosize) | 
|  | 535 |  | 
|  | 536 | benchmark.direct_io = argv_obj.directio | 
|  | 537 |  | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 538 | if argv_obj.sync: | 
|  | 539 | benchmark.sync = True | 
|  | 540 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 541 | test_file_name = argv_obj.test_file | 
|  | 542 | if test_file_name is None: | 
|  | 543 | with warnings.catch_warnings(): | 
|  | 544 | warnings.simplefilter("ignore") | 
|  | 545 | test_file_name = os.tmpnam() | 
|  | 546 |  | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 547 | remove_binary, binary_path = locate_binary(argv_obj.type, | 
|  | 548 | argv_obj.binary_url, | 
|  | 549 | argv_obj.binary_path) | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 550 |  | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 551 | if binary_path is None: | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 552 | sys.stderr.write("Can't locate binary {0}\n".format(argv_obj.type)) | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 553 | return 1 | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 554 |  | 
|  | 555 | try: | 
|  | 556 | if argv_obj.sync_time is not None: | 
|  | 557 | dt = argv_obj.sync_time - time.time() | 
|  | 558 | if dt > 0: | 
|  | 559 | time.sleep(dt) | 
|  | 560 |  | 
| koder aka kdanilov | e21d747 | 2015-02-14 19:02:04 -0800 | [diff] [blame^] | 561 | if argv_obj.with_sensors != "": | 
|  | 562 | oq = Queue.Queue() | 
|  | 563 | iq = Queue.Queue() | 
|  | 564 | argv = (argv_obj.with_sensors, oq, iq) | 
|  | 565 | th = threading.Thread(None, sensor_thread, None, argv) | 
|  | 566 | th.daemon = True | 
|  | 567 | th.start() | 
|  | 568 |  | 
| koder aka kdanilov | 4a72f12 | 2015-02-09 12:25:54 +0200 | [diff] [blame] | 569 | res, cmd = run_benchmark(argv_obj.type, | 
|  | 570 | benchmark, | 
|  | 571 | binary_path, | 
| koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 572 | test_file_name, | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 573 | argv_obj.prepare_only, | 
|  | 574 | argv_obj.timeout) | 
| koder aka kdanilov | e21d747 | 2015-02-14 19:02:04 -0800 | [diff] [blame^] | 575 | if argv_obj.with_sensors != "": | 
|  | 576 | iq.put(None) | 
|  | 577 | stats = oq.get() | 
|  | 578 | else: | 
|  | 579 | stats = None | 
| koder aka kdanilov | 3f35626 | 2015-02-13 08:06:14 -0800 | [diff] [blame] | 580 |  | 
| koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 581 | if not argv_obj.prepare_only: | 
|  | 582 | res['__meta__'] = benchmark.__dict__.copy() | 
|  | 583 | res['__meta__']['cmdline'] = cmd | 
|  | 584 |  | 
| koder aka kdanilov | e21d747 | 2015-02-14 19:02:04 -0800 | [diff] [blame^] | 585 | if stats is not None: | 
|  | 586 | res['__meta__']['sensor_data'] = stats | 
|  | 587 |  | 
| koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 588 | sys.stdout.write(json.dumps(res)) | 
|  | 589 |  | 
|  | 590 | if not argv_obj.prepare_only: | 
|  | 591 | sys.stdout.write("\n") | 
|  | 592 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 593 | finally: | 
| koder aka kdanilov | 98615bf | 2015-02-02 00:59:07 +0200 | [diff] [blame] | 594 | if remove_binary: | 
|  | 595 | os.unlink(binary_path) | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 596 |  | 
| koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 597 | if os.path.isfile(test_file_name) and not argv_obj.prepare_only: | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 598 | os.unlink(test_file_name) | 
|  | 599 |  | 
|  | 600 |  | 
| koder aka kdanilov | 4af8085 | 2015-02-01 23:36:38 +0200 | [diff] [blame] | 601 | if __name__ == '__main__': | 
| koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 602 | exit(main(sys.argv[1:])) |