blob: 8b83d5de203ea7267e3d66ddf99f20977196f394 [file] [log] [blame]
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03001import os
koder aka kdanilov88407ff2015-05-26 15:35:57 +03002import csv
koder aka kdanilov4a510ee2015-04-21 18:50:42 +03003import bisect
koder aka kdanilova047e1b2015-04-21 23:16:59 +03004import logging
koder aka kdanilov88407ff2015-05-26 15:35:57 +03005import itertools
koder aka kdanilov416b87a2015-05-12 00:26:04 +03006import collections
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +03007from cStringIO import StringIO
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03008
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +03009try:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030010 import numpy
11 import scipy
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030012 import matplotlib
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +030013 import matplotlib.pyplot as plt
14except ImportError:
15 plt = None
16
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030017import wally
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030018from wally.utils import ssize2b
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +030019from wally.statistic import round_3_digit
koder aka kdanilov88407ff2015-05-26 15:35:57 +030020from wally.suits.io.fio_task_parser import (get_test_sync_mode,
21 get_test_summary,
22 parse_all_in_1,
23 abbv_name_to_full)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030024
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030025
koder aka kdanilova047e1b2015-04-21 23:16:59 +030026logger = logging.getLogger("wally.report")
27
28
koder aka kdanilov209e85d2015-04-27 23:11:05 +030029class DiskInfo(object):
30 def __init__(self):
31 self.direct_iops_r_max = 0
32 self.direct_iops_w_max = 0
koder aka kdanilov88407ff2015-05-26 15:35:57 +030033
34 # 64 used instead of 4k to faster feed caches
35 self.direct_iops_w64_max = 0
36
koder aka kdanilov209e85d2015-04-27 23:11:05 +030037 self.rws4k_10ms = 0
38 self.rws4k_30ms = 0
39 self.rws4k_100ms = 0
40 self.bw_write_max = 0
41 self.bw_read_max = 0
42
43
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +030044report_funcs = []
45
46
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030047class Attrmapper(object):
48 def __init__(self, dct):
49 self.__dct = dct
50
51 def __getattr__(self, name):
52 try:
53 return self.__dct[name]
54 except KeyError:
55 raise AttributeError(name)
56
57
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030058class PerfInfo(object):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030059 def __init__(self, name, summary, intervals, params, testnodes_count):
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030060 self.name = name
61 self.bw = None
62 self.iops = None
63 self.lat = None
koder aka kdanilov88407ff2015-05-26 15:35:57 +030064 self.lat_50 = None
65 self.lat_95 = None
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030066
67 self.raw_bw = []
68 self.raw_iops = []
69 self.raw_lat = []
70
koder aka kdanilov416b87a2015-05-12 00:26:04 +030071 self.params = params
72 self.intervals = intervals
73 self.testnodes_count = testnodes_count
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030074 self.summary = summary
75 self.p = Attrmapper(self.params.vals)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030076
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030077 self.sync_mode = get_test_sync_mode(self.params)
78 self.concurence = self.params.vals.get('numjobs', 1)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030079
80
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +030081# disk_info = None
82# base = None
83# linearity = None
84
85
koder aka kdanilov416b87a2015-05-12 00:26:04 +030086def group_by_name(test_data):
87 name_map = collections.defaultdict(lambda: [])
88
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030089 for data in test_data:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +030090 name_map[(data.name, data.summary())].append(data)
koder aka kdanilov416b87a2015-05-12 00:26:04 +030091
92 return name_map
93
94
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +030095def report(name, required_fields):
96 def closure(func):
97 report_funcs.append((required_fields.split(","), name, func))
98 return func
99 return closure
100
101
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300102def get_test_lcheck_params(pinfo):
103 res = [{
104 's': 'sync',
105 'd': 'direct',
106 'a': 'async',
107 'x': 'sync direct'
108 }[pinfo.sync_mode]]
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300109
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300110 res.append(pinfo.p.rw)
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300111
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300112 return " ".join(res)
koder aka kdanilov63e9c5a2015-04-28 23:06:07 +0300113
114
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300115def get_emb_data_svg(plt):
116 sio = StringIO()
117 plt.savefig(sio, format='svg')
118 img_start = "<!-- Created with matplotlib (http://matplotlib.org/) -->"
119 return sio.getvalue().split(img_start, 1)[1]
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300120
121
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300122def get_template(templ_name):
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300123 very_root_dir = os.path.dirname(os.path.dirname(wally.__file__))
124 templ_dir = os.path.join(very_root_dir, 'report_templates')
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300125 templ_file = os.path.join(templ_dir, templ_name)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300126 return open(templ_file, 'r').read()
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300127
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300128
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300129def group_by(data, func):
130 if len(data) < 2:
131 yield data
132 return
133
134 ndata = [(func(dt), dt) for dt in data]
135 ndata.sort(key=func)
136 pkey, dt = ndata[0]
137 curr_list = [dt]
138
139 for key, val in ndata[1:]:
140 if pkey != key:
141 yield curr_list
142 curr_list = [val]
143 else:
144 curr_list.append(val)
145 pkey = key
146
147 yield curr_list
148
149
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300150@report('linearity', 'linearity_test')
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300151def linearity_report(processed_results, lab_info, comment):
152 labels_and_data_mp = collections.defaultdict(lambda: [])
153 vls = {}
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300154
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300155 # plot io_time = func(bsize)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300156 for res in processed_results.values():
157 if res.name.startswith('linearity_test'):
158 iotimes = [1000. / val for val in res.iops.raw]
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300159
160 op_summ = get_test_summary(res.params)[:3]
161
162 labels_and_data_mp[op_summ].append(
163 [res.p.blocksize, res.iops.raw, iotimes])
164
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300165 cvls = res.params.vals.copy()
166 del cvls['blocksize']
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300167 del cvls['rw']
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300168
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300169 cvls.pop('sync', None)
170 cvls.pop('direct', None)
171 cvls.pop('buffered', None)
172
173 if op_summ not in vls:
174 vls[op_summ] = cvls
175 else:
176 assert cvls == vls[op_summ]
177
178 all_labels = None
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300179 _, ax1 = plt.subplots()
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300180 for name, labels_and_data in labels_and_data_mp.items():
181 labels_and_data.sort(key=lambda x: ssize2b(x[0]))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300182
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300183 labels, _, iotimes = zip(*labels_and_data)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300184
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300185 if all_labels is None:
186 all_labels = labels
187 else:
188 assert all_labels == labels
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300189
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300190 plt.boxplot(iotimes)
191 if len(labels_and_data) > 2 and \
192 ssize2b(labels_and_data[-2][0]) >= 4096:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300193
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300194 xt = range(1, len(labels) + 1)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300195
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300196 def io_time(sz, bw, initial_lat):
197 return sz / bw + initial_lat
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300198
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300199 x = numpy.array(map(ssize2b, labels))
200 y = numpy.array([sum(dt) / len(dt) for dt in iotimes])
201 popt, _ = scipy.optimize.curve_fit(io_time, x, y, p0=(100., 1.))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300202
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300203 y1 = io_time(x, *popt)
204 plt.plot(xt, y1, linestyle='--',
205 label=name + ' LS linear approx')
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300206
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300207 for idx, (sz, _, _) in enumerate(labels_and_data):
208 if ssize2b(sz) >= 4096:
209 break
210
211 bw = (x[-1] - x[idx]) / (y[-1] - y[idx])
212 lat = y[-1] - x[-1] / bw
213 y2 = io_time(x, bw, lat)
214 plt.plot(xt, y2, linestyle='--',
215 label=abbv_name_to_full(name) +
216 ' (4k & max) linear approx')
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300217
218 plt.setp(ax1, xticklabels=labels)
219
220 plt.xlabel("Block size")
221 plt.ylabel("IO time, ms")
222
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300223 plt.subplots_adjust(top=0.85)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300224 plt.legend(bbox_to_anchor=(0.5, 1.15),
225 loc='upper center',
226 prop={'size': 10}, ncol=2)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300227 plt.grid()
228 iotime_plot = get_emb_data_svg(plt)
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300229 plt.clf()
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300230
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300231 # plot IOPS = func(bsize)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300232 _, ax1 = plt.subplots()
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300233
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300234 for name, labels_and_data in labels_and_data_mp.items():
235 labels_and_data.sort(key=lambda x: ssize2b(x[0]))
236 _, data, _ = zip(*labels_and_data)
237 plt.boxplot(data)
238 avg = [float(sum(arr)) / len(arr) for arr in data]
239 xt = range(1, len(data) + 1)
240 plt.plot(xt, avg, linestyle='--',
241 label=abbv_name_to_full(name) + " avg")
242
243 plt.setp(ax1, xticklabels=labels)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300244 plt.xlabel("Block size")
245 plt.ylabel("IOPS")
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300246 plt.legend(bbox_to_anchor=(0.5, 1.15),
247 loc='upper center',
248 prop={'size': 10}, ncol=2)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300249 plt.grid()
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300250 plt.subplots_adjust(top=0.85)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300251
252 iops_plot = get_emb_data_svg(plt)
253
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300254 res = set(get_test_lcheck_params(res) for res in processed_results.values())
255 ncount = list(set(res.testnodes_count for res in processed_results.values()))
256 conc = list(set(res.concurence for res in processed_results.values()))
257
258 assert len(conc) == 1
259 assert len(ncount) == 1
260
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300261 descr = {
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300262 'vm_count': ncount[0],
263 'concurence': conc[0],
264 'oper_descr': ", ".join(res).capitalize()
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300265 }
266
267 params_map = {'iotime_vs_size': iotime_plot,
268 'iops_vs_size': iops_plot,
269 'descr': descr}
270
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300271 return get_template('report_linearity.html').format(**params_map)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300272
273
274@report('lat_vs_iops', 'lat_vs_iops')
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300275def lat_vs_iops(processed_results, lab_info, comment):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300276 lat_iops = collections.defaultdict(lambda: [])
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300277 requsted_vs_real = collections.defaultdict(lambda: {})
278
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300279 for res in processed_results.values():
280 if res.name.startswith('lat_vs_iops'):
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300281 lat_iops[res.concurence].append((res.lat,
282 0,
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300283 res.iops.average,
284 res.iops.deviation))
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300285 # lat_iops[res.concurence].append((res.lat.average / 1000.0,
286 # res.lat.deviation / 1000.0,
287 # res.iops.average,
288 # res.iops.deviation))
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300289 requested_iops = res.p.rate_iops * res.concurence
290 requsted_vs_real[res.concurence][requested_iops] = \
291 (res.iops.average, res.iops.deviation)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300292
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300293 colors = ['red', 'green', 'blue', 'orange', 'magenta', "teal"]
294 colors_it = iter(colors)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300295 for conc, lat_iops in sorted(lat_iops.items()):
296 lat, dev, iops, iops_dev = zip(*lat_iops)
297 plt.errorbar(iops, lat, xerr=iops_dev, yerr=dev, fmt='ro',
298 label=str(conc) + " threads",
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300299 color=next(colors_it))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300300
301 plt.xlabel("IOPS")
302 plt.ylabel("Latency, ms")
303 plt.grid()
304 plt.legend(loc=0)
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300305 plt_iops_vs_lat = get_emb_data_svg(plt)
306 plt.clf()
307
308 colors_it = iter(colors)
309 for conc, req_vs_real in sorted(requsted_vs_real.items()):
310 req, real = zip(*sorted(req_vs_real.items()))
311 iops, dev = zip(*real)
312 plt.errorbar(req, iops, yerr=dev, fmt='ro',
313 label=str(conc) + " threads",
314 color=next(colors_it))
315 plt.xlabel("Requested IOPS")
316 plt.ylabel("Get IOPS")
317 plt.grid()
318 plt.legend(loc=0)
319 plt_iops_vs_requested = get_emb_data_svg(plt)
320
321 res1 = processed_results.values()[0]
322 params_map = {'iops_vs_lat': plt_iops_vs_lat,
323 'iops_vs_requested': plt_iops_vs_requested,
324 'oper_descr': get_test_lcheck_params(res1).capitalize()}
325
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300326 return get_template('report_iops_vs_lat.html').format(**params_map)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300327
328
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300329def render_all_html(comment, info, lab_description, images, templ_name):
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300330 data = info.__dict__.copy()
331 for name, val in data.items():
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300332 if not name.startswith('__'):
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300333 if val is None:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300334 data[name] = '-'
335 elif isinstance(val, (int, float, long)):
336 data[name] = round_3_digit(val)
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300337
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300338 data['bw_read_max'] = (data['bw_read_max'][0] // 1024,
339 data['bw_read_max'][1])
340 data['bw_write_max'] = (data['bw_write_max'][0] // 1024,
341 data['bw_write_max'][1])
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300342
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300343 images.update(data)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300344 return get_template(templ_name).format(lab_info=lab_description,
345 comment=comment,
346 **images)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300347
348
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300349def io_chart(title, concurence,
350 latv, latv_min, latv_max,
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300351 iops_or_bw, iops_or_bw_err,
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300352 legend,
353 log_iops=False,
354 log_lat=False,
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300355 boxplots=False,
356 latv_50=None, latv_95=None):
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300357
358 matplotlib.rcParams.update({'font.size': 10})
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300359 points = " MiBps" if legend == 'BW' else ""
360 lc = len(concurence)
361 width = 0.35
362 xt = range(1, lc + 1)
363
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300364 op_per_vm = [v / (vm * th) for v, (vm, th) in zip(iops_or_bw, concurence)]
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300365 fig, p1 = plt.subplots()
366 xpos = [i - width / 2 for i in xt]
367
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300368 p1.bar(xpos, iops_or_bw,
369 width=width,
370 yerr=iops_or_bw_err,
371 ecolor='m',
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300372 color='y',
373 label=legend)
374
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300375 p1.grid(True)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300376 p1.plot(xt, op_per_vm, '--', label=legend + "/thread", color='black')
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300377 handles1, labels1 = p1.get_legend_handles_labels()
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300378
379 p2 = p1.twinx()
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300380
381 if latv_50 is None:
382 p2.plot(xt, latv_max, label="lat max")
383 p2.plot(xt, latv, label="lat avg")
384 p2.plot(xt, latv_min, label="lat min")
385 else:
386 p2.plot(xt, latv_50, label="lat med")
387 p2.plot(xt, latv_95, label="lat 95%")
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300388
389 plt.xlim(0.5, lc + 0.5)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300390 plt.xticks(xt, ["{0} * {1}".format(vm, th) for (vm, th) in concurence])
391 p1.set_xlabel("VM Count * Thread per VM")
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300392 p1.set_ylabel(legend + points)
393 p2.set_ylabel("Latency ms")
394 plt.title(title)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300395 handles2, labels2 = p2.get_legend_handles_labels()
396
397 plt.legend(handles1 + handles2, labels1 + labels2,
398 loc='center left', bbox_to_anchor=(1.1, 0.81))
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300399
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300400 if log_iops:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300401 p1.set_yscale('log')
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300402
403 if log_lat:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300404 p2.set_yscale('log')
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300405
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300406 plt.subplots_adjust(right=0.68)
407
408 return get_emb_data_svg(plt)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300409
410
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300411def make_plots(processed_results, plots):
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300412 """
413 processed_results: [PerfInfo]
414 plots = [(test_name_prefix:str, fname:str, description:str)]
415 """
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300416 files = {}
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300417 for name_pref, fname, desc in plots:
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300418 chart_data = []
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300419
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300420 for res in processed_results:
421 summ = res.name + "_" + res.summary
422 if summ.startswith(name_pref):
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300423 chart_data.append(res)
424
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300425 if len(chart_data) == 0:
426 raise ValueError("Can't found any date for " + name_pref)
427
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300428 use_bw = ssize2b(chart_data[0].p.blocksize) > 16 * 1024
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300429
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300430 chart_data.sort(key=lambda x: x.params['vals']['numjobs'])
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300431
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300432 lat = None
433 lat_min = None
434 lat_max = None
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300435
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300436 lat_50 = [x.lat_50 for x in chart_data]
437 lat_95 = [x.lat_95 for x in chart_data]
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300438
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300439 lat_diff_max = max(x.lat_95 / x.lat_50 for x in chart_data)
440 lat_log_scale = (lat_diff_max > 10)
441
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300442 testnodes_count = x.testnodes_count
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300443 concurence = [(testnodes_count, x.concurence)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300444 for x in chart_data]
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300445
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300446 if use_bw:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300447 data = [x.bw.average / 1000 for x in chart_data]
448 data_dev = [x.bw.confidence / 1000 for x in chart_data]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300449 # data_dev = [x.bw.deviation / 1000 for x in chart_data]
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300450 name = "BW"
451 else:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300452 data = [x.iops.average for x in chart_data]
453 data_dev = [x.iops.confidence for x in chart_data]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300454 # data_dev = [x.iops.deviation for x in chart_data]
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300455 name = "IOPS"
456
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300457 fc = io_chart(title=desc,
458 concurence=concurence,
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300459
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300460 latv=lat,
461 latv_min=lat_min,
462 latv_max=lat_max,
463
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300464 iops_or_bw=data,
465 iops_or_bw_err=data_dev,
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300466
467 legend=name,
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300468 log_lat=lat_log_scale,
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300469
470 latv_50=lat_50,
471 latv_95=lat_95)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300472 files[fname] = fc
473
474 return files
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300475
476
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300477def find_max_where(processed_results, sync_mode, blocksize, rw, iops=True):
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300478 result = None
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300479 attr = 'iops' if iops else 'bw'
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300480 for measurement in processed_results:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300481 ok = measurement.sync_mode == sync_mode
482 ok = ok and (measurement.p.blocksize == blocksize)
483 ok = ok and (measurement.p.rw == rw)
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300484
485 if ok:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300486 field = getattr(measurement, attr)
487
488 if result is None:
489 result = field
490 elif field.average > result.average:
491 result = field
492
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300493 return result
494
495
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300496def get_disk_info(processed_results):
497 di = DiskInfo()
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300498 di.direct_iops_w_max = find_max_where(processed_results,
499 'd', '4k', 'randwrite')
500 di.direct_iops_r_max = find_max_where(processed_results,
501 'd', '4k', 'randread')
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300502
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300503 di.direct_iops_w64_max = find_max_where(processed_results,
504 'd', '64k', 'randwrite')
505
506 for sz in ('16m', '64m'):
507 di.bw_write_max = find_max_where(processed_results,
508 'd', sz, 'randwrite', False)
509 if di.bw_write_max is not None:
510 break
511
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300512 if di.bw_write_max is None:
koder aka kdanilov170936a2015-06-27 22:51:17 +0300513 for sz in ('1m', '2m', '4m', '8m'):
514 di.bw_write_max = find_max_where(processed_results,
515 'd', sz, 'write', False)
516 if di.bw_write_max is not None:
517 break
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300518
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300519 for sz in ('16m', '64m'):
520 di.bw_read_max = find_max_where(processed_results,
521 'd', sz, 'randread', False)
522 if di.bw_read_max is not None:
523 break
524
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300525 if di.bw_read_max is None:
526 di.bw_read_max = find_max_where(processed_results,
527 'd', '1m', 'read', False)
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300528
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300529 rws4k_iops_lat_th = []
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300530 for res in processed_results:
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300531 if res.sync_mode in 'xs' and res.p.blocksize == '4k':
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300532 if res.p.rw != 'randwrite':
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300533 continue
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300534 rws4k_iops_lat_th.append((res.iops.average,
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300535 res.lat,
536 # res.lat.average,
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300537 res.concurence))
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300538
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300539 rws4k_iops_lat_th.sort(key=lambda (_1, _2, conc): conc)
540
541 latv = [lat for _, lat, _ in rws4k_iops_lat_th]
542
koder aka kdanilov170936a2015-06-27 22:51:17 +0300543 for tlat in [10, 30, 100]:
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300544 pos = bisect.bisect_left(latv, tlat)
545 if 0 == pos:
koder aka kdanilov170936a2015-06-27 22:51:17 +0300546 setattr(di, 'rws4k_{}ms'.format(tlat), 0)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300547 elif pos == len(latv):
koder aka kdanilovbb5fe072015-05-21 02:50:23 +0300548 iops3, _, _ = rws4k_iops_lat_th[-1]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300549 iops3 = int(round_3_digit(iops3))
koder aka kdanilov170936a2015-06-27 22:51:17 +0300550 setattr(di, 'rws4k_{}ms'.format(tlat), ">=" + str(iops3))
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300551 else:
552 lat1 = latv[pos - 1]
553 lat2 = latv[pos]
554
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300555 iops1, _, th1 = rws4k_iops_lat_th[pos - 1]
556 iops2, _, th2 = rws4k_iops_lat_th[pos]
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300557
558 th_lat_coef = (th2 - th1) / (lat2 - lat1)
559 th3 = th_lat_coef * (tlat - lat1) + th1
560
561 th_iops_coef = (iops2 - iops1) / (th2 - th1)
562 iops3 = th_iops_coef * (th3 - th1) + iops1
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300563 iops3 = int(round_3_digit(iops3))
564 setattr(di, 'rws4k_{}ms'.format(tlat), iops3)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300565
566 hdi = DiskInfo()
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300567
568 def pp(x):
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300569 med, conf = x.rounded_average_conf()
570 conf_perc = int(float(conf) / med * 100)
koder aka kdanilov170936a2015-06-27 22:51:17 +0300571 return (round_3_digit(med), conf_perc)
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300572
573 hdi.direct_iops_r_max = pp(di.direct_iops_r_max)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300574
575 if di.direct_iops_w_max is not None:
576 hdi.direct_iops_w_max = pp(di.direct_iops_w_max)
577 else:
578 hdi.direct_iops_w_max = None
579
580 if di.direct_iops_w64_max is not None:
581 hdi.direct_iops_w64_max = pp(di.direct_iops_w64_max)
582 else:
583 hdi.direct_iops_w64_max = None
584
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300585 hdi.bw_write_max = pp(di.bw_write_max)
586 hdi.bw_read_max = pp(di.bw_read_max)
587
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300588 hdi.rws4k_10ms = di.rws4k_10ms if 0 != di.rws4k_10ms else None
589 hdi.rws4k_30ms = di.rws4k_30ms if 0 != di.rws4k_30ms else None
590 hdi.rws4k_100ms = di.rws4k_100ms if 0 != di.rws4k_100ms else None
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300591 return hdi
592
593
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300594@report('hdd', 'hdd')
595def make_hdd_report(processed_results, lab_info, comment):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300596 plots = [
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300597 ('hdd_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
598 ('hdd_rwx4k', 'rand_write_4k', 'Random write 4k sync IOPS')
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300599 ]
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300600 perf_infos = [res.disk_perf_info() for res in processed_results]
601 images = make_plots(perf_infos, plots)
602 di = get_disk_info(perf_infos)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300603 return render_all_html(comment, di, lab_info, images, "report_hdd.html")
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300604
605
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300606@report('cinder_iscsi', 'cinder_iscsi')
607def make_cinder_iscsi_report(processed_results, lab_info, comment):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300608 plots = [
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300609 ('cinder_iscsi_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
610 ('cinder_iscsi_rwx4k', 'rand_write_4k', 'Random write 4k sync IOPS')
611 ]
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300612 perf_infos = [res.disk_perf_info() for res in processed_results]
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300613 try:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300614 images = make_plots(perf_infos, plots)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300615 except ValueError:
616 plots = [
617 ('cinder_iscsi_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
618 ('cinder_iscsi_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS')
619 ]
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300620 images = make_plots(perf_infos, plots)
621 di = get_disk_info(perf_infos)
koder aka kdanilov170936a2015-06-27 22:51:17 +0300622
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300623 return render_all_html(comment, di, lab_info, images, "report_cinder_iscsi.html")
624
625
626@report('ceph', 'ceph')
627def make_ceph_report(processed_results, lab_info, comment):
628 plots = [
629 ('ceph_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
630 ('ceph_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS'),
631 ('ceph_rrd16m', 'rand_read_16m', 'Random read 16m direct MiBps'),
632 ('ceph_rwd16m', 'rand_write_16m',
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300633 'Random write 16m direct MiBps'),
634 ]
635
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300636 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 kdanilov88407ff2015-05-26 15:35:57 +0300639 return render_all_html(comment, di, lab_info, images, "report_ceph.html")
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300640
641
koder aka kdanilov7248c7b2015-05-31 22:53:03 +0300642@report('mixed', 'mixed')
643def make_mixed_report(processed_results, lab_info, comment):
644 #
645 # IOPS(X% read) = 100 / ( X / IOPS_W + (100 - X) / IOPS_R )
646 #
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300647
648 perf_infos = [res.disk_perf_info() for res in processed_results]
koder aka kdanilov7248c7b2015-05-31 22:53:03 +0300649 mixed = collections.defaultdict(lambda: [])
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300650
651 is_ssd = False
652 for res in perf_infos:
koder aka kdanilov7248c7b2015-05-31 22:53:03 +0300653 if res.name.startswith('mixed'):
654 if res.name.startswith('mixed-ssd'):
655 is_ssd = True
656 mixed[res.concurence].append((res.p.rwmixread,
koder aka kdanilovf236b9c2015-06-24 18:17:22 +0300657 res.lat,
658 0,
659 # res.lat.average / 1000.0,
660 # res.lat.deviation / 1000.0,
koder aka kdanilov7248c7b2015-05-31 22:53:03 +0300661 res.iops.average,
662 res.iops.deviation))
663
664 if len(mixed) == 0:
665 raise ValueError("No mixed load found")
666
667 fig, p1 = plt.subplots()
668 p2 = p1.twinx()
669
670 colors = ['red', 'green', 'blue', 'orange', 'magenta', "teal"]
671 colors_it = iter(colors)
672 for conc, mix_lat_iops in sorted(mixed.items()):
673 mix_lat_iops = sorted(mix_lat_iops)
674 read_perc, lat, dev, iops, iops_dev = zip(*mix_lat_iops)
675 p1.errorbar(read_perc, iops, color=next(colors_it),
676 yerr=iops_dev, label=str(conc) + " th")
677
678 p2.errorbar(read_perc, lat, color=next(colors_it),
679 ls='--', yerr=dev, label=str(conc) + " th lat")
680
681 if is_ssd:
682 p1.set_yscale('log')
683 p2.set_yscale('log')
684
685 p1.set_xlim(-5, 105)
686
687 read_perc = set(read_perc)
688 read_perc.add(0)
689 read_perc.add(100)
690 read_perc = sorted(read_perc)
691
692 plt.xticks(read_perc, map(str, read_perc))
693
694 p1.grid(True)
695 p1.set_xlabel("% of reads")
696 p1.set_ylabel("Mixed IOPS")
697 p2.set_ylabel("Latency, ms")
698
699 handles1, labels1 = p1.get_legend_handles_labels()
700 handles2, labels2 = p2.get_legend_handles_labels()
701 plt.subplots_adjust(top=0.85)
702 plt.legend(handles1 + handles2, labels1 + labels2,
703 bbox_to_anchor=(0.5, 1.15),
704 loc='upper center',
705 prop={'size': 12}, ncol=3)
706 plt.show()
707
708
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300709def make_load_report(idx, results_dir, fname):
710 dpath = os.path.join(results_dir, "io_" + str(idx))
711 files = sorted(os.listdir(dpath))
712 gf = lambda x: "_".join(x.rsplit(".", 1)[0].split('_')[:3])
713
714 for key, group in itertools.groupby(files, gf):
715 fname = os.path.join(dpath, key + ".fio")
716
717 cfgs = list(parse_all_in_1(open(fname).read(), fname))
718
719 fname = os.path.join(dpath, key + "_lat.log")
720
721 curr = []
722 arrays = []
723
724 with open(fname) as fd:
725 for offset, lat, _, _ in csv.reader(fd):
726 offset = int(offset)
727 lat = int(lat)
728 if len(curr) > 0 and curr[-1][0] > offset:
729 arrays.append(curr)
730 curr = []
731 curr.append((offset, lat))
732 arrays.append(curr)
733 conc = int(cfgs[0].vals.get('numjobs', 1))
734
735 if conc != 5:
736 continue
737
738 assert len(arrays) == len(cfgs) * conc
739
740 garrays = [[(0, 0)] for _ in range(conc)]
741
742 for offset in range(len(cfgs)):
743 for acc, new_arr in zip(garrays, arrays[offset * conc:(offset + 1) * conc]):
744 last = acc[-1][0]
745 for off, lat in new_arr:
746 acc.append((off / 1000. + last, lat / 1000.))
747
748 for cfg, arr in zip(cfgs, garrays):
749 plt.plot(*zip(*arr[1:]))
750 plt.show()
751 exit(1)
752
753
754def make_io_report(dinfo, comment, path, lab_info=None):
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300755 lab_info = {
756 "total_disk": "None",
757 "total_memory": "None",
758 "nodes_count": "None",
759 "processor_count": "None"
760 }
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300761
koder aka kdanilova047e1b2015-04-21 23:16:59 +0300762 try:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300763 res_fields = sorted(v.name for v in dinfo)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300764
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300765 found = False
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300766 for fields, name, func in report_funcs:
koder aka kdanilovafd98742015-04-24 01:27:22 +0300767 for field in fields:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300768 pos = bisect.bisect_left(res_fields, field)
769
770 if pos == len(res_fields):
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300771 break
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300772
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300773 if not res_fields[pos].startswith(field):
koder aka kdanilovafd98742015-04-24 01:27:22 +0300774 break
775 else:
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300776 found = True
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300777 hpath = path.format(name)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300778
779 try:
780 report = func(dinfo, lab_info, comment)
781 except:
782 logger.exception("Diring {0} report generation".format(name))
783 continue
784
koder aka kdanilov7248c7b2015-05-31 22:53:03 +0300785 if report is not None:
786 try:
787 with open(hpath, "w") as fd:
788 fd.write(report)
789 except:
790 logger.exception("Diring saving {0} report".format(name))
791 continue
792 logger.info("Report {0} saved into {1}".format(name, hpath))
793 else:
794 logger.warning("No report produced by {0!r}".format(name))
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300795
796 if not found:
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300797 logger.warning("No report generator found for this load")
koder aka kdanilovafd98742015-04-24 01:27:22 +0300798
koder aka kdanilova047e1b2015-04-21 23:16:59 +0300799 except Exception as exc:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300800 import traceback
801 traceback.print_exc()
koder aka kdanilovec1b9732015-04-23 20:43:29 +0300802 logger.error("Failed to generate html report:" + str(exc))