blob: aa7a4b07d75c80b61e10d2c669c30266b3a9ec97 [file] [log] [blame]
import os
import bisect
import logging
import wally
from wally import charts
from wally.utils import parse_creds
from wally.suits.io.results_loader import process_disk_info
from wally.meta_info import total_lab_info, collect_lab_data
logger = logging.getLogger("wally.report")
def render_html(dest, info, lab_description):
very_root_dir = os.path.dirname(os.path.dirname(wally.__file__))
templ_dir = os.path.join(very_root_dir, 'report_templates')
templ_file = os.path.join(templ_dir, "report.html")
templ = open(templ_file, 'r').read()
report = templ.format(lab_info=lab_description, **info.__dict__)
open(dest, 'w').write(report)
def io_chart(title, concurence, latv, iops_or_bw, iops_or_bw_dev,
legend, fname):
bar_data, bar_dev = iops_or_bw, iops_or_bw_dev
legend = [legend]
iops_or_bw_per_vm = []
for i in range(len(concurence)):
iops_or_bw_per_vm.append(iops_or_bw[i] / concurence[i])
bar_dev_bottom = []
bar_dev_top = []
for i in range(len(bar_data)):
bar_dev_top.append(bar_data[i] + bar_dev[i])
bar_dev_bottom.append(bar_data[i] - bar_dev[i])
latv = [lat / 1000 for lat in latv]
ch = charts.render_vertical_bar(title, legend, [bar_data], [bar_dev_top],
[bar_dev_bottom], file_name=fname,
scale_x=concurence,
lines=[
(latv, "msec", "rr", "lat"),
(iops_or_bw_per_vm, None, None,
"IOPS per thread")
])
return str(ch)
def make_plots(processed_results, path):
name_filters = [
('hdd_test_rrd4k', 'rand_read_4k', 'Random read 4k sync IOPS'),
('hdd_test_rws4k', 'rand_write_4k', 'Random write 4k sync IOPS')
]
for name_pref, fname, desc in name_filters:
chart_data = []
for res in processed_results.values():
if res.name.startswith(name_pref):
chart_data.append(res)
chart_data.sort(key=lambda x: x.raw['concurence'])
lat = [x.lat for x in chart_data]
concurence = [x.raw['concurence'] for x in chart_data]
iops = [x.iops for x in chart_data]
iops_dev = [x.iops * x.dev for x in chart_data]
io_chart(desc, concurence, lat, iops, iops_dev, 'bw', fname)
class DiskInfo(object):
def __init__(self):
self.direct_iops_r_max = 0
self.direct_iops_w_max = 0
self.rws4k_10ms = 0
self.rws4k_30ms = 0
self.rws4k_100ms = 0
self.bw_write_max = 0
self.bw_read_max = 0
def get_disk_info(processed_results):
di = DiskInfo()
rws4k_iops_lat_th = []
for res in processed_results.values():
if res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '4k':
if res.raw['rw'] == 'randwrite':
di.direct_iops_w_max = max(di.direct_iops_w_max, res.iops)
elif res.raw['rw'] == 'randread':
di.direct_iops_r_max = max(di.direct_iops_r_max, res.iops)
elif res.raw['sync_mode'] == 's' and res.raw['blocksize'] == '4k':
if res.raw['rw'] != 'randwrite':
continue
rws4k_iops_lat_th.append((res.iops, res.lat,
res.raw['concurence']))
elif res.raw['sync_mode'] == 'd' and res.raw['blocksize'] == '1m':
if res.raw['rw'] == 'write':
di.bw_write_max = max(di.bw_write_max, res.bw)
elif res.raw['rw'] == 'read':
di.bw_read_max = max(di.bw_read_max, res.bw)
di.bw_write_max /= 1000
di.bw_read_max /= 1000
rws4k_iops_lat_th.sort(key=lambda (_1, _2, conc): conc)
latv = [lat for _, lat, _ in rws4k_iops_lat_th]
for tlatv_ms in [10, 30, 100]:
tlat = tlatv_ms * 1000
pos = bisect.bisect_left(latv, tlat)
if 0 == pos:
iops3 = 0
elif pos == len(latv):
iops3 = latv[-1]
else:
lat1 = latv[pos - 1]
lat2 = latv[pos]
th1 = rws4k_iops_lat_th[pos - 1][2]
th2 = rws4k_iops_lat_th[pos][2]
iops1 = rws4k_iops_lat_th[pos - 1][0]
iops2 = rws4k_iops_lat_th[pos][0]
th_lat_coef = (th2 - th1) / (lat2 - lat1)
th3 = th_lat_coef * (tlat - lat1) + th1
th_iops_coef = (iops2 - iops1) / (th2 - th1)
iops3 = th_iops_coef * (th3 - th1) + iops1
setattr(di, 'rws4k_{}ms'.format(tlatv_ms), int(iops3))
hdi = DiskInfo()
hdi.direct_iops_r_max = di.direct_iops_r_max
hdi.direct_iops_w_max = di.direct_iops_w_max
hdi.rws4k_10ms = di.rws4k_10ms if 0 != di.rws4k_10ms else '-'
hdi.rws4k_30ms = di.rws4k_30ms if 0 != di.rws4k_30ms else '-'
hdi.rws4k_100ms = di.rws4k_100ms if 0 != di.rws4k_100ms else '-'
hdi.bw_write_max = di.bw_write_max
hdi.bw_read_max = di.bw_read_max
return hdi
report_funcs = []
def report(name, required_fields):
def closure(func):
report_funcs.append((required_fields.split(","), name, func))
return func
return closure
@report('HDD', 'hdd_test_rrd4k,hdd_test_rws4k')
def make_hdd_report(processed_results, path, lab_info):
make_plots(processed_results, path)
di = get_disk_info(processed_results)
render_html(path, di, lab_info)
def make_io_report(results, path, lab_url=None, creds=None):
if lab_url is not None:
username, password, tenant_name = parse_creds(creds)
creds = {'username': username,
'password': password,
"tenant_name": tenant_name}
data = collect_lab_data(lab_url, creds)
lab_info = total_lab_info(data)
else:
lab_info = {
"total_disk": "None",
"total_memory": "None",
"nodes_count": "None",
"processor_count": "None"
}
try:
processed_results = process_disk_info(results)
res_fields = sorted(processed_results.keys())
for fields, name, func in report_funcs:
for field in fields:
pos = bisect.bisect_left(res_fields, field)
if pos == len(res_fields):
continue
if not res_fields[pos + 1].startswith(field):
break
else:
hpath = path.format(name)
func(processed_results, hpath, lab_info)
logger.debug(name + " report generated into " + hpath)
break
else:
logger.warning("No report generator found for this load")
except Exception as exc:
import traceback
traceback.print_exc()
logger.error("Failed to generate html report:" + str(exc))