blob: 61fd4e7394dc7b74e301dd6fd40fdeec44579f1a [file] [log] [blame]
koder aka kdanilov4af80852015-02-01 23:36:38 +02001import os
2import re
3import sys
4import time
5import yaml
6import json
koder aka kdanilov78ba8952015-02-03 01:11:23 +02007import pprint
koder aka kdanilov4af80852015-02-01 23:36:38 +02008import os.path
koder aka kdanilov80cb6192015-02-02 03:06:08 +02009import argparse
koder aka kdanilov4af80852015-02-01 23:36:38 +020010import datetime
11import warnings
12import functools
13import contextlib
14import multiprocessing
15
16from rally import exceptions
17from rally.cmd import cliutils
18from rally.cmd.main import categories
19from rally.benchmark.scenarios.vm.utils import VMScenario
20
koder aka kdanilov80cb6192015-02-02 03:06:08 +020021from ssh_copy_directory import put_dir_recursively, ssh_copy_file
22
koder aka kdanilov4af80852015-02-01 23:36:38 +020023
24def log(x):
25 dt_str = datetime.datetime.now().strftime("%H:%M:%S")
26 pref = dt_str + " " + str(os.getpid()) + " >>>> "
27 sys.stderr.write(pref + x.replace("\n", "\n" + pref) + "\n")
28
29
30def get_barrier(count):
31 val = multiprocessing.Value('i', count)
32 cond = multiprocessing.Condition()
33
34 def closure(timeout):
koder aka kdanilov80cb6192015-02-02 03:06:08 +020035 me_released = False
koder aka kdanilov4af80852015-02-01 23:36:38 +020036 with cond:
37 val.value -= 1
koder aka kdanilov4af80852015-02-01 23:36:38 +020038 if val.value == 0:
koder aka kdanilov80cb6192015-02-02 03:06:08 +020039 me_released = True
koder aka kdanilov4af80852015-02-01 23:36:38 +020040 cond.notify_all()
41 else:
42 cond.wait(timeout)
43 return val.value == 0
44
koder aka kdanilov80cb6192015-02-02 03:06:08 +020045 if me_released:
46 log("Test begins!")
47
koder aka kdanilov4af80852015-02-01 23:36:38 +020048 return closure
49
50
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +020051# should actually use mock module for this,
52# but don't wanna to add new dependency
53
koder aka kdanilov4af80852015-02-01 23:36:38 +020054@contextlib.contextmanager
55def patch_VMScenario_run_command_over_ssh(paths,
koder aka kdanilov80cb6192015-02-02 03:06:08 +020056 on_result_cb,
koder aka kdanilov4af80852015-02-01 23:36:38 +020057 barrier=None,
58 latest_start_time=None):
59
koder aka kdanilov1b0d3502015-02-03 21:32:31 +020060 try:
61 orig = VMScenario.run_action
62 except AttributeError:
63 # rally code was changed
64 log("VMScenario class was changed and have no run_action"
65 " method anymore. Update patch code.")
66 raise exceptions.ScriptError("monkeypatch code fails on "
67 "VMScenario.run_action")
koder aka kdanilov4af80852015-02-01 23:36:38 +020068
69 @functools.wraps(orig)
70 def closure(self, ssh, *args, **kwargs):
71 try:
72 sftp = ssh._client.open_sftp()
73 except AttributeError:
74 # rally code was changed
75 log("Prototype of VMScenario.run_command_over_ssh "
76 "was changed. Update patch code.")
koder aka kdanilov80cb6192015-02-02 03:06:08 +020077 raise exceptions.ScriptError("monkeypatch code fails on "
koder aka kdanilov4af80852015-02-01 23:36:38 +020078 "ssh._client.open_sftp()")
koder aka kdanilov80cb6192015-02-02 03:06:08 +020079 try:
80 for src, dst in paths.items():
81 try:
82 if os.path.isfile(src):
83 ssh_copy_file(sftp, src, dst)
84 elif os.path.isdir(src):
85 put_dir_recursively(sftp, src, dst)
86 else:
87 templ = "Can't copy {0!r} - " + \
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +020088 "it neither a file not a directory"
koder aka kdanilov80cb6192015-02-02 03:06:08 +020089 msg = templ.format(src)
90 log(msg)
91 raise exceptions.ScriptError(msg)
92 except exceptions.ScriptError:
93 raise
94 except Exception as exc:
95 tmpl = "Scp {0!r} => {1!r} failed - {2!r}"
96 msg = tmpl.format(src, dst, exc)
97 log(msg)
98 raise exceptions.ScriptError(msg)
99 finally:
100 sftp.close()
koder aka kdanilov4af80852015-02-01 23:36:38 +0200101
102 log("Start io test")
103
104 if barrier is not None:
105 if latest_start_time is not None:
106 timeout = latest_start_time - time.time()
107 else:
108 timeout = None
109
110 if timeout is not None and timeout > 0:
111 msg = "Ready and waiting on barrier. " + \
112 "Will wait at most {0} seconds"
113 log(msg.format(int(timeout)))
114
115 if not barrier(timeout):
116 log("Barrier timeouted")
117
118 try:
119 code, out, err = orig(self, ssh, *args, **kwargs)
120 except Exception as exc:
121 log("Rally raises exception {0}".format(exc.message))
122 raise
123
124 if 0 != code:
125 templ = "Script returns error! code={0}\n {1}"
126 log(templ.format(code, err.rstrip()))
127 else:
128 log("Test finished")
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200129
koder aka kdanilov4af80852015-02-01 23:36:38 +0200130 try:
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200131 for line in out.split("\n"):
132 if line.strip() != "":
133 result = json.loads(line)
134 on_result_cb(result)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200135 except Exception as err:
136 log("Error during postprocessing results: {0!r}".format(err))
137
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200138 # result = {"rally": 0, "srally": 1}
139 result = {"rally": 0}
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200140 out = json.dumps(result)
141
koder aka kdanilov4af80852015-02-01 23:36:38 +0200142 return code, out, err
143
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200144 VMScenario.run_action = closure
koder aka kdanilov4af80852015-02-01 23:36:38 +0200145
146 try:
147 yield
148 finally:
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200149 VMScenario.run_action = orig
koder aka kdanilov4af80852015-02-01 23:36:38 +0200150
151
152def run_rally(rally_args):
153 return cliutils.run(['rally'] + rally_args, categories)
154
155
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200156def prepare_files(testtool_py_args_v, dst_testtool_path, files_dir):
koder aka kdanilov4af80852015-02-01 23:36:38 +0200157
158 # we do need temporary named files
159 with warnings.catch_warnings():
160 warnings.simplefilter("ignore")
161 py_file = os.tmpnam()
162 yaml_file = os.tmpnam()
163
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200164 testtool_py_inp_path = os.path.join(files_dir, "io.py")
165 py_src_cont = open(testtool_py_inp_path).read()
166 args_repl_rr = r'INSERT_TOOL_ARGS\(sys\.argv.*?\)'
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200167 py_dst_cont = re.sub(args_repl_rr, repr(testtool_py_args_v), py_src_cont)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200168
169 if py_dst_cont == args_repl_rr:
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200170 templ = "Can't find replace marker in file {0}"
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200171 msg = templ.format(testtool_py_inp_path)
172 log(msg)
173 raise ValueError(msg)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200174
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200175 yaml_src_cont = open(os.path.join(files_dir, "io.yaml")).read()
koder aka kdanilov4af80852015-02-01 23:36:38 +0200176 task_params = yaml.load(yaml_src_cont)
177 rcd_params = task_params['VMTasks.boot_runcommand_delete']
178 rcd_params[0]['args']['script'] = py_file
179 yaml_dst_cont = yaml.dump(task_params)
180
181 open(py_file, "w").write(py_dst_cont)
182 open(yaml_file, "w").write(yaml_dst_cont)
183
184 return yaml_file, py_file
185
186
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200187def run_test(tool, testtool_py_args_v, dst_testtool_path, files_dir,
koder aka kdanilov4a72f122015-02-09 12:25:54 +0200188 rally_extra_opts, max_preparation_time=300,
189 keet_temp_files=False):
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200190
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200191 path = 'iozone' if 'iozone' == tool else 'fio'
192 testtool_local = os.path.join(files_dir, path)
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200193 yaml_file, py_file = prepare_files(testtool_py_args_v,
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200194 dst_testtool_path,
koder aka kdanilov4af80852015-02-01 23:36:38 +0200195 files_dir)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200196 try:
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200197 config = yaml.load(open(yaml_file).read())
198
199 vm_sec = 'VMTasks.boot_runcommand_delete'
200 concurrency = config[vm_sec][0]['runner']['concurrency']
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200201 copy_files = {testtool_local: dst_testtool_path}
koder aka kdanilov4af80852015-02-01 23:36:38 +0200202
203 result_queue = multiprocessing.Queue()
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200204 results_cb = result_queue.put
koder aka kdanilov4af80852015-02-01 23:36:38 +0200205
206 do_patch = patch_VMScenario_run_command_over_ssh
207
208 barrier = get_barrier(concurrency)
209 max_release_time = time.time() + max_preparation_time
210
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200211 with do_patch(copy_files, results_cb, barrier, max_release_time):
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200212 opts = ['task', 'start', yaml_file] + list(rally_extra_opts)
213 log("Start rally with opts '{0}'".format(" ".join(opts)))
214 run_rally(opts)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200215
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200216 rally_result = []
217 while not result_queue.empty():
218 rally_result.append(result_queue.get())
koder aka kdanilov4af80852015-02-01 23:36:38 +0200219
220 return rally_result
221
222 finally:
koder aka kdanilov4a72f122015-02-09 12:25:54 +0200223 if not keet_temp_files:
224 os.unlink(yaml_file)
225 os.unlink(py_file)
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200226
227
228def parse_args(argv):
229 parser = argparse.ArgumentParser(
230 description="Run rally disk io performance test")
231 parser.add_argument("tool_type", help="test tool type",
232 choices=['iozone', 'fio'])
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200233 parser.add_argument("-l", dest='extra_logs',
234 action='store_true', default=False,
235 help="print some extra log info")
236 parser.add_argument("-o", "--io-opts", dest='io_opts',
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200237 nargs="*", default=[],
238 help="cmd line options for io.py")
239 parser.add_argument("-t", "--test-directory", help="directory with test",
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200240 dest="test_directory", required=True)
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200241 parser.add_argument("--max-preparation-time", default=300,
242 type=int, dest="max_preparation_time")
koder aka kdanilov4a72f122015-02-09 12:25:54 +0200243 parser.add_argument("-k", "--keep", default=False,
244 help="keep temporary files",
245 dest="keet_temp_files", action='store_true')
246 parser.add_argument("--rally-extra-opts", dest="rally_extra_opts",
247 default="", help="rally extra options")
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200248
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200249 return parser.parse_args(argv)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200250
251
252def main(argv):
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200253 opts = parse_args(argv)
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200254 dst_testtool_path = '/tmp/io_tool'
255
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200256 if not opts.extra_logs:
257 global log
258
259 def nolog(x):
260 pass
261
262 log = nolog
263
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200264 if opts.io_opts == []:
265 testtool_py_args_v = []
266
267 block_sizes = ["4k", "64k"]
268 ops = ['randwrite']
269 iodepths = ['8']
270 syncs = [True]
271
272 for block_size in block_sizes:
273 for op in ops:
274 for iodepth in iodepths:
275 for sync in syncs:
276 tt_argv = ['--type', opts.tool_type,
277 '-a', op,
278 '--iodepth', iodepth,
279 '--blocksize', block_size,
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200280 '--iosize', '20M']
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200281 if sync:
282 tt_argv.append('-s')
283 testtool_py_args_v.append(tt_argv)
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200284 else:
koder aka kdanilov4a72f122015-02-09 12:25:54 +0200285 testtool_py_args_v = []
286 for o in opts.io_opts:
287 ttopts = [opt.strip() for opt in o.split(" ") if opt.strip() != ""]
288 testtool_py_args_v.append(ttopts)
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200289
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200290 for io_argv_list in testtool_py_args_v:
291 io_argv_list.extend(['--binary-path', dst_testtool_path])
292
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200293 res = run_test(opts.tool_type,
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200294 testtool_py_args_v,
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200295 dst_testtool_path,
296 files_dir=opts.test_directory,
koder aka kdanilov4a72f122015-02-09 12:25:54 +0200297 rally_extra_opts=opts.rally_extra_opts.split(" "),
298 max_preparation_time=opts.max_preparation_time,
299 keet_temp_files=opts.keet_temp_files)
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200300
koder aka kdanilov4a72f122015-02-09 12:25:54 +0200301 print "=" * 80
302 print pprint.pformat(res)
303 print "=" * 80
304
305 if len(res) != 0:
306 bw_mean = 0.0
307 for measurement in res:
308 bw_mean += measurement["bw_mean"]
309
310 bw_mean /= len(res)
311
312 it = ((bw_mean - measurement["bw_mean"]) ** 2 for measurement in res)
313 bw_dev = sum(it) ** 0.5
314
315 meta = res[0]['__meta__']
316 key = "{0} {1} {2}k".format(meta['action'],
317 's' if meta['sync'] else 'a',
318 meta['blocksize'])
319
320 print
321 print "====> " + json.dumps({key: (int(bw_mean), int(bw_dev))})
322 print
323 print "=" * 80
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200324
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200325 return 0
koder aka kdanilov4af80852015-02-01 23:36:38 +0200326
327# ubuntu cloud image
328# https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
329
330# glance image-create --name 'ubuntu' --disk-format qcow2
331# --container-format bare --is-public true --copy-from
332# https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
koder aka kdanilov4a72f122015-02-09 12:25:54 +0200333# nova flavor-create ceph.512 ceph.512 512 50 1
334# nova server-group-create --policy anti-affinity ceph
koder aka kdanilov4af80852015-02-01 23:36:38 +0200335
336if __name__ == '__main__':
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200337 exit(main(sys.argv[1:]))