| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 1 | import re | 
 | 2 | import time | 
 | 3 | import json | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 4 | import stat | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 5 | import random | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 6 | import os.path | 
 | 7 | import logging | 
 | 8 | import datetime | 
 | 9 | import functools | 
 | 10 | import subprocess | 
 | 11 | import collections | 
 | 12 |  | 
 | 13 | import yaml | 
 | 14 | import paramiko | 
 | 15 | import texttable | 
 | 16 | from paramiko.ssh_exception import SSHException | 
 | 17 | from concurrent.futures import ThreadPoolExecutor | 
 | 18 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 19 | import wally | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 20 | from wally.pretty_yaml import dumps | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 21 | from wally.statistic import round_3_digit, data_property, average | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 22 | from wally.utils import ssize2b, sec_to_str, StopTestError, Barrier, get_os | 
 | 23 | from wally.ssh_utils import (save_to_remote, read_from_remote, BGSSHTask, reconnect) | 
 | 24 |  | 
 | 25 | from .fio_task_parser import (execution_time, fio_cfg_compile, | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 26 |                               get_test_summary, get_test_summary_tuple, | 
 | 27 |                               get_test_sync_mode, FioJobSection) | 
 | 28 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 29 | from ..itest import (TimeSeriesValue, PerfTest, TestResults, | 
 | 30 |                      run_on_node, TestConfig, MeasurementMatrix) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 31 |  | 
 | 32 | logger = logging.getLogger("wally") | 
 | 33 |  | 
 | 34 |  | 
 | 35 | # Results folder structure | 
 | 36 | # results/ | 
 | 37 | #     {loadtype}_{num}/ | 
 | 38 | #         config.yaml | 
 | 39 | #         ...... | 
 | 40 |  | 
 | 41 |  | 
 | 42 | class NoData(object): | 
 | 43 |     pass | 
 | 44 |  | 
 | 45 |  | 
 | 46 | def cached_prop(func): | 
 | 47 |     @property | 
 | 48 |     @functools.wraps(func) | 
 | 49 |     def closure(self): | 
 | 50 |         val = getattr(self, "_" + func.__name__) | 
 | 51 |         if val is NoData: | 
 | 52 |             val = func(self) | 
 | 53 |             setattr(self, "_" + func.__name__, val) | 
 | 54 |         return val | 
 | 55 |     return closure | 
 | 56 |  | 
 | 57 |  | 
 | 58 | def load_fio_log_file(fname): | 
 | 59 |     with open(fname) as fd: | 
 | 60 |         it = [ln.split(',')[:2] for ln in fd] | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 61 |  | 
 | 62 |     vals = [(float(off) / 1000,  # convert us to ms | 
 | 63 |              float(val.strip()) + 0.5)  # add 0.5 to compemsate average value | 
 | 64 |                                         # as fio trimm all values in log to integer | 
 | 65 |             for off, val in it] | 
 | 66 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 67 |     return TimeSeriesValue(vals) | 
 | 68 |  | 
 | 69 |  | 
| koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 70 | def load_test_results(folder, run_num): | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 71 |     res = {} | 
 | 72 |     params = None | 
 | 73 |  | 
 | 74 |     fn = os.path.join(folder, str(run_num) + '_params.yaml') | 
 | 75 |     params = yaml.load(open(fn).read()) | 
 | 76 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 77 |     conn_ids_set = set() | 
 | 78 |     rr = r"{0}_(?P<conn_id>.*?)_(?P<type>[^_.]*)\.\d+\.log$".format(run_num) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 79 |     for fname in os.listdir(folder): | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 80 |         rm = re.match(rr, fname) | 
 | 81 |         if rm is None: | 
 | 82 |             continue | 
 | 83 |  | 
 | 84 |         conn_id_s = rm.group('conn_id') | 
 | 85 |         conn_id = conn_id_s.replace('_', ':') | 
 | 86 |         ftype = rm.group('type') | 
 | 87 |  | 
 | 88 |         if ftype not in ('iops', 'bw', 'lat'): | 
 | 89 |             continue | 
 | 90 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 91 |         ts = load_fio_log_file(os.path.join(folder, fname)) | 
 | 92 |         res.setdefault(ftype, {}).setdefault(conn_id, []).append(ts) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 93 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 94 |         conn_ids_set.add(conn_id) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 95 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 96 |     mm_res = {} | 
 | 97 |  | 
| koder aka kdanilov | 9e0512a | 2015-08-10 14:51:59 +0300 | [diff] [blame^] | 98 |     if len(res) == 0: | 
 | 99 |         raise ValueError("No data was found") | 
 | 100 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 101 |     for key, data in res.items(): | 
 | 102 |         conn_ids = sorted(conn_ids_set) | 
 | 103 |         matr = [data[conn_id] for conn_id in conn_ids] | 
 | 104 |  | 
 | 105 |         mm_res[key] = MeasurementMatrix(matr, conn_ids) | 
 | 106 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 107 |     raw_res = {} | 
 | 108 |     for conn_id in conn_ids: | 
 | 109 |         fn = os.path.join(folder, "{0}_{1}_rawres.json".format(run_num, conn_id_s)) | 
 | 110 |  | 
 | 111 |         # remove message hack | 
 | 112 |         fc = "{" + open(fn).read().split('{', 1)[1] | 
 | 113 |         raw_res[conn_id] = json.loads(fc) | 
 | 114 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 115 |     fio_task = FioJobSection(params['name']) | 
 | 116 |     fio_task.vals.update(params['vals']) | 
 | 117 |  | 
 | 118 |     config = TestConfig('io', params, None, params['nodes'], folder, None) | 
| koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 119 |     return FioRunResult(config, fio_task, mm_res, raw_res, params['intervals'], run_num) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 120 |  | 
 | 121 |  | 
 | 122 | class Attrmapper(object): | 
 | 123 |     def __init__(self, dct): | 
 | 124 |         self.__dct = dct | 
 | 125 |  | 
 | 126 |     def __getattr__(self, name): | 
 | 127 |         try: | 
 | 128 |             return self.__dct[name] | 
 | 129 |         except KeyError: | 
 | 130 |             raise AttributeError(name) | 
 | 131 |  | 
 | 132 |  | 
 | 133 | class DiskPerfInfo(object): | 
 | 134 |     def __init__(self, name, summary, params, testnodes_count): | 
 | 135 |         self.name = name | 
 | 136 |         self.bw = None | 
 | 137 |         self.iops = None | 
 | 138 |         self.lat = None | 
 | 139 |         self.lat_50 = None | 
 | 140 |         self.lat_95 = None | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 141 |         self.lat_avg = None | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 142 |  | 
 | 143 |         self.raw_bw = [] | 
 | 144 |         self.raw_iops = [] | 
 | 145 |         self.raw_lat = [] | 
 | 146 |  | 
 | 147 |         self.params = params | 
 | 148 |         self.testnodes_count = testnodes_count | 
 | 149 |         self.summary = summary | 
 | 150 |         self.p = Attrmapper(self.params['vals']) | 
 | 151 |  | 
 | 152 |         self.sync_mode = get_test_sync_mode(self.params['vals']) | 
 | 153 |         self.concurence = self.params['vals'].get('numjobs', 1) | 
 | 154 |  | 
 | 155 |  | 
 | 156 | def get_lat_perc_50_95(lat_mks): | 
 | 157 |     curr_perc = 0 | 
 | 158 |     perc_50 = None | 
 | 159 |     perc_95 = None | 
 | 160 |     pkey = None | 
 | 161 |     for key, val in sorted(lat_mks.items()): | 
 | 162 |         if curr_perc + val >= 50 and perc_50 is None: | 
 | 163 |             if pkey is None or val < 1.: | 
 | 164 |                 perc_50 = key | 
 | 165 |             else: | 
 | 166 |                 perc_50 = (50. - curr_perc) / val * (key - pkey) + pkey | 
 | 167 |  | 
 | 168 |         if curr_perc + val >= 95: | 
 | 169 |             if pkey is None or val < 1.: | 
 | 170 |                 perc_95 = key | 
 | 171 |             else: | 
 | 172 |                 perc_95 = (95. - curr_perc) / val * (key - pkey) + pkey | 
 | 173 |             break | 
 | 174 |  | 
 | 175 |         pkey = key | 
 | 176 |         curr_perc += val | 
 | 177 |  | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 178 |     # for k, v in sorted(lat_mks.items()): | 
 | 179 |     #     if k / 1000 > 0: | 
 | 180 |     #         print "{0:>4}".format(k / 1000), v | 
 | 181 |  | 
 | 182 |     # print perc_50 / 1000., perc_95 / 1000. | 
 | 183 |     # exit(1) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 184 |     return perc_50 / 1000., perc_95 / 1000. | 
 | 185 |  | 
 | 186 |  | 
| koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 187 | class IOTestResults(object): | 
 | 188 |     def __init__(self, suite_name, fio_results, log_directory): | 
 | 189 |         self.suite_name = suite_name | 
 | 190 |         self.fio_results = fio_results | 
 | 191 |         self.log_directory = log_directory | 
 | 192 |  | 
 | 193 |     def __iter__(self): | 
 | 194 |         return iter(self.fio_results) | 
 | 195 |  | 
 | 196 |     def __len__(self): | 
 | 197 |         return len(self.fio_results) | 
 | 198 |  | 
 | 199 |     def get_yamable(self): | 
 | 200 |         items = [(fio_res.summary(), fio_res.idx) for fio_res in self] | 
 | 201 |         return {self.suite_name: [self.log_directory] + items} | 
 | 202 |  | 
 | 203 |  | 
 | 204 | class FioRunResult(TestResults): | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 205 |     """ | 
 | 206 |     Fio run results | 
 | 207 |     config: TestConfig | 
 | 208 |     fio_task: FioJobSection | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 209 |     ts_results: {str: MeasurementMatrix[TimeSeriesValue]} | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 210 |     raw_result: ???? | 
 | 211 |     run_interval:(float, float) - test tun time, used for sensors | 
 | 212 |     """ | 
| koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 213 |     def __init__(self, config, fio_task, ts_results, raw_result, run_interval, idx): | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 214 |  | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 215 |         self.name = fio_task.name.rsplit("_", 1)[0] | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 216 |         self.fio_task = fio_task | 
| koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 217 |         self.idx = idx | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 218 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 219 |         self.bw = ts_results.get('bw') | 
 | 220 |         self.lat = ts_results.get('lat') | 
 | 221 |         self.iops = ts_results.get('iops') | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 222 |  | 
 | 223 |         res = {"bw": self.bw, "lat": self.lat, "iops": self.iops} | 
 | 224 |  | 
 | 225 |         self.sensors_data = None | 
 | 226 |         self._pinfo = None | 
 | 227 |         TestResults.__init__(self, config, res, raw_result, run_interval) | 
 | 228 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 229 |     def get_params_from_fio_report(self): | 
 | 230 |         nodes = self.bw.connections_ids | 
 | 231 |  | 
 | 232 |         iops = [self.raw_result[node]['jobs'][0]['mixed']['iops'] for node in nodes] | 
 | 233 |         total_ios = [self.raw_result[node]['jobs'][0]['mixed']['total_ios'] for node in nodes] | 
 | 234 |         runtime = [self.raw_result[node]['jobs'][0]['mixed']['runtime'] / 1000 for node in nodes] | 
 | 235 |         flt_iops = [float(ios) / rtime for ios, rtime in zip(total_ios, runtime)] | 
 | 236 |  | 
 | 237 |         bw = [self.raw_result[node]['jobs'][0]['mixed']['bw'] for node in nodes] | 
 | 238 |         total_bytes = [self.raw_result[node]['jobs'][0]['mixed']['io_bytes'] for node in nodes] | 
 | 239 |         flt_bw = [float(tbytes) / rtime for tbytes, rtime in zip(total_bytes, runtime)] | 
 | 240 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 241 |         return {'iops': iops, | 
 | 242 |                 'flt_iops': flt_iops, | 
 | 243 |                 'bw': bw, | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 244 |                 'flt_bw': flt_bw} | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 245 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 246 |     def summary(self): | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 247 |         return get_test_summary(self.fio_task, len(self.config.nodes)) | 
 | 248 |  | 
 | 249 |     def summary_tpl(self): | 
 | 250 |         return get_test_summary_tuple(self.fio_task, len(self.config.nodes)) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 251 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 252 |     def get_lat_perc_50_95_multy(self): | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 253 |         lat_mks = collections.defaultdict(lambda: 0) | 
 | 254 |         num_res = 0 | 
 | 255 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 256 |         for result in self.raw_result.values(): | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 257 |             num_res += len(result['jobs']) | 
 | 258 |             for job_info in result['jobs']: | 
 | 259 |                 for k, v in job_info['latency_ms'].items(): | 
 | 260 |                     if isinstance(k, basestring) and k.startswith('>='): | 
 | 261 |                         lat_mks[int(k[2:]) * 1000] += v | 
 | 262 |                     else: | 
 | 263 |                         lat_mks[int(k) * 1000] += v | 
 | 264 |  | 
 | 265 |                 for k, v in job_info['latency_us'].items(): | 
 | 266 |                     lat_mks[int(k)] += v | 
 | 267 |  | 
 | 268 |         for k, v in lat_mks.items(): | 
 | 269 |             lat_mks[k] = float(v) / num_res | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 270 |         return get_lat_perc_50_95(lat_mks) | 
 | 271 |  | 
 | 272 |     def disk_perf_info(self, avg_interval=2.0): | 
 | 273 |  | 
 | 274 |         if self._pinfo is not None: | 
 | 275 |             return self._pinfo | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 276 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 277 |         testnodes_count = len(self.config.nodes) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 278 |  | 
 | 279 |         pinfo = DiskPerfInfo(self.name, | 
 | 280 |                              self.summary(), | 
 | 281 |                              self.params, | 
 | 282 |                              testnodes_count) | 
 | 283 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 284 |         def prepare(data, drop=1): | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 285 |             if data is None: | 
 | 286 |                 return data | 
 | 287 |  | 
 | 288 |             res = [] | 
 | 289 |             for ts_data in data: | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 290 |                 if ts_data.average_interval() < avg_interval: | 
 | 291 |                     ts_data = ts_data.derived(avg_interval) | 
 | 292 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 293 |                 # drop last value on bounds | 
 | 294 |                 # as they may contains ranges without activities | 
| koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 295 |                 assert len(ts_data.values) >= drop + 1, str(drop) + " " + str(ts_data.values) | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 296 |  | 
 | 297 |                 if drop > 0: | 
 | 298 |                     res.append(ts_data.values[:-drop]) | 
 | 299 |                 else: | 
 | 300 |                     res.append(ts_data.values) | 
 | 301 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 302 |             return res | 
 | 303 |  | 
 | 304 |         def agg_data(matr): | 
 | 305 |             arr = sum(matr, []) | 
 | 306 |             min_len = min(map(len, arr)) | 
 | 307 |             res = [] | 
 | 308 |             for idx in range(min_len): | 
 | 309 |                 res.append(sum(dt[idx] for dt in arr)) | 
 | 310 |             return res | 
 | 311 |  | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 312 |         pinfo.raw_lat = map(prepare, self.lat.per_vm()) | 
 | 313 |         num_th = sum(map(len, pinfo.raw_lat)) | 
 | 314 |         lat_avg = [val / num_th for val in agg_data(pinfo.raw_lat)] | 
 | 315 |         pinfo.lat_avg = data_property(lat_avg).average / 1000  # us to ms | 
 | 316 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 317 |         pinfo.lat_50, pinfo.lat_95 = self.get_lat_perc_50_95_multy() | 
 | 318 |         pinfo.lat = pinfo.lat_50 | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 319 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 320 |         pinfo.raw_bw = map(prepare, self.bw.per_vm()) | 
 | 321 |         pinfo.raw_iops = map(prepare, self.iops.per_vm()) | 
 | 322 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 323 |         fparams = self.get_params_from_fio_report() | 
 | 324 |         fio_report_bw = sum(fparams['flt_bw']) | 
 | 325 |         fio_report_iops = sum(fparams['flt_iops']) | 
 | 326 |  | 
 | 327 |         agg_bw = agg_data(pinfo.raw_bw) | 
 | 328 |         agg_iops = agg_data(pinfo.raw_iops) | 
 | 329 |  | 
 | 330 |         log_bw_avg = average(agg_bw) | 
 | 331 |         log_iops_avg = average(agg_iops) | 
 | 332 |  | 
 | 333 |         # update values to match average from fio report | 
 | 334 |         coef_iops = fio_report_iops / float(log_iops_avg) | 
 | 335 |         coef_bw = fio_report_bw / float(log_bw_avg) | 
 | 336 |  | 
 | 337 |         bw_log = data_property([val * coef_bw for val in agg_bw]) | 
 | 338 |         iops_log = data_property([val * coef_iops for val in agg_iops]) | 
 | 339 |  | 
 | 340 |         bw_report = data_property([fio_report_bw]) | 
 | 341 |         iops_report = data_property([fio_report_iops]) | 
 | 342 |  | 
 | 343 |         # When IOPS/BW per thread is too low | 
 | 344 |         # data from logs is rounded to match | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 345 |         iops_per_th = sum(sum(pinfo.raw_iops, []), []) | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 346 |         if average(iops_per_th) > 10: | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 347 |             pinfo.iops = iops_log | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 348 |             pinfo.iops2 = iops_report | 
 | 349 |         else: | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 350 |             pinfo.iops = iops_report | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 351 |             pinfo.iops2 = iops_log | 
 | 352 |  | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 353 |         bw_per_th = sum(sum(pinfo.raw_bw, []), []) | 
 | 354 |         if average(bw_per_th) > 10: | 
 | 355 |             pinfo.bw = bw_log | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 356 |             pinfo.bw2 = bw_report | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 357 |         else: | 
 | 358 |             pinfo.bw = bw_report | 
 | 359 |             pinfo.bw2 = bw_log | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 360 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 361 |         self._pinfo = pinfo | 
 | 362 |  | 
 | 363 |         return pinfo | 
 | 364 |  | 
 | 365 |  | 
 | 366 | class IOPerfTest(PerfTest): | 
 | 367 |     tcp_conn_timeout = 30 | 
 | 368 |     max_pig_timeout = 5 | 
 | 369 |     soft_runcycle = 5 * 60 | 
 | 370 |  | 
 | 371 |     def __init__(self, config): | 
 | 372 |         PerfTest.__init__(self, config) | 
 | 373 |  | 
 | 374 |         get = self.config.params.get | 
 | 375 |         do_get = self.config.params.__getitem__ | 
 | 376 |  | 
 | 377 |         self.config_fname = do_get('cfg') | 
 | 378 |  | 
 | 379 |         if '/' not in self.config_fname and '.' not in self.config_fname: | 
 | 380 |             cfgs_dir = os.path.dirname(__file__) | 
 | 381 |             self.config_fname = os.path.join(cfgs_dir, | 
 | 382 |                                              self.config_fname + '.cfg') | 
 | 383 |  | 
 | 384 |         self.alive_check_interval = get('alive_check_interval') | 
 | 385 |         self.use_system_fio = get('use_system_fio', False) | 
 | 386 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 387 |         if get('prefill_files') is not None: | 
 | 388 |             logger.warning("prefill_files option is depricated. Use force_prefill instead") | 
 | 389 |  | 
 | 390 |         self.force_prefill = get('force_prefill', False) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 391 |         self.config_params = get('params', {}).copy() | 
 | 392 |  | 
 | 393 |         self.io_py_remote = self.join_remote("agent.py") | 
 | 394 |         self.results_file = self.join_remote("results.json") | 
 | 395 |         self.pid_file = self.join_remote("pid") | 
 | 396 |         self.task_file = self.join_remote("task.cfg") | 
 | 397 |         self.sh_file = self.join_remote("cmd.sh") | 
 | 398 |         self.err_out_file = self.join_remote("fio_err_out") | 
 | 399 |         self.exit_code_file = self.join_remote("exit_code") | 
 | 400 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 401 |         self.max_latency = get("max_lat", None) | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 402 |         self.min_bw_per_thread = get("min_bw", None) | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 403 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 404 |         self.use_sudo = get("use_sudo", True) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 405 |  | 
 | 406 |         self.raw_cfg = open(self.config_fname).read() | 
 | 407 |         self.fio_configs = fio_cfg_compile(self.raw_cfg, | 
 | 408 |                                            self.config_fname, | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 409 |                                            self.config_params) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 410 |         self.fio_configs = list(self.fio_configs) | 
 | 411 |  | 
 | 412 |     @classmethod | 
| koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 413 |     def load(cls, suite_name, folder): | 
 | 414 |         res = [] | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 415 |         for fname in os.listdir(folder): | 
 | 416 |             if re.match("\d+_params.yaml$", fname): | 
 | 417 |                 num = int(fname.split('_')[0]) | 
| koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 418 |                 res.append(load_test_results(folder, num)) | 
 | 419 |         return IOTestResults(suite_name, res, folder) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 420 |  | 
 | 421 |     def cleanup(self): | 
 | 422 |         # delete_file(conn, self.io_py_remote) | 
 | 423 |         # Need to remove tempo files, used for testing | 
 | 424 |         pass | 
 | 425 |  | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 426 |     # size is megabytes | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 427 |     def check_prefill_required(self, rossh, fname, size, num_blocks=16): | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 428 |         try: | 
 | 429 |             with rossh.connection.open_sftp() as sftp: | 
 | 430 |                 fstats = sftp.stat(fname) | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 431 |  | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 432 |             if stat.S_ISREG(fstats.st_mode) and fstats.st_size < size * 1024 ** 2: | 
 | 433 |                 return True | 
 | 434 |         except EnvironmentError: | 
| koder aka kdanilov | f95cfc1 | 2015-06-23 03:33:19 +0300 | [diff] [blame] | 435 |             return True | 
 | 436 |  | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 437 |         cmd = 'python -c "' + \ | 
 | 438 |               "import sys;" + \ | 
 | 439 |               "fd = open('{0}', 'rb');" + \ | 
 | 440 |               "fd.seek({1});" + \ | 
 | 441 |               "data = fd.read(1024); " + \ | 
 | 442 |               "sys.stdout.write(data + ' ' * ( 1024 - len(data)))\" | md5sum" | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 443 |  | 
 | 444 |         if self.use_sudo: | 
 | 445 |             cmd = "sudo " + cmd | 
 | 446 |  | 
| koder aka kdanilov | f95cfc1 | 2015-06-23 03:33:19 +0300 | [diff] [blame] | 447 |         zero_md5 = '0f343b0931126a20f133d67c2b018a3b' | 
| koder aka kdanilov | 8fbb27f | 2015-07-17 22:23:31 +0300 | [diff] [blame] | 448 |         bsize = size * (1024 ** 2) | 
 | 449 |         offsets = [random.randrange(bsize - 1024) for _ in range(num_blocks)] | 
 | 450 |         offsets.append(bsize - 1024) | 
 | 451 |         offsets.append(0) | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 452 |  | 
| koder aka kdanilov | f95cfc1 | 2015-06-23 03:33:19 +0300 | [diff] [blame] | 453 |         for offset in offsets: | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 454 |             data = rossh(cmd.format(fname, offset), nolog=True) | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 455 |  | 
 | 456 |             md = "" | 
 | 457 |             for line in data.split("\n"): | 
 | 458 |                 if "unable to resolve" not in line: | 
 | 459 |                     md = line.split()[0].strip() | 
 | 460 |                     break | 
| koder aka kdanilov | f95cfc1 | 2015-06-23 03:33:19 +0300 | [diff] [blame] | 461 |  | 
 | 462 |             if len(md) != 32: | 
 | 463 |                 logger.error("File data check is failed - " + data) | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 464 |                 return True | 
| koder aka kdanilov | f95cfc1 | 2015-06-23 03:33:19 +0300 | [diff] [blame] | 465 |  | 
 | 466 |             if zero_md5 == md: | 
 | 467 |                 return True | 
 | 468 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 469 |         return False | 
 | 470 |  | 
 | 471 |     def prefill_test_files(self, rossh, files, force=False): | 
 | 472 |         if self.use_system_fio: | 
 | 473 |             cmd_templ = "fio " | 
 | 474 |         else: | 
 | 475 |             cmd_templ = "{0}/fio ".format(self.config.remote_dir) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 476 |  | 
 | 477 |         if self.use_sudo: | 
 | 478 |             cmd_templ = "sudo " + cmd_templ | 
 | 479 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 480 |         cmd_templ += "--name=xxx --filename={0} --direct=1" + \ | 
 | 481 |                      " --bs=4m --size={1}m --rw=write" | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 482 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 483 |         ssize = 0 | 
 | 484 |  | 
 | 485 |         if force: | 
 | 486 |             logger.info("File prefilling is forced") | 
 | 487 |  | 
 | 488 |         ddtime = 0 | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 489 |         for fname, curr_sz in files.items(): | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 490 |             if not force: | 
 | 491 |                 if not self.check_prefill_required(rossh, fname, curr_sz): | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 492 |                     logger.debug("prefill is skipped") | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 493 |                     continue | 
 | 494 |  | 
 | 495 |             logger.info("Prefilling file {0}".format(fname)) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 496 |             cmd = cmd_templ.format(fname, curr_sz) | 
 | 497 |             ssize += curr_sz | 
 | 498 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 499 |             stime = time.time() | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 500 |             rossh(cmd, timeout=curr_sz) | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 501 |             ddtime += time.time() - stime | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 502 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 503 |         if ddtime > 1.0: | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 504 |             fill_bw = int(ssize / ddtime) | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 505 |             mess = "Initiall fio fill bw is {0} MiBps for this vm" | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 506 |             logger.info(mess.format(fill_bw)) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 507 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 508 |     def install_utils(self, node, rossh, max_retry=3, timeout=5): | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 509 |         need_install = [] | 
 | 510 |         packs = [('screen', 'screen')] | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 511 |         os_info = get_os(rossh) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 512 |  | 
 | 513 |         if self.use_system_fio: | 
 | 514 |             packs.append(('fio', 'fio')) | 
 | 515 |         else: | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 516 |             packs.append(('bzip2', 'bzip2')) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 517 |  | 
 | 518 |         for bin_name, package in packs: | 
 | 519 |             if bin_name is None: | 
 | 520 |                 need_install.append(package) | 
 | 521 |                 continue | 
 | 522 |  | 
 | 523 |             try: | 
 | 524 |                 rossh('which ' + bin_name, nolog=True) | 
 | 525 |             except OSError: | 
 | 526 |                 need_install.append(package) | 
 | 527 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 528 |         if len(need_install) != 0: | 
 | 529 |             if 'redhat' == os_info.distro: | 
 | 530 |                 cmd = "sudo yum -y install " + " ".join(need_install) | 
 | 531 |             else: | 
 | 532 |                 cmd = "sudo apt-get -y install " + " ".join(need_install) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 533 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 534 |             for _ in range(max_retry): | 
 | 535 |                 try: | 
 | 536 |                     rossh(cmd) | 
 | 537 |                     break | 
 | 538 |                 except OSError as err: | 
 | 539 |                     time.sleep(timeout) | 
 | 540 |             else: | 
 | 541 |                 raise OSError("Can't install - " + str(err)) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 542 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 543 |         if not self.use_system_fio: | 
 | 544 |             fio_dir = os.path.dirname(os.path.dirname(wally.__file__)) | 
 | 545 |             fio_dir = os.path.join(os.getcwd(), fio_dir) | 
 | 546 |             fio_dir = os.path.join(fio_dir, 'fio_binaries') | 
 | 547 |             fname = 'fio_{0.release}_{0.arch}.bz2'.format(os_info) | 
 | 548 |             fio_path = os.path.join(fio_dir, fname) | 
 | 549 |  | 
 | 550 |             if not os.path.exists(fio_path): | 
 | 551 |                 raise RuntimeError("No prebuild fio available for {0}".format(os_info)) | 
 | 552 |  | 
 | 553 |             bz_dest = self.join_remote('fio.bz2') | 
 | 554 |             with node.connection.open_sftp() as sftp: | 
 | 555 |                 sftp.put(fio_path, bz_dest) | 
 | 556 |  | 
 | 557 |             rossh("bzip2 --decompress " + bz_dest, nolog=True) | 
 | 558 |             rossh("chmod a+x " + self.join_remote("fio"), nolog=True) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 559 |  | 
 | 560 |     def pre_run(self): | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 561 |         files = {} | 
 | 562 |         for section in self.fio_configs: | 
 | 563 |             sz = ssize2b(section.vals['size']) | 
 | 564 |             msz = sz / (1024 ** 2) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 565 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 566 |             if sz % (1024 ** 2) != 0: | 
 | 567 |                 msz += 1 | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 568 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 569 |             fname = section.vals['filename'] | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 570 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 571 |             # if already has other test with the same file name | 
 | 572 |             # take largest size | 
 | 573 |             files[fname] = max(files.get(fname, 0), msz) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 574 |  | 
 | 575 |         with ThreadPoolExecutor(len(self.config.nodes)) as pool: | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 576 |             fc = functools.partial(self.pre_run_th, | 
 | 577 |                                    files=files, | 
 | 578 |                                    force=self.force_prefill) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 579 |             list(pool.map(fc, self.config.nodes)) | 
 | 580 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 581 |     def pre_run_th(self, node, files, force): | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 582 |         try: | 
