blob: e435a6a23932f2d91a495f9e2933acd64a801e5a [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('__'):
105 info.__dict__[name] = round_3_digit(info.__dict__[name])
106
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300107 report = templ.format(lab_info=lab_description, **info.__dict__)
108 open(dest, 'w').write(report)
109
110
111def render_ceph_html(dest, info, lab_description):
112 very_root_dir = os.path.dirname(os.path.dirname(wally.__file__))
113 templ_dir = os.path.join(very_root_dir, 'report_templates')
114 templ_file = os.path.join(templ_dir, "report_ceph.html")
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300115 templ = open(templ_file, 'r').read()
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300116
117 for name, val in info.__dict__.items():
118 if not name.startswith('__') and isinstance(val, (int, long, float)):
119 setattr(info, name, round_3_digit(val))
120
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300121 report = templ.format(lab_info=lab_description, **info.__dict__)
122 open(dest, 'w').write(report)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300123
124
125def io_chart(title, concurence, latv, iops_or_bw, iops_or_bw_dev,
126 legend, fname):
127 bar_data, bar_dev = iops_or_bw, iops_or_bw_dev
128 legend = [legend]
129
130 iops_or_bw_per_vm = []
131 for i in range(len(concurence)):
132 iops_or_bw_per_vm.append(iops_or_bw[i] / concurence[i])
133
134 bar_dev_bottom = []
135 bar_dev_top = []
136 for i in range(len(bar_data)):
137 bar_dev_top.append(bar_data[i] + bar_dev[i])
138 bar_dev_bottom.append(bar_data[i] - bar_dev[i])
139
140 latv = [lat / 1000 for lat in latv]
141 ch = charts.render_vertical_bar(title, legend, [bar_data], [bar_dev_top],
142 [bar_dev_bottom], file_name=fname,
143 scale_x=concurence,
144 lines=[
145 (latv, "msec", "rr", "lat"),
146 (iops_or_bw_per_vm, None, None,
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300147 legend[0] + " per thread")
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300148 ])
149 return str(ch)
150
151
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300152def make_hdd_plots(processed_results, path):
153 plots = [
154 ('hdd_test_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
koder aka kdanilovea7eac72015-04-21 21:37:27 +0300155 ('hdd_test_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS')
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300156 ]
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300157 make_plots(processed_results, path, plots)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300158
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300159
160def make_ceph_plots(processed_results, path):
161 plots = [
162 ('ceph_test_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
163 ('ceph_test_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS'),
164 ('ceph_test_rrd16m', 'rand_read_16m', 'Random read 16m direct MiBps'),
165 ('ceph_test_swd1m', 'seq_write_1m',
166 'Sequential write 1m direct MiBps'),
167 ]
168 make_plots(processed_results, path, plots)
169
170
171def make_plots(processed_results, path, plots):
172 for name_pref, fname, desc in plots:
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300173 chart_data = []
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300174
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300175 for res in processed_results.values():
176 if res.name.startswith(name_pref):
177 chart_data.append(res)
178
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300179 if len(chart_data) == 0:
180 raise ValueError("Can't found any date for " + name_pref)
181
182 use_bw = ssize_to_b(chart_data[0].raw['blocksize']) > 16 * 1024
183
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300184 chart_data.sort(key=lambda x: x.raw['concurence'])
185
186 lat = [x.lat for x in chart_data]
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300187 vm_count = x.meta['testnodes_count']
188 concurence = [x.raw['concurence'] * vm_count for x in chart_data]
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300189
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300190 if use_bw:
191 data = [x.bw for x in chart_data]
192 data_dev = [x.bw * x.dev for x in chart_data]
193 name = "BW"
194 else:
195 data = [x.iops for x in chart_data]
196 data_dev = [x.iops * x.dev for x in chart_data]
197 name = "IOPS"
198
199 io_chart(desc, concurence, lat, data, data_dev, name, fname)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300200
201
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300202def get_disk_info(processed_results):
203 di = DiskInfo()
204 rws4k_iops_lat_th = []
205
206 for res in processed_results.values():
207 if res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '4k':
208 if res.raw['rw'] == 'randwrite':
209 di.direct_iops_w_max = max(di.direct_iops_w_max, res.iops)
210 elif res.raw['rw'] == 'randread':
211 di.direct_iops_r_max = max(di.direct_iops_r_max, res.iops)
212 elif res.raw['sync_mode'] == 's' and res.raw['blocksize'] == '4k':
213 if res.raw['rw'] != 'randwrite':
214 continue
215
216 rws4k_iops_lat_th.append((res.iops, res.lat,
217 res.raw['concurence']))
218
219 elif res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '1m':
220
221 if res.raw['rw'] == 'write':
222 di.bw_write_max = max(di.bw_write_max, res.bw)
223 elif res.raw['rw'] == 'read':
224 di.bw_read_max = max(di.bw_read_max, res.bw)
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300225 elif res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '16m':
226 if res.raw['rw'] == 'write' or res.raw['rw'] == 'randwrite':
227 di.bw_write_max = max(di.bw_write_max, res.bw)
228 elif res.raw['rw'] == 'read' or res.raw['rw'] == 'randread':
229 di.bw_read_max = max(di.bw_read_max, res.bw)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300230
231 di.bw_write_max /= 1000
232 di.bw_read_max /= 1000
233
234 rws4k_iops_lat_th.sort(key=lambda (_1, _2, conc): conc)
235
236 latv = [lat for _, lat, _ in rws4k_iops_lat_th]
237
238 for tlatv_ms in [10, 30, 100]:
239 tlat = tlatv_ms * 1000
240 pos = bisect.bisect_left(latv, tlat)
241 if 0 == pos:
242 iops3 = 0
243 elif pos == len(latv):
244 iops3 = latv[-1]
245 else:
246 lat1 = latv[pos - 1]
247 lat2 = latv[pos]
248
249 th1 = rws4k_iops_lat_th[pos - 1][2]
250 th2 = rws4k_iops_lat_th[pos][2]
251
252 iops1 = rws4k_iops_lat_th[pos - 1][0]
253 iops2 = rws4k_iops_lat_th[pos][0]
254
255 th_lat_coef = (th2 - th1) / (lat2 - lat1)
256 th3 = th_lat_coef * (tlat - lat1) + th1
257
258 th_iops_coef = (iops2 - iops1) / (th2 - th1)
259 iops3 = th_iops_coef * (th3 - th1) + iops1
260 setattr(di, 'rws4k_{}ms'.format(tlatv_ms), int(iops3))
261
262 hdi = DiskInfo()
263 hdi.direct_iops_r_max = di.direct_iops_r_max
264 hdi.direct_iops_w_max = di.direct_iops_w_max
265 hdi.rws4k_10ms = di.rws4k_10ms if 0 != di.rws4k_10ms else '-'
266 hdi.rws4k_30ms = di.rws4k_30ms if 0 != di.rws4k_30ms else '-'
267 hdi.rws4k_100ms = di.rws4k_100ms if 0 != di.rws4k_100ms else '-'
268 hdi.bw_write_max = di.bw_write_max
269 hdi.bw_read_max = di.bw_read_max
270 return hdi
271
272
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300273@report('HDD', 'hdd_test_rrd4k,hdd_test_rws4k')
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300274def make_hdd_report(processed_results, path, lab_info):
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300275 make_hdd_plots(processed_results, path)
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300276 di = get_disk_info(processed_results)
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300277 render_hdd_html(path, di, lab_info)
278
279
280@report('Ceph', 'ceph_test')
281def make_ceph_report(processed_results, path, lab_info):
282 make_ceph_plots(processed_results, path)
283 di = get_disk_info(processed_results)
284 render_ceph_html(path, di, lab_info)
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300285
286
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300287def make_io_report(results, path, lab_url=None, creds=None):
288 if lab_url is not None:
289 username, password, tenant_name = parse_creds(creds)
290 creds = {'username': username,
291 'password': password,
292 "tenant_name": tenant_name}
293 data = collect_lab_data(lab_url, creds)
294 lab_info = total_lab_info(data)
295 else:
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300296 lab_info = {
297 "total_disk": "None",
298 "total_memory": "None",
299 "nodes_count": "None",
300 "processor_count": "None"
301 }
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300302
koder aka kdanilova047e1b2015-04-21 23:16:59 +0300303 try:
304 processed_results = process_disk_info(results)
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300305 res_fields = sorted(processed_results.keys())
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300306 for fields, name, func in report_funcs:
koder aka kdanilovafd98742015-04-24 01:27:22 +0300307 for field in fields:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300308 pos = bisect.bisect_left(res_fields, field)
309
310 if pos == len(res_fields):
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300311 break
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300312
koder aka kdanilovbe8f89f2015-04-28 14:51:51 +0300313 if not res_fields[pos].startswith(field):
koder aka kdanilovafd98742015-04-24 01:27:22 +0300314 break
315 else:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300316 hpath = path.format(name)
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300317 logger.debug("Generatins report " + name + " into " + hpath)
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300318 func(processed_results, hpath, lab_info)
koder aka kdanilovafd98742015-04-24 01:27:22 +0300319 break
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300320 else:
321 logger.warning("No report generator found for this load")
koder aka kdanilovafd98742015-04-24 01:27:22 +0300322
koder aka kdanilova047e1b2015-04-21 23:16:59 +0300323 except Exception as exc:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300324 import traceback
325 traceback.print_exc()
koder aka kdanilovec1b9732015-04-23 20:43:29 +0300326 logger.error("Failed to generate html report:" + str(exc))