blob: 50bb1fdd4b1aa1a965dc9c5e291c07e095cde48b [file] [log] [blame]
koder aka kdanilovbc2c8982015-06-13 02:50:43 +03001import re
2import time
3import json
koder aka kdanilovf236b9c2015-06-24 18:17:22 +03004import stat
koder aka kdanilov6ab4d432015-06-22 00:26:28 +03005import random
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +03006import hashlib
koder aka kdanilovbc2c8982015-06-13 02:50:43 +03007import os.path
8import logging
9import datetime
10import functools
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030011import collections
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030012from typing import Dict, List, Callable, Any, Tuple, Optional
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030013
14import yaml
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030015import texttable
16from paramiko.ssh_exception import SSHException
koder aka kdanilova94dfe12015-08-19 13:04:51 +030017from concurrent.futures import ThreadPoolExecutor, wait
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030018
koder aka kdanilov6ab4d432015-06-22 00:26:28 +030019import wally
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030020
21from ...pretty_yaml import dumps
22from ...statistic import round_3_digit, data_property, average
23from ...utils import ssize2b, sec_to_str, StopTestError, Barrier, get_os
24from ...inode import INode
25
26from ..itest import (TimeSeriesValue, PerfTest, TestResults, TestConfig)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030027
28from .fio_task_parser import (execution_time, fio_cfg_compile,
koder aka kdanilovf236b9c2015-06-24 18:17:22 +030029 get_test_summary, get_test_summary_tuple,
30 get_test_sync_mode, FioJobSection)
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030031from .rpc_plugin import parse_fio_result
koder aka kdanilovf236b9c2015-06-24 18:17:22 +030032
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030033
34logger = logging.getLogger("wally")
35
36
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030037class NoData:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030038 pass
39
40
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030041def cached_prop(func: Callable[..., Any]) -> Callable[..., Any]:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030042 @property
43 @functools.wraps(func)
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030044 def closure(self) -> Any:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030045 val = getattr(self, "_" + func.__name__)
46 if val is NoData:
47 val = func(self)
48 setattr(self, "_" + func.__name__, val)
49 return val
50 return closure
51
52
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030053def load_fio_log_file(fname: str) -> TimeSeriesValue:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030054 with open(fname) as fd:
55 it = [ln.split(',')[:2] for ln in fd]
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +030056
57 vals = [(float(off) / 1000, # convert us to ms
58 float(val.strip()) + 0.5) # add 0.5 to compemsate average value
59 # as fio trimm all values in log to integer
60 for off, val in it]
61
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030062 return TimeSeriesValue(vals)
63
64
koder aka kdanilova94dfe12015-08-19 13:04:51 +030065READ_IOPS_DISCSTAT_POS = 3
66WRITE_IOPS_DISCSTAT_POS = 7
67
68
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030069def load_sys_log_file(ftype: str, fname: str) -> TimeSeriesValue:
koder aka kdanilova94dfe12015-08-19 13:04:51 +030070 assert ftype == 'iops'
71 pval = None
72 with open(fname) as fd:
73 iops = []
74 for ln in fd:
75 params = ln.split()
76 cval = int(params[WRITE_IOPS_DISCSTAT_POS]) + \
77 int(params[READ_IOPS_DISCSTAT_POS])
78 if pval is not None:
79 iops.append(cval - pval)
80 pval = cval
81
82 vals = [(idx * 1000, val) for idx, val in enumerate(iops)]
83 return TimeSeriesValue(vals)
84
85
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030086def load_test_results(folder: str, run_num: int) -> 'FioRunResult':
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030087 res = {}
88 params = None
89
90 fn = os.path.join(folder, str(run_num) + '_params.yaml')
91 params = yaml.load(open(fn).read())
92
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +030093 conn_ids_set = set()
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030094 rr = r"{}_(?P<conn_id>.*?)_(?P<type>[^_.]*)\.\d+\.log$".format(run_num)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030095 for fname in os.listdir(folder):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030096 rm = re.match(rr, fname)
97 if rm is None:
98 continue
99
100 conn_id_s = rm.group('conn_id')
101 conn_id = conn_id_s.replace('_', ':')
102 ftype = rm.group('type')
103
104 if ftype not in ('iops', 'bw', 'lat'):
105 continue
106
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300107 ts = load_fio_log_file(os.path.join(folder, fname))
108 res.setdefault(ftype, {}).setdefault(conn_id, []).append(ts)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300109
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300110 conn_ids_set.add(conn_id)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300111
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300112 rr = r"{}_(?P<conn_id>.*?)_(?P<type>[^_.]*)\.sys\.log$".format(run_num)
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300113 for fname in os.listdir(folder):
114 rm = re.match(rr, fname)
115 if rm is None:
116 continue
117
118 conn_id_s = rm.group('conn_id')
119 conn_id = conn_id_s.replace('_', ':')
120 ftype = rm.group('type')
121
122 if ftype not in ('iops', 'bw', 'lat'):
123 continue
124
125 ts = load_sys_log_file(ftype, os.path.join(folder, fname))
126 res.setdefault(ftype + ":sys", {}).setdefault(conn_id, []).append(ts)
127
128 conn_ids_set.add(conn_id)
129
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300130 mm_res = {}
131
koder aka kdanilov9e0512a2015-08-10 14:51:59 +0300132 if len(res) == 0:
133 raise ValueError("No data was found")
134
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300135 for key, data in res.items():
136 conn_ids = sorted(conn_ids_set)
koder aka kdanilov765920a2016-04-12 00:35:48 +0300137 awail_ids = [conn_id for conn_id in conn_ids if conn_id in data]
138 matr = [data[conn_id] for conn_id in awail_ids]
139 mm_res[key] = MeasurementMatrix(matr, awail_ids)
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300140
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300141 raw_res = {}
142 for conn_id in conn_ids:
143 fn = os.path.join(folder, "{0}_{1}_rawres.json".format(run_num, conn_id_s))
144
145 # remove message hack
146 fc = "{" + open(fn).read().split('{', 1)[1]
147 raw_res[conn_id] = json.loads(fc)
148
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300149 fio_task = FioJobSection(params['name'])
150 fio_task.vals.update(params['vals'])
151
152 config = TestConfig('io', params, None, params['nodes'], folder, None)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300153 return FioRunResult(config, fio_task, mm_res, raw_res, params['intervals'], run_num)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300154
155
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300156class Attrmapper:
157 def __init__(self, dct: Dict[str, Any]):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300158 self.__dct = dct
159
160 def __getattr__(self, name):
161 try:
162 return self.__dct[name]
163 except KeyError:
164 raise AttributeError(name)
165
166
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300167class DiskPerfInfo:
168 def __init__(self, name: str, summary: str, params: Dict[str, Any], testnodes_count: int):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300169 self.name = name
170 self.bw = None
171 self.iops = None
172 self.lat = None
173 self.lat_50 = None
174 self.lat_95 = None
koder aka kdanilov170936a2015-06-27 22:51:17 +0300175 self.lat_avg = None
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300176
177 self.raw_bw = []
178 self.raw_iops = []
179 self.raw_lat = []
180
181 self.params = params
182 self.testnodes_count = testnodes_count
183 self.summary = summary
184 self.p = Attrmapper(self.params['vals'])
185
186 self.sync_mode = get_test_sync_mode(self.params['vals'])
187 self.concurence = self.params['vals'].get('numjobs', 1)
188
189
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300190def get_lat_perc_50_95(lat_mks: List[float]) -> Tuple[float, float]:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300191 curr_perc = 0
192 perc_50 = None
193 perc_95 = None
194 pkey = None
195 for key, val in sorted(lat_mks.items()):
196 if curr_perc + val >= 50 and perc_50 is None:
197 if pkey is None or val < 1.:
198 perc_50 = key
199 else:
200 perc_50 = (50. - curr_perc) / val * (key - pkey) + pkey
201
202 if curr_perc + val >= 95:
203 if pkey is None or val < 1.:
204 perc_95 = key
205 else:
206 perc_95 = (95. - curr_perc) / val * (key - pkey) + pkey
207 break
208
209 pkey = key
210 curr_perc += val
211
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300212 # for k, v in sorted(lat_mks.items()):
213 # if k / 1000 > 0:
214 # print "{0:>4}".format(k / 1000), v
215
216 # print perc_50 / 1000., perc_95 / 1000.
217 # exit(1)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300218 return perc_50 / 1000., perc_95 / 1000.
219
220
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300221class IOTestResults:
222 def __init__(self, suite_name: str, fio_results: 'FioRunResult', log_directory: str):
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300223 self.suite_name = suite_name
224 self.fio_results = fio_results
225 self.log_directory = log_directory
226
227 def __iter__(self):
228 return iter(self.fio_results)
229
230 def __len__(self):
231 return len(self.fio_results)
232
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300233 def get_yamable(self) -> Dict[str, List[str]]:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300234 items = [(fio_res.summary(), fio_res.idx) for fio_res in self]
235 return {self.suite_name: [self.log_directory] + items}
236
237
238class FioRunResult(TestResults):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300239 """
240 Fio run results
241 config: TestConfig
242 fio_task: FioJobSection
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300243 ts_results: {str: MeasurementMatrix[TimeSeriesValue]}
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300244 raw_result: ????
245 run_interval:(float, float) - test tun time, used for sensors
246 """
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300247 def __init__(self, config, fio_task, ts_results, raw_result, run_interval, idx):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300248
koder aka kdanilov170936a2015-06-27 22:51:17 +0300249 self.name = fio_task.name.rsplit("_", 1)[0]
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300250 self.fio_task = fio_task
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300251 self.idx = idx
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300252
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300253 self.bw = ts_results['bw']
254 self.lat = ts_results['lat']
255 self.iops = ts_results['iops']
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300256
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300257 if 'iops:sys' in ts_results:
258 self.iops_sys = ts_results['iops:sys']
259 else:
260 self.iops_sys = None
261
262 res = {"bw": self.bw,
263 "lat": self.lat,
264 "iops": self.iops,
265 "iops:sys": self.iops_sys}
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300266
267 self.sensors_data = None
268 self._pinfo = None
269 TestResults.__init__(self, config, res, raw_result, run_interval)
270
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300271 def get_params_from_fio_report(self):
272 nodes = self.bw.connections_ids
273
274 iops = [self.raw_result[node]['jobs'][0]['mixed']['iops'] for node in nodes]
275 total_ios = [self.raw_result[node]['jobs'][0]['mixed']['total_ios'] for node in nodes]
276 runtime = [self.raw_result[node]['jobs'][0]['mixed']['runtime'] / 1000 for node in nodes]
277 flt_iops = [float(ios) / rtime for ios, rtime in zip(total_ios, runtime)]
278
279 bw = [self.raw_result[node]['jobs'][0]['mixed']['bw'] for node in nodes]
280 total_bytes = [self.raw_result[node]['jobs'][0]['mixed']['io_bytes'] for node in nodes]
281 flt_bw = [float(tbytes) / rtime for tbytes, rtime in zip(total_bytes, runtime)]
282
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300283 return {'iops': iops,
284 'flt_iops': flt_iops,
285 'bw': bw,
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300286 'flt_bw': flt_bw}
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300287
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300288 def summary(self):
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300289 return get_test_summary(self.fio_task, len(self.config.nodes))
290
291 def summary_tpl(self):
292 return get_test_summary_tuple(self.fio_task, len(self.config.nodes))
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300293
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300294 def get_lat_perc_50_95_multy(self):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300295 lat_mks = collections.defaultdict(lambda: 0)
296 num_res = 0
297
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300298 for result in self.raw_result.values():
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300299 num_res += len(result['jobs'])
300 for job_info in result['jobs']:
301 for k, v in job_info['latency_ms'].items():
302 if isinstance(k, basestring) and k.startswith('>='):
303 lat_mks[int(k[2:]) * 1000] += v
304 else:
305 lat_mks[int(k) * 1000] += v
306
307 for k, v in job_info['latency_us'].items():
308 lat_mks[int(k)] += v
309
310 for k, v in lat_mks.items():
311 lat_mks[k] = float(v) / num_res
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300312 return get_lat_perc_50_95(lat_mks)
313
314 def disk_perf_info(self, avg_interval=2.0):
315
316 if self._pinfo is not None:
317 return self._pinfo
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300318
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300319 testnodes_count = len(self.config.nodes)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300320
321 pinfo = DiskPerfInfo(self.name,
322 self.summary(),
323 self.params,
324 testnodes_count)
325
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300326 def prepare(data, drop=1):
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300327 if data is None:
328 return data
329
330 res = []
331 for ts_data in data:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300332 if ts_data.average_interval() < avg_interval:
333 ts_data = ts_data.derived(avg_interval)
334
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300335 # drop last value on bounds
336 # as they may contains ranges without activities
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300337 assert len(ts_data.values) >= drop + 1, str(drop) + " " + str(ts_data.values)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300338
339 if drop > 0:
340 res.append(ts_data.values[:-drop])
341 else:
342 res.append(ts_data.values)
343
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300344 return res
345
346 def agg_data(matr):
347 arr = sum(matr, [])
348 min_len = min(map(len, arr))
349 res = []
350 for idx in range(min_len):
351 res.append(sum(dt[idx] for dt in arr))
352 return res
353
koder aka kdanilov170936a2015-06-27 22:51:17 +0300354 pinfo.raw_lat = map(prepare, self.lat.per_vm())
355 num_th = sum(map(len, pinfo.raw_lat))
356 lat_avg = [val / num_th for val in agg_data(pinfo.raw_lat)]
357 pinfo.lat_avg = data_property(lat_avg).average / 1000 # us to ms
358
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300359 pinfo.lat_50, pinfo.lat_95 = self.get_lat_perc_50_95_multy()
360 pinfo.lat = pinfo.lat_50
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300361
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300362 pinfo.raw_bw = map(prepare, self.bw.per_vm())
363 pinfo.raw_iops = map(prepare, self.iops.per_vm())
364
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300365 if self.iops_sys is not None:
366 pinfo.raw_iops_sys = map(prepare, self.iops_sys.per_vm())
367 pinfo.iops_sys = data_property(agg_data(pinfo.raw_iops_sys))
368 else:
369 pinfo.raw_iops_sys = None
370 pinfo.iops_sys = None
371
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300372 fparams = self.get_params_from_fio_report()
373 fio_report_bw = sum(fparams['flt_bw'])
374 fio_report_iops = sum(fparams['flt_iops'])
375
376 agg_bw = agg_data(pinfo.raw_bw)
377 agg_iops = agg_data(pinfo.raw_iops)
378
379 log_bw_avg = average(agg_bw)
380 log_iops_avg = average(agg_iops)
381
382 # update values to match average from fio report
383 coef_iops = fio_report_iops / float(log_iops_avg)
384 coef_bw = fio_report_bw / float(log_bw_avg)
385
386 bw_log = data_property([val * coef_bw for val in agg_bw])
387 iops_log = data_property([val * coef_iops for val in agg_iops])
388
389 bw_report = data_property([fio_report_bw])
390 iops_report = data_property([fio_report_iops])
391
392 # When IOPS/BW per thread is too low
393 # data from logs is rounded to match
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300394 iops_per_th = sum(sum(pinfo.raw_iops, []), [])
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300395 if average(iops_per_th) > 10:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300396 pinfo.iops = iops_log
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300397 pinfo.iops2 = iops_report
398 else:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300399 pinfo.iops = iops_report
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300400 pinfo.iops2 = iops_log
401
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300402 bw_per_th = sum(sum(pinfo.raw_bw, []), [])
403 if average(bw_per_th) > 10:
404 pinfo.bw = bw_log
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300405 pinfo.bw2 = bw_report
koder aka kdanilov170936a2015-06-27 22:51:17 +0300406 else:
407 pinfo.bw = bw_report
408 pinfo.bw2 = bw_log
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300409
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300410 self._pinfo = pinfo
411
412 return pinfo
413
414
415class IOPerfTest(PerfTest):
416 tcp_conn_timeout = 30
417 max_pig_timeout = 5
418 soft_runcycle = 5 * 60
Michael Semenov8ba6e232015-08-28 10:57:18 +0000419 retry_time = 30
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300420
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300421 zero_md5_hash = hashlib.md5()
422 zero_md5_hash.update(b"\x00" * 1024)
423 zero_md5 = zero_md5_hash.hexdigest()
424
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300425 def __init__(self, config):
426 PerfTest.__init__(self, config)
427
428 get = self.config.params.get
429 do_get = self.config.params.__getitem__
430
431 self.config_fname = do_get('cfg')
432
433 if '/' not in self.config_fname and '.' not in self.config_fname:
434 cfgs_dir = os.path.dirname(__file__)
435 self.config_fname = os.path.join(cfgs_dir,
436 self.config_fname + '.cfg')
437
438 self.alive_check_interval = get('alive_check_interval')
439 self.use_system_fio = get('use_system_fio', False)
440
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300441 if get('prefill_files') is not None:
442 logger.warning("prefill_files option is depricated. Use force_prefill instead")
443
444 self.force_prefill = get('force_prefill', False)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300445 self.config_params = get('params', {}).copy()
446
447 self.io_py_remote = self.join_remote("agent.py")
448 self.results_file = self.join_remote("results.json")
449 self.pid_file = self.join_remote("pid")
450 self.task_file = self.join_remote("task.cfg")
451 self.sh_file = self.join_remote("cmd.sh")
452 self.err_out_file = self.join_remote("fio_err_out")
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300453 self.io_log_file = self.join_remote("io_log.txt")
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300454 self.exit_code_file = self.join_remote("exit_code")
455
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300456 self.max_latency = get("max_lat", None)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300457 self.min_bw_per_thread = get("min_bw", None)
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300458
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300459 self.use_sudo = get("use_sudo", True)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300460
461 self.raw_cfg = open(self.config_fname).read()
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300462 self.fio_configs = None
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300463
464 @classmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300465 def load(cls, suite_name: str, folder: str) -> IOTestResults:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300466 res = []
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300467 for fname in os.listdir(folder):
468 if re.match("\d+_params.yaml$", fname):
469 num = int(fname.split('_')[0])
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300470 res.append(load_test_results(folder, num))
471 return IOTestResults(suite_name, res, folder)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300472
473 def cleanup(self):
474 # delete_file(conn, self.io_py_remote)
475 # Need to remove tempo files, used for testing
476 pass
477
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300478 # size is megabytes
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300479 def check_prefill_required(self, node: INode, fname: str, size: int, num_blocks: Optional[int]=16) -> bool:
koder aka kdanilov170936a2015-06-27 22:51:17 +0300480 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300481 fstats = node.stat_file(fname)
koder aka kdanilov170936a2015-06-27 22:51:17 +0300482 if stat.S_ISREG(fstats.st_mode) and fstats.st_size < size * 1024 ** 2:
483 return True
484 except EnvironmentError:
koder aka kdanilovf95cfc12015-06-23 03:33:19 +0300485 return True
486
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300487 cmd = 'python -c "' + \
488 "import sys;" + \
489 "fd = open('{0}', 'rb');" + \
490 "fd.seek({1});" + \
491 "data = fd.read(1024); " + \
492 "sys.stdout.write(data + ' ' * ( 1024 - len(data)))\" | md5sum"
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300493
494 if self.use_sudo:
495 cmd = "sudo " + cmd
496
koder aka kdanilov8fbb27f2015-07-17 22:23:31 +0300497 bsize = size * (1024 ** 2)
498 offsets = [random.randrange(bsize - 1024) for _ in range(num_blocks)]
499 offsets.append(bsize - 1024)
500 offsets.append(0)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300501
koder aka kdanilovf95cfc12015-06-23 03:33:19 +0300502 for offset in offsets:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300503 data = node.run(cmd.format(fname, offset), nolog=True)
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300504
505 md = ""
506 for line in data.split("\n"):
507 if "unable to resolve" not in line:
508 md = line.split()[0].strip()
509 break
koder aka kdanilovf95cfc12015-06-23 03:33:19 +0300510
511 if len(md) != 32:
512 logger.error("File data check is failed - " + data)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300513 return True
koder aka kdanilovf95cfc12015-06-23 03:33:19 +0300514
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300515 if self.zero_md5 == md:
koder aka kdanilovf95cfc12015-06-23 03:33:19 +0300516 return True
517
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300518 return False
519
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300520 def prefill_test_files(self, node: INode, files: List[str], force:bool=False) -> None:
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300521 if self.use_system_fio:
522 cmd_templ = "fio "
523 else:
524 cmd_templ = "{0}/fio ".format(self.config.remote_dir)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300525
526 if self.use_sudo:
527 cmd_templ = "sudo " + cmd_templ
528
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300529 cmd_templ += "--name=xxx --filename={0} --direct=1" + \
530 " --bs=4m --size={1}m --rw=write"
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300531
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300532 ssize = 0
533
534 if force:
535 logger.info("File prefilling is forced")
536
537 ddtime = 0
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300538 for fname, curr_sz in files.items():
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300539 if not force:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300540 if not self.check_prefill_required(node, fname, curr_sz):
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300541 logger.debug("prefill is skipped")
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300542 continue
543
544 logger.info("Prefilling file {0}".format(fname))
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300545 cmd = cmd_templ.format(fname, curr_sz)
546 ssize += curr_sz
547
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300548 stime = time.time()
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300549 node.run(cmd, timeout=curr_sz)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300550 ddtime += time.time() - stime
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300551
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300552 if ddtime > 1.0:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300553 fill_bw = int(ssize / ddtime)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300554 mess = "Initiall fio fill bw is {0} MiBps for this vm"
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300555 logger.info(mess.format(fill_bw))
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300556
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300557 def install_utils(self, node: INode) -> None:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300558 need_install = []
559 packs = [('screen', 'screen')]
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300560 os_info = get_os(node)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300561
562 if self.use_system_fio:
563 packs.append(('fio', 'fio'))
564 else:
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300565 packs.append(('bzip2', 'bzip2'))
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300566
567 for bin_name, package in packs:
568 if bin_name is None:
569 need_install.append(package)
570 continue
571
572 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300573 node.run('which ' + bin_name, nolog=True)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300574 except OSError:
575 need_install.append(package)
576
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300577 if len(need_install) != 0:
578 if 'redhat' == os_info.distro:
579 cmd = "sudo yum -y install " + " ".join(need_install)
580 else:
581 cmd = "sudo apt-get -y install " + " ".join(need_install)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300582
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300583 try:
584 node.run(cmd)
585 except OSError as err:
586 raise OSError("Can't install - {}".format(" ".join(need_install))) from err
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300587
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300588 if not self.use_system_fio:
589 fio_dir = os.path.dirname(os.path.dirname(wally.__file__))
590 fio_dir = os.path.join(os.getcwd(), fio_dir)
591 fio_dir = os.path.join(fio_dir, 'fio_binaries')
592 fname = 'fio_{0.release}_{0.arch}.bz2'.format(os_info)
593 fio_path = os.path.join(fio_dir, fname)
594
595 if not os.path.exists(fio_path):
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300596 raise RuntimeError("No prebuild fio binary available for {0}".format(os_info))
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300597
598 bz_dest = self.join_remote('fio.bz2')
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300599 node.copy_file(fio_path, bz_dest)
600 node.run("bzip2 --decompress {}" + bz_dest, nolog=True)
601 node.run("chmod a+x " + self.join_remote("fio"), nolog=True)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300602
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300603 def pre_run(self) -> None:
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300604 if 'FILESIZE' not in self.config_params:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300605 raise NotImplementedError("File size detection is not implemented")
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300606
607 self.fio_configs = fio_cfg_compile(self.raw_cfg,
608 self.config_fname,
609 self.config_params)
610 self.fio_configs = list(self.fio_configs)
611
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300612 files = {}
613 for section in self.fio_configs:
614 sz = ssize2b(section.vals['size'])
615 msz = sz / (1024 ** 2)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300616
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300617 if sz % (1024 ** 2) != 0:
618 msz += 1
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300619
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300620 fname = section.vals['filename']
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300621
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300622 # if already has other test with the same file name
623 # take largest size
624 files[fname] = max(files.get(fname, 0), msz)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300625
626 with ThreadPoolExecutor(len(self.config.nodes)) as pool:
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300627 fc = functools.partial(self.pre_run_th,
628 files=files,
629 force=self.force_prefill)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300630 list(pool.map(fc, self.config.nodes))
631
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300632 def pre_run_th(self, node: INode, files: List[str], force_prefil: Optional[bool]=False) -> None:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300633 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300634 cmd = 'mkdir -p "{0}"'.format(self.config.remote_dir)
635 if self.use_sudo:
636 cmd = "sudo " + cmd
637 cmd += " ; sudo chown {0} {1}".format(node.get_user(),
638 self.config.remote_dir)
639 node.run(cmd, nolog=True)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300640
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300641 assert self.config.remote_dir != "" and self.config.remote_dir != "/"
642 node.run("rm -rf {}/*".format(self.config.remote_dir), nolog=True)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300643
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300644 except Exception as exc:
645 msg = "Failed to create folder {} on remote {}."
646 msg = msg.format(self.config.remote_dir, node, exc)
647 logger.exception(msg)
648 raise StopTestError(msg) from exc
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300649
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300650 self.install_utils(node)
651 self.prefill_test_files(node, files, force_prefil)
koder aka kdanilov8fbb27f2015-07-17 22:23:31 +0300652
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300653 def show_expected_execution_time(self) -> None:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300654 if len(self.fio_configs) > 1:
655 # +10% - is a rough estimation for additional operations
656 # like sftp, etc
657 exec_time = int(sum(map(execution_time, self.fio_configs)) * 1.1)
658 exec_time_s = sec_to_str(exec_time)
659 now_dt = datetime.datetime.now()
660 end_dt = now_dt + datetime.timedelta(0, exec_time)
661 msg = "Entire test should takes aroud: {0} and finished at {1}"
662 logger.info(msg.format(exec_time_s,
663 end_dt.strftime("%H:%M:%S")))
664
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300665 def run(self) -> IOTestResults:
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300666 logger.debug("Run preparation")
667 self.pre_run()
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300668 self.show_expected_execution_time()
669 num_nodes = len(self.config.nodes)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300670
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300671 tname = os.path.basename(self.config_fname)
672 if tname.endswith('.cfg'):
673 tname = tname[:-4]
674
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300675 barrier = Barrier(num_nodes)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300676 results = []
677
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300678 # set of Operation_Mode_BlockSize str's
679 # which should not be tested anymore, as
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300680 # they already too slow with previous thread count
681 lat_bw_limit_reached = set()
682
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300683 with ThreadPoolExecutor(num_nodes) as pool:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300684 for pos, fio_cfg in enumerate(self.fio_configs):
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300685 test_descr = get_test_summary(fio_cfg.vals, noqd=True)
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300686 if test_descr in lat_bw_limit_reached:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300687 continue
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300688
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300689 logger.info("Will run {} test".format(fio_cfg.name))
690 templ = "Test should takes about {}. Should finish at {}, will wait at most till {}"
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300691 exec_time = execution_time(fio_cfg)
692 exec_time_str = sec_to_str(exec_time)
693 timeout = int(exec_time + max(300, exec_time))
694
695 now_dt = datetime.datetime.now()
696 end_dt = now_dt + datetime.timedelta(0, exec_time)
697 wait_till = now_dt + datetime.timedelta(0, timeout)
698
699 logger.info(templ.format(exec_time_str,
700 end_dt.strftime("%H:%M:%S"),
701 wait_till.strftime("%H:%M:%S")))
702
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300703 run_test_func = functools.partial(self.do_run,
704 barrier=barrier,
705 fio_cfg=fio_cfg,
706 pos=pos)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300707
708 max_retr = 3
709 for idx in range(max_retr):
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300710 if 0 != idx:
711 logger.info("Sleeping %ss and retrying", self.retry_time)
712 time.sleep(self.retry_time)
713
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300714 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300715 intervals = list(pool.map(run_test_func, self.config.nodes))
koder aka kdanilov76471642015-08-14 11:44:43 +0300716 if None not in intervals:
717 break
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300718 except (EnvironmentError, SSHException) as exc:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300719 if max_retr - 1 == idx:
720 raise StopTestError("Fio failed") from exc
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300721 logger.exception("During fio run")
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300722
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300723 fname = "{}_task.fio".format(pos)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300724 with open(os.path.join(self.config.log_directory, fname), "w") as fd:
725 fd.write(str(fio_cfg))
726
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300727 params = {'vm_count': num_nodes}
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300728 params['name'] = fio_cfg.name
729 params['vals'] = dict(fio_cfg.vals.items())
730 params['intervals'] = intervals
731 params['nodes'] = [node.get_conn_id() for node in self.config.nodes]
732
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300733 fname = "{}_params.yaml".format(pos)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300734 with open(os.path.join(self.config.log_directory, fname), "w") as fd:
735 fd.write(dumps(params))
736
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300737 res = load_test_results(self.config.log_directory, pos)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300738 results.append(res)
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300739
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300740 if self.max_latency is not None:
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300741 lat_50, _ = res.get_lat_perc_50_95_multy()
742
743 # conver us to ms
744 if self.max_latency < lat_50:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300745 logger.info(("Will skip all subsequent tests of {} " +
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300746 "due to lat/bw limits").format(fio_cfg.name))
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300747 lat_bw_limit_reached.add(test_descr)
748
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300749 test_res = res.get_params_from_fio_report()
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300750 if self.min_bw_per_thread is not None:
751 if self.min_bw_per_thread > average(test_res['bw']):
752 lat_bw_limit_reached.add(test_descr)
753
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300754 return IOTestResults(self.config.params['cfg'],
755 results, self.config.log_directory)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300756
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300757 def do_run(self, node: INode, barrier: Barrier, fio_cfg, pos: int, nolog: bool=False):
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300758 exec_folder = self.config.remote_dir
759
760 if self.use_system_fio:
761 fio_path = ""
762 else:
763 if not exec_folder.endswith("/"):
764 fio_path = exec_folder + "/"
765 else:
766 fio_path = exec_folder
767
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300768 exec_time = execution_time(fio_cfg)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300769 barrier.wait()
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300770 run_data = node.rpc.fio.run_fio(self.use_sudo,
771 fio_path,
772 exec_folder,
773 str(fio_cfg),
774 exec_time + max(300, exec_time))
775 return parse_fio_result(run_data)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300776
777 @classmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300778 def prepare_data(cls, results) -> List[Dict[str, Any]]:
779 """create a table with io performance report for console"""
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300780
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300781 def key_func(data) -> Tuple(str, str, str, str, int):
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300782 tpl = data.summary_tpl()
koder aka kdanilov170936a2015-06-27 22:51:17 +0300783 return (data.name,
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300784 tpl.oper,
785 tpl.mode,
koder aka kdanilov170936a2015-06-27 22:51:17 +0300786 ssize2b(tpl.bsize),
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300787 int(tpl.th_count) * int(tpl.vm_count))
koder aka kdanilov6b872662015-06-23 01:58:36 +0300788 res = []
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300789
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300790 for item in sorted(results, key=key_func):
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300791 test_dinfo = item.disk_perf_info()
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300792 testnodes_count = len(item.config.nodes)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300793
794 iops, _ = test_dinfo.iops.rounded_average_conf()
795
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300796 if test_dinfo.iops_sys is not None:
797 iops_sys, iops_sys_conf = test_dinfo.iops_sys.rounded_average_conf()
798 _, iops_sys_dev = test_dinfo.iops_sys.rounded_average_dev()
799 iops_sys_per_vm = round_3_digit(iops_sys / testnodes_count)
800 iops_sys = round_3_digit(iops_sys)
801 else:
802 iops_sys = None
803 iops_sys_per_vm = None
804 iops_sys_dev = None
805 iops_sys_conf = None
806
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300807 bw, bw_conf = test_dinfo.bw.rounded_average_conf()
808 _, bw_dev = test_dinfo.bw.rounded_average_dev()
809 conf_perc = int(round(bw_conf * 100 / bw))
810 dev_perc = int(round(bw_dev * 100 / bw))
811
koder aka kdanilov6b872662015-06-23 01:58:36 +0300812 lat_50 = round_3_digit(int(test_dinfo.lat_50))
813 lat_95 = round_3_digit(int(test_dinfo.lat_95))
koder aka kdanilov170936a2015-06-27 22:51:17 +0300814 lat_avg = round_3_digit(int(test_dinfo.lat_avg))
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300815
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300816 iops_per_vm = round_3_digit(iops / testnodes_count)
817 bw_per_vm = round_3_digit(bw / testnodes_count)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300818
819 iops = round_3_digit(iops)
820 bw = round_3_digit(bw)
821
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300822 summ = "{0.oper}{0.mode} {0.bsize:>4} {0.th_count:>3}th {0.vm_count:>2}vm".format(item.summary_tpl())
823
824 res.append({"name": key_func(item)[0],
825 "key": key_func(item)[:4],
826 "summ": summ,
koder aka kdanilov6b872662015-06-23 01:58:36 +0300827 "iops": int(iops),
828 "bw": int(bw),
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300829 "conf": str(conf_perc),
830 "dev": str(dev_perc),
koder aka kdanilov6b872662015-06-23 01:58:36 +0300831 "iops_per_vm": int(iops_per_vm),
832 "bw_per_vm": int(bw_per_vm),
833 "lat_50": lat_50,
koder aka kdanilov170936a2015-06-27 22:51:17 +0300834 "lat_95": lat_95,
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300835 "lat_avg": lat_avg,
836
837 "iops_sys": iops_sys,
838 "iops_sys_per_vm": iops_sys_per_vm,
839 "sys_conf": iops_sys_conf,
840 "sys_dev": iops_sys_dev})
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300841
koder aka kdanilov6b872662015-06-23 01:58:36 +0300842 return res
843
844 Field = collections.namedtuple("Field", ("header", "attr", "allign", "size"))
845 fiels_and_header = [
846 Field("Name", "name", "l", 7),
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300847 Field("Description", "summ", "l", 19),
koder aka kdanilov6b872662015-06-23 01:58:36 +0300848 Field("IOPS\ncum", "iops", "r", 3),
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300849 # Field("IOPS_sys\ncum", "iops_sys", "r", 3),
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300850 Field("KiBps\ncum", "bw", "r", 6),
851 Field("Cnf %\n95%", "conf", "r", 3),
852 Field("Dev%", "dev", "r", 3),
853 Field("iops\n/vm", "iops_per_vm", "r", 3),
854 Field("KiBps\n/vm", "bw_per_vm", "r", 6),
koder aka kdanilov6b872662015-06-23 01:58:36 +0300855 Field("lat ms\nmedian", "lat_50", "r", 3),
koder aka kdanilov170936a2015-06-27 22:51:17 +0300856 Field("lat ms\n95%", "lat_95", "r", 3),
857 Field("lat\navg", "lat_avg", "r", 3),
koder aka kdanilov6b872662015-06-23 01:58:36 +0300858 ]
859
860 fiels_and_header_dct = dict((item.attr, item) for item in fiels_and_header)
861
862 @classmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300863 def format_for_console(cls, results) -> str:
864 """create a table with io performance report for console"""
koder aka kdanilov6b872662015-06-23 01:58:36 +0300865
866 tab = texttable.Texttable(max_width=120)
867 tab.set_deco(tab.HEADER | tab.VLINES | tab.BORDER)
868 tab.set_cols_align([f.allign for f in cls.fiels_and_header])
869 sep = ["-" * f.size for f in cls.fiels_and_header]
870 tab.header([f.header for f in cls.fiels_and_header])
koder aka kdanilov6b872662015-06-23 01:58:36 +0300871 prev_k = None
872 for item in cls.prepare_data(results):
koder aka kdanilov6b872662015-06-23 01:58:36 +0300873 if prev_k is not None:
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300874 if prev_k != item["key"]:
koder aka kdanilov6b872662015-06-23 01:58:36 +0300875 tab.add_row(sep)
876
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300877 prev_k = item["key"]
koder aka kdanilov6b872662015-06-23 01:58:36 +0300878 tab.add_row([item[f.attr] for f in cls.fiels_and_header])
879
880 return tab.draw()
881
882 @classmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300883 def format_diff_for_console(cls, list_of_results: List[Any]) -> str:
884 """create a table with io performance report for console"""
koder aka kdanilov6b872662015-06-23 01:58:36 +0300885
886 tab = texttable.Texttable(max_width=200)
887 tab.set_deco(tab.HEADER | tab.VLINES | tab.BORDER)
888
889 header = [
890 cls.fiels_and_header_dct["name"].header,
891 cls.fiels_and_header_dct["summ"].header,
892 ]
893 allign = ["l", "l"]
894
895 header.append("IOPS ~ Cnf% ~ Dev%")
896 allign.extend(["r"] * len(list_of_results))
897 header.extend(
898 "IOPS_{0} %".format(i + 2) for i in range(len(list_of_results[1:]))
899 )
900
901 header.append("BW")
902 allign.extend(["r"] * len(list_of_results))
903 header.extend(
904 "BW_{0} %".format(i + 2) for i in range(len(list_of_results[1:]))
905 )
906
907 header.append("LAT")
908 allign.extend(["r"] * len(list_of_results))
909 header.extend(
910 "LAT_{0}".format(i + 2) for i in range(len(list_of_results[1:]))
911 )
912
913 tab.header(header)
914 sep = ["-" * 3] * len(header)
915 processed_results = map(cls.prepare_data, list_of_results)
916
917 key2results = []
918 for res in processed_results:
919 key2results.append(dict(
920 ((item["name"], item["summ"]), item) for item in res
921 ))
922
923 prev_k = None
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300924 iops_frmt = "{0[iops]} ~ {0[conf]:>2} ~ {0[dev]:>2}"
koder aka kdanilov6b872662015-06-23 01:58:36 +0300925 for item in processed_results[0]:
koder aka kdanilov6b872662015-06-23 01:58:36 +0300926 if prev_k is not None:
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300927 if prev_k != item["key"]:
koder aka kdanilov6b872662015-06-23 01:58:36 +0300928 tab.add_row(sep)
929
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300930 prev_k = item["key"]
koder aka kdanilov6b872662015-06-23 01:58:36 +0300931
932 key = (item['name'], item['summ'])
933 line = list(key)
934 base = key2results[0][key]
935
936 line.append(iops_frmt.format(base))
937
938 for test_results in key2results[1:]:
939 val = test_results.get(key)
940 if val is None:
941 line.append("-")
942 elif base['iops'] == 0:
943 line.append("Nan")
944 else:
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300945 prc_val = {'dev': val['dev'], 'conf': val['conf']}
koder aka kdanilov6b872662015-06-23 01:58:36 +0300946 prc_val['iops'] = int(100 * val['iops'] / base['iops'])
947 line.append(iops_frmt.format(prc_val))
948
949 line.append(base['bw'])
950
951 for test_results in key2results[1:]:
952 val = test_results.get(key)
953 if val is None:
954 line.append("-")
955 elif base['bw'] == 0:
956 line.append("Nan")
957 else:
958 line.append(int(100 * val['bw'] / base['bw']))
959
960 for test_results in key2results:
961 val = test_results.get(key)
962 if val is None:
963 line.append("-")
964 else:
965 line.append("{0[lat_50]} - {0[lat_95]}".format(val))
966
967 tab.add_row(line)
968
969 tab.set_cols_align(allign)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300970 return tab.draw()