koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 1 | import os |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 2 | import csv |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 3 | import bisect |
koder aka kdanilov | a047e1b | 2015-04-21 23:16:59 +0300 | [diff] [blame] | 4 | import logging |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 5 | import itertools |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 6 | import collections |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 7 | from io import StringIO |
| 8 | from typing import Dict |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 9 | |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 10 | try: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 11 | import numpy |
| 12 | import scipy |
koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 13 | import matplotlib |
koder aka kdanilov | 9e0512a | 2015-08-10 14:51:59 +0300 | [diff] [blame] | 14 | matplotlib.use('svg') |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 15 | import matplotlib.pyplot as plt |
| 16 | except ImportError: |
| 17 | plt = None |
| 18 | |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 19 | import wally |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 20 | from .utils import ssize2b |
| 21 | from .statistic import round_3_digit |
| 22 | from .suits.io.fio_task_parser import (get_test_sync_mode, |
| 23 | get_test_summary, |
| 24 | parse_all_in_1, |
| 25 | abbv_name_to_full) |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 26 | |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 27 | |
koder aka kdanilov | a047e1b | 2015-04-21 23:16:59 +0300 | [diff] [blame] | 28 | logger = logging.getLogger("wally.report") |
| 29 | |
| 30 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 31 | class DiskInfo: |
koder aka kdanilov | 209e85d | 2015-04-27 23:11:05 +0300 | [diff] [blame] | 32 | def __init__(self): |
| 33 | self.direct_iops_r_max = 0 |
| 34 | self.direct_iops_w_max = 0 |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 35 | |
| 36 | # 64 used instead of 4k to faster feed caches |
| 37 | self.direct_iops_w64_max = 0 |
| 38 | |
koder aka kdanilov | 209e85d | 2015-04-27 23:11:05 +0300 | [diff] [blame] | 39 | self.rws4k_10ms = 0 |
| 40 | self.rws4k_30ms = 0 |
| 41 | self.rws4k_100ms = 0 |
| 42 | self.bw_write_max = 0 |
| 43 | self.bw_read_max = 0 |
| 44 | |
| 45 | |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 46 | report_funcs = [] |
| 47 | |
| 48 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 49 | class Attrmapper(object): |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 50 | def __init__(self, dct: Dict): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 51 | self.__dct = dct |
| 52 | |
| 53 | def __getattr__(self, name): |
| 54 | try: |
| 55 | return self.__dct[name] |
| 56 | except KeyError: |
| 57 | raise AttributeError(name) |
| 58 | |
| 59 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 60 | class PerfInfo(object): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 61 | def __init__(self, name, summary, intervals, params, testnodes_count): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 62 | self.name = name |
| 63 | self.bw = None |
| 64 | self.iops = None |
| 65 | self.lat = None |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 66 | self.lat_50 = None |
| 67 | self.lat_95 = None |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 68 | |
| 69 | self.raw_bw = [] |
| 70 | self.raw_iops = [] |
| 71 | self.raw_lat = [] |
| 72 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 73 | self.params = params |
| 74 | self.intervals = intervals |
| 75 | self.testnodes_count = testnodes_count |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 76 | self.summary = summary |
| 77 | self.p = Attrmapper(self.params.vals) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 78 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 79 | self.sync_mode = get_test_sync_mode(self.params) |
| 80 | self.concurence = self.params.vals.get('numjobs', 1) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 81 | |
| 82 | |
koder aka kdanilov | fd2cfa5 | 2015-05-20 03:17:42 +0300 | [diff] [blame] | 83 | # disk_info = None |
| 84 | # base = None |
| 85 | # linearity = None |
| 86 | |
| 87 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 88 | def group_by_name(test_data): |
| 89 | name_map = collections.defaultdict(lambda: []) |
| 90 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 91 | for data in test_data: |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 92 | name_map[(data.name, data.summary())].append(data) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 93 | |
| 94 | return name_map |
| 95 | |
| 96 | |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 97 | def report(name, required_fields): |
| 98 | def closure(func): |
| 99 | report_funcs.append((required_fields.split(","), name, func)) |
| 100 | return func |
| 101 | return closure |
| 102 | |
| 103 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 104 | def get_test_lcheck_params(pinfo): |
| 105 | res = [{ |
| 106 | 's': 'sync', |
| 107 | 'd': 'direct', |
| 108 | 'a': 'async', |
| 109 | 'x': 'sync direct' |
| 110 | }[pinfo.sync_mode]] |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 111 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 112 | res.append(pinfo.p.rw) |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 113 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 114 | return " ".join(res) |
koder aka kdanilov | 63e9c5a | 2015-04-28 23:06:07 +0300 | [diff] [blame] | 115 | |
| 116 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 117 | def get_emb_data_svg(plt): |
| 118 | sio = StringIO() |
| 119 | plt.savefig(sio, format='svg') |
| 120 | img_start = "<!-- Created with matplotlib (http://matplotlib.org/) -->" |
| 121 | return sio.getvalue().split(img_start, 1)[1] |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 122 | |
| 123 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 124 | def get_template(templ_name): |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 125 | very_root_dir = os.path.dirname(os.path.dirname(wally.__file__)) |
| 126 | templ_dir = os.path.join(very_root_dir, 'report_templates') |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 127 | templ_file = os.path.join(templ_dir, templ_name) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 128 | return open(templ_file, 'r').read() |
koder aka kdanilov | 209e85d | 2015-04-27 23:11:05 +0300 | [diff] [blame] | 129 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 130 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 131 | def group_by(data, func): |
| 132 | if len(data) < 2: |
| 133 | yield data |
| 134 | return |
| 135 | |
| 136 | ndata = [(func(dt), dt) for dt in data] |
| 137 | ndata.sort(key=func) |
| 138 | pkey, dt = ndata[0] |
| 139 | curr_list = [dt] |
| 140 | |
| 141 | for key, val in ndata[1:]: |
| 142 | if pkey != key: |
| 143 | yield curr_list |
| 144 | curr_list = [val] |
| 145 | else: |
| 146 | curr_list.append(val) |
| 147 | pkey = key |
| 148 | |
| 149 | yield curr_list |
| 150 | |
| 151 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 152 | @report('linearity', 'linearity_test') |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 153 | def linearity_report(processed_results, lab_info, comment): |
| 154 | labels_and_data_mp = collections.defaultdict(lambda: []) |
| 155 | vls = {} |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 156 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 157 | # plot io_time = func(bsize) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 158 | for res in processed_results.values(): |
| 159 | if res.name.startswith('linearity_test'): |
| 160 | iotimes = [1000. / val for val in res.iops.raw] |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 161 | |
| 162 | op_summ = get_test_summary(res.params)[:3] |
| 163 | |
| 164 | labels_and_data_mp[op_summ].append( |
| 165 | [res.p.blocksize, res.iops.raw, iotimes]) |
| 166 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 167 | cvls = res.params.vals.copy() |
| 168 | del cvls['blocksize'] |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 169 | del cvls['rw'] |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 170 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 171 | cvls.pop('sync', None) |
| 172 | cvls.pop('direct', None) |
| 173 | cvls.pop('buffered', None) |
| 174 | |
| 175 | if op_summ not in vls: |
| 176 | vls[op_summ] = cvls |
| 177 | else: |
| 178 | assert cvls == vls[op_summ] |
| 179 | |
| 180 | all_labels = None |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 181 | _, ax1 = plt.subplots() |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 182 | for name, labels_and_data in labels_and_data_mp.items(): |
| 183 | labels_and_data.sort(key=lambda x: ssize2b(x[0])) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 184 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 185 | labels, _, iotimes = zip(*labels_and_data) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 186 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 187 | if all_labels is None: |
| 188 | all_labels = labels |
| 189 | else: |
| 190 | assert all_labels == labels |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 191 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 192 | plt.boxplot(iotimes) |
| 193 | if len(labels_and_data) > 2 and \ |
| 194 | ssize2b(labels_and_data[-2][0]) >= 4096: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 195 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 196 | xt = range(1, len(labels) + 1) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 197 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 198 | def io_time(sz, bw, initial_lat): |
| 199 | return sz / bw + initial_lat |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 200 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 201 | x = numpy.array(map(ssize2b, labels)) |
| 202 | y = numpy.array([sum(dt) / len(dt) for dt in iotimes]) |
| 203 | popt, _ = scipy.optimize.curve_fit(io_time, x, y, p0=(100., 1.)) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 204 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 205 | y1 = io_time(x, *popt) |
| 206 | plt.plot(xt, y1, linestyle='--', |
| 207 | label=name + ' LS linear approx') |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 208 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 209 | for idx, (sz, _, _) in enumerate(labels_and_data): |
| 210 | if ssize2b(sz) >= 4096: |
| 211 | break |
| 212 | |
| 213 | bw = (x[-1] - x[idx]) / (y[-1] - y[idx]) |
| 214 | lat = y[-1] - x[-1] / bw |
| 215 | y2 = io_time(x, bw, lat) |
| 216 | plt.plot(xt, y2, linestyle='--', |
| 217 | label=abbv_name_to_full(name) + |
| 218 | ' (4k & max) linear approx') |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 219 | |
| 220 | plt.setp(ax1, xticklabels=labels) |
| 221 | |
| 222 | plt.xlabel("Block size") |
| 223 | plt.ylabel("IO time, ms") |
| 224 | |
koder aka kdanilov | fd2cfa5 | 2015-05-20 03:17:42 +0300 | [diff] [blame] | 225 | plt.subplots_adjust(top=0.85) |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 226 | plt.legend(bbox_to_anchor=(0.5, 1.15), |
| 227 | loc='upper center', |
| 228 | prop={'size': 10}, ncol=2) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 229 | plt.grid() |
| 230 | iotime_plot = get_emb_data_svg(plt) |
koder aka kdanilov | fd2cfa5 | 2015-05-20 03:17:42 +0300 | [diff] [blame] | 231 | plt.clf() |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 232 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 233 | # plot IOPS = func(bsize) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 234 | _, ax1 = plt.subplots() |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 235 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 236 | for name, labels_and_data in labels_and_data_mp.items(): |
| 237 | labels_and_data.sort(key=lambda x: ssize2b(x[0])) |
| 238 | _, data, _ = zip(*labels_and_data) |
| 239 | plt.boxplot(data) |
| 240 | avg = [float(sum(arr)) / len(arr) for arr in data] |
| 241 | xt = range(1, len(data) + 1) |
| 242 | plt.plot(xt, avg, linestyle='--', |
| 243 | label=abbv_name_to_full(name) + " avg") |
| 244 | |
| 245 | plt.setp(ax1, xticklabels=labels) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 246 | plt.xlabel("Block size") |
| 247 | plt.ylabel("IOPS") |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 248 | plt.legend(bbox_to_anchor=(0.5, 1.15), |
| 249 | loc='upper center', |
| 250 | prop={'size': 10}, ncol=2) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 251 | plt.grid() |
koder aka kdanilov | fd2cfa5 | 2015-05-20 03:17:42 +0300 | [diff] [blame] | 252 | plt.subplots_adjust(top=0.85) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 253 | |
| 254 | iops_plot = get_emb_data_svg(plt) |
| 255 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 256 | res = set(get_test_lcheck_params(res) for res in processed_results.values()) |
| 257 | ncount = list(set(res.testnodes_count for res in processed_results.values())) |
| 258 | conc = list(set(res.concurence for res in processed_results.values())) |
| 259 | |
| 260 | assert len(conc) == 1 |
| 261 | assert len(ncount) == 1 |
| 262 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 263 | descr = { |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 264 | 'vm_count': ncount[0], |
| 265 | 'concurence': conc[0], |
| 266 | 'oper_descr': ", ".join(res).capitalize() |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 267 | } |
| 268 | |
| 269 | params_map = {'iotime_vs_size': iotime_plot, |
| 270 | 'iops_vs_size': iops_plot, |
| 271 | 'descr': descr} |
| 272 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 273 | return get_template('report_linearity.html').format(**params_map) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 274 | |
| 275 | |
| 276 | @report('lat_vs_iops', 'lat_vs_iops') |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 277 | def lat_vs_iops(processed_results, lab_info, comment): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 278 | lat_iops = collections.defaultdict(lambda: []) |
koder aka kdanilov | fd2cfa5 | 2015-05-20 03:17:42 +0300 | [diff] [blame] | 279 | requsted_vs_real = collections.defaultdict(lambda: {}) |
| 280 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 281 | for res in processed_results.values(): |
| 282 | if res.name.startswith('lat_vs_iops'): |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 283 | lat_iops[res.concurence].append((res.lat, |
| 284 | 0, |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 285 | res.iops.average, |
| 286 | res.iops.deviation)) |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 287 | # lat_iops[res.concurence].append((res.lat.average / 1000.0, |
| 288 | # res.lat.deviation / 1000.0, |
| 289 | # res.iops.average, |
| 290 | # res.iops.deviation)) |
koder aka kdanilov | fd2cfa5 | 2015-05-20 03:17:42 +0300 | [diff] [blame] | 291 | requested_iops = res.p.rate_iops * res.concurence |
| 292 | requsted_vs_real[res.concurence][requested_iops] = \ |
| 293 | (res.iops.average, res.iops.deviation) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 294 | |
koder aka kdanilov | fd2cfa5 | 2015-05-20 03:17:42 +0300 | [diff] [blame] | 295 | colors = ['red', 'green', 'blue', 'orange', 'magenta', "teal"] |
| 296 | colors_it = iter(colors) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 297 | for conc, lat_iops in sorted(lat_iops.items()): |
| 298 | lat, dev, iops, iops_dev = zip(*lat_iops) |
| 299 | plt.errorbar(iops, lat, xerr=iops_dev, yerr=dev, fmt='ro', |
| 300 | label=str(conc) + " threads", |
koder aka kdanilov | fd2cfa5 | 2015-05-20 03:17:42 +0300 | [diff] [blame] | 301 | color=next(colors_it)) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 302 | |
| 303 | plt.xlabel("IOPS") |
| 304 | plt.ylabel("Latency, ms") |
| 305 | plt.grid() |
| 306 | plt.legend(loc=0) |
koder aka kdanilov | fd2cfa5 | 2015-05-20 03:17:42 +0300 | [diff] [blame] | 307 | plt_iops_vs_lat = get_emb_data_svg(plt) |
| 308 | plt.clf() |
| 309 | |
| 310 | colors_it = iter(colors) |
| 311 | for conc, req_vs_real in sorted(requsted_vs_real.items()): |
| 312 | req, real = zip(*sorted(req_vs_real.items())) |
| 313 | iops, dev = zip(*real) |
| 314 | plt.errorbar(req, iops, yerr=dev, fmt='ro', |
| 315 | label=str(conc) + " threads", |
| 316 | color=next(colors_it)) |
| 317 | plt.xlabel("Requested IOPS") |
| 318 | plt.ylabel("Get IOPS") |
| 319 | plt.grid() |
| 320 | plt.legend(loc=0) |
| 321 | plt_iops_vs_requested = get_emb_data_svg(plt) |
| 322 | |
| 323 | res1 = processed_results.values()[0] |
| 324 | params_map = {'iops_vs_lat': plt_iops_vs_lat, |
| 325 | 'iops_vs_requested': plt_iops_vs_requested, |
| 326 | 'oper_descr': get_test_lcheck_params(res1).capitalize()} |
| 327 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 328 | return get_template('report_iops_vs_lat.html').format(**params_map) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 329 | |
| 330 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 331 | def render_all_html(comment, info, lab_description, images, templ_name): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 332 | data = info.__dict__.copy() |
| 333 | for name, val in data.items(): |
koder aka kdanilov | 209e85d | 2015-04-27 23:11:05 +0300 | [diff] [blame] | 334 | if not name.startswith('__'): |
koder aka kdanilov | c368eb6 | 2015-04-28 18:22:01 +0300 | [diff] [blame] | 335 | if val is None: |
koder aka kdanilov | 765920a | 2016-04-12 00:35:48 +0300 | [diff] [blame] | 336 | if name in ('direct_iops_w64_max', 'direct_iops_w_max'): |
| 337 | data[name] = ('-', '-', '-') |
| 338 | else: |
| 339 | data[name] = '-' |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 340 | elif isinstance(val, (int, float, long)): |
| 341 | data[name] = round_3_digit(val) |
koder aka kdanilov | 209e85d | 2015-04-27 23:11:05 +0300 | [diff] [blame] | 342 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 343 | data['bw_read_max'] = (data['bw_read_max'][0] // 1024, |
koder aka kdanilov | c0c97e2 | 2015-07-21 00:08:33 +0300 | [diff] [blame] | 344 | data['bw_read_max'][1], |
| 345 | data['bw_read_max'][2]) |
| 346 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 347 | data['bw_write_max'] = (data['bw_write_max'][0] // 1024, |
koder aka kdanilov | c0c97e2 | 2015-07-21 00:08:33 +0300 | [diff] [blame] | 348 | data['bw_write_max'][1], |
| 349 | data['bw_write_max'][2]) |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 350 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 351 | images.update(data) |
koder aka kdanilov | 765920a | 2016-04-12 00:35:48 +0300 | [diff] [blame] | 352 | templ = get_template(templ_name) |
| 353 | return templ.format(lab_info=lab_description, |
| 354 | comment=comment, |
| 355 | **images) |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 356 | |
| 357 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 358 | def io_chart(title, concurence, |
| 359 | latv, latv_min, latv_max, |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 360 | iops_or_bw, iops_or_bw_err, |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 361 | legend, |
| 362 | log_iops=False, |
| 363 | log_lat=False, |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 364 | boxplots=False, |
koder aka kdanilov | c0c97e2 | 2015-07-21 00:08:33 +0300 | [diff] [blame] | 365 | latv_50=None, |
| 366 | latv_95=None, |
| 367 | error2=None): |
koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 368 | |
| 369 | matplotlib.rcParams.update({'font.size': 10}) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 370 | points = " MiBps" if legend == 'BW' else "" |
| 371 | lc = len(concurence) |
| 372 | width = 0.35 |
| 373 | xt = range(1, lc + 1) |
| 374 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 375 | op_per_vm = [v / (vm * th) for v, (vm, th) in zip(iops_or_bw, concurence)] |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 376 | fig, p1 = plt.subplots() |
| 377 | xpos = [i - width / 2 for i in xt] |
| 378 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 379 | p1.bar(xpos, iops_or_bw, |
| 380 | width=width, |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 381 | color='y', |
| 382 | label=legend) |
| 383 | |
koder aka kdanilov | c0c97e2 | 2015-07-21 00:08:33 +0300 | [diff] [blame] | 384 | err1_leg = None |
| 385 | for pos, y, err in zip(xpos, iops_or_bw, iops_or_bw_err): |
| 386 | err1_leg = p1.errorbar(pos + width / 2, |
| 387 | y, |
| 388 | err, |
| 389 | color='magenta') |
| 390 | |
| 391 | err2_leg = None |
| 392 | if error2 is not None: |
| 393 | for pos, y, err in zip(xpos, iops_or_bw, error2): |
| 394 | err2_leg = p1.errorbar(pos + width / 2 + 0.08, |
| 395 | y, |
| 396 | err, |
| 397 | lw=2, |
| 398 | alpha=0.5, |
| 399 | color='teal') |
| 400 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 401 | p1.grid(True) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 402 | p1.plot(xt, op_per_vm, '--', label=legend + "/thread", color='black') |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 403 | handles1, labels1 = p1.get_legend_handles_labels() |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 404 | |
koder aka kdanilov | c0c97e2 | 2015-07-21 00:08:33 +0300 | [diff] [blame] | 405 | handles1 += [err1_leg] |
| 406 | labels1 += ["95% conf"] |
| 407 | |
| 408 | if err2_leg is not None: |
| 409 | handles1 += [err2_leg] |
| 410 | labels1 += ["95% dev"] |
| 411 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 412 | p2 = p1.twinx() |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 413 | |
| 414 | if latv_50 is None: |
| 415 | p2.plot(xt, latv_max, label="lat max") |
| 416 | p2.plot(xt, latv, label="lat avg") |
| 417 | p2.plot(xt, latv_min, label="lat min") |
| 418 | else: |
| 419 | p2.plot(xt, latv_50, label="lat med") |
| 420 | p2.plot(xt, latv_95, label="lat 95%") |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 421 | |
| 422 | plt.xlim(0.5, lc + 0.5) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 423 | plt.xticks(xt, ["{0} * {1}".format(vm, th) for (vm, th) in concurence]) |
| 424 | p1.set_xlabel("VM Count * Thread per VM") |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 425 | p1.set_ylabel(legend + points) |
| 426 | p2.set_ylabel("Latency ms") |
| 427 | plt.title(title) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 428 | handles2, labels2 = p2.get_legend_handles_labels() |
| 429 | |
| 430 | plt.legend(handles1 + handles2, labels1 + labels2, |
| 431 | loc='center left', bbox_to_anchor=(1.1, 0.81)) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 432 | |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 433 | if log_iops: |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 434 | p1.set_yscale('log') |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 435 | |
| 436 | if log_lat: |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 437 | p2.set_yscale('log') |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 438 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 439 | plt.subplots_adjust(right=0.68) |
| 440 | |
| 441 | return get_emb_data_svg(plt) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 442 | |
| 443 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 444 | def make_plots(processed_results, plots): |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 445 | """ |
| 446 | processed_results: [PerfInfo] |
| 447 | plots = [(test_name_prefix:str, fname:str, description:str)] |
| 448 | """ |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 449 | files = {} |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 450 | for name_pref, fname, desc in plots: |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 451 | chart_data = [] |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 452 | |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 453 | for res in processed_results: |
| 454 | summ = res.name + "_" + res.summary |
| 455 | if summ.startswith(name_pref): |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 456 | chart_data.append(res) |
| 457 | |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 458 | if len(chart_data) == 0: |
| 459 | raise ValueError("Can't found any date for " + name_pref) |
| 460 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 461 | use_bw = ssize2b(chart_data[0].p.blocksize) > 16 * 1024 |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 462 | |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 463 | chart_data.sort(key=lambda x: x.params['vals']['numjobs']) |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 464 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 465 | lat = None |
| 466 | lat_min = None |
| 467 | lat_max = None |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 468 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 469 | lat_50 = [x.lat_50 for x in chart_data] |
| 470 | lat_95 = [x.lat_95 for x in chart_data] |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 471 | |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 472 | lat_diff_max = max(x.lat_95 / x.lat_50 for x in chart_data) |
| 473 | lat_log_scale = (lat_diff_max > 10) |
| 474 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 475 | testnodes_count = x.testnodes_count |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 476 | concurence = [(testnodes_count, x.concurence) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 477 | for x in chart_data] |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 478 | |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 479 | if use_bw: |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 480 | data = [x.bw.average / 1000 for x in chart_data] |
koder aka kdanilov | c0c97e2 | 2015-07-21 00:08:33 +0300 | [diff] [blame] | 481 | data_conf = [x.bw.confidence / 1000 for x in chart_data] |
| 482 | data_dev = [x.bw.deviation * 2.5 / 1000 for x in chart_data] |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 483 | name = "BW" |
| 484 | else: |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 485 | data = [x.iops.average for x in chart_data] |
koder aka kdanilov | c0c97e2 | 2015-07-21 00:08:33 +0300 | [diff] [blame] | 486 | data_conf = [x.iops.confidence for x in chart_data] |
| 487 | data_dev = [x.iops.deviation * 2 for x in chart_data] |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 488 | name = "IOPS" |
| 489 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 490 | fc = io_chart(title=desc, |
| 491 | concurence=concurence, |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 492 | |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 493 | latv=lat, |
| 494 | latv_min=lat_min, |
| 495 | latv_max=lat_max, |
| 496 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 497 | iops_or_bw=data, |
koder aka kdanilov | c0c97e2 | 2015-07-21 00:08:33 +0300 | [diff] [blame] | 498 | iops_or_bw_err=data_conf, |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 499 | |
| 500 | legend=name, |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 501 | log_lat=lat_log_scale, |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 502 | |
| 503 | latv_50=lat_50, |
koder aka kdanilov | c0c97e2 | 2015-07-21 00:08:33 +0300 | [diff] [blame] | 504 | latv_95=lat_95, |
| 505 | |
| 506 | error2=data_dev) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 507 | files[fname] = fc |
| 508 | |
| 509 | return files |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 510 | |
| 511 | |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 512 | def find_max_where(processed_results, sync_mode, blocksize, rw, iops=True): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 513 | result = None |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 514 | attr = 'iops' if iops else 'bw' |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 515 | for measurement in processed_results: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 516 | ok = measurement.sync_mode == sync_mode |
| 517 | ok = ok and (measurement.p.blocksize == blocksize) |
| 518 | ok = ok and (measurement.p.rw == rw) |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 519 | |
| 520 | if ok: |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 521 | field = getattr(measurement, attr) |
| 522 | |
| 523 | if result is None: |
| 524 | result = field |
| 525 | elif field.average > result.average: |
| 526 | result = field |
| 527 | |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 528 | return result |
| 529 | |
| 530 | |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 531 | def get_disk_info(processed_results): |
| 532 | di = DiskInfo() |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 533 | di.direct_iops_w_max = find_max_where(processed_results, |
| 534 | 'd', '4k', 'randwrite') |
| 535 | di.direct_iops_r_max = find_max_where(processed_results, |
| 536 | 'd', '4k', 'randread') |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 537 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 538 | di.direct_iops_w64_max = find_max_where(processed_results, |
| 539 | 'd', '64k', 'randwrite') |
| 540 | |
| 541 | for sz in ('16m', '64m'): |
| 542 | di.bw_write_max = find_max_where(processed_results, |
| 543 | 'd', sz, 'randwrite', False) |
| 544 | if di.bw_write_max is not None: |
| 545 | break |
| 546 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 547 | if di.bw_write_max is None: |
koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 548 | for sz in ('1m', '2m', '4m', '8m'): |
| 549 | di.bw_write_max = find_max_where(processed_results, |
| 550 | 'd', sz, 'write', False) |
| 551 | if di.bw_write_max is not None: |
| 552 | break |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 553 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 554 | for sz in ('16m', '64m'): |
| 555 | di.bw_read_max = find_max_where(processed_results, |
| 556 | 'd', sz, 'randread', False) |
| 557 | if di.bw_read_max is not None: |
| 558 | break |
| 559 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 560 | if di.bw_read_max is None: |
| 561 | di.bw_read_max = find_max_where(processed_results, |
| 562 | 'd', '1m', 'read', False) |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 563 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 564 | rws4k_iops_lat_th = [] |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 565 | for res in processed_results: |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 566 | if res.sync_mode in 'xs' and res.p.blocksize == '4k': |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 567 | if res.p.rw != 'randwrite': |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 568 | continue |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 569 | rws4k_iops_lat_th.append((res.iops.average, |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 570 | res.lat, |
| 571 | # res.lat.average, |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 572 | res.concurence)) |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 573 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 574 | rws4k_iops_lat_th.sort(key=lambda x: x[2]) |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 575 | |
| 576 | latv = [lat for _, lat, _ in rws4k_iops_lat_th] |
| 577 | |
koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 578 | for tlat in [10, 30, 100]: |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 579 | pos = bisect.bisect_left(latv, tlat) |
| 580 | if 0 == pos: |
koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 581 | setattr(di, 'rws4k_{}ms'.format(tlat), 0) |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 582 | elif pos == len(latv): |
koder aka kdanilov | bb5fe07 | 2015-05-21 02:50:23 +0300 | [diff] [blame] | 583 | iops3, _, _ = rws4k_iops_lat_th[-1] |
koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 584 | iops3 = int(round_3_digit(iops3)) |
koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 585 | setattr(di, 'rws4k_{}ms'.format(tlat), ">=" + str(iops3)) |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 586 | else: |
| 587 | lat1 = latv[pos - 1] |
| 588 | lat2 = latv[pos] |
| 589 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 590 | iops1, _, th1 = rws4k_iops_lat_th[pos - 1] |
| 591 | iops2, _, th2 = rws4k_iops_lat_th[pos] |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 592 | |
| 593 | th_lat_coef = (th2 - th1) / (lat2 - lat1) |
| 594 | th3 = th_lat_coef * (tlat - lat1) + th1 |
| 595 | |
| 596 | th_iops_coef = (iops2 - iops1) / (th2 - th1) |
| 597 | iops3 = th_iops_coef * (th3 - th1) + iops1 |
koder aka kdanilov | 0fdaaee | 2015-06-30 11:10:48 +0300 | [diff] [blame] | 598 | iops3 = int(round_3_digit(iops3)) |
| 599 | setattr(di, 'rws4k_{}ms'.format(tlat), iops3) |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 600 | |
| 601 | hdi = DiskInfo() |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 602 | |
| 603 | def pp(x): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 604 | med, conf = x.rounded_average_conf() |
| 605 | conf_perc = int(float(conf) / med * 100) |
koder aka kdanilov | c0c97e2 | 2015-07-21 00:08:33 +0300 | [diff] [blame] | 606 | dev_perc = int(float(x.deviation) / med * 100) |
| 607 | return (round_3_digit(med), conf_perc, dev_perc) |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 608 | |
| 609 | hdi.direct_iops_r_max = pp(di.direct_iops_r_max) |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 610 | |
| 611 | if di.direct_iops_w_max is not None: |
| 612 | hdi.direct_iops_w_max = pp(di.direct_iops_w_max) |
| 613 | else: |
| 614 | hdi.direct_iops_w_max = None |
| 615 | |
| 616 | if di.direct_iops_w64_max is not None: |
| 617 | hdi.direct_iops_w64_max = pp(di.direct_iops_w64_max) |
| 618 | else: |
| 619 | hdi.direct_iops_w64_max = None |
| 620 | |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 621 | hdi.bw_write_max = pp(di.bw_write_max) |
| 622 | hdi.bw_read_max = pp(di.bw_read_max) |
| 623 | |
koder aka kdanilov | c368eb6 | 2015-04-28 18:22:01 +0300 | [diff] [blame] | 624 | hdi.rws4k_10ms = di.rws4k_10ms if 0 != di.rws4k_10ms else None |
| 625 | hdi.rws4k_30ms = di.rws4k_30ms if 0 != di.rws4k_30ms else None |
| 626 | hdi.rws4k_100ms = di.rws4k_100ms if 0 != di.rws4k_100ms else None |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 627 | return hdi |
| 628 | |
| 629 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 630 | @report('hdd', 'hdd') |
| 631 | def make_hdd_report(processed_results, lab_info, comment): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 632 | plots = [ |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 633 | ('hdd_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'), |
| 634 | ('hdd_rwx4k', 'rand_write_4k', 'Random write 4k sync IOPS') |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 635 | ] |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 636 | perf_infos = [res.disk_perf_info() for res in processed_results] |
| 637 | images = make_plots(perf_infos, plots) |
| 638 | di = get_disk_info(perf_infos) |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 639 | return render_all_html(comment, di, lab_info, images, "report_hdd.html") |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 640 | |
| 641 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 642 | @report('cinder_iscsi', 'cinder_iscsi') |
| 643 | def make_cinder_iscsi_report(processed_results, lab_info, comment): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 644 | plots = [ |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 645 | ('cinder_iscsi_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'), |
| 646 | ('cinder_iscsi_rwx4k', 'rand_write_4k', 'Random write 4k sync IOPS') |
| 647 | ] |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 648 | perf_infos = [res.disk_perf_info() for res in processed_results] |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 649 | try: |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 650 | images = make_plots(perf_infos, plots) |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 651 | except ValueError: |
| 652 | plots = [ |
| 653 | ('cinder_iscsi_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'), |
| 654 | ('cinder_iscsi_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS') |
| 655 | ] |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 656 | images = make_plots(perf_infos, plots) |
| 657 | di = get_disk_info(perf_infos) |
koder aka kdanilov | 170936a | 2015-06-27 22:51:17 +0300 | [diff] [blame] | 658 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 659 | return render_all_html(comment, di, lab_info, images, "report_cinder_iscsi.html") |
| 660 | |
| 661 | |
| 662 | @report('ceph', 'ceph') |
| 663 | def make_ceph_report(processed_results, lab_info, comment): |
| 664 | plots = [ |
| 665 | ('ceph_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'), |
| 666 | ('ceph_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS'), |
| 667 | ('ceph_rrd16m', 'rand_read_16m', 'Random read 16m direct MiBps'), |
| 668 | ('ceph_rwd16m', 'rand_write_16m', |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 669 | 'Random write 16m direct MiBps'), |
| 670 | ] |
| 671 | |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 672 | perf_infos = [res.disk_perf_info() for res in processed_results] |
| 673 | images = make_plots(perf_infos, plots) |
| 674 | di = get_disk_info(perf_infos) |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 675 | return render_all_html(comment, di, lab_info, images, "report_ceph.html") |
koder aka kdanilov | a4a570f | 2015-04-23 22:11:40 +0300 | [diff] [blame] | 676 | |
| 677 | |
koder aka kdanilov | 7248c7b | 2015-05-31 22:53:03 +0300 | [diff] [blame] | 678 | @report('mixed', 'mixed') |
| 679 | def make_mixed_report(processed_results, lab_info, comment): |
| 680 | # |
| 681 | # IOPS(X% read) = 100 / ( X / IOPS_W + (100 - X) / IOPS_R ) |
| 682 | # |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 683 | |
| 684 | perf_infos = [res.disk_perf_info() for res in processed_results] |
koder aka kdanilov | 7248c7b | 2015-05-31 22:53:03 +0300 | [diff] [blame] | 685 | mixed = collections.defaultdict(lambda: []) |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 686 | |
| 687 | is_ssd = False |
| 688 | for res in perf_infos: |
koder aka kdanilov | 7248c7b | 2015-05-31 22:53:03 +0300 | [diff] [blame] | 689 | if res.name.startswith('mixed'): |
| 690 | if res.name.startswith('mixed-ssd'): |
| 691 | is_ssd = True |
| 692 | mixed[res.concurence].append((res.p.rwmixread, |
koder aka kdanilov | f236b9c | 2015-06-24 18:17:22 +0300 | [diff] [blame] | 693 | res.lat, |
| 694 | 0, |
| 695 | # res.lat.average / 1000.0, |
| 696 | # res.lat.deviation / 1000.0, |
koder aka kdanilov | 7248c7b | 2015-05-31 22:53:03 +0300 | [diff] [blame] | 697 | res.iops.average, |
| 698 | res.iops.deviation)) |
| 699 | |
| 700 | if len(mixed) == 0: |
| 701 | raise ValueError("No mixed load found") |
| 702 | |
| 703 | fig, p1 = plt.subplots() |
| 704 | p2 = p1.twinx() |
| 705 | |
| 706 | colors = ['red', 'green', 'blue', 'orange', 'magenta', "teal"] |
| 707 | colors_it = iter(colors) |
| 708 | for conc, mix_lat_iops in sorted(mixed.items()): |
| 709 | mix_lat_iops = sorted(mix_lat_iops) |
| 710 | read_perc, lat, dev, iops, iops_dev = zip(*mix_lat_iops) |
| 711 | p1.errorbar(read_perc, iops, color=next(colors_it), |
| 712 | yerr=iops_dev, label=str(conc) + " th") |
| 713 | |
| 714 | p2.errorbar(read_perc, lat, color=next(colors_it), |
| 715 | ls='--', yerr=dev, label=str(conc) + " th lat") |
| 716 | |
| 717 | if is_ssd: |
| 718 | p1.set_yscale('log') |
| 719 | p2.set_yscale('log') |
| 720 | |
| 721 | p1.set_xlim(-5, 105) |
| 722 | |
| 723 | read_perc = set(read_perc) |
| 724 | read_perc.add(0) |
| 725 | read_perc.add(100) |
| 726 | read_perc = sorted(read_perc) |
| 727 | |
| 728 | plt.xticks(read_perc, map(str, read_perc)) |
| 729 | |
| 730 | p1.grid(True) |
| 731 | p1.set_xlabel("% of reads") |
| 732 | p1.set_ylabel("Mixed IOPS") |
| 733 | p2.set_ylabel("Latency, ms") |
| 734 | |
| 735 | handles1, labels1 = p1.get_legend_handles_labels() |
| 736 | handles2, labels2 = p2.get_legend_handles_labels() |
| 737 | plt.subplots_adjust(top=0.85) |
| 738 | plt.legend(handles1 + handles2, labels1 + labels2, |
| 739 | bbox_to_anchor=(0.5, 1.15), |
| 740 | loc='upper center', |
| 741 | prop={'size': 12}, ncol=3) |
| 742 | plt.show() |
| 743 | |
| 744 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 745 | def make_load_report(idx, results_dir, fname): |
| 746 | dpath = os.path.join(results_dir, "io_" + str(idx)) |
| 747 | files = sorted(os.listdir(dpath)) |
| 748 | gf = lambda x: "_".join(x.rsplit(".", 1)[0].split('_')[:3]) |
| 749 | |
| 750 | for key, group in itertools.groupby(files, gf): |
| 751 | fname = os.path.join(dpath, key + ".fio") |
| 752 | |
| 753 | cfgs = list(parse_all_in_1(open(fname).read(), fname)) |
| 754 | |
| 755 | fname = os.path.join(dpath, key + "_lat.log") |
| 756 | |
| 757 | curr = [] |
| 758 | arrays = [] |
| 759 | |
| 760 | with open(fname) as fd: |
| 761 | for offset, lat, _, _ in csv.reader(fd): |
| 762 | offset = int(offset) |
| 763 | lat = int(lat) |
| 764 | if len(curr) > 0 and curr[-1][0] > offset: |
| 765 | arrays.append(curr) |
| 766 | curr = [] |
| 767 | curr.append((offset, lat)) |
| 768 | arrays.append(curr) |
| 769 | conc = int(cfgs[0].vals.get('numjobs', 1)) |
| 770 | |
| 771 | if conc != 5: |
| 772 | continue |
| 773 | |
| 774 | assert len(arrays) == len(cfgs) * conc |
| 775 | |
| 776 | garrays = [[(0, 0)] for _ in range(conc)] |
| 777 | |
| 778 | for offset in range(len(cfgs)): |
| 779 | for acc, new_arr in zip(garrays, arrays[offset * conc:(offset + 1) * conc]): |
| 780 | last = acc[-1][0] |
| 781 | for off, lat in new_arr: |
| 782 | acc.append((off / 1000. + last, lat / 1000.)) |
| 783 | |
| 784 | for cfg, arr in zip(cfgs, garrays): |
| 785 | plt.plot(*zip(*arr[1:])) |
| 786 | plt.show() |
| 787 | exit(1) |
| 788 | |
| 789 | |
| 790 | def make_io_report(dinfo, comment, path, lab_info=None): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 791 | lab_info = { |
| 792 | "total_disk": "None", |
| 793 | "total_memory": "None", |
| 794 | "nodes_count": "None", |
| 795 | "processor_count": "None" |
| 796 | } |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 797 | |
koder aka kdanilov | a047e1b | 2015-04-21 23:16:59 +0300 | [diff] [blame] | 798 | try: |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 799 | res_fields = sorted(v.name for v in dinfo) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 800 | |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 801 | found = False |
koder aka kdanilov | 57ce4db | 2015-04-25 21:25:51 +0300 | [diff] [blame] | 802 | for fields, name, func in report_funcs: |
koder aka kdanilov | afd9874 | 2015-04-24 01:27:22 +0300 | [diff] [blame] | 803 | for field in fields: |
koder aka kdanilov | 57ce4db | 2015-04-25 21:25:51 +0300 | [diff] [blame] | 804 | pos = bisect.bisect_left(res_fields, field) |
| 805 | |
| 806 | if pos == len(res_fields): |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 807 | break |
koder aka kdanilov | 57ce4db | 2015-04-25 21:25:51 +0300 | [diff] [blame] | 808 | |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 809 | if not res_fields[pos].startswith(field): |
koder aka kdanilov | afd9874 | 2015-04-24 01:27:22 +0300 | [diff] [blame] | 810 | break |
| 811 | else: |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 812 | found = True |
koder aka kdanilov | 57ce4db | 2015-04-25 21:25:51 +0300 | [diff] [blame] | 813 | hpath = path.format(name) |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 814 | |
| 815 | try: |
| 816 | report = func(dinfo, lab_info, comment) |
| 817 | except: |
| 818 | logger.exception("Diring {0} report generation".format(name)) |
| 819 | continue |
| 820 | |
koder aka kdanilov | 7248c7b | 2015-05-31 22:53:03 +0300 | [diff] [blame] | 821 | if report is not None: |
| 822 | try: |
| 823 | with open(hpath, "w") as fd: |
| 824 | fd.write(report) |
| 825 | except: |
| 826 | logger.exception("Diring saving {0} report".format(name)) |
| 827 | continue |
| 828 | logger.info("Report {0} saved into {1}".format(name, hpath)) |
| 829 | else: |
| 830 | logger.warning("No report produced by {0!r}".format(name)) |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 831 | |
| 832 | if not found: |
koder aka kdanilov | a4a570f | 2015-04-23 22:11:40 +0300 | [diff] [blame] | 833 | logger.warning("No report generator found for this load") |
koder aka kdanilov | afd9874 | 2015-04-24 01:27:22 +0300 | [diff] [blame] | 834 | |
koder aka kdanilov | a047e1b | 2015-04-21 23:16:59 +0300 | [diff] [blame] | 835 | except Exception as exc: |
koder aka kdanilov | 57ce4db | 2015-04-25 21:25:51 +0300 | [diff] [blame] | 836 | import traceback |
| 837 | traceback.print_exc() |
koder aka kdanilov | ec1b973 | 2015-04-23 20:43:29 +0300 | [diff] [blame] | 838 | logger.error("Failed to generate html report:" + str(exc)) |