| koder aka kdanilov | 8fbb27f | 2015-07-17 22:23:31 +0300 | [diff] [blame] | 583 |             # fill files with pseudo-random data | 
 | 584 |             rossh = run_on_node(node) | 
 | 585 |             rossh.connection = node.connection | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 586 |  | 
| koder aka kdanilov | 8fbb27f | 2015-07-17 22:23:31 +0300 | [diff] [blame] | 587 |             try: | 
 | 588 |                 cmd = 'mkdir -p "{0}"'.format(self.config.remote_dir) | 
 | 589 |                 if self.use_sudo: | 
 | 590 |                     cmd = "sudo " + cmd | 
 | 591 |                     cmd += " ; sudo chown {0} {1}".format(node.get_user(), | 
 | 592 |                                                           self.config.remote_dir) | 
 | 593 |                 rossh(cmd, nolog=True) | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 594 |  | 
| koder aka kdanilov | 8fbb27f | 2015-07-17 22:23:31 +0300 | [diff] [blame] | 595 |                 assert self.config.remote_dir != "" and self.config.remote_dir != "/" | 
 | 596 |                 rossh("rm -rf {0}/*".format(self.config.remote_dir), nolog=True) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 597 |  | 
| koder aka kdanilov | 8fbb27f | 2015-07-17 22:23:31 +0300 | [diff] [blame] | 598 |             except Exception as exc: | 
 | 599 |                 msg = "Failed to create folder {0} on remote {1}. Error: {2!s}" | 
 | 600 |                 msg = msg.format(self.config.remote_dir, node.get_conn_id(), exc) | 
 | 601 |                 logger.exception(msg) | 
 | 602 |                 raise StopTestError(msg, exc) | 
 | 603 |  | 
 | 604 |             self.install_utils(node, rossh) | 
 | 605 |             self.prefill_test_files(rossh, files, force) | 
 | 606 |         except: | 
 | 607 |             logger.exception("XXXX") | 
 | 608 |             raise | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 609 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 610 |     def show_test_execution_time(self): | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 611 |         if len(self.fio_configs) > 1: | 
 | 612 |             # +10% - is a rough estimation for additional operations | 
 | 613 |             # like sftp, etc | 
 | 614 |             exec_time = int(sum(map(execution_time, self.fio_configs)) * 1.1) | 
 | 615 |             exec_time_s = sec_to_str(exec_time) | 
 | 616 |             now_dt = datetime.datetime.now() | 
 | 617 |             end_dt = now_dt + datetime.timedelta(0, exec_time) | 
 | 618 |             msg = "Entire test should takes aroud: {0} and finished at {1}" | 
 | 619 |             logger.info(msg.format(exec_time_s, | 
 | 620 |                                    end_dt.strftime("%H:%M:%S"))) | 
 | 621 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 622 |     def run(self): | 
 | 623 |         logger.debug("Run preparation") | 
 | 624 |         self.pre_run() | 
 | 625 |         self.show_test_execution_time() | 
 | 626 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 627 |         tname = os.path.basename(self.config_fname) | 
 | 628 |         if tname.endswith('.cfg'): | 
 | 629 |             tname = tname[:-4] | 
 | 630 |  | 
 | 631 |         barrier = Barrier(len(self.config.nodes)) | 
 | 632 |         results = [] | 
 | 633 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 634 |         # set of Operation_Mode_BlockSize str's | 
 | 635 |         # which should not be tested anymore, as | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 636 |         # they already too slow with previous thread count | 
 | 637 |         lat_bw_limit_reached = set() | 
 | 638 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 639 |         with ThreadPoolExecutor(len(self.config.nodes)) as pool: | 
 | 640 |             for pos, fio_cfg in enumerate(self.fio_configs): | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 641 |                 test_descr = get_test_summary(fio_cfg.vals).split("th")[0] | 
 | 642 |                 if test_descr in lat_bw_limit_reached: | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 643 |                     continue | 
 | 644 |                 else: | 
 | 645 |                     logger.info("Will run {0} test".format(fio_cfg.name)) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 646 |  | 
 | 647 |                 templ = "Test should takes about {0}." + \ | 
 | 648 |                         " Should finish at {1}," + \ | 
 | 649 |                         " will wait at most till {2}" | 
 | 650 |                 exec_time = execution_time(fio_cfg) | 
 | 651 |                 exec_time_str = sec_to_str(exec_time) | 
 | 652 |                 timeout = int(exec_time + max(300, exec_time)) | 
 | 653 |  | 
 | 654 |                 now_dt = datetime.datetime.now() | 
 | 655 |                 end_dt = now_dt + datetime.timedelta(0, exec_time) | 
 | 656 |                 wait_till = now_dt + datetime.timedelta(0, timeout) | 
 | 657 |  | 
 | 658 |                 logger.info(templ.format(exec_time_str, | 
 | 659 |                                          end_dt.strftime("%H:%M:%S"), | 
 | 660 |                                          wait_till.strftime("%H:%M:%S"))) | 
 | 661 |  | 
 | 662 |                 func = functools.partial(self.do_run, | 
 | 663 |                                          barrier=barrier, | 
 | 664 |                                          fio_cfg=fio_cfg, | 
 | 665 |                                          pos=pos) | 
 | 666 |  | 
 | 667 |                 max_retr = 3 | 
 | 668 |                 for idx in range(max_retr): | 
 | 669 |                     try: | 
 | 670 |                         intervals = list(pool.map(func, self.config.nodes)) | 
 | 671 |                         break | 
 | 672 |                     except (EnvironmentError, SSHException) as exc: | 
 | 673 |                         logger.exception("During fio run") | 
 | 674 |                         if idx == max_retr - 1: | 
 | 675 |                             raise StopTestError("Fio failed", exc) | 
 | 676 |  | 
 | 677 |                     logger.info("Sleeping 30s and retrying") | 
 | 678 |                     time.sleep(30) | 
 | 679 |  | 
 | 680 |                 fname = "{0}_task.fio".format(pos) | 
 | 681 |                 with open(os.path.join(self.config.log_directory, fname), "w") as fd: | 
 | 682 |                     fd.write(str(fio_cfg)) | 
 | 683 |  | 
 | 684 |                 params = {'vm_count': len(self.config.nodes)} | 
 | 685 |                 params['name'] = fio_cfg.name | 
 | 686 |                 params['vals'] = dict(fio_cfg.vals.items()) | 
 | 687 |                 params['intervals'] = intervals | 
 | 688 |                 params['nodes'] = [node.get_conn_id() for node in self.config.nodes] | 
 | 689 |  | 
 | 690 |                 fname = "{0}_params.yaml".format(pos) | 
 | 691 |                 with open(os.path.join(self.config.log_directory, fname), "w") as fd: | 
 | 692 |                     fd.write(dumps(params)) | 
 | 693 |  | 
| koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 694 |                 res = load_test_results(self.config.log_directory, pos) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 695 |                 results.append(res) | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 696 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 697 |                 if self.max_latency is not None: | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 698 |                     lat_50, _ = res.get_lat_perc_50_95_multy() | 
 | 699 |  | 
 | 700 |                     # conver us to ms | 
 | 701 |                     if self.max_latency < lat_50: | 
 | 702 |                         logger.info(("Will skip all subsequent tests of {0} " + | 
 | 703 |                                      "due to lat/bw limits").format(fio_cfg.name)) | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 704 |                         lat_bw_limit_reached.add(test_descr) | 
 | 705 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 706 |                 test_res = res.get_params_from_fio_report() | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 707 |                 if self.min_bw_per_thread is not None: | 
 | 708 |                     if self.min_bw_per_thread > average(test_res['bw']): | 
 | 709 |                         lat_bw_limit_reached.add(test_descr) | 
 | 710 |  | 
| koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 711 |         return IOTestResults(self.config.params['cfg'], | 
 | 712 |                              results, self.config.log_directory) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 713 |  | 
 | 714 |     def do_run(self, node, barrier, fio_cfg, pos, nolog=False): | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 715 |         if self.use_sudo: | 
 | 716 |             sudo = "sudo " | 
 | 717 |         else: | 
 | 718 |             sudo = "" | 
 | 719 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 720 |         bash_file = "#!/bin/bash\n" + \ | 
 | 721 |                     "cd {exec_folder}\n" + \ | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 722 |                     "{fio_path}fio --output-format=json --output={out_file} " + \ | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 723 |                     "--alloc-size=262144 {job_file} " + \ | 
 | 724 |                     " >{err_out_file} 2>&1 \n" + \ | 
 | 725 |                     "echo $? >{res_code_file}\n" | 
 | 726 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 727 |         exec_folder = self.config.remote_dir | 
 | 728 |  | 
 | 729 |         if self.use_system_fio: | 
 | 730 |             fio_path = "" | 
 | 731 |         else: | 
 | 732 |             if not exec_folder.endswith("/"): | 
 | 733 |                 fio_path = exec_folder + "/" | 
 | 734 |             else: | 
 | 735 |                 fio_path = exec_folder | 
 | 736 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 737 |         bash_file = bash_file.format(out_file=self.results_file, | 
 | 738 |                                      job_file=self.task_file, | 
 | 739 |                                      err_out_file=self.err_out_file, | 
 | 740 |                                      res_code_file=self.exit_code_file, | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 741 |                                      exec_folder=exec_folder, | 
 | 742 |                                      fio_path=fio_path) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 743 |  | 
 | 744 |         with node.connection.open_sftp() as sftp: | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 745 |             save_to_remote(sftp, self.task_file, str(fio_cfg)) | 
 | 746 |             save_to_remote(sftp, self.sh_file, bash_file) | 
 | 747 |  | 
 | 748 |         exec_time = execution_time(fio_cfg) | 
 | 749 |  | 
 | 750 |         timeout = int(exec_time + max(300, exec_time)) | 
 | 751 |         soft_tout = exec_time | 
 | 752 |  | 
 | 753 |         begin = time.time() | 
 | 754 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 755 |         fnames_before = run_on_node(node)("ls -1 " + exec_folder, nolog=True) | 
 | 756 |  | 
 | 757 |         barrier.wait() | 
 | 758 |  | 
