add rally patch-and-run script and iozone scenario
diff --git a/fix_rally.py b/fix_rally.py
new file mode 100644
index 0000000..6ab3410
--- /dev/null
+++ b/fix_rally.py
@@ -0,0 +1,220 @@
+import os
+import re
+import sys
+import time
+import yaml
+import json
+import os.path
+import datetime
+import warnings
+import functools
+import contextlib
+import multiprocessing
+
+from rally import exceptions
+from rally.cmd import cliutils
+from rally.cmd.main import categories
+from rally.benchmark.scenarios.vm.utils import VMScenario
+
+
+def log(x):
+ dt_str = datetime.datetime.now().strftime("%H:%M:%S")
+ pref = dt_str + " " + str(os.getpid()) + " >>>> "
+ sys.stderr.write(pref + x.replace("\n", "\n" + pref) + "\n")
+
+
+def get_barrier(count):
+ val = multiprocessing.Value('i', count)
+ cond = multiprocessing.Condition()
+
+ def closure(timeout):
+ with cond:
+ val.value -= 1
+
+ log("barrier value == {0}".format(val.value))
+
+ if val.value == 0:
+ cond.notify_all()
+ else:
+ cond.wait(timeout)
+ return val.value == 0
+
+ return closure
+
+
+MAX_WAIT_TOUT = 60
+
+
+@contextlib.contextmanager
+def patch_VMScenario_run_command_over_ssh(paths,
+ add_meta_cb,
+ barrier=None,
+ latest_start_time=None):
+
+ orig = VMScenario.run_command_over_ssh
+
+ @functools.wraps(orig)
+ def closure(self, ssh, *args, **kwargs):
+ try:
+ sftp = ssh._client.open_sftp()
+ except AttributeError:
+ # rally code was changed
+ log("Prototype of VMScenario.run_command_over_ssh "
+ "was changed. Update patch code.")
+ raise exceptions.ScriptError("monkeypath code fails on "
+ "ssh._client.open_sftp()")
+
+ for src, dst in paths.items():
+ try:
+ sftp.put(src, dst)
+ except Exception as exc:
+ tmpl = "Scp {0!r} => {1!r} failed - {2!r}"
+ msg = tmpl.format(src, dst, exc)
+ log(msg)
+ raise exceptions.ScriptError(
+ "monkeypath code fails on " + msg)
+
+ sftp.close()
+
+ log("Start io test")
+
+ if barrier is not None:
+ if latest_start_time is not None:
+ timeout = latest_start_time - time.time()
+ else:
+ timeout = None
+
+ if timeout is not None and timeout > 0:
+ msg = "Ready and waiting on barrier. " + \
+ "Will wait at most {0} seconds"
+ log(msg.format(int(timeout)))
+
+ if not barrier(timeout):
+ log("Barrier timeouted")
+
+ try:
+ code, out, err = orig(self, ssh, *args, **kwargs)
+ except Exception as exc:
+ log("Rally raises exception {0}".format(exc.message))
+ raise
+
+ if 0 != code:
+ templ = "Script returns error! code={0}\n {1}"
+ log(templ.format(code, err.rstrip()))
+ else:
+ log("Test finished")
+ try:
+ try:
+ result = json.loads(out)
+ except:
+ pass
+ else:
+ if '__meta__' in result:
+ add_meta_cb(result.pop('__meta__'))
+ out = json.dumps(result)
+ except Exception as err:
+ log("Error during postprocessing results: {0!r}".format(err))
+
+ return code, out, err
+
+ VMScenario.run_command_over_ssh = closure
+
+ try:
+ yield
+ finally:
+ VMScenario.run_command_over_ssh = orig
+
+
+def run_rally(rally_args):
+ return cliutils.run(['rally'] + rally_args, categories)
+
+
+def prepare_files(iozone_py_argv, dst_iozone_path, files_dir):
+
+ # we do need temporary named files
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ py_file = os.tmpnam()
+ yaml_file = os.tmpnam()
+
+ iozone_py_inp_path = os.path.join(files_dir, "iozone.py")
+ py_src_cont = open(iozone_py_inp_path).read()
+ args_repl_rr = r'INSERT_IOZONE_ARGS\(sys\.argv.*?\)'
+ py_dst_cont = re.sub(args_repl_rr, repr(iozone_py_argv), py_src_cont)
+
+ if py_dst_cont == args_repl_rr:
+ log("Can't find replace marker in file {0}".format(iozone_py_inp_path))
+ exit(1)
+
+ yaml_src_cont = open(os.path.join(files_dir, "iozone.yaml")).read()
+ task_params = yaml.load(yaml_src_cont)
+ rcd_params = task_params['VMTasks.boot_runcommand_delete']
+ rcd_params[0]['args']['script'] = py_file
+ yaml_dst_cont = yaml.dump(task_params)
+
+ open(py_file, "w").write(py_dst_cont)
+ open(yaml_file, "w").write(yaml_dst_cont)
+
+ return yaml_file, py_file
+
+
+def run_test(iozone_py_argv, dst_iozone_path, files_dir):
+ iozone_local = os.path.join(files_dir, 'iozone')
+
+ yaml_file, py_file = prepare_files(iozone_py_argv,
+ dst_iozone_path,
+ files_dir)
+
+ config = yaml.load(open(yaml_file).read())
+
+ vm_sec = 'VMTasks.boot_runcommand_delete'
+ concurrency = config[vm_sec][0]['runner']['concurrency']
+
+ max_preparation_time = 300
+
+ try:
+ copy_files = {iozone_local: dst_iozone_path}
+
+ result_queue = multiprocessing.Queue()
+ cb = result_queue.put
+
+ do_patch = patch_VMScenario_run_command_over_ssh
+
+ barrier = get_barrier(concurrency)
+ max_release_time = time.time() + max_preparation_time
+
+ with do_patch(copy_files, cb, barrier, max_release_time):
+ log("Start rally with 'task start {0}'".format(yaml_file))
+ rally_result = run_rally(['task', 'start', yaml_file])
+
+ # while not result_queue.empty():
+ # log("meta = {0!r}\n".format(result_queue.get()))
+
+ return rally_result
+
+ finally:
+ os.unlink(yaml_file)
+ os.unlink(py_file)
+ # store and process meta and results
+
+
+def main(argv):
+ files_dir = '.'
+ dst_iozone_path = '/tmp/iozone'
+ iozone_py_argv = ['-a', 'randwrite',
+ '--iodepth', '2',
+ '--blocksize', '4k',
+ '--iosize', '20M',
+ '--iozone-path', dst_iozone_path,
+ '-d']
+ run_test(iozone_py_argv, dst_iozone_path, files_dir)
+
+# ubuntu cloud image
+# https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
+
+# glance image-create --name 'ubuntu' --disk-format qcow2
+# --container-format bare --is-public true --copy-from
+# https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
+
+if __name__ == '__main__':
+ exit(main(sys.argv))
diff --git a/iozone-scenario/iozone b/iozone-scenario/iozone
new file mode 100755
index 0000000..5edce95
--- /dev/null
+++ b/iozone-scenario/iozone
Binary files differ
diff --git a/iozone-scenario/iozone.py b/iozone-scenario/iozone.py
new file mode 100644
index 0000000..5969062
--- /dev/null
+++ b/iozone-scenario/iozone.py
@@ -0,0 +1,403 @@
+import re
+import os
+import sys
+import stat
+import time
+import json
+import os.path
+import argparse
+import warnings
+import subprocess
+
+
+class BenchmarkOption(object):
+ def __init__(self, concurence, iodepth, action, blocksize, size):
+ self.iodepth = iodepth
+ self.action = action
+ self.blocksize = blocksize
+ self.concurence = concurence
+ self.size = size
+ self.direct_io = False
+ self.use_hight_io_priority = True
+ self.sync = False
+
+
+class IOZoneParser(object):
+ "class to parse iozone results"
+
+ start_tests = re.compile(r"^\s+KB\s+reclen\s+")
+ resuts = re.compile(r"[\s0-9]+")
+ mt_iozone_re = re.compile(r"\s+Children see throughput " +
+ r"for\s+\d+\s+(?P<cmd>.*?)\s+=\s+" +
+ r"(?P<perf>[\d.]+)\s+KB/sec")
+
+ cmap = {'initial writers': 'write',
+ 'rewriters': 'rewrite',
+ 'initial readers': 'read',
+ 're-readers': 'reread',
+ 'random readers': 'random read',
+ 'random writers': 'random write'}
+
+ string1 = " " + \
+ " random random " + \
+ "bkwd record stride "
+
+ string2 = "KB reclen write rewrite " + \
+ "read reread read write " + \
+ "read rewrite read fwrite frewrite fread freread"
+
+ @classmethod
+ def apply_parts(cls, parts, string, sep=' \t\n'):
+ add_offset = 0
+ for part in parts:
+ _, start, stop = part
+ start += add_offset
+ add_offset = 0
+
+ # condition splited to make pylint happy
+ while stop + add_offset < len(string):
+
+ # condition splited to make pylint happy
+ if not (string[stop + add_offset] not in sep):
+ break
+
+ add_offset += 1
+
+ yield part, string[start:stop + add_offset]
+
+ @classmethod
+ def make_positions(cls):
+ items = [i for i in cls.string2.split() if i]
+
+ pos = 0
+ cls.positions = []
+
+ for item in items:
+ npos = cls.string2.index(item, 0 if pos == 0 else pos + 1)
+ cls.positions.append([item, pos, npos + len(item)])
+ pos = npos + len(item)
+
+ for itm, val in cls.apply_parts(cls.positions, cls.string1):
+ if val.strip():
+ itm[0] = val.strip() + " " + itm[0]
+
+ @classmethod
+ def parse_iozone_res(cls, res, mthreads=False):
+ parsed_res = None
+
+ sres = res.split('\n')
+
+ if not mthreads:
+ for pos, line in enumerate(sres[1:]):
+ if line.strip() == cls.string2 and \
+ sres[pos].strip() == cls.string1.strip():
+ add_pos = line.index(cls.string2)
+ parsed_res = {}
+
+ npos = [(name, start + add_pos, stop + add_pos)
+ for name, start, stop in cls.positions]
+
+ for itm, res in cls.apply_parts(npos, sres[pos + 2]):
+ if res.strip() != '':
+ parsed_res[itm[0]] = int(res.strip())
+
+ del parsed_res['KB']
+ del parsed_res['reclen']
+ else:
+ parsed_res = {}
+ for line in sres:
+ rr = cls.mt_iozone_re.match(line)
+ if rr is not None:
+ cmd = rr.group('cmd')
+ key = cls.cmap.get(cmd, cmd)
+ perf = int(float(rr.group('perf')))
+ parsed_res[key] = perf
+ return parsed_res
+
+
+def ssize_to_kb(ssize):
+ try:
+ smap = dict(k=1, K=1, M=1024, m=1024, G=1024**2, g=1024**2)
+ for ext, coef in smap.items():
+ if ssize.endswith(ext):
+ return int(ssize[:-1]) * coef
+
+ if int(ssize) % 1024 != 0:
+ raise ValueError()
+
+ return int(ssize) / 1024
+
+ except (ValueError, TypeError, AttributeError):
+ tmpl = "Unknow size format {0!r} (or size not multiples 1024)"
+ raise ValueError(tmpl.format(ssize))
+
+
+IOZoneParser.make_positions()
+
+
+def do_run_iozone(params, filename, timeout, iozone_path='iozone',
+ microsecond_mode=False):
+
+ cmd = [iozone_path]
+
+ if params.sync:
+ cmd.append('-o')
+
+ if params.direct_io:
+ cmd.append('-I')
+
+ if microsecond_mode:
+ cmd.append('-N')
+
+ all_files = []
+ threads = int(params.concurence)
+ if 1 != threads:
+ cmd.extend(('-t', str(threads), '-F'))
+ filename = filename + "_{}"
+ cmd.extend(filename % i for i in range(threads))
+ all_files.extend(filename % i for i in range(threads))
+ else:
+ cmd.extend(('-f', filename))
+ all_files.append(filename)
+
+ bsz = 1024 if params.size > 1024 else params.size
+ if params.size % bsz != 0:
+ fsz = (params.size // bsz + 1) * bsz
+ else:
+ fsz = params.size
+
+ for fname in all_files:
+ ccmd = [iozone_path, "-f", fname, "-i", "0",
+ "-s", str(fsz), "-r", str(bsz), "-w"]
+ subprocess.check_output(ccmd)
+
+ cmd.append('-i')
+
+ if params.action == 'write':
+ cmd.append("0")
+ elif params.action == 'randwrite':
+ cmd.append("2")
+ else:
+ raise ValueError("Unknown action {0!r}".format(params.action))
+
+ cmd.extend(('-s', str(params.size)))
+ cmd.extend(('-r', str(params.blocksize)))
+
+ raw_res = subprocess.check_output(cmd)
+
+ try:
+ parsed_res = IOZoneParser.parse_iozone_res(raw_res, threads > 1)
+
+ res = {}
+
+ if params.action == 'write':
+ res['bw_mean'] = parsed_res['write']
+ elif params.action == 'randwrite':
+ res['bw_mean'] = parsed_res['random write']
+ elif params.action == 'read':
+ res['bw_mean'] = parsed_res['read']
+ elif params.action == 'randread':
+ res['bw_mean'] = parsed_res['random read']
+ except:
+ print raw_res
+ raise
+
+ # res['bw_dev'] = 0
+ # res['bw_max'] = res["bw_mean"]
+ # res['bw_min'] = res["bw_mean"]
+
+ return res
+
+
+def run_iozone(benchmark, iozone_path, tmpname, timeout=None):
+ if timeout is not None:
+ benchmark.size = benchmark.blocksize * 50
+ res_time = do_run_iozone(benchmark, tmpname, timeout,
+ iozone_path=iozone_path,
+ microsecond_mode=True)
+
+ size = (benchmark.blocksize * timeout * 1000000)
+ size /= res_time["bw_mean"]
+ size = (size // benchmark.blocksize + 1) * benchmark.blocksize
+ benchmark.size = size
+
+ return do_run_iozone(benchmark, tmpname, timeout,
+ iozone_path=iozone_path)
+
+
+def which(program):
+ def is_exe(fpath):
+ return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
+
+ fpath, fname = os.path.split(program)
+ if fpath:
+ if is_exe(program):
+ return program
+ else:
+ for path in os.environ["PATH"].split(os.pathsep):
+ path = path.strip('"')
+ exe_file = os.path.join(path, program)
+ if is_exe(exe_file):
+ return exe_file
+
+ return None
+
+
+def install_iozone_package():
+ if which('iozone'):
+ return
+
+ is_redhat = os.path.exists('/etc/centos-release')
+ is_redhat = is_redhat or os.path.exists('/etc/fedora-release')
+ is_redhat = is_redhat or os.path.exists('/etc/redhat-release')
+
+ if is_redhat:
+ subprocess.check_output(["yum", "install", 'iozone3'])
+ return
+
+ try:
+ os_release_cont = open('/etc/os-release').read()
+
+ is_ubuntu = "Ubuntu" in os_release_cont
+
+ if is_ubuntu or "Debian GNU/Linux" in os_release_cont:
+ subprocess.check_output(["apt-get", "install", "iozone3"])
+ return
+ except (IOError, OSError) as exc:
+ print exc
+ pass
+
+ raise RuntimeError("Unknown host OS.")
+
+
+def install_iozone_static(iozone_url, dst):
+ if not os.path.isfile(dst):
+ import urllib
+ urllib.urlretrieve(iozone_url, dst)
+
+ st = os.stat(dst)
+ os.chmod(dst, st.st_mode | stat.S_IEXEC)
+
+
+def type_size(string):
+ try:
+ return re.match("\d+[KGBM]?", string, re.I).group(0)
+ except:
+ msg = "{0!r} don't looks like size-description string".format(string)
+ raise ValueError(msg)
+
+
+def parse_args(argv):
+ parser = argparse.ArgumentParser(
+ description="Run set of `iozone` invocations and return result")
+ parser.add_argument(
+ "--iodepth", metavar="IODEPTH", type=int,
+ help="I/O depths to test in kb", required=True)
+ parser.add_argument(
+ '-a', "--action", metavar="ACTION", type=str,
+ help="actions to run", required=True,
+ choices=["read", "write", "randread", "randwrite"])
+ parser.add_argument(
+ "--blocksize", metavar="BLOCKSIZE", type=type_size,
+ help="single operation block size", required=True)
+ parser.add_argument(
+ "--timeout", metavar="TIMEOUT", type=int,
+ help="runtime of a single run", default=None)
+ parser.add_argument(
+ "--iosize", metavar="SIZE", type=type_size,
+ help="file size", default=None)
+ parser.add_argument(
+ "-s", "--sync", default=False, action="store_true",
+ help="exec sync after each write")
+ parser.add_argument(
+ "-d", "--direct-io", default=False, action="store_true",
+ help="use O_DIRECT", dest='directio')
+ parser.add_argument(
+ "-t", "--sync-time", default=None, type=int,
+ help="sleep till sime utc time", dest='sync_time')
+ parser.add_argument(
+ "--iozone-url", help="iozone static binary url",
+ dest="iozone_url", default=None)
+ parser.add_argument(
+ "--test-file", help="file path to run test on",
+ default=None, dest='test_file')
+ parser.add_argument(
+ "--iozone-path", help="iozone path",
+ default=None, dest='iozone_path')
+ return parser.parse_args(argv)
+
+
+def main(argv):
+ argv_obj = parse_args(argv)
+ argv_obj.blocksize = ssize_to_kb(argv_obj.blocksize)
+
+ if argv_obj.iosize is not None:
+ argv_obj.iosize = ssize_to_kb(argv_obj.iosize)
+
+ benchmark = BenchmarkOption(1,
+ argv_obj.iodepth,
+ argv_obj.action,
+ argv_obj.blocksize,
+ argv_obj.iosize)
+
+ benchmark.direct_io = argv_obj.directio
+
+ test_file_name = argv_obj.test_file
+ if test_file_name is None:
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ test_file_name = os.tmpnam()
+
+ remove_iozone_bin = False
+ if argv_obj.iozone_url is not None:
+ if argv_obj.iozone_path is not None:
+ sys.stderr.write("At most one option from --iozone-path and "
+ "--iozone-url should be provided")
+ exit(1)
+ iozone_binary = os.tmpnam()
+ install_iozone_static(argv_obj.iozone_url, iozone_binary)
+ remove_iozone_bin = True
+ elif argv_obj.iozone_path is not None:
+ iozone_binary = argv_obj.iozone_path
+ if os.path.isfile(iozone_binary):
+ if not os.access(iozone_binary, os.X_OK):
+ st = os.stat(iozone_binary)
+ os.chmod(iozone_binary, st.st_mode | stat.S_IEXEC)
+
+ else:
+ iozone_binary = which('iozone')
+
+ if iozone_binary is None:
+ iozone_binary = which('iozone3')
+
+ if iozone_binary is None:
+ sys.stderr.write("Can't found neither iozone not iozone3 binary"
+ "Provide --iozone-path or --iozone-url option")
+ exit(1)
+
+ try:
+ if argv_obj.sync_time is not None:
+ dt = argv_obj.sync_time - time.time()
+ if dt > 0:
+ time.sleep(dt)
+
+ res = run_iozone(benchmark, iozone_binary, test_file_name)
+ sys.stdout.write(json.dumps(res) + "\n")
+ finally:
+ if remove_iozone_bin:
+ os.unlink(iozone_binary)
+
+ if os.path.isfile(test_file_name):
+ os.unlink(test_file_name)
+
+
+# function-marker for patching, don't 'optimize' it
+def INSERT_IOZONE_ARGS(x):
+ return x
+
+
+if __name__ == '__main__':
+ # this line would be patched in case of run under rally
+ # don't modify it!
+ argv = INSERT_IOZONE_ARGS(sys.argv[1:])
+ exit(main(argv))
diff --git a/iozone-scenario/iozone.yaml b/iozone-scenario/iozone.yaml
new file mode 100644
index 0000000..2044281
--- /dev/null
+++ b/iozone-scenario/iozone.yaml
@@ -0,0 +1,22 @@
+---
+ VMTasks.boot_runcommand_delete:
+ -
+ args:
+ flavor:
+ name: "m1.small"
+ image:
+ name: "ubuntu"
+ floating_network: "net04_ext"
+ force_delete: false
+ script: "iozone.py"
+ interpreter: "/usr/bin/env python2"
+ username: "ubuntu"
+ runner:
+ type: "constant"
+ times: 4
+ concurrency: 4
+ context:
+ users:
+ tenants: 3
+ users_per_tenant: 2
+ network: {}
\ No newline at end of file