blob: 209fa31d513c1904ffa7a03f75b0af9e1f02b391 [file] [log] [blame]
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03001import os
koder aka kdanilov4a510ee2015-04-21 18:50:42 +03002import bisect
koder aka kdanilova047e1b2015-04-21 23:16:59 +03003import logging
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03004
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +03005try:
6 import matplotlib.pyplot as plt
7except ImportError:
8 plt = None
9
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030010import wally
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030011from wally import charts
koder aka kdanilov209e85d2015-04-27 23:11:05 +030012from wally.statistic import round_3_digit
koder aka kdanilov63ad2062015-04-27 13:11:40 +030013from wally.utils import parse_creds, ssize_to_b
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030014from wally.suits.io.results_loader import process_disk_info
koder aka kdanilovea7eac72015-04-21 21:37:27 +030015from wally.meta_info import total_lab_info, collect_lab_data
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030016
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030017
koder aka kdanilova047e1b2015-04-21 23:16:59 +030018logger = logging.getLogger("wally.report")
19
20
koder aka kdanilov209e85d2015-04-27 23:11:05 +030021class DiskInfo(object):
22 def __init__(self):
23 self.direct_iops_r_max = 0
24 self.direct_iops_w_max = 0
25 self.rws4k_10ms = 0
26 self.rws4k_30ms = 0
27 self.rws4k_100ms = 0
28 self.bw_write_max = 0
29 self.bw_read_max = 0
30
31
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +030032report_funcs = []
33
34
35def report(name, required_fields):
36 def closure(func):
37 report_funcs.append((required_fields.split(","), name, func))
38 return func
39 return closure
40
41
42# def linearity_report(processed_results, path, lab_info):
43# names = {}
44# for tp1 in ('rand', 'seq'):
45# for oper in ('read', 'write'):
46# for sync in ('sync', 'direct', 'async'):
47# sq = (tp1, oper, sync)
48# name = "{0} {1} {2}".format(*sq)
49# names["".join(word[0] for word in sq)] = name
50
51# colors = ['red', 'green', 'blue', 'cyan',
52# 'magenta', 'black', 'yellow', 'burlywood']
53# markers = ['*', '^', 'x', 'o', '+', '.']
54# color = 0
55# marker = 0
56
57# name_pref = 'linearity_test_'
58# plot_data = []
59
60# x = []
61# y = []
62# e = []
63# # values to make line
64# ax = []
65# ay = []
66
67# for res in processed_results.values():
68# if res.name.startswith(name_pref):
69# res
70
71# for sz, med, dev in sorted(filtered_data(data)):
72# iotime_ms = 1000. // med
73# iotime_max = 1000. // (med - dev * 3)
74
75# x.append(sz / 1024.0)
76# y.append(iotime_ms)
77# e.append(iotime_max - iotime_ms)
78# if vals is None or sz in vals:
79# ax.append(sz / 1024.0)
80# ay.append(iotime_ms)
81
82# plt.errorbar(x, y, e, linestyle='None', label=names[tp],
83# color=colors[color], ecolor="black",
84# marker=markers[marker])
85# ynew = approximate_line(ax, ay, ax, True)
86# plt.plot(ax, ynew, color=colors[color])
87# color += 1
88# marker += 1
89# plt.legend(loc=2)
90# plt.title("Linearity test by %i dots" % (len(vals)))
91
92
93# if plt:
94# linearity_report = report('linearity', 'linearity_test')(linearity_report)
95
96
koder aka kdanilov63ad2062015-04-27 13:11:40 +030097def render_hdd_html(dest, info, lab_description):
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030098 very_root_dir = os.path.dirname(os.path.dirname(wally.__file__))
99 templ_dir = os.path.join(very_root_dir, 'report_templates')
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300100 templ_file = os.path.join(templ_dir, "report_hdd.html")
101 templ = open(templ_file, 'r').read()
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300102
103 for name in info.__dict__:
104 if not name.startswith('__'):
Yulia Portnova0d1b45f2015-04-28 16:51:11 +0300105 if info.__dict__[name] == "-":
106 continue
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300107 info.__dict__[name] = round_3_digit(info.__dict__[name])
108
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300109 report = templ.format(lab_info=lab_description, **info.__dict__)
110 open(dest, 'w').write(report)
111
112
113def render_ceph_html(dest, info, lab_description):
114 very_root_dir = os.path.dirname(os.path.dirname(wally.__file__))
115 templ_dir = os.path.join(very_root_dir, 'report_templates')
116 templ_file = os.path.join(templ_dir, "report_ceph.html")
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300117 templ = open(templ_file, 'r').read()
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300118
119 for name, val in info.__dict__.items():
120 if not name.startswith('__') and isinstance(val, (int, long, float)):
121 setattr(info, name, round_3_digit(val))
122
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300123 report = templ.format(lab_info=lab_description, **info.__dict__)
124 open(dest, 'w').write(report)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300125
126
127def io_chart(title, concurence, latv, iops_or_bw, iops_or_bw_dev,
128 legend, fname):
129 bar_data, bar_dev = iops_or_bw, iops_or_bw_dev
130 legend = [legend]
131
132 iops_or_bw_per_vm = []
133 for i in range(len(concurence)):
134 iops_or_bw_per_vm.append(iops_or_bw[i] / concurence[i])
135
136 bar_dev_bottom = []
137 bar_dev_top = []
138 for i in range(len(bar_data)):
139 bar_dev_top.append(bar_data[i] + bar_dev[i])
140 bar_dev_bottom.append(bar_data[i] - bar_dev[i])
141
142 latv = [lat / 1000 for lat in latv]
143 ch = charts.render_vertical_bar(title, legend, [bar_data], [bar_dev_top],
144 [bar_dev_bottom], file_name=fname,
Yulia Portnova0d1b45f2015-04-28 16:51:11 +0300145 scale_x=concurence, label_x="clients",
146 label_y="iops",
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300147 lines=[
148 (latv, "msec", "rr", "lat"),
149 (iops_or_bw_per_vm, None, None,
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300150 legend[0] + " per thread")
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300151 ])
152 return str(ch)
153
154
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300155def make_hdd_plots(processed_results, path):
156 plots = [
157 ('hdd_test_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
koder aka kdanilovea7eac72015-04-21 21:37:27 +0300158 ('hdd_test_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS')
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300159 ]
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300160 make_plots(processed_results, path, plots)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300161
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300162
163def make_ceph_plots(processed_results, path):
164 plots = [
165 ('ceph_test_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
166 ('ceph_test_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS'),
167 ('ceph_test_rrd16m', 'rand_read_16m', 'Random read 16m direct MiBps'),
168 ('ceph_test_swd1m', 'seq_write_1m',
169 'Sequential write 1m direct MiBps'),
170 ]
171 make_plots(processed_results, path, plots)
172
173
174def make_plots(processed_results, path, plots):
175 for name_pref, fname, desc in plots:
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300176 chart_data = []
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300177
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300178 for res in processed_results.values():
179 if res.name.startswith(name_pref):
180 chart_data.append(res)
181
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300182 if len(chart_data) == 0:
183 raise ValueError("Can't found any date for " + name_pref)
184
185 use_bw = ssize_to_b(chart_data[0].raw['blocksize']) > 16 * 1024
186
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300187 chart_data.sort(key=lambda x: x.raw['concurence'])
188
189 lat = [x.lat for x in chart_data]
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300190 vm_count = x.meta['testnodes_count']
191 concurence = [x.raw['concurence'] * vm_count for x in chart_data]
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300192
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300193 if use_bw:
194 data = [x.bw for x in chart_data]
195 data_dev = [x.bw * x.dev for x in chart_data]
196 name = "BW"
197 else:
198 data = [x.iops for x in chart_data]
199 data_dev = [x.iops * x.dev for x in chart_data]
200 name = "IOPS"
201
202 io_chart(desc, concurence, lat, data, data_dev, name, fname)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300203
204
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300205def get_disk_info(processed_results):
206 di = DiskInfo()
207 rws4k_iops_lat_th = []
208
209 for res in processed_results.values():
210 if res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '4k':
211 if res.raw['rw'] == 'randwrite':
212 di.direct_iops_w_max = max(di.direct_iops_w_max, res.iops)
213 elif res.raw['rw'] == 'randread':
214 di.direct_iops_r_max = max(di.direct_iops_r_max, res.iops)
215 elif res.raw['sync_mode'] == 's' and res.raw['blocksize'] == '4k':
216 if res.raw['rw'] != 'randwrite':
217 continue
218
219 rws4k_iops_lat_th.append((res.iops, res.lat,
220 res.raw['concurence']))
221
222 elif res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '1m':
223
224 if res.raw['rw'] == 'write':
225 di.bw_write_max = max(di.bw_write_max, res.bw)
226 elif res.raw['rw'] == 'read':
227 di.bw_read_max = max(di.bw_read_max, res.bw)
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300228 elif res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '16m':
229 if res.raw['rw'] == 'write' or res.raw['rw'] == 'randwrite':
230 di.bw_write_max = max(di.bw_write_max, res.bw)
231 elif res.raw['rw'] == 'read' or res.raw['rw'] == 'randread':
232 di.bw_read_max = max(di.bw_read_max, res.bw)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300233
234 di.bw_write_max /= 1000
235 di.bw_read_max /= 1000
236
237 rws4k_iops_lat_th.sort(key=lambda (_1, _2, conc): conc)
238
239 latv = [lat for _, lat, _ in rws4k_iops_lat_th]
240
241 for tlatv_ms in [10, 30, 100]:
242 tlat = tlatv_ms * 1000
243 pos = bisect.bisect_left(latv, tlat)
244 if 0 == pos:
245 iops3 = 0
246 elif pos == len(latv):
247 iops3 = latv[-1]
248 else:
249 lat1 = latv[pos - 1]
250 lat2 = latv[pos]
251
252 th1 = rws4k_iops_lat_th[pos - 1][2]
253 th2 = rws4k_iops_lat_th[pos][2]
254
255 iops1 = rws4k_iops_lat_th[pos - 1][0]
256 iops2 = rws4k_iops_lat_th[pos][0]
257
258 th_lat_coef = (th2 - th1) / (lat2 - lat1)
259 th3 = th_lat_coef * (tlat - lat1) + th1
260
261 th_iops_coef = (iops2 - iops1) / (th2 - th1)
262 iops3 = th_iops_coef * (th3 - th1) + iops1
263 setattr(di, 'rws4k_{}ms'.format(tlatv_ms), int(iops3))
264
265 hdi = DiskInfo()
266 hdi.direct_iops_r_max = di.direct_iops_r_max
267 hdi.direct_iops_w_max = di.direct_iops_w_max
268 hdi.rws4k_10ms = di.rws4k_10ms if 0 != di.rws4k_10ms else '-'
269 hdi.rws4k_30ms = di.rws4k_30ms if 0 != di.rws4k_30ms else '-'
270 hdi.rws4k_100ms = di.rws4k_100ms if 0 != di.rws4k_100ms else '-'
271 hdi.bw_write_max = di.bw_write_max
272 hdi.bw_read_max = di.bw_read_max
273 return hdi
274
275
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300276@report('HDD', 'hdd_test_rrd4k,hdd_test_rws4k')
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300277def make_hdd_report(processed_results, path, lab_info):
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300278 make_hdd_plots(processed_results, path)
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300279 di = get_disk_info(processed_results)
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300280 render_hdd_html(path, di, lab_info)
281
282
283@report('Ceph', 'ceph_test')
284def make_ceph_report(processed_results, path, lab_info):
285 make_ceph_plots(processed_results, path)
286 di = get_disk_info(processed_results)
287 render_ceph_html(path, di, lab_info)
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300288
289
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300290def make_io_report(results, path, lab_url=None, creds=None):
291 if lab_url is not None:
292 username, password, tenant_name = parse_creds(creds)
293 creds = {'username': username,
294 'password': password,
295 "tenant_name": tenant_name}
296 data = collect_lab_data(lab_url, creds)
297 lab_info = total_lab_info(data)
298 else:
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300299 lab_info = {
300 "total_disk": "None",
301 "total_memory": "None",
302 "nodes_count": "None",
303 "processor_count": "None"
304 }
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300305
koder aka kdanilova047e1b2015-04-21 23:16:59 +0300306 try:
307 processed_results = process_disk_info(results)
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300308 res_fields = sorted(processed_results.keys())
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300309 for fields, name, func in report_funcs:
koder aka kdanilovafd98742015-04-24 01:27:22 +0300310 for field in fields:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300311 pos = bisect.bisect_left(res_fields, field)
312
313 if pos == len(res_fields):
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300314 break
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300315
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300316 if not res_fields[pos].startswith(field):
koder aka kdanilovafd98742015-04-24 01:27:22 +0300317 break
318 else:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300319 hpath = path.format(name)
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300320 logger.debug("Generatins report " + name + " into " + hpath)
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300321 func(processed_results, hpath, lab_info)
koder aka kdanilovafd98742015-04-24 01:27:22 +0300322 break
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300323 else:
324 logger.warning("No report generator found for this load")
koder aka kdanilovafd98742015-04-24 01:27:22 +0300325
koder aka kdanilova047e1b2015-04-21 23:16:59 +0300326 except Exception as exc:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300327 import traceback
328 traceback.print_exc()
koder aka kdanilovec1b9732015-04-23 20:43:29 +0300329 logger.error("Failed to generate html report:" + str(exc))