koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 1 | import os |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 2 | import bisect |
koder aka kdanilov | a047e1b | 2015-04-21 23:16:59 +0300 | [diff] [blame] | 3 | import logging |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 4 | import collections |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 5 | from cStringIO import StringIO |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 6 | |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 7 | try: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 8 | import numpy |
| 9 | import scipy |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 10 | import matplotlib.pyplot as plt |
| 11 | except ImportError: |
| 12 | plt = None |
| 13 | |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 14 | import wally |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 15 | from wally.utils import ssize2b |
| 16 | from wally.statistic import round_3_digit, data_property |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 17 | from wally.suits.io.fio_task_parser import get_test_sync_mode |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 18 | |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 19 | |
koder aka kdanilov | a047e1b | 2015-04-21 23:16:59 +0300 | [diff] [blame] | 20 | logger = logging.getLogger("wally.report") |
| 21 | |
| 22 | |
koder aka kdanilov | 209e85d | 2015-04-27 23:11:05 +0300 | [diff] [blame] | 23 | class DiskInfo(object): |
| 24 | def __init__(self): |
| 25 | self.direct_iops_r_max = 0 |
| 26 | self.direct_iops_w_max = 0 |
| 27 | self.rws4k_10ms = 0 |
| 28 | self.rws4k_30ms = 0 |
| 29 | self.rws4k_100ms = 0 |
| 30 | self.bw_write_max = 0 |
| 31 | self.bw_read_max = 0 |
| 32 | |
| 33 | |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 34 | report_funcs = [] |
| 35 | |
| 36 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 37 | class Attrmapper(object): |
| 38 | def __init__(self, dct): |
| 39 | self.__dct = dct |
| 40 | |
| 41 | def __getattr__(self, name): |
| 42 | try: |
| 43 | return self.__dct[name] |
| 44 | except KeyError: |
| 45 | raise AttributeError(name) |
| 46 | |
| 47 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 48 | class PerfInfo(object): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 49 | def __init__(self, name, summary, intervals, params, testnodes_count): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 50 | self.name = name |
| 51 | self.bw = None |
| 52 | self.iops = None |
| 53 | self.lat = None |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 54 | |
| 55 | self.raw_bw = [] |
| 56 | self.raw_iops = [] |
| 57 | self.raw_lat = [] |
| 58 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 59 | self.params = params |
| 60 | self.intervals = intervals |
| 61 | self.testnodes_count = testnodes_count |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 62 | self.summary = summary |
| 63 | self.p = Attrmapper(self.params.vals) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 64 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 65 | self.sync_mode = get_test_sync_mode(self.params) |
| 66 | self.concurence = self.params.vals.get('numjobs', 1) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 67 | |
| 68 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 69 | def group_by_name(test_data): |
| 70 | name_map = collections.defaultdict(lambda: []) |
| 71 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 72 | for data in test_data: |
| 73 | name_map[(data.config.name, data.summary())].append(data) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 74 | |
| 75 | return name_map |
| 76 | |
| 77 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 78 | def process_disk_info(test_data): |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 79 | name_map = group_by_name(test_data) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 80 | data = {} |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 81 | for (name, summary), results in name_map.items(): |
| 82 | testnodes_count_set = set(dt.vm_count for dt in results) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 83 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 84 | assert len(testnodes_count_set) == 1 |
| 85 | testnodes_count, = testnodes_count_set |
| 86 | assert len(results) % testnodes_count == 0 |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 87 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 88 | intervals = [result.run_interval for result in results] |
| 89 | p = results[0].config |
| 90 | pinfo = PerfInfo(p.name, result.summary(), intervals, |
| 91 | p, testnodes_count) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 92 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 93 | pinfo.raw_bw = [result.results['bw'] for result in results] |
| 94 | pinfo.raw_iops = [result.results['iops'] for result in results] |
| 95 | pinfo.raw_lat = [result.results['lat'] for result in results] |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 96 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 97 | pinfo.bw = data_property(map(sum, zip(*pinfo.raw_bw))) |
| 98 | pinfo.iops = data_property(map(sum, zip(*pinfo.raw_iops))) |
| 99 | pinfo.lat = data_property(sum(pinfo.raw_lat, [])) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 100 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 101 | data[(p.name, summary)] = pinfo |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 102 | return data |
| 103 | |
| 104 | |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 105 | def report(name, required_fields): |
| 106 | def closure(func): |
| 107 | report_funcs.append((required_fields.split(","), name, func)) |
| 108 | return func |
| 109 | return closure |
| 110 | |
| 111 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 112 | def get_test_lcheck_params(pinfo): |
| 113 | res = [{ |
| 114 | 's': 'sync', |
| 115 | 'd': 'direct', |
| 116 | 'a': 'async', |
| 117 | 'x': 'sync direct' |
| 118 | }[pinfo.sync_mode]] |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 119 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 120 | res.append(pinfo.p.rw) |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 121 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 122 | return " ".join(res) |
koder aka kdanilov | 63e9c5a | 2015-04-28 23:06:07 +0300 | [diff] [blame] | 123 | |
| 124 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 125 | def get_emb_data_svg(plt): |
| 126 | sio = StringIO() |
| 127 | plt.savefig(sio, format='svg') |
| 128 | img_start = "<!-- Created with matplotlib (http://matplotlib.org/) -->" |
| 129 | return sio.getvalue().split(img_start, 1)[1] |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 130 | |
| 131 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 132 | def get_template(templ_name): |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 133 | very_root_dir = os.path.dirname(os.path.dirname(wally.__file__)) |
| 134 | templ_dir = os.path.join(very_root_dir, 'report_templates') |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 135 | templ_file = os.path.join(templ_dir, templ_name) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 136 | return open(templ_file, 'r').read() |
koder aka kdanilov | 209e85d | 2015-04-27 23:11:05 +0300 | [diff] [blame] | 137 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 138 | |
| 139 | @report('linearity', 'linearity_test') |
| 140 | def linearity_report(processed_results, path, lab_info): |
| 141 | labels_and_data = [] |
| 142 | |
| 143 | vls = processed_results.values()[0].params.vals.copy() |
| 144 | del vls['blocksize'] |
| 145 | |
| 146 | for res in processed_results.values(): |
| 147 | if res.name.startswith('linearity_test'): |
| 148 | iotimes = [1000. / val for val in res.iops.raw] |
| 149 | labels_and_data.append([res.p.blocksize, res.iops.raw, iotimes]) |
| 150 | cvls = res.params.vals.copy() |
| 151 | del cvls['blocksize'] |
| 152 | assert cvls == vls |
| 153 | |
| 154 | labels_and_data.sort(key=lambda x: ssize2b(x[0])) |
| 155 | _, ax1 = plt.subplots() |
| 156 | |
| 157 | labels, data, iotimes = zip(*labels_and_data) |
| 158 | plt.boxplot(iotimes) |
| 159 | |
| 160 | if len(labels_and_data) > 2 and ssize2b(labels_and_data[-2][0]) >= 4096: |
| 161 | xt = range(1, len(labels) + 1) |
| 162 | |
| 163 | def io_time(sz, bw, initial_lat): |
| 164 | return sz / bw + initial_lat |
| 165 | |
| 166 | x = numpy.array(map(ssize2b, labels)) |
| 167 | y = numpy.array([sum(dt) / len(dt) for dt in iotimes]) |
| 168 | popt, _ = scipy.optimize.curve_fit(io_time, x, y, p0=(100., 1.)) |
| 169 | |
| 170 | y1 = io_time(x, *popt) |
| 171 | plt.plot(xt, y1, linestyle='--', label='LS linear approxomation') |
| 172 | |
| 173 | for idx, (sz, _, _) in enumerate(labels_and_data): |
| 174 | if ssize2b(sz) >= 4096: |
| 175 | break |
| 176 | |
| 177 | bw = (x[-1] - x[idx]) / (y[-1] - y[idx]) |
| 178 | lat = y[-1] - x[-1] / bw |
| 179 | y2 = io_time(x, bw, lat) |
| 180 | |
| 181 | plt.plot(xt, y2, linestyle='--', |
| 182 | label='(4k & max) linear approxomation') |
| 183 | |
| 184 | plt.setp(ax1, xticklabels=labels) |
| 185 | |
| 186 | plt.xlabel("Block size") |
| 187 | plt.ylabel("IO time, ms") |
| 188 | |
| 189 | plt.legend(loc=0) |
| 190 | plt.grid() |
| 191 | iotime_plot = get_emb_data_svg(plt) |
| 192 | |
| 193 | _, ax1 = plt.subplots() |
| 194 | plt.boxplot(data) |
| 195 | plt.setp(ax1, xticklabels=labels) |
| 196 | |
| 197 | plt.xlabel("Block size") |
| 198 | plt.ylabel("IOPS") |
| 199 | plt.grid() |
| 200 | |
| 201 | iops_plot = get_emb_data_svg(plt) |
| 202 | |
| 203 | res1 = processed_results.values()[0] |
| 204 | descr = { |
| 205 | 'vm_count': res1.testnodes_count, |
| 206 | 'concurence': res1.concurence, |
| 207 | 'oper_descr': get_test_lcheck_params(res1).capitalize() |
| 208 | } |
| 209 | |
| 210 | params_map = {'iotime_vs_size': iotime_plot, |
| 211 | 'iops_vs_size': iops_plot, |
| 212 | 'descr': descr} |
| 213 | |
| 214 | with open(path, 'w') as fd: |
| 215 | fd.write(get_template('report_linearity.html').format(**params_map)) |
| 216 | |
| 217 | |
| 218 | @report('lat_vs_iops', 'lat_vs_iops') |
| 219 | def lat_vs_iops(processed_results, path, lab_info): |
| 220 | lat_iops = collections.defaultdict(lambda: []) |
| 221 | for res in processed_results.values(): |
| 222 | if res.name.startswith('lat_vs_iops'): |
| 223 | lat_iops[res.concurence].append((res.lat.average / 1000.0, |
| 224 | res.lat.deviation / 1000.0, |
| 225 | res.iops.average, |
| 226 | res.iops.deviation)) |
| 227 | |
| 228 | colors = ['red', 'green', 'blue', 'orange', 'magenta', "teal"][::-1] |
| 229 | for conc, lat_iops in sorted(lat_iops.items()): |
| 230 | lat, dev, iops, iops_dev = zip(*lat_iops) |
| 231 | plt.errorbar(iops, lat, xerr=iops_dev, yerr=dev, fmt='ro', |
| 232 | label=str(conc) + " threads", |
| 233 | color=colors.pop()) |
| 234 | |
| 235 | plt.xlabel("IOPS") |
| 236 | plt.ylabel("Latency, ms") |
| 237 | plt.grid() |
| 238 | plt.legend(loc=0) |
| 239 | plt.show() |
| 240 | exit(1) |
| 241 | |
| 242 | |
| 243 | def render_all_html(dest, info, lab_description, images, templ_name): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 244 | data = info.__dict__.copy() |
| 245 | for name, val in data.items(): |
koder aka kdanilov | 209e85d | 2015-04-27 23:11:05 +0300 | [diff] [blame] | 246 | if not name.startswith('__'): |
koder aka kdanilov | c368eb6 | 2015-04-28 18:22:01 +0300 | [diff] [blame] | 247 | if val is None: |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 248 | data[name] = '-' |
| 249 | elif isinstance(val, (int, float, long)): |
| 250 | data[name] = round_3_digit(val) |
koder aka kdanilov | 209e85d | 2015-04-27 23:11:05 +0300 | [diff] [blame] | 251 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 252 | data['bw_read_max'] = (data['bw_read_max'][0] // 1024, |
| 253 | data['bw_read_max'][1]) |
| 254 | data['bw_write_max'] = (data['bw_write_max'][0] // 1024, |
| 255 | data['bw_write_max'][1]) |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 256 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 257 | images.update(data) |
| 258 | report = get_template(templ_name).format(lab_info=lab_description, |
| 259 | **images) |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 260 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 261 | with open(dest, 'w') as fd: |
| 262 | fd.write(report) |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 263 | |
| 264 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 265 | def io_chart(title, concurence, |
| 266 | latv, latv_min, latv_max, |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 267 | iops_or_bw, iops_or_bw_err, |
| 268 | legend, log=False, |
| 269 | boxplots=False): |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 270 | points = " MiBps" if legend == 'BW' else "" |
| 271 | lc = len(concurence) |
| 272 | width = 0.35 |
| 273 | xt = range(1, lc + 1) |
| 274 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 275 | 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] | 276 | fig, p1 = plt.subplots() |
| 277 | xpos = [i - width / 2 for i in xt] |
| 278 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 279 | p1.bar(xpos, iops_or_bw, |
| 280 | width=width, |
| 281 | yerr=iops_or_bw_err, |
| 282 | ecolor='m', |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 283 | color='y', |
| 284 | label=legend) |
| 285 | |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 286 | p1.grid(True) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 287 | p1.plot(xt, op_per_vm, '--', label=legend + "/thread", color='black') |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 288 | handles1, labels1 = p1.get_legend_handles_labels() |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 289 | |
| 290 | p2 = p1.twinx() |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 291 | p2.plot(xt, latv_max, label="lat max") |
| 292 | p2.plot(xt, latv, label="lat avg") |
| 293 | p2.plot(xt, latv_min, label="lat min") |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 294 | |
| 295 | plt.xlim(0.5, lc + 0.5) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 296 | plt.xticks(xt, ["{0} * {1}".format(vm, th) for (vm, th) in concurence]) |
| 297 | p1.set_xlabel("VM Count * Thread per VM") |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 298 | p1.set_ylabel(legend + points) |
| 299 | p2.set_ylabel("Latency ms") |
| 300 | plt.title(title) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 301 | handles2, labels2 = p2.get_legend_handles_labels() |
| 302 | |
| 303 | plt.legend(handles1 + handles2, labels1 + labels2, |
| 304 | loc='center left', bbox_to_anchor=(1.1, 0.81)) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 305 | |
| 306 | if log: |
| 307 | p1.set_yscale('log') |
| 308 | p2.set_yscale('log') |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 309 | plt.subplots_adjust(right=0.68) |
| 310 | |
| 311 | return get_emb_data_svg(plt) |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 312 | |
| 313 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 314 | def make_plots(processed_results, plots): |
| 315 | files = {} |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 316 | for name_pref, fname, desc in plots: |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 317 | chart_data = [] |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 318 | |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 319 | for res in processed_results.values(): |
| 320 | if res.name.startswith(name_pref): |
| 321 | chart_data.append(res) |
| 322 | |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 323 | if len(chart_data) == 0: |
| 324 | raise ValueError("Can't found any date for " + name_pref) |
| 325 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 326 | use_bw = ssize2b(chart_data[0].p.blocksize) > 16 * 1024 |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 327 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 328 | chart_data.sort(key=lambda x: x.concurence) |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 329 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 330 | # if x.lat.average < max_lat] |
| 331 | lat = [x.lat.average / 1000 for x in chart_data] |
koder aka kdanilov | d5ed4da | 2015-05-07 23:33:23 +0300 | [diff] [blame] | 332 | lat_min = [x.lat.min / 1000 for x in chart_data] |
| 333 | lat_max = [x.lat.max / 1000 for x in chart_data] |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 334 | |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 335 | testnodes_count = x.testnodes_count |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 336 | concurence = [(testnodes_count, x.concurence) |
koder aka kdanilov | 416b87a | 2015-05-12 00:26:04 +0300 | [diff] [blame] | 337 | for x in chart_data] |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 338 | |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 339 | if use_bw: |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 340 | data = [x.bw.average / 1000 for x in chart_data] |
| 341 | data_dev = [x.bw.confidence / 1000 for x in chart_data] |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 342 | name = "BW" |
| 343 | else: |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 344 | data = [x.iops.average for x in chart_data] |
| 345 | data_dev = [x.iops.confidence for x in chart_data] |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 346 | name = "IOPS" |
| 347 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 348 | fc = io_chart(title=desc, |
| 349 | concurence=concurence, |
| 350 | latv=lat, latv_min=lat_min, latv_max=lat_max, |
| 351 | iops_or_bw=data, |
| 352 | iops_or_bw_err=data_dev, |
| 353 | legend=name) |
| 354 | files[fname] = fc |
| 355 | |
| 356 | return files |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 357 | |
| 358 | |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 359 | 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] | 360 | result = None |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 361 | attr = 'iops' if iops else 'bw' |
| 362 | for measurement in processed_results.values(): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 363 | ok = measurement.sync_mode == sync_mode |
| 364 | ok = ok and (measurement.p.blocksize == blocksize) |
| 365 | ok = ok and (measurement.p.rw == rw) |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 366 | |
| 367 | if ok: |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 368 | field = getattr(measurement, attr) |
| 369 | |
| 370 | if result is None: |
| 371 | result = field |
| 372 | elif field.average > result.average: |
| 373 | result = field |
| 374 | |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 375 | return result |
| 376 | |
| 377 | |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 378 | def get_disk_info(processed_results): |
| 379 | di = DiskInfo() |
| 380 | rws4k_iops_lat_th = [] |
| 381 | |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 382 | di.direct_iops_w_max = find_max_where(processed_results, |
| 383 | 'd', '4k', 'randwrite') |
| 384 | di.direct_iops_r_max = find_max_where(processed_results, |
| 385 | 'd', '4k', 'randread') |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 386 | |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 387 | di.bw_write_max = find_max_where(processed_results, |
| 388 | 'd', '16m', 'randwrite', False) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 389 | if di.bw_write_max is None: |
| 390 | di.bw_write_max = find_max_where(processed_results, |
| 391 | 'd', '1m', 'write', False) |
| 392 | |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 393 | di.bw_read_max = find_max_where(processed_results, |
| 394 | 'd', '16m', 'randread', False) |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 395 | if di.bw_read_max is None: |
| 396 | di.bw_read_max = find_max_where(processed_results, |
| 397 | 'd', '1m', 'read', False) |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 398 | |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 399 | for res in processed_results.values(): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 400 | if res.sync_mode == 's' and res.p.blocksize == '4k': |
| 401 | if res.p.rw != 'randwrite': |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 402 | continue |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 403 | rws4k_iops_lat_th.append((res.iops.average, |
| 404 | res.lat.average, |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 405 | res.concurence)) |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 406 | |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 407 | rws4k_iops_lat_th.sort(key=lambda (_1, _2, conc): conc) |
| 408 | |
| 409 | latv = [lat for _, lat, _ in rws4k_iops_lat_th] |
| 410 | |
| 411 | for tlatv_ms in [10, 30, 100]: |
| 412 | tlat = tlatv_ms * 1000 |
| 413 | pos = bisect.bisect_left(latv, tlat) |
| 414 | if 0 == pos: |
| 415 | iops3 = 0 |
| 416 | elif pos == len(latv): |
| 417 | iops3 = latv[-1] |
| 418 | else: |
| 419 | lat1 = latv[pos - 1] |
| 420 | lat2 = latv[pos] |
| 421 | |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 422 | iops1, _, th1 = rws4k_iops_lat_th[pos - 1] |
| 423 | iops2, _, th2 = rws4k_iops_lat_th[pos] |
koder aka kdanilov | 4a510ee | 2015-04-21 18:50:42 +0300 | [diff] [blame] | 424 | |
| 425 | th_lat_coef = (th2 - th1) / (lat2 - lat1) |
| 426 | th3 = th_lat_coef * (tlat - lat1) + th1 |
| 427 | |
| 428 | th_iops_coef = (iops2 - iops1) / (th2 - th1) |
| 429 | iops3 = th_iops_coef * (th3 - th1) + iops1 |
| 430 | setattr(di, 'rws4k_{}ms'.format(tlatv_ms), int(iops3)) |
| 431 | |
| 432 | hdi = DiskInfo() |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 433 | |
| 434 | def pp(x): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 435 | med, conf = x.rounded_average_conf() |
| 436 | conf_perc = int(float(conf) / med * 100) |
| 437 | return (med, conf_perc) |
koder aka kdanilov | 7e0f7cf | 2015-05-01 17:24:35 +0300 | [diff] [blame] | 438 | |
| 439 | hdi.direct_iops_r_max = pp(di.direct_iops_r_max) |
| 440 | hdi.direct_iops_w_max = pp(di.direct_iops_w_max) |
| 441 | hdi.bw_write_max = pp(di.bw_write_max) |
| 442 | hdi.bw_read_max = pp(di.bw_read_max) |
| 443 | |
koder aka kdanilov | c368eb6 | 2015-04-28 18:22:01 +0300 | [diff] [blame] | 444 | hdi.rws4k_10ms = di.rws4k_10ms if 0 != di.rws4k_10ms else None |
| 445 | hdi.rws4k_30ms = di.rws4k_30ms if 0 != di.rws4k_30ms else None |
| 446 | 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] | 447 | return hdi |
| 448 | |
| 449 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 450 | @report('HDD', 'hdd_test') |
| 451 | def make_hdd_report(processed_results, path, lab_info): |
| 452 | plots = [ |
| 453 | ('hdd_test_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'), |
| 454 | ('hdd_test_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS') |
| 455 | ] |
| 456 | images = make_plots(processed_results, plots) |
koder aka kdanilov | a4a570f | 2015-04-23 22:11:40 +0300 | [diff] [blame] | 457 | di = get_disk_info(processed_results) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 458 | render_all_html(path, di, lab_info, images, "report_hdd.html") |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 459 | |
| 460 | |
| 461 | @report('Ceph', 'ceph_test') |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 462 | def make_ceph_report(processed_results, path, lab_info): |
| 463 | plots = [ |
| 464 | ('ceph_test_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'), |
| 465 | ('ceph_test_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS'), |
| 466 | ('ceph_test_rrd16m', 'rand_read_16m', 'Random read 16m direct MiBps'), |
| 467 | ('ceph_test_rwd16m', 'rand_write_16m', |
| 468 | 'Random write 16m direct MiBps'), |
| 469 | ] |
| 470 | |
| 471 | images = make_plots(processed_results, plots) |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 472 | di = get_disk_info(processed_results) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 473 | render_all_html(path, di, lab_info, images, "report_ceph.html") |
koder aka kdanilov | a4a570f | 2015-04-23 22:11:40 +0300 | [diff] [blame] | 474 | |
| 475 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 476 | def make_io_report(dinfo, results, path, lab_info=None): |
koder aka kdanilov | f86d7af | 2015-05-06 04:01:54 +0300 | [diff] [blame] | 477 | lab_info = { |
| 478 | "total_disk": "None", |
| 479 | "total_memory": "None", |
| 480 | "nodes_count": "None", |
| 481 | "processor_count": "None" |
| 482 | } |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 483 | |
koder aka kdanilov | a047e1b | 2015-04-21 23:16:59 +0300 | [diff] [blame] | 484 | try: |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 485 | res_fields = sorted(v.name for v in dinfo.values()) |
| 486 | |
koder aka kdanilov | 57ce4db | 2015-04-25 21:25:51 +0300 | [diff] [blame] | 487 | for fields, name, func in report_funcs: |
koder aka kdanilov | afd9874 | 2015-04-24 01:27:22 +0300 | [diff] [blame] | 488 | for field in fields: |
koder aka kdanilov | 57ce4db | 2015-04-25 21:25:51 +0300 | [diff] [blame] | 489 | pos = bisect.bisect_left(res_fields, field) |
| 490 | |
| 491 | if pos == len(res_fields): |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 492 | break |
koder aka kdanilov | 57ce4db | 2015-04-25 21:25:51 +0300 | [diff] [blame] | 493 | |
koder aka kdanilov | be8f89f | 2015-04-28 14:51:51 +0300 | [diff] [blame] | 494 | if not res_fields[pos].startswith(field): |
koder aka kdanilov | afd9874 | 2015-04-24 01:27:22 +0300 | [diff] [blame] | 495 | break |
| 496 | else: |
koder aka kdanilov | 57ce4db | 2015-04-25 21:25:51 +0300 | [diff] [blame] | 497 | hpath = path.format(name) |
koder aka kdanilov | 63ad206 | 2015-04-27 13:11:40 +0300 | [diff] [blame] | 498 | logger.debug("Generatins report " + name + " into " + hpath) |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame^] | 499 | func(dinfo, hpath, lab_info) |
koder aka kdanilov | afd9874 | 2015-04-24 01:27:22 +0300 | [diff] [blame] | 500 | break |
koder aka kdanilov | a4a570f | 2015-04-23 22:11:40 +0300 | [diff] [blame] | 501 | else: |
| 502 | logger.warning("No report generator found for this load") |
koder aka kdanilov | afd9874 | 2015-04-24 01:27:22 +0300 | [diff] [blame] | 503 | |
koder aka kdanilov | a047e1b | 2015-04-21 23:16:59 +0300 | [diff] [blame] | 504 | except Exception as exc: |
koder aka kdanilov | 57ce4db | 2015-04-25 21:25:51 +0300 | [diff] [blame] | 505 | import traceback |
| 506 | traceback.print_exc() |
koder aka kdanilov | ec1b973 | 2015-04-23 20:43:29 +0300 | [diff] [blame] | 507 | logger.error("Failed to generate html report:" + str(exc)) |