blob: 2aa53382ced2bf1bcf201c005c6750f4cd4f72e5 [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 kdanilovbe8f89f2015-04-28 14:51:51 +030012 import matplotlib.pyplot as plt
13except ImportError:
14 plt = None
15
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030016import wally
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030017from wally.utils import ssize2b
18from wally.statistic import round_3_digit, data_property
koder aka kdanilov88407ff2015-05-26 15:35:57 +030019from wally.suits.io.fio_task_parser import (get_test_sync_mode,
20 get_test_summary,
21 parse_all_in_1,
22 abbv_name_to_full)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030023
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030024
koder aka kdanilova047e1b2015-04-21 23:16:59 +030025logger = logging.getLogger("wally.report")
26
27
koder aka kdanilov209e85d2015-04-27 23:11:05 +030028class DiskInfo(object):
29 def __init__(self):
30 self.direct_iops_r_max = 0
31 self.direct_iops_w_max = 0
koder aka kdanilov88407ff2015-05-26 15:35:57 +030032
33 # 64 used instead of 4k to faster feed caches
34 self.direct_iops_w64_max = 0
35
koder aka kdanilov209e85d2015-04-27 23:11:05 +030036 self.rws4k_10ms = 0
37 self.rws4k_30ms = 0
38 self.rws4k_100ms = 0
39 self.bw_write_max = 0
40 self.bw_read_max = 0
41
42
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +030043report_funcs = []
44
45
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030046class Attrmapper(object):
47 def __init__(self, dct):
48 self.__dct = dct
49
50 def __getattr__(self, name):
51 try:
52 return self.__dct[name]
53 except KeyError:
54 raise AttributeError(name)
55
56
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030057class PerfInfo(object):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030058 def __init__(self, name, summary, intervals, params, testnodes_count):
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030059 self.name = name
60 self.bw = None
61 self.iops = None
62 self.lat = None
koder aka kdanilov88407ff2015-05-26 15:35:57 +030063 self.lat_50 = None
64 self.lat_95 = None
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030065
66 self.raw_bw = []
67 self.raw_iops = []
68 self.raw_lat = []
69
koder aka kdanilov416b87a2015-05-12 00:26:04 +030070 self.params = params
71 self.intervals = intervals
72 self.testnodes_count = testnodes_count
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030073 self.summary = summary
74 self.p = Attrmapper(self.params.vals)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030075
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030076 self.sync_mode = get_test_sync_mode(self.params)
77 self.concurence = self.params.vals.get('numjobs', 1)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030078
79
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +030080# disk_info = None
81# base = None
82# linearity = None
83
84
koder aka kdanilov416b87a2015-05-12 00:26:04 +030085def group_by_name(test_data):
86 name_map = collections.defaultdict(lambda: [])
87
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030088 for data in test_data:
89 name_map[(data.config.name, data.summary())].append(data)
koder aka kdanilov416b87a2015-05-12 00:26:04 +030090
91 return name_map
92
93
koder aka kdanilov88407ff2015-05-26 15:35:57 +030094def get_lat_perc_50_95(lat_mks):
95 curr_perc = 0
96 perc_50 = None
97 perc_95 = None
98 pkey = None
99 for key, val in sorted(lat_mks.items()):
100 if curr_perc + val >= 50 and perc_50 is None:
101 if pkey is None or val < 1.:
102 perc_50 = key
103 else:
104 perc_50 = (50. - curr_perc) / val * (key - pkey) + pkey
105
106 if curr_perc + val >= 95:
107 if pkey is None or val < 1.:
108 perc_95 = key
109 else:
110 perc_95 = (95. - curr_perc) / val * (key - pkey) + pkey
111 break
112
113 pkey = key
114 curr_perc += val
115
116 return perc_50 / 1000., perc_95 / 1000.
117
118
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300119def process_disk_info(test_data):
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300120
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300121 name_map = group_by_name(test_data)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300122 data = {}
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300123 for (name, summary), results in name_map.items():
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300124 lat_mks = collections.defaultdict(lambda: 0)
125 num_res = 0
126
127 for result in results:
128 num_res += len(result.raw_result['jobs'])
129 for job_info in result.raw_result['jobs']:
130 for k, v in job_info['latency_ms'].items():
131 if isinstance(k, str):
132 assert k[:2] == '>='
133 lat_mks[int(k[2:]) * 1000] += v
134 else:
135 lat_mks[k * 1000] += v
136
137 for k, v in job_info['latency_us'].items():
138 lat_mks[k] += v
139
140 for k, v in lat_mks.items():
141 lat_mks[k] = float(v) / num_res
142
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300143 testnodes_count_set = set(dt.vm_count for dt in results)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300144
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300145 assert len(testnodes_count_set) == 1
146 testnodes_count, = testnodes_count_set
147 assert len(results) % testnodes_count == 0
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300148
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300149 intervals = [result.run_interval for result in results]
150 p = results[0].config
151 pinfo = PerfInfo(p.name, result.summary(), intervals,
152 p, testnodes_count)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300153
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300154 pinfo.raw_bw = [result.results['bw'] for result in results]
155 pinfo.raw_iops = [result.results['iops'] for result in results]
156 pinfo.raw_lat = [result.results['lat'] for result in results]
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300157
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300158 pinfo.bw = data_property(map(sum, zip(*pinfo.raw_bw)))
159 pinfo.iops = data_property(map(sum, zip(*pinfo.raw_iops)))
160 pinfo.lat = data_property(sum(pinfo.raw_lat, []))
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300161 pinfo.lat_50, pinfo.lat_95 = get_lat_perc_50_95(lat_mks)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300162
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300163 data[(p.name, summary)] = pinfo
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300164 return data
165
166
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300167def report(name, required_fields):
168 def closure(func):
169 report_funcs.append((required_fields.split(","), name, func))
170 return func
171 return closure
172
173
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300174def get_test_lcheck_params(pinfo):
175 res = [{
176 's': 'sync',
177 'd': 'direct',
178 'a': 'async',
179 'x': 'sync direct'
180 }[pinfo.sync_mode]]
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300181
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300182 res.append(pinfo.p.rw)
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300183
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300184 return " ".join(res)
koder aka kdanilov63e9c5a2015-04-28 23:06:07 +0300185
186
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300187def get_emb_data_svg(plt):
188 sio = StringIO()
189 plt.savefig(sio, format='svg')
190 img_start = "<!-- Created with matplotlib (http://matplotlib.org/) -->"
191 return sio.getvalue().split(img_start, 1)[1]
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300192
193
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300194def get_template(templ_name):
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300195 very_root_dir = os.path.dirname(os.path.dirname(wally.__file__))
196 templ_dir = os.path.join(very_root_dir, 'report_templates')
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300197 templ_file = os.path.join(templ_dir, templ_name)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300198 return open(templ_file, 'r').read()
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300199
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300200
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300201def group_by(data, func):
202 if len(data) < 2:
203 yield data
204 return
205
206 ndata = [(func(dt), dt) for dt in data]
207 ndata.sort(key=func)
208 pkey, dt = ndata[0]
209 curr_list = [dt]
210
211 for key, val in ndata[1:]:
212 if pkey != key:
213 yield curr_list
214 curr_list = [val]
215 else:
216 curr_list.append(val)
217 pkey = key
218
219 yield curr_list
220
221
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300222@report('linearity', 'linearity_test')
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300223def linearity_report(processed_results, lab_info, comment):
224 labels_and_data_mp = collections.defaultdict(lambda: [])
225 vls = {}
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300226
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300227 # plot io_time = func(bsize)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300228 for res in processed_results.values():
229 if res.name.startswith('linearity_test'):
230 iotimes = [1000. / val for val in res.iops.raw]
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300231
232 op_summ = get_test_summary(res.params)[:3]
233
234 labels_and_data_mp[op_summ].append(
235 [res.p.blocksize, res.iops.raw, iotimes])
236
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300237 cvls = res.params.vals.copy()
238 del cvls['blocksize']
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300239 del cvls['rw']
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300240
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300241 cvls.pop('sync', None)
242 cvls.pop('direct', None)
243 cvls.pop('buffered', None)
244
245 if op_summ not in vls:
246 vls[op_summ] = cvls
247 else:
248 assert cvls == vls[op_summ]
249
250 all_labels = None
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300251 _, ax1 = plt.subplots()
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300252 for name, labels_and_data in labels_and_data_mp.items():
253 labels_and_data.sort(key=lambda x: ssize2b(x[0]))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300254
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300255 labels, _, iotimes = zip(*labels_and_data)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300256
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300257 if all_labels is None:
258 all_labels = labels
259 else:
260 assert all_labels == labels
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300261
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300262 plt.boxplot(iotimes)
263 if len(labels_and_data) > 2 and \
264 ssize2b(labels_and_data[-2][0]) >= 4096:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300265
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300266 xt = range(1, len(labels) + 1)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300267
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300268 def io_time(sz, bw, initial_lat):
269 return sz / bw + initial_lat
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300270
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300271 x = numpy.array(map(ssize2b, labels))
272 y = numpy.array([sum(dt) / len(dt) for dt in iotimes])
273 popt, _ = scipy.optimize.curve_fit(io_time, x, y, p0=(100., 1.))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300274
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300275 y1 = io_time(x, *popt)
276 plt.plot(xt, y1, linestyle='--',
277 label=name + ' LS linear approx')
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300278
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300279 for idx, (sz, _, _) in enumerate(labels_and_data):
280 if ssize2b(sz) >= 4096:
281 break
282
283 bw = (x[-1] - x[idx]) / (y[-1] - y[idx])
284 lat = y[-1] - x[-1] / bw
285 y2 = io_time(x, bw, lat)
286 plt.plot(xt, y2, linestyle='--',
287 label=abbv_name_to_full(name) +
288 ' (4k & max) linear approx')
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300289
290 plt.setp(ax1, xticklabels=labels)
291
292 plt.xlabel("Block size")
293 plt.ylabel("IO time, ms")
294
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300295 plt.subplots_adjust(top=0.85)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300296 plt.legend(bbox_to_anchor=(0.5, 1.15),
297 loc='upper center',
298 prop={'size': 10}, ncol=2)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300299 plt.grid()
300 iotime_plot = get_emb_data_svg(plt)
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300301 plt.clf()
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300302
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300303 # plot IOPS = func(bsize)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300304 _, ax1 = plt.subplots()
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300305
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300306 for name, labels_and_data in labels_and_data_mp.items():
307 labels_and_data.sort(key=lambda x: ssize2b(x[0]))
308 _, data, _ = zip(*labels_and_data)
309 plt.boxplot(data)
310 avg = [float(sum(arr)) / len(arr) for arr in data]
311 xt = range(1, len(data) + 1)
312 plt.plot(xt, avg, linestyle='--',
313 label=abbv_name_to_full(name) + " avg")
314
315 plt.setp(ax1, xticklabels=labels)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300316 plt.xlabel("Block size")
317 plt.ylabel("IOPS")
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300318 plt.legend(bbox_to_anchor=(0.5, 1.15),
319 loc='upper center',
320 prop={'size': 10}, ncol=2)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300321 plt.grid()
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300322 plt.subplots_adjust(top=0.85)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300323
324 iops_plot = get_emb_data_svg(plt)
325
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300326 res = set(get_test_lcheck_params(res) for res in processed_results.values())
327 ncount = list(set(res.testnodes_count for res in processed_results.values()))
328 conc = list(set(res.concurence for res in processed_results.values()))
329
330 assert len(conc) == 1
331 assert len(ncount) == 1
332
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300333 descr = {
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300334 'vm_count': ncount[0],
335 'concurence': conc[0],
336 'oper_descr': ", ".join(res).capitalize()
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300337 }
338
339 params_map = {'iotime_vs_size': iotime_plot,
340 'iops_vs_size': iops_plot,
341 'descr': descr}
342
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300343 return get_template('report_linearity.html').format(**params_map)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300344
345
346@report('lat_vs_iops', 'lat_vs_iops')
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300347def lat_vs_iops(processed_results, lab_info, comment):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300348 lat_iops = collections.defaultdict(lambda: [])
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300349 requsted_vs_real = collections.defaultdict(lambda: {})
350
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300351 for res in processed_results.values():
352 if res.name.startswith('lat_vs_iops'):
353 lat_iops[res.concurence].append((res.lat.average / 1000.0,
354 res.lat.deviation / 1000.0,
355 res.iops.average,
356 res.iops.deviation))
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300357 requested_iops = res.p.rate_iops * res.concurence
358 requsted_vs_real[res.concurence][requested_iops] = \
359 (res.iops.average, res.iops.deviation)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300360
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300361 colors = ['red', 'green', 'blue', 'orange', 'magenta', "teal"]
362 colors_it = iter(colors)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300363 for conc, lat_iops in sorted(lat_iops.items()):
364 lat, dev, iops, iops_dev = zip(*lat_iops)
365 plt.errorbar(iops, lat, xerr=iops_dev, yerr=dev, fmt='ro',
366 label=str(conc) + " threads",
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300367 color=next(colors_it))
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300368
369 plt.xlabel("IOPS")
370 plt.ylabel("Latency, ms")
371 plt.grid()
372 plt.legend(loc=0)
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300373 plt_iops_vs_lat = get_emb_data_svg(plt)
374 plt.clf()
375
376 colors_it = iter(colors)
377 for conc, req_vs_real in sorted(requsted_vs_real.items()):
378 req, real = zip(*sorted(req_vs_real.items()))
379 iops, dev = zip(*real)
380 plt.errorbar(req, iops, yerr=dev, fmt='ro',
381 label=str(conc) + " threads",
382 color=next(colors_it))
383 plt.xlabel("Requested IOPS")
384 plt.ylabel("Get IOPS")
385 plt.grid()
386 plt.legend(loc=0)
387 plt_iops_vs_requested = get_emb_data_svg(plt)
388
389 res1 = processed_results.values()[0]
390 params_map = {'iops_vs_lat': plt_iops_vs_lat,
391 'iops_vs_requested': plt_iops_vs_requested,
392 'oper_descr': get_test_lcheck_params(res1).capitalize()}
393
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300394 return get_template('report_iops_vs_lat.html').format(**params_map)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300395
396
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300397def render_all_html(comment, info, lab_description, images, templ_name):
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300398 data = info.__dict__.copy()
399 for name, val in data.items():
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300400 if not name.startswith('__'):
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300401 if val is None:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300402 data[name] = '-'
403 elif isinstance(val, (int, float, long)):
404 data[name] = round_3_digit(val)
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300405
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300406 data['bw_read_max'] = (data['bw_read_max'][0] // 1024,
407 data['bw_read_max'][1])
408 data['bw_write_max'] = (data['bw_write_max'][0] // 1024,
409 data['bw_write_max'][1])
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300410
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300411 images.update(data)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300412 return get_template(templ_name).format(lab_info=lab_description,
413 comment=comment,
414 **images)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300415
416
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300417def io_chart(title, concurence,
418 latv, latv_min, latv_max,
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300419 iops_or_bw, iops_or_bw_err,
420 legend, log=False,
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300421 boxplots=False,
422 latv_50=None, latv_95=None):
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300423 points = " MiBps" if legend == 'BW' else ""
424 lc = len(concurence)
425 width = 0.35
426 xt = range(1, lc + 1)
427
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300428 op_per_vm = [v / (vm * th) for v, (vm, th) in zip(iops_or_bw, concurence)]
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300429 fig, p1 = plt.subplots()
430 xpos = [i - width / 2 for i in xt]
431
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300432 p1.bar(xpos, iops_or_bw,
433 width=width,
434 yerr=iops_or_bw_err,
435 ecolor='m',
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300436 color='y',
437 label=legend)
438
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300439 p1.grid(True)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300440 p1.plot(xt, op_per_vm, '--', label=legend + "/thread", color='black')
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300441 handles1, labels1 = p1.get_legend_handles_labels()
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300442
443 p2 = p1.twinx()
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300444
445 if latv_50 is None:
446 p2.plot(xt, latv_max, label="lat max")
447 p2.plot(xt, latv, label="lat avg")
448 p2.plot(xt, latv_min, label="lat min")
449 else:
450 p2.plot(xt, latv_50, label="lat med")
451 p2.plot(xt, latv_95, label="lat 95%")
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300452
453 plt.xlim(0.5, lc + 0.5)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300454 plt.xticks(xt, ["{0} * {1}".format(vm, th) for (vm, th) in concurence])
455 p1.set_xlabel("VM Count * Thread per VM")
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300456 p1.set_ylabel(legend + points)
457 p2.set_ylabel("Latency ms")
458 plt.title(title)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300459 handles2, labels2 = p2.get_legend_handles_labels()
460
461 plt.legend(handles1 + handles2, labels1 + labels2,
462 loc='center left', bbox_to_anchor=(1.1, 0.81))
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300463
464 if log:
465 p1.set_yscale('log')
466 p2.set_yscale('log')
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300467 plt.subplots_adjust(right=0.68)
468
469 return get_emb_data_svg(plt)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300470
471
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300472def make_plots(processed_results, plots):
473 files = {}
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300474 for name_pref, fname, desc in plots:
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300475 chart_data = []
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300476
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300477 for res in processed_results.values():
478 if res.name.startswith(name_pref):
479 chart_data.append(res)
480
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300481 if len(chart_data) == 0:
482 raise ValueError("Can't found any date for " + name_pref)
483
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300484 use_bw = ssize2b(chart_data[0].p.blocksize) > 16 * 1024
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300485
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300486 chart_data.sort(key=lambda x: x.concurence)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300487
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300488 # if x.lat.average < max_lat]
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300489 # lat = [x.lat.average / 1000 for x in chart_data]
490 # lat_min = [x.lat.min / 1000 for x in chart_data]
491 # lat_max = [x.lat.max / 1000 for x in chart_data]
492 lat = None
493 lat_min = None
494 lat_max = None
495 lat_50 = [x.lat_50 for x in chart_data]
496 lat_95 = [x.lat_95 for x in chart_data]
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300497
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300498 testnodes_count = x.testnodes_count
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300499 concurence = [(testnodes_count, x.concurence)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300500 for x in chart_data]
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300501
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300502 if use_bw:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300503 data = [x.bw.average / 1000 for x in chart_data]
504 data_dev = [x.bw.confidence / 1000 for x in chart_data]
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300505 name = "BW"
506 else:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300507 data = [x.iops.average for x in chart_data]
508 data_dev = [x.iops.confidence for x in chart_data]
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300509 name = "IOPS"
510
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300511 fc = io_chart(title=desc,
512 concurence=concurence,
513 latv=lat, latv_min=lat_min, latv_max=lat_max,
514 iops_or_bw=data,
515 iops_or_bw_err=data_dev,
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300516 legend=name, latv_50=lat_50, latv_95=lat_95)
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300517 files[fname] = fc
518
519 return files
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300520
521
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300522def find_max_where(processed_results, sync_mode, blocksize, rw, iops=True):
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300523 result = None
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300524 attr = 'iops' if iops else 'bw'
525 for measurement in processed_results.values():
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300526 ok = measurement.sync_mode == sync_mode
527 ok = ok and (measurement.p.blocksize == blocksize)
528 ok = ok and (measurement.p.rw == rw)
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300529
530 if ok:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300531 field = getattr(measurement, attr)
532
533 if result is None:
534 result = field
535 elif field.average > result.average:
536 result = field
537
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300538 return result
539
540
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300541def get_disk_info(processed_results):
542 di = DiskInfo()
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300543 di.direct_iops_w_max = find_max_where(processed_results,
544 'd', '4k', 'randwrite')
545 di.direct_iops_r_max = find_max_where(processed_results,
546 'd', '4k', 'randread')
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300547
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300548 di.direct_iops_w64_max = find_max_where(processed_results,
549 'd', '64k', 'randwrite')
550
551 for sz in ('16m', '64m'):
552 di.bw_write_max = find_max_where(processed_results,
553 'd', sz, 'randwrite', False)
554 if di.bw_write_max is not None:
555 break
556
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300557 if di.bw_write_max is None:
558 di.bw_write_max = find_max_where(processed_results,
559 'd', '1m', 'write', False)
560
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300561 for sz in ('16m', '64m'):
562 di.bw_read_max = find_max_where(processed_results,
563 'd', sz, 'randread', False)
564 if di.bw_read_max is not None:
565 break
566
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300567 if di.bw_read_max is None:
568 di.bw_read_max = find_max_where(processed_results,
569 'd', '1m', 'read', False)
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300570
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300571 rws4k_iops_lat_th = []
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300572 for res in processed_results.values():
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300573 if res.sync_mode in 'xs' and res.p.blocksize == '4k':
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300574 if res.p.rw != 'randwrite':
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300575 continue
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300576 rws4k_iops_lat_th.append((res.iops.average,
577 res.lat.average,
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300578 res.concurence))
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300579
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300580 rws4k_iops_lat_th.sort(key=lambda (_1, _2, conc): conc)
581
582 latv = [lat for _, lat, _ in rws4k_iops_lat_th]
583
584 for tlatv_ms in [10, 30, 100]:
585 tlat = tlatv_ms * 1000
586 pos = bisect.bisect_left(latv, tlat)
587 if 0 == pos:
koder aka kdanilovbb5fe072015-05-21 02:50:23 +0300588 setattr(di, 'rws4k_{}ms'.format(tlatv_ms), 0)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300589 elif pos == len(latv):
koder aka kdanilovbb5fe072015-05-21 02:50:23 +0300590 iops3, _, _ = rws4k_iops_lat_th[-1]
591 setattr(di, 'rws4k_{}ms'.format(tlatv_ms), ">=" + str(iops3))
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300592 else:
593 lat1 = latv[pos - 1]
594 lat2 = latv[pos]
595
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300596 iops1, _, th1 = rws4k_iops_lat_th[pos - 1]
597 iops2, _, th2 = rws4k_iops_lat_th[pos]
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300598
599 th_lat_coef = (th2 - th1) / (lat2 - lat1)
600 th3 = th_lat_coef * (tlat - lat1) + th1
601
602 th_iops_coef = (iops2 - iops1) / (th2 - th1)
603 iops3 = th_iops_coef * (th3 - th1) + iops1
koder aka kdanilovbb5fe072015-05-21 02:50:23 +0300604 setattr(di, 'rws4k_{}ms'.format(tlatv_ms), int(iops3))
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300605
606 hdi = DiskInfo()
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300607
608 def pp(x):
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300609 med, conf = x.rounded_average_conf()
610 conf_perc = int(float(conf) / med * 100)
611 return (med, conf_perc)
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300612
613 hdi.direct_iops_r_max = pp(di.direct_iops_r_max)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300614
615 if di.direct_iops_w_max is not None:
616 hdi.direct_iops_w_max = pp(di.direct_iops_w_max)
617 else:
618 hdi.direct_iops_w_max = None
619
620 if di.direct_iops_w64_max is not None:
621 hdi.direct_iops_w64_max = pp(di.direct_iops_w64_max)
622 else:
623 hdi.direct_iops_w64_max = None
624
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300625 hdi.bw_write_max = pp(di.bw_write_max)
626 hdi.bw_read_max = pp(di.bw_read_max)
627
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300628 hdi.rws4k_10ms = di.rws4k_10ms if 0 != di.rws4k_10ms else None
629 hdi.rws4k_30ms = di.rws4k_30ms if 0 != di.rws4k_30ms else None
630 hdi.rws4k_100ms = di.rws4k_100ms if 0 != di.rws4k_100ms else None
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300631 return hdi
632
633
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300634@report('hdd', 'hdd')
635def make_hdd_report(processed_results, lab_info, comment):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300636 plots = [
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300637 ('hdd_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
638 ('hdd_rwx4k', 'rand_write_4k', 'Random write 4k sync IOPS')
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300639 ]
640 images = make_plots(processed_results, plots)
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300641 di = get_disk_info(processed_results)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300642 return render_all_html(comment, di, lab_info, images, "report_hdd.html")
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300643
644
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300645@report('cinder_iscsi', 'cinder_iscsi')
646def make_cinder_iscsi_report(processed_results, lab_info, comment):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300647 plots = [
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300648 ('cinder_iscsi_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
649 ('cinder_iscsi_rwx4k', 'rand_write_4k', 'Random write 4k sync IOPS')
650 ]
651 try:
652 images = make_plots(processed_results, plots)
653 except ValueError:
654 plots = [
655 ('cinder_iscsi_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
656 ('cinder_iscsi_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS')
657 ]
658 images = make_plots(processed_results, plots)
659 di = get_disk_info(processed_results)
660 return render_all_html(comment, di, lab_info, images, "report_cinder_iscsi.html")
661
662
663@report('ceph', 'ceph')
664def make_ceph_report(processed_results, lab_info, comment):
665 plots = [
666 ('ceph_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
667 ('ceph_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS'),
668 ('ceph_rrd16m', 'rand_read_16m', 'Random read 16m direct MiBps'),
669 ('ceph_rwd16m', 'rand_write_16m',
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300670 'Random write 16m direct MiBps'),
671 ]
672
673 images = make_plots(processed_results, plots)
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300674 di = get_disk_info(processed_results)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300675 return render_all_html(comment, di, lab_info, images, "report_ceph.html")
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300676
677
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300678def make_load_report(idx, results_dir, fname):
679 dpath = os.path.join(results_dir, "io_" + str(idx))
680 files = sorted(os.listdir(dpath))
681 gf = lambda x: "_".join(x.rsplit(".", 1)[0].split('_')[:3])
682
683 for key, group in itertools.groupby(files, gf):
684 fname = os.path.join(dpath, key + ".fio")
685
686 cfgs = list(parse_all_in_1(open(fname).read(), fname))
687
688 fname = os.path.join(dpath, key + "_lat.log")
689
690 curr = []
691 arrays = []
692
693 with open(fname) as fd:
694 for offset, lat, _, _ in csv.reader(fd):
695 offset = int(offset)
696 lat = int(lat)
697 if len(curr) > 0 and curr[-1][0] > offset:
698 arrays.append(curr)
699 curr = []
700 curr.append((offset, lat))
701 arrays.append(curr)
702 conc = int(cfgs[0].vals.get('numjobs', 1))
703
704 if conc != 5:
705 continue
706
707 assert len(arrays) == len(cfgs) * conc
708
709 garrays = [[(0, 0)] for _ in range(conc)]
710
711 for offset in range(len(cfgs)):
712 for acc, new_arr in zip(garrays, arrays[offset * conc:(offset + 1) * conc]):
713 last = acc[-1][0]
714 for off, lat in new_arr:
715 acc.append((off / 1000. + last, lat / 1000.))
716
717 for cfg, arr in zip(cfgs, garrays):
718 plt.plot(*zip(*arr[1:]))
719 plt.show()
720 exit(1)
721
722
723def make_io_report(dinfo, comment, path, lab_info=None):
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300724 lab_info = {
725 "total_disk": "None",
726 "total_memory": "None",
727 "nodes_count": "None",
728 "processor_count": "None"
729 }
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300730
koder aka kdanilova047e1b2015-04-21 23:16:59 +0300731 try:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300732 res_fields = sorted(v.name for v in dinfo.values())
733
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300734 found = False
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300735 for fields, name, func in report_funcs:
koder aka kdanilovafd98742015-04-24 01:27:22 +0300736 for field in fields:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300737 pos = bisect.bisect_left(res_fields, field)
738
739 if pos == len(res_fields):
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300740 break
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300741
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300742 if not res_fields[pos].startswith(field):
koder aka kdanilovafd98742015-04-24 01:27:22 +0300743 break
744 else:
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300745 found = True
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300746 hpath = path.format(name)
koder aka kdanilov88407ff2015-05-26 15:35:57 +0300747
748 try:
749 report = func(dinfo, lab_info, comment)
750 except:
751 logger.exception("Diring {0} report generation".format(name))
752 continue
753
754 try:
755 with open(hpath, "w") as fd:
756 fd.write(report)
757 except:
758 logger.exception("Diring saving {0} report".format(name))
759 continue
760 logger.info("Report {0} saved into {1}".format(name, hpath))
761
762 if not found:
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300763 logger.warning("No report generator found for this load")
koder aka kdanilovafd98742015-04-24 01:27:22 +0300764
koder aka kdanilova047e1b2015-04-21 23:16:59 +0300765 except Exception as exc:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300766 import traceback
767 traceback.print_exc()
koder aka kdanilovec1b9732015-04-23 20:43:29 +0300768 logger.error("Failed to generate html report:" + str(exc))