blob: 0f8afd44415974ede62b29a82051f0d1e0e2214b [file] [log] [blame]
koder aka kdanilov4643fd62015-02-10 16:20:13 -08001import abc
koder aka kdanilov66839a92015-04-11 13:22:31 +03002import time
koder aka kdanilov4643fd62015-02-10 16:20:13 -08003import os.path
koder aka kdanilove21d7472015-02-14 19:02:04 -08004import logging
koder aka kdanilovea22c3d2015-04-21 03:42:22 +03005import datetime
koder aka kdanilove21d7472015-02-14 19:02:04 -08006
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03007from wally.ssh_utils import copy_paths, run_over_ssh, delete_file
8from wally.utils import ssize_to_b, open_for_append_or_create, sec_to_str
9
10from . import postgres
11from .io import agent as io_agent
12from .io import formatter as io_formatter
13from .io.results_loader import parse_output
koder aka kdanilov652cd802015-04-13 12:21:07 +030014
koder aka kdanilov4643fd62015-02-10 16:20:13 -080015
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030016logger = logging.getLogger("wally")
koder aka kdanilove21d7472015-02-14 19:02:04 -080017
18
koder aka kdanilov4643fd62015-02-10 16:20:13 -080019class IPerfTest(object):
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030020 def __init__(self, on_result_cb, log_directory=None, node=None):
koder aka kdanilov4643fd62015-02-10 16:20:13 -080021 self.on_result_cb = on_result_cb
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030022 self.log_directory = log_directory
23 self.node = node
koder aka kdanilov4643fd62015-02-10 16:20:13 -080024
koder aka kdanilov4643fd62015-02-10 16:20:13 -080025 def pre_run(self, conn):
26 pass
27
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030028 def cleanup(self, conn):
29 pass
30
koder aka kdanilov4643fd62015-02-10 16:20:13 -080031 @abc.abstractmethod
koder aka kdanilov2c473092015-03-29 17:12:13 +030032 def run(self, conn, barrier):
koder aka kdanilov4643fd62015-02-10 16:20:13 -080033 pass
34
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030035 @classmethod
36 def format_for_console(cls, data):
37 msg = "{0}.format_for_console".format(cls.__name__)
38 raise NotImplementedError(msg)
39
koder aka kdanilov4643fd62015-02-10 16:20:13 -080040
Yulia Portnova7ddfa732015-02-24 17:32:58 +020041class TwoScriptTest(IPerfTest):
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030042 remote_tmp_dir = '/tmp'
43
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030044 def __init__(self, opts, on_result_cb, log_directory=None, node=None):
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030045 IPerfTest.__init__(self, on_result_cb, log_directory, node=node)
Yulia Portnova7ddfa732015-02-24 17:32:58 +020046 self.opts = opts
Yulia Portnova7ddfa732015-02-24 17:32:58 +020047
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030048 if 'run_script' in self.opts:
49 self.run_script = self.opts['run_script']
50 self.prepare_script = self.opts['prepare_script']
Yulia Portnova7ddfa732015-02-24 17:32:58 +020051
52 def get_remote_for_script(self, script):
53 return os.path.join(self.tmp_dir, script.rpartition('/')[2])
54
55 def copy_script(self, conn, src):
56 remote_path = self.get_remote_for_script(src)
57 copy_paths(conn, {src: remote_path})
58 return remote_path
59
60 def pre_run(self, conn):
61 remote_script = self.copy_script(conn, self.pre_run_script)
62 cmd = remote_script
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030063 run_over_ssh(conn, cmd, node=self.node)
Yulia Portnova7ddfa732015-02-24 17:32:58 +020064
koder aka kdanilov2c473092015-03-29 17:12:13 +030065 def run(self, conn, barrier):
Yulia Portnova7ddfa732015-02-24 17:32:58 +020066 remote_script = self.copy_script(conn, self.run_script)
Yulia Portnova886a2562015-04-07 11:16:13 +030067 cmd_opts = ' '.join(["%s %s" % (key, val) for key, val
68 in self.opts.items()])
69 cmd = remote_script + ' ' + cmd_opts
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030070 out_err = run_over_ssh(conn, cmd, node=self.node)
koder aka kdanilov66839a92015-04-11 13:22:31 +030071 self.on_result(out_err, cmd)
Yulia Portnova7ddfa732015-02-24 17:32:58 +020072
73 def parse_results(self, out):
74 for line in out.split("\n"):
75 key, separator, value = line.partition(":")
76 if key and value:
77 self.on_result_cb((key, float(value)))
78
koder aka kdanilov66839a92015-04-11 13:22:31 +030079 def on_result(self, out_err, cmd):
80 try:
81 self.parse_results(out_err)
82 except Exception as exc:
83 msg_templ = "Error during postprocessing results: {0!r}. {1}"
84 raise RuntimeError(msg_templ.format(exc.message, out_err))
Yulia Portnova7ddfa732015-02-24 17:32:58 +020085
86
87class PgBenchTest(TwoScriptTest):
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030088 root = os.path.dirname(postgres.__file__)
89 prepare_script = os.path.join(root, "prepare.sh")
90 run_script = os.path.join(root, "run.sh")
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030091
92
koder aka kdanilov4643fd62015-02-10 16:20:13 -080093class IOPerfTest(IPerfTest):
koder aka kdanilovda45e882015-04-06 02:24:42 +030094 io_py_remote = "/tmp/disk_test_agent.py"
koder aka kdanilov2c473092015-03-29 17:12:13 +030095
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030096 def __init__(self, test_options, on_result_cb,
97 log_directory=None, node=None):
98 IPerfTest.__init__(self, on_result_cb, log_directory, node=node)
koder aka kdanilov2c473092015-03-29 17:12:13 +030099 self.options = test_options
koder aka kdanilovda45e882015-04-06 02:24:42 +0300100 self.config_fname = test_options['cfg']
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300101 self.alive_check_interval = test_options.get('alive_check_interval')
koder aka kdanilovda45e882015-04-06 02:24:42 +0300102 self.config_params = test_options.get('params', {})
103 self.tool = test_options.get('tool', 'fio')
104 self.raw_cfg = open(self.config_fname).read()
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300105 self.configs = list(io_agent.parse_all_in_1(self.raw_cfg,
106 self.config_params))
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800107
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300108 cmd_log = os.path.join(self.log_directory, "task_compiled.cfg")
109 raw_res = os.path.join(self.log_directory, "raw_results.txt")
koder aka kdanilovda45e882015-04-06 02:24:42 +0300110
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300111 fio_command_file = open_for_append_or_create(cmd_log)
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300112
113 cfg_s_it = io_agent.compile_all_in_1(self.raw_cfg, self.config_params)
114 splitter = "\n\n" + "-" * 60 + "\n\n"
115 fio_command_file.write(splitter.join(cfg_s_it))
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300116 self.fio_raw_results_file = open_for_append_or_create(raw_res)
117
118 def cleanup(self, conn):
119 delete_file(conn, self.io_py_remote)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300120 # Need to remove tempo files, used for testing
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300121
122 def pre_run(self, conn):
koder aka kdanilov652cd802015-04-13 12:21:07 +0300123 try:
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300124 run_over_ssh(conn, 'which fio', node=self.node)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300125 except OSError:
126 # TODO: install fio, if not installed
127 cmd = "sudo apt-get -y install fio"
koder aka kdanilov66839a92015-04-11 13:22:31 +0300128
koder aka kdanilov652cd802015-04-13 12:21:07 +0300129 for i in range(3):
130 try:
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300131 run_over_ssh(conn, cmd, node=self.node)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300132 break
133 except OSError as err:
134 time.sleep(3)
135 else:
136 raise OSError("Can't install fio - " + err.message)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300137
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300138 local_fname = io_agent.__file__.rsplit('.')[0] + ".py"
koder aka kdanilov2c473092015-03-29 17:12:13 +0300139 self.files_to_copy = {local_fname: self.io_py_remote}
koder aka kdanilov50f18642015-02-11 08:54:44 -0800140 copy_paths(conn, self.files_to_copy)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800141
koder aka kdanilove87ae652015-04-20 02:14:35 +0300142 if self.options.get('prefill_files', True):
143 files = {}
koder aka kdanilov652cd802015-04-13 12:21:07 +0300144
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300145 for section in self.configs:
146 sz = ssize_to_b(section.vals['size'])
koder aka kdanilove87ae652015-04-20 02:14:35 +0300147 msz = sz / (1024 ** 2)
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300148
koder aka kdanilove87ae652015-04-20 02:14:35 +0300149 if sz % (1024 ** 2) != 0:
150 msz += 1
koder aka kdanilov6e2ae792015-03-04 18:02:24 -0800151
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300152 fname = section.vals['filename']
koder aka kdanilov652cd802015-04-13 12:21:07 +0300153
koder aka kdanilove87ae652015-04-20 02:14:35 +0300154 # if already has other test with the same file name
155 # take largest size
156 files[fname] = max(files.get(fname, 0), msz)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300157
koder aka kdanilove87ae652015-04-20 02:14:35 +0300158 # logger.warning("dd run DISABLED")
koder aka kdanilov6b1341a2015-04-21 22:44:21 +0300159 # cmd_templ = "dd if=/dev/zero of={0} bs={1} count={2}"
koder aka kdanilove87ae652015-04-20 02:14:35 +0300160
koder aka kdanilov6b1341a2015-04-21 22:44:21 +0300161 cmd_templ = "sudo dd if=/dev/zero of={0} bs={1} count={2}"
koder aka kdanilove87ae652015-04-20 02:14:35 +0300162 ssize = 0
163 stime = time.time()
164
165 for fname, curr_sz in files.items():
166 cmd = cmd_templ.format(fname, 1024 ** 2, curr_sz)
167 ssize += curr_sz
168 run_over_ssh(conn, cmd, timeout=curr_sz, node=self.node)
169
170 ddtime = time.time() - stime
171 if ddtime > 1E-3:
172 fill_bw = int(ssize / ddtime)
173 mess = "Initiall dd fill bw is {0} MiBps for this vm"
174 logger.info(mess.format(fill_bw))
175 else:
176 logger.warning("Test files prefill disabled")
koder aka kdanilov6e2ae792015-03-04 18:02:24 -0800177
koder aka kdanilov2c473092015-03-29 17:12:13 +0300178 def run(self, conn, barrier):
koder aka kdanilov6b1341a2015-04-21 22:44:21 +0300179 cmd_templ = "sudo env python2 {0} --type {1} {2} --json -"
180 # cmd_templ = "env python2 {0} --type {1} {2} --json -"
koder aka kdanilov66839a92015-04-11 13:22:31 +0300181
182 params = " ".join("{0}={1}".format(k, v)
183 for k, v in self.config_params.items())
184
185 if "" != params:
186 params = "--params " + params
187
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300188 cmd = cmd_templ.format(self.io_py_remote, self.tool, params)
koder aka kdanilov66839a92015-04-11 13:22:31 +0300189 logger.debug("Waiting on barrier")
koder aka kdanilov652cd802015-04-13 12:21:07 +0300190
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300191 exec_time = io_agent.calculate_execution_time(self.configs)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300192 exec_time_str = sec_to_str(exec_time)
193
koder aka kdanilov2c473092015-03-29 17:12:13 +0300194 try:
koder aka kdanilove87ae652015-04-20 02:14:35 +0300195 timeout = int(exec_time * 1.2 + 300)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300196 if barrier.wait():
koder aka kdanilovea22c3d2015-04-21 03:42:22 +0300197 templ = "Test should takes about {0}." + \
198 " Should finish at {1}," + \
199 " will wait at most till {2}"
200 now_dt = datetime.datetime.now()
201 end_dt = now_dt + datetime.timedelta(0, exec_time)
202 wait_till = now_dt + datetime.timedelta(0, timeout)
203
204 logger.info(templ.format(exec_time_str,
205 end_dt.strftime("%H:%M:%S"),
206 wait_till.strftime("%H:%M:%S")))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300207
208 out_err = run_over_ssh(conn, cmd,
209 stdin_data=self.raw_cfg,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300210 timeout=timeout,
211 node=self.node)
koder aka kdanilov12ae0632015-04-15 01:13:43 +0300212 logger.info("Done")
koder aka kdanilov2c473092015-03-29 17:12:13 +0300213 finally:
214 barrier.exit()
215
koder aka kdanilov652cd802015-04-13 12:21:07 +0300216 self.on_result(out_err, cmd)
217
koder aka kdanilov66839a92015-04-11 13:22:31 +0300218 def on_result(self, out_err, cmd):
219 try:
220 for data in parse_output(out_err):
221 self.on_result_cb(data)
222 except Exception as exc:
223 msg_templ = "Error during postprocessing results: {0!r}"
224 raise RuntimeError(msg_templ.format(exc.message))
225
226 def merge_results(self, results):
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300227 if len(results) == 0:
228 return None
229
koder aka kdanilov66839a92015-04-11 13:22:31 +0300230 merged_result = results[0]
231 merged_data = merged_result['res']
232 expected_keys = set(merged_data.keys())
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300233 mergable_fields = ['bw', 'clat', 'iops', 'lat', 'slat']
koder aka kdanilov66839a92015-04-11 13:22:31 +0300234
235 for res in results[1:]:
236 assert res['__meta__'] == merged_result['__meta__']
237
238 data = res['res']
239 diff = set(data.keys()).symmetric_difference(expected_keys)
240
241 msg = "Difference: {0}".format(",".join(diff))
242 assert len(diff) == 0, msg
243
244 for testname, test_data in data.items():
245 res_test_data = merged_data[testname]
246
247 diff = set(test_data.keys()).symmetric_difference(
248 res_test_data.keys())
249
250 msg = "Difference: {0}".format(",".join(diff))
251 assert len(diff) == 0, msg
252
253 for k, v in test_data.items():
254 if k in mergable_fields:
255 res_test_data[k].extend(v)
256 else:
257 msg = "{0!r} != {1!r}".format(res_test_data[k], v)
258 assert res_test_data[k] == v, msg
259
260 return merged_result
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300261
262 @classmethod
263 def format_for_console(cls, data):
264 return io_formatter.format_results_for_console(data)