blob: 0b8a131be0724d635033bbeb5cb1a0252162be86 [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 kdanilov0c598a12015-04-21 03:01:40 +0300159 cmd_templ = "dd if=/dev/zero of={0} bs={1} count={2}"
koder aka kdanilove87ae652015-04-20 02:14:35 +0300160
koder aka kdanilov0c598a12015-04-21 03:01:40 +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 kdanilov168f6092015-04-19 02:33:38 +0300179 # logger.warning("No tests runned")
180 # return
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300181 # cmd_templ = "sudo env python2 {0} --type {1} {2} --json -"
182 cmd_templ = "env python2 {0} --type {1} {2} --json -"
koder aka kdanilov66839a92015-04-11 13:22:31 +0300183
184 params = " ".join("{0}={1}".format(k, v)
185 for k, v in self.config_params.items())
186
187 if "" != params:
188 params = "--params " + params
189
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300190 cmd = cmd_templ.format(self.io_py_remote, self.tool, params)
koder aka kdanilov66839a92015-04-11 13:22:31 +0300191 logger.debug("Waiting on barrier")
koder aka kdanilov652cd802015-04-13 12:21:07 +0300192
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300193 exec_time = io_agent.calculate_execution_time(self.configs)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300194 exec_time_str = sec_to_str(exec_time)
195
koder aka kdanilov2c473092015-03-29 17:12:13 +0300196 try:
koder aka kdanilove87ae652015-04-20 02:14:35 +0300197 timeout = int(exec_time * 1.2 + 300)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300198 if barrier.wait():
koder aka kdanilovea22c3d2015-04-21 03:42:22 +0300199 templ = "Test should takes about {0}." + \
200 " Should finish at {1}," + \
201 " will wait at most till {2}"
202 now_dt = datetime.datetime.now()
203 end_dt = now_dt + datetime.timedelta(0, exec_time)
204 wait_till = now_dt + datetime.timedelta(0, timeout)
205
206 logger.info(templ.format(exec_time_str,
207 end_dt.strftime("%H:%M:%S"),
208 wait_till.strftime("%H:%M:%S")))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300209
210 out_err = run_over_ssh(conn, cmd,
211 stdin_data=self.raw_cfg,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300212 timeout=timeout,
213 node=self.node)
koder aka kdanilov12ae0632015-04-15 01:13:43 +0300214 logger.info("Done")
koder aka kdanilov2c473092015-03-29 17:12:13 +0300215 finally:
216 barrier.exit()
217
koder aka kdanilov652cd802015-04-13 12:21:07 +0300218 self.on_result(out_err, cmd)
219
koder aka kdanilov66839a92015-04-11 13:22:31 +0300220 def on_result(self, out_err, cmd):
221 try:
222 for data in parse_output(out_err):
223 self.on_result_cb(data)
224 except Exception as exc:
225 msg_templ = "Error during postprocessing results: {0!r}"
226 raise RuntimeError(msg_templ.format(exc.message))
227
228 def merge_results(self, results):
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300229 if len(results) == 0:
230 return None
231
koder aka kdanilov66839a92015-04-11 13:22:31 +0300232 merged_result = results[0]
233 merged_data = merged_result['res']
234 expected_keys = set(merged_data.keys())
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300235 mergable_fields = ['bw', 'clat', 'iops', 'lat', 'slat']
koder aka kdanilov66839a92015-04-11 13:22:31 +0300236
237 for res in results[1:]:
238 assert res['__meta__'] == merged_result['__meta__']
239
240 data = res['res']
241 diff = set(data.keys()).symmetric_difference(expected_keys)
242
243 msg = "Difference: {0}".format(",".join(diff))
244 assert len(diff) == 0, msg
245
246 for testname, test_data in data.items():
247 res_test_data = merged_data[testname]
248
249 diff = set(test_data.keys()).symmetric_difference(
250 res_test_data.keys())
251
252 msg = "Difference: {0}".format(",".join(diff))
253 assert len(diff) == 0, msg
254
255 for k, v in test_data.items():
256 if k in mergable_fields:
257 res_test_data[k].extend(v)
258 else:
259 msg = "{0!r} != {1!r}".format(res_test_data[k], v)
260 assert res_test_data[k] == v, msg
261
262 return merged_result
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300263
264 @classmethod
265 def format_for_console(cls, data):
266 return io_formatter.format_results_for_console(data)