| koder aka kdanilov | 5414a99 | 2015-06-13 03:07:25 +0300 | [diff] [blame] | 759 |         task = BGSSHTask(node, self.use_sudo) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 760 |         task.start(sudo + "bash " + self.sh_file) | 
 | 761 |  | 
 | 762 |         while True: | 
 | 763 |             try: | 
 | 764 |                 task.wait(soft_tout, timeout) | 
 | 765 |                 break | 
 | 766 |             except paramiko.SSHException: | 
 | 767 |                 pass | 
 | 768 |  | 
 | 769 |             try: | 
 | 770 |                 node.connection.close() | 
 | 771 |             except: | 
 | 772 |                 pass | 
 | 773 |  | 
 | 774 |             reconnect(node.connection, node.conn_url) | 
 | 775 |  | 
 | 776 |         end = time.time() | 
 | 777 |         rossh = run_on_node(node) | 
 | 778 |         fnames_after = rossh("ls -1 " + exec_folder, nolog=True) | 
 | 779 |  | 
 | 780 |         conn_id = node.get_conn_id().replace(":", "_") | 
 | 781 |         if not nolog: | 
 | 782 |             logger.debug("Test on node {0} is finished".format(conn_id)) | 
 | 783 |  | 
 | 784 |         log_files_pref = [] | 
 | 785 |         if 'write_lat_log' in fio_cfg.vals: | 
 | 786 |             fname = fio_cfg.vals['write_lat_log'] | 
 | 787 |             log_files_pref.append(fname + '_clat') | 
 | 788 |             log_files_pref.append(fname + '_lat') | 
 | 789 |             log_files_pref.append(fname + '_slat') | 
 | 790 |  | 
 | 791 |         if 'write_iops_log' in fio_cfg.vals: | 
 | 792 |             fname = fio_cfg.vals['write_iops_log'] | 
 | 793 |             log_files_pref.append(fname + '_iops') | 
 | 794 |  | 
 | 795 |         if 'write_bw_log' in fio_cfg.vals: | 
 | 796 |             fname = fio_cfg.vals['write_bw_log'] | 
 | 797 |             log_files_pref.append(fname + '_bw') | 
 | 798 |  | 
 | 799 |         files = collections.defaultdict(lambda: []) | 
 | 800 |         all_files = [os.path.basename(self.results_file)] | 
 | 801 |         new_files = set(fnames_after.split()) - set(fnames_before.split()) | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 802 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 803 |         for fname in new_files: | 
 | 804 |             if fname.endswith('.log') and fname.split('.')[0] in log_files_pref: | 
 | 805 |                 name, _ = os.path.splitext(fname) | 
 | 806 |                 if fname.count('.') == 1: | 
 | 807 |                     tp = name.split("_")[-1] | 
 | 808 |                     cnt = 0 | 
 | 809 |                 else: | 
 | 810 |                     tp_cnt = name.split("_")[-1] | 
 | 811 |                     tp, cnt = tp_cnt.split('.') | 
 | 812 |                 files[tp].append((int(cnt), fname)) | 
 | 813 |                 all_files.append(fname) | 
 | 814 |  | 
 | 815 |         arch_name = self.join_remote('wally_result.tar.gz') | 
 | 816 |         tmp_dir = os.path.join(self.config.log_directory, 'tmp_' + conn_id) | 
 | 817 |         os.mkdir(tmp_dir) | 
 | 818 |         loc_arch_name = os.path.join(tmp_dir, 'wally_result.{0}.tar.gz'.format(conn_id)) | 
 | 819 |         file_full_names = " ".join(all_files) | 
 | 820 |  | 
 | 821 |         try: | 
 | 822 |             os.unlink(loc_arch_name) | 
 | 823 |         except: | 
 | 824 |             pass | 
 | 825 |  | 
 | 826 |         with node.connection.open_sftp() as sftp: | 
 | 827 |             exit_code = read_from_remote(sftp, self.exit_code_file) | 
 | 828 |             err_out = read_from_remote(sftp, self.err_out_file) | 
 | 829 |             exit_code = exit_code.strip() | 
 | 830 |  | 
 | 831 |             if exit_code != '0': | 
 | 832 |                 msg = "fio exit with code {0}: {1}".format(exit_code, err_out) | 
 | 833 |                 logger.critical(msg.strip()) | 
 | 834 |                 raise StopTestError("fio failed") | 
 | 835 |  | 
 | 836 |             rossh("rm -f {0}".format(arch_name), nolog=True) | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 837 |             pack_files_cmd = "cd {0} ; tar zcvf {1} {2}".format(exec_folder, arch_name, file_full_names) | 
 | 838 |             rossh(pack_files_cmd, nolog=True) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 839 |             sftp.get(arch_name, loc_arch_name) | 
 | 840 |  | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 841 |         unpack_files_cmd = "cd {0} ; tar xvzf {1} >/dev/null".format(tmp_dir, loc_arch_name) | 
 | 842 |         subprocess.check_call(unpack_files_cmd, shell=True) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 843 |         os.unlink(loc_arch_name) | 
 | 844 |  | 
 | 845 |         for ftype, fls in files.items(): | 
 | 846 |             for idx, fname in fls: | 
 | 847 |                 cname = os.path.join(tmp_dir, fname) | 
 | 848 |                 loc_fname = "{0}_{1}_{2}.{3}.log".format(pos, conn_id, ftype, idx) | 
 | 849 |                 loc_path = os.path.join(self.config.log_directory, loc_fname) | 
 | 850 |                 os.rename(cname, loc_path) | 
 | 851 |  | 
 | 852 |         cname = os.path.join(tmp_dir, | 
 | 853 |                              os.path.basename(self.results_file)) | 
 | 854 |         loc_fname = "{0}_{1}_rawres.json".format(pos, conn_id) | 
 | 855 |         loc_path = os.path.join(self.config.log_directory, loc_fname) | 
 | 856 |         os.rename(cname, loc_path) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 857 |         os.rmdir(tmp_dir) | 
| koder aka kdanilov | 6ab4d43 | 2015-06-22 00:26:28 +0300 | [diff] [blame] | 858 |  | 
 | 859 |         remove_remote_res_files_cmd = "cd {0} ; rm -f {1} {2}".format(exec_folder, | 
 | 860 |                                                                       arch_name, | 
 | 861 |                                                                       file_full_names) | 
 | 862 |         rossh(remove_remote_res_files_cmd, nolog=True) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 863 |         return begin, end | 
 | 864 |  | 
 | 865 |     @classmethod | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 866 |     def prepare_data(cls, results): | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 867 |         """ | 
 | 868 |         create a table with io performance report | 
 | 869 |         for console | 
 | 870 |         """ | 
 | 871 |  | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 872 |         def key_func(data): | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 873 |             tpl = data.summary_tpl() | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 874 |             return (data.name, | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 875 |                     tpl.oper, | 
 | 876 |                     tpl.mode, | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 877 |                     ssize2b(tpl.bsize), | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 878 |                     int(tpl.th_count) * int(tpl.vm_count)) | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 879 |         res = [] | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 880 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 881 |         for item in sorted(results, key=key_func): | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 882 |             test_dinfo = item.disk_perf_info() | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 883 |  | 
 | 884 |             iops, _ = test_dinfo.iops.rounded_average_conf() | 
 | 885 |  | 
 | 886 |             bw, bw_conf = test_dinfo.bw.rounded_average_conf() | 
 | 887 |             _, bw_dev = test_dinfo.bw.rounded_average_dev() | 
 | 888 |             conf_perc = int(round(bw_conf * 100 / bw)) | 
 | 889 |             dev_perc = int(round(bw_dev * 100 / bw)) | 
 | 890 |  | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 891 |             lat_50 = round_3_digit(int(test_dinfo.lat_50)) | 
 | 892 |             lat_95 = round_3_digit(int(test_dinfo.lat_95)) | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 893 |             lat_avg = round_3_digit(int(test_dinfo.lat_avg)) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 894 |  | 
| koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 895 |             testnodes_count = len(item.config.nodes) | 
 | 896 |             iops_per_vm = round_3_digit(iops / testnodes_count) | 
 | 897 |             bw_per_vm = round_3_digit(bw / testnodes_count) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 898 |  | 
 | 899 |             iops = round_3_digit(iops) | 
 | 900 |             bw = round_3_digit(bw) | 
 | 901 |  | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 902 |             summ = "{0.oper}{0.mode} {0.bsize:>4} {0.th_count:>3}th {0.vm_count:>2}vm".format(item.summary_tpl()) | 
 | 903 |  | 
 | 904 |             res.append({"name": key_func(item)[0], | 
 | 905 |                         "key": key_func(item)[:4], | 
 | 906 |                         "summ": summ, | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 907 |                         "iops": int(iops), | 
 | 908 |                         "bw": int(bw), | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 909 |                         "conf": str(conf_perc), | 
 | 910 |                         "dev": str(dev_perc), | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 911 |                         "iops_per_vm": int(iops_per_vm), | 
 | 912 |                         "bw_per_vm": int(bw_per_vm), | 
 | 913 |                         "lat_50": lat_50, | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 914 |                         "lat_95": lat_95, | 
 | 915 |                         "lat_avg": lat_avg}) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 916 |  | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 917 |         return res | 
 | 918 |  | 
 | 919 |     Field = collections.namedtuple("Field", ("header", "attr", "allign", "size")) | 
 | 920 |     fiels_and_header = [ | 
 | 921 |         Field("Name",           "name",        "l",  7), | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 922 |         Field("Description",    "summ",        "l", 19), | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 923 |         Field("IOPS\ncum",      "iops",        "r",  3), | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 924 |         Field("KiBps\ncum",     "bw",          "r",  6), | 
 | 925 |         Field("Cnf %\n95%",     "conf",        "r",  3), | 
 | 926 |         Field("Dev%",           "dev",         "r",  3), | 
 | 927 |         Field("iops\n/vm",      "iops_per_vm", "r",  3), | 
 | 928 |         Field("KiBps\n/vm",     "bw_per_vm",   "r",  6), | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 929 |         Field("lat ms\nmedian", "lat_50",      "r",  3), | 
| koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 930 |         Field("lat ms\n95%",    "lat_95",      "r",  3), | 
 | 931 |         Field("lat\navg",       "lat_avg",     "r",  3), | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 932 |     ] | 
 | 933 |  | 
 | 934 |     fiels_and_header_dct = dict((item.attr, item) for item in fiels_and_header) | 
 | 935 |  | 
 | 936 |     @classmethod | 
 | 937 |     def format_for_console(cls, results): | 
 | 938 |         """ | 
 | 939 |         create a table with io performance report | 
 | 940 |         for console | 
 | 941 |         """ | 
 | 942 |  | 
 | 943 |         tab = texttable.Texttable(max_width=120) | 
 | 944 |         tab.set_deco(tab.HEADER | tab.VLINES | tab.BORDER) | 
 | 945 |         tab.set_cols_align([f.allign for f in cls.fiels_and_header]) | 
 | 946 |         sep = ["-" * f.size for f in cls.fiels_and_header] | 
 | 947 |         tab.header([f.header for f in cls.fiels_and_header]) | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 948 |         prev_k = None | 
 | 949 |         for item in cls.prepare_data(results): | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 950 |             if prev_k is not None: | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 951 |                 if prev_k != item["key"]: | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 952 |                     tab.add_row(sep) | 
 | 953 |  | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 954 |             prev_k = item["key"] | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 955 |             tab.add_row([item[f.attr] for f in cls.fiels_and_header]) | 
 | 956 |  | 
 | 957 |         return tab.draw() | 
 | 958 |  | 
 | 959 |     @classmethod | 
 | 960 |     def format_diff_for_console(cls, list_of_results): | 
 | 961 |         """ | 
 | 962 |         create a table with io performance report | 
 | 963 |         for console | 
 | 964 |         """ | 
 | 965 |  | 
 | 966 |         tab = texttable.Texttable(max_width=200) | 
 | 967 |         tab.set_deco(tab.HEADER | tab.VLINES | tab.BORDER) | 
 | 968 |  | 
 | 969 |         header = [ | 
 | 970 |             cls.fiels_and_header_dct["name"].header, | 
 | 971 |             cls.fiels_and_header_dct["summ"].header, | 
 | 972 |         ] | 
 | 973 |         allign = ["l", "l"] | 
 | 974 |  | 
 | 975 |         header.append("IOPS ~ Cnf% ~ Dev%") | 
 | 976 |         allign.extend(["r"] * len(list_of_results)) | 
 | 977 |         header.extend( | 
 | 978 |             "IOPS_{0} %".format(i + 2) for i in range(len(list_of_results[1:])) | 
 | 979 |         ) | 
 | 980 |  | 
 | 981 |         header.append("BW") | 
 | 982 |         allign.extend(["r"] * len(list_of_results)) | 
 | 983 |         header.extend( | 
 | 984 |             "BW_{0} %".format(i + 2) for i in range(len(list_of_results[1:])) | 
 | 985 |         ) | 
 | 986 |  | 
 | 987 |         header.append("LAT") | 
 | 988 |         allign.extend(["r"] * len(list_of_results)) | 
 | 989 |         header.extend( | 
 | 990 |             "LAT_{0}".format(i + 2) for i in range(len(list_of_results[1:])) | 
 | 991 |         ) | 
 | 992 |  | 
 | 993 |         tab.header(header) | 
 | 994 |         sep = ["-" * 3] * len(header) | 
 | 995 |         processed_results = map(cls.prepare_data, list_of_results) | 
 | 996 |  | 
 | 997 |         key2results = [] | 
 | 998 |         for res in processed_results: | 
 | 999 |             key2results.append(dict( | 
 | 1000 |                 ((item["name"], item["summ"]), item) for item in res | 
 | 1001 |             )) | 
 | 1002 |  | 
 | 1003 |         prev_k = None | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 1004 |         iops_frmt = "{0[iops]} ~ {0[conf]:>2} ~ {0[dev]:>2}" | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 1005 |         for item in processed_results[0]: | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 1006 |             if prev_k is not None: | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 1007 |                 if prev_k != item["key"]: | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 1008 |                     tab.add_row(sep) | 
 | 1009 |  | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 1010 |             prev_k = item["key"] | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 1011 |  | 
 | 1012 |             key = (item['name'], item['summ']) | 
 | 1013 |             line = list(key) | 
 | 1014 |             base = key2results[0][key] | 
 | 1015 |  | 
 | 1016 |             line.append(iops_frmt.format(base)) | 
 | 1017 |  | 
 | 1018 |             for test_results in key2results[1:]: | 
 | 1019 |                 val = test_results.get(key) | 
 | 1020 |                 if val is None: | 
 | 1021 |                     line.append("-") | 
 | 1022 |                 elif base['iops'] == 0: | 
 | 1023 |                     line.append("Nan") | 
 | 1024 |                 else: | 
| koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 1025 |                     prc_val = {'dev': val['dev'], 'conf': val['conf']} | 
| koder aka kdanilov | 6b87266 | 2015-06-23 01:58:36 +0300 | [diff] [blame] | 1026 |                     prc_val['iops'] = int(100 * val['iops'] / base['iops']) | 
 | 1027 |                     line.append(iops_frmt.format(prc_val)) | 
 | 1028 |  | 
 | 1029 |             line.append(base['bw']) | 
 | 1030 |  | 
 | 1031 |             for test_results in key2results[1:]: | 
 | 1032 |                 val = test_results.get(key) | 
 | 1033 |                 if val is None: | 
 | 1034 |                     line.append("-") | 
 | 1035 |                 elif base['bw'] == 0: | 
 | 1036 |                     line.append("Nan") | 
 | 1037 |                 else: | 
 | 1038 |                     line.append(int(100 * val['bw'] / base['bw'])) | 
 | 1039 |  | 
 | 1040 |             for test_results in key2results: | 
 | 1041 |                 val = test_results.get(key) | 
 | 1042 |                 if val is None: | 
 | 1043 |                     line.append("-") | 
 | 1044 |                 else: | 
 | 1045 |                     line.append("{0[lat_50]} - {0[lat_95]}".format(val)) | 
 | 1046 |  | 
 | 1047 |             tab.add_row(line) | 
 | 1048 |  | 
 | 1049 |         tab.set_cols_align(allign) | 
| koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 1050 |         return tab.draw() |