blob: e24e7b6c6d58538ffc1d22bdf956afe6422acffb [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 kdanilov4a510ee2015-04-21 18:50:42 +03005import wally
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03006from wally import charts
koder aka kdanilov209e85d2015-04-27 23:11:05 +03007from wally.statistic import round_3_digit
koder aka kdanilov63ad2062015-04-27 13:11:40 +03008from wally.utils import parse_creds, ssize_to_b
koder aka kdanilov4a510ee2015-04-21 18:50:42 +03009from wally.suits.io.results_loader import process_disk_info
koder aka kdanilovea7eac72015-04-21 21:37:27 +030010from wally.meta_info import total_lab_info, collect_lab_data
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030011
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030012
koder aka kdanilova047e1b2015-04-21 23:16:59 +030013logger = logging.getLogger("wally.report")
14
15
koder aka kdanilov209e85d2015-04-27 23:11:05 +030016class DiskInfo(object):
17 def __init__(self):
18 self.direct_iops_r_max = 0
19 self.direct_iops_w_max = 0
20 self.rws4k_10ms = 0
21 self.rws4k_30ms = 0
22 self.rws4k_100ms = 0
23 self.bw_write_max = 0
24 self.bw_read_max = 0
25
26
koder aka kdanilov63ad2062015-04-27 13:11:40 +030027def render_hdd_html(dest, info, lab_description):
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030028 very_root_dir = os.path.dirname(os.path.dirname(wally.__file__))
29 templ_dir = os.path.join(very_root_dir, 'report_templates')
koder aka kdanilov63ad2062015-04-27 13:11:40 +030030 templ_file = os.path.join(templ_dir, "report_hdd.html")
31 templ = open(templ_file, 'r').read()
koder aka kdanilov209e85d2015-04-27 23:11:05 +030032
33 for name in info.__dict__:
34 if not name.startswith('__'):
35 info.__dict__[name] = round_3_digit(info.__dict__[name])
36
koder aka kdanilov63ad2062015-04-27 13:11:40 +030037 report = templ.format(lab_info=lab_description, **info.__dict__)
38 open(dest, 'w').write(report)
39
40
41def render_ceph_html(dest, info, lab_description):
42 very_root_dir = os.path.dirname(os.path.dirname(wally.__file__))
43 templ_dir = os.path.join(very_root_dir, 'report_templates')
44 templ_file = os.path.join(templ_dir, "report_ceph.html")
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030045 templ = open(templ_file, 'r').read()
koder aka kdanilov209e85d2015-04-27 23:11:05 +030046
47 for name, val in info.__dict__.items():
48 if not name.startswith('__') and isinstance(val, (int, long, float)):
49 setattr(info, name, round_3_digit(val))
50
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030051 report = templ.format(lab_info=lab_description, **info.__dict__)
52 open(dest, 'w').write(report)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030053
54
55def io_chart(title, concurence, latv, iops_or_bw, iops_or_bw_dev,
56 legend, fname):
57 bar_data, bar_dev = iops_or_bw, iops_or_bw_dev
58 legend = [legend]
59
60 iops_or_bw_per_vm = []
61 for i in range(len(concurence)):
62 iops_or_bw_per_vm.append(iops_or_bw[i] / concurence[i])
63
64 bar_dev_bottom = []
65 bar_dev_top = []
66 for i in range(len(bar_data)):
67 bar_dev_top.append(bar_data[i] + bar_dev[i])
68 bar_dev_bottom.append(bar_data[i] - bar_dev[i])
69
70 latv = [lat / 1000 for lat in latv]
71 ch = charts.render_vertical_bar(title, legend, [bar_data], [bar_dev_top],
72 [bar_dev_bottom], file_name=fname,
73 scale_x=concurence,
74 lines=[
75 (latv, "msec", "rr", "lat"),
76 (iops_or_bw_per_vm, None, None,
koder aka kdanilov63ad2062015-04-27 13:11:40 +030077 legend[0] + " per thread")
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030078 ])
79 return str(ch)
80
81
koder aka kdanilov63ad2062015-04-27 13:11:40 +030082def make_hdd_plots(processed_results, path):
83 plots = [
84 ('hdd_test_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
koder aka kdanilovea7eac72015-04-21 21:37:27 +030085 ('hdd_test_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS')
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030086 ]
koder aka kdanilov63ad2062015-04-27 13:11:40 +030087 make_plots(processed_results, path, plots)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +030088
koder aka kdanilov63ad2062015-04-27 13:11:40 +030089
90def make_ceph_plots(processed_results, path):
91 plots = [
92 ('ceph_test_rrd4k', 'rand_read_4k', 'Random read 4k direct IOPS'),
93 ('ceph_test_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS'),
94 ('ceph_test_rrd16m', 'rand_read_16m', 'Random read 16m direct MiBps'),
95 ('ceph_test_swd1m', 'seq_write_1m',
96 'Sequential write 1m direct MiBps'),
97 ]
98 make_plots(processed_results, path, plots)
99
100
101def make_plots(processed_results, path, plots):
102 for name_pref, fname, desc in plots:
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300103 chart_data = []
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300104
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300105 for res in processed_results.values():
106 if res.name.startswith(name_pref):
107 chart_data.append(res)
108
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300109 if len(chart_data) == 0:
110 raise ValueError("Can't found any date for " + name_pref)
111
112 use_bw = ssize_to_b(chart_data[0].raw['blocksize']) > 16 * 1024
113
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300114 chart_data.sort(key=lambda x: x.raw['concurence'])
115
116 lat = [x.lat for x in chart_data]
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300117 vm_count = x.meta['testnodes_count']
118 concurence = [x.raw['concurence'] * vm_count for x in chart_data]
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300119
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300120 if use_bw:
121 data = [x.bw for x in chart_data]
122 data_dev = [x.bw * x.dev for x in chart_data]
123 name = "BW"
124 else:
125 data = [x.iops for x in chart_data]
126 data_dev = [x.iops * x.dev for x in chart_data]
127 name = "IOPS"
128
129 io_chart(desc, concurence, lat, data, data_dev, name, fname)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300130
131
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300132def get_disk_info(processed_results):
133 di = DiskInfo()
134 rws4k_iops_lat_th = []
135
136 for res in processed_results.values():
137 if res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '4k':
138 if res.raw['rw'] == 'randwrite':
139 di.direct_iops_w_max = max(di.direct_iops_w_max, res.iops)
140 elif res.raw['rw'] == 'randread':
141 di.direct_iops_r_max = max(di.direct_iops_r_max, res.iops)
142 elif res.raw['sync_mode'] == 's' and res.raw['blocksize'] == '4k':
143 if res.raw['rw'] != 'randwrite':
144 continue
145
146 rws4k_iops_lat_th.append((res.iops, res.lat,
147 res.raw['concurence']))
148
149 elif res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '1m':
150
151 if res.raw['rw'] == 'write':
152 di.bw_write_max = max(di.bw_write_max, res.bw)
153 elif res.raw['rw'] == 'read':
154 di.bw_read_max = max(di.bw_read_max, res.bw)
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300155 elif res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '16m':
156 if res.raw['rw'] == 'write' or res.raw['rw'] == 'randwrite':
157 di.bw_write_max = max(di.bw_write_max, res.bw)
158 elif res.raw['rw'] == 'read' or res.raw['rw'] == 'randread':
159 di.bw_read_max = max(di.bw_read_max, res.bw)
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300160
161 di.bw_write_max /= 1000
162 di.bw_read_max /= 1000
163
164 rws4k_iops_lat_th.sort(key=lambda (_1, _2, conc): conc)
165
166 latv = [lat for _, lat, _ in rws4k_iops_lat_th]
167
168 for tlatv_ms in [10, 30, 100]:
169 tlat = tlatv_ms * 1000
170 pos = bisect.bisect_left(latv, tlat)
171 if 0 == pos:
172 iops3 = 0
173 elif pos == len(latv):
174 iops3 = latv[-1]
175 else:
176 lat1 = latv[pos - 1]
177 lat2 = latv[pos]
178
179 th1 = rws4k_iops_lat_th[pos - 1][2]
180 th2 = rws4k_iops_lat_th[pos][2]
181
182 iops1 = rws4k_iops_lat_th[pos - 1][0]
183 iops2 = rws4k_iops_lat_th[pos][0]
184
185 th_lat_coef = (th2 - th1) / (lat2 - lat1)
186 th3 = th_lat_coef * (tlat - lat1) + th1
187
188 th_iops_coef = (iops2 - iops1) / (th2 - th1)
189 iops3 = th_iops_coef * (th3 - th1) + iops1
190 setattr(di, 'rws4k_{}ms'.format(tlatv_ms), int(iops3))
191
192 hdi = DiskInfo()
193 hdi.direct_iops_r_max = di.direct_iops_r_max
194 hdi.direct_iops_w_max = di.direct_iops_w_max
195 hdi.rws4k_10ms = di.rws4k_10ms if 0 != di.rws4k_10ms else '-'
196 hdi.rws4k_30ms = di.rws4k_30ms if 0 != di.rws4k_30ms else '-'
197 hdi.rws4k_100ms = di.rws4k_100ms if 0 != di.rws4k_100ms else '-'
198 hdi.bw_write_max = di.bw_write_max
199 hdi.bw_read_max = di.bw_read_max
200 return hdi
201
202
koder aka kdanilovafd98742015-04-24 01:27:22 +0300203report_funcs = []
204
205
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300206def report(name, required_fields):
koder aka kdanilovafd98742015-04-24 01:27:22 +0300207 def closure(func):
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300208 report_funcs.append((required_fields.split(","), name, func))
koder aka kdanilovafd98742015-04-24 01:27:22 +0300209 return func
210 return closure
211
212
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300213@report('HDD', 'hdd_test_rrd4k,hdd_test_rws4k')
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300214def make_hdd_report(processed_results, path, lab_info):
215 make_plots(processed_results, path)
216 di = get_disk_info(processed_results)
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300217 render_hdd_html(path, di, lab_info)
218
219
220@report('Ceph', 'ceph_test')
221def make_ceph_report(processed_results, path, lab_info):
222 make_ceph_plots(processed_results, path)
223 di = get_disk_info(processed_results)
224 render_ceph_html(path, di, lab_info)
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300225
226
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300227def make_io_report(results, path, lab_url=None, creds=None):
228 if lab_url is not None:
229 username, password, tenant_name = parse_creds(creds)
230 creds = {'username': username,
231 'password': password,
232 "tenant_name": tenant_name}
233 data = collect_lab_data(lab_url, creds)
234 lab_info = total_lab_info(data)
235 else:
koder aka kdanilov4a510ee2015-04-21 18:50:42 +0300236 lab_info = {
237 "total_disk": "None",
238 "total_memory": "None",
239 "nodes_count": "None",
240 "processor_count": "None"
241 }
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300242
koder aka kdanilova047e1b2015-04-21 23:16:59 +0300243 try:
244 processed_results = process_disk_info(results)
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300245 res_fields = sorted(processed_results.keys())
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300246 for fields, name, func in report_funcs:
koder aka kdanilovafd98742015-04-24 01:27:22 +0300247 for field in fields:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300248 pos = bisect.bisect_left(res_fields, field)
249
250 if pos == len(res_fields):
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300251 break
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300252
253 if not res_fields[pos + 1].startswith(field):
koder aka kdanilovafd98742015-04-24 01:27:22 +0300254 break
255 else:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300256 hpath = path.format(name)
koder aka kdanilov63ad2062015-04-27 13:11:40 +0300257 logger.debug("Generatins report " + name + " into " + hpath)
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300258 func(processed_results, hpath, lab_info)
koder aka kdanilovafd98742015-04-24 01:27:22 +0300259 break
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300260 else:
261 logger.warning("No report generator found for this load")
koder aka kdanilovafd98742015-04-24 01:27:22 +0300262
koder aka kdanilova047e1b2015-04-21 23:16:59 +0300263 except Exception as exc:
koder aka kdanilov57ce4db2015-04-25 21:25:51 +0300264 import traceback
265 traceback.print_exc()
koder aka kdanilovec1b9732015-04-23 20:43:29 +0300266 logger.error("Failed to generate html report:" + str(exc))