fix reports
diff --git a/report.html b/report.html
deleted file mode 100644
index 5e78689..0000000
--- a/report.html
+++ /dev/null
@@ -1,66 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>Report</title>
- <link rel="stylesheet"
- href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
-</head>
-
-<body>
-<div class="page-header text-center">
- <h2>Performance Report</h2>
-</div>
-<div class="container">
- <div class="row">
- <table style="width: auto;" class="table table-bordered table-striped">
- <tr>
- <td>Test</td>
- <td>Iops</td>
- <td>Latency</td>
- </tr>
- <tr>
- <td>rand read 4k</td>
- <td>{data[rand_read_4k][iops][0]} ± {data[rand_read_4k][iops][1]}</td>
- <td>{data[rand_read_4k][lat]}</td>
- </tr>
- <tr>
- <td>rand write 4k</td>
- <td>{data[rand_write_4k][iops][0]} ± {data[rand_write_4k][iops][1]}</td>
- <td>{data[rand_write_4k][lat]}</td>
- </tr>
- <tr></tr>
- </table>
- </div>
- <div class="row">
- <table>
- <tr>
- <td><img src={urls[0]}></td>
- <td><img src={urls[1]}></td>
- </tr>
- </table>
- </div>
-
- <div class="row">
- <table style="width: auto;" class="table table-bordered table-striped">
- <tr>
- <td>Disk total</td>
- <td>{lab_info[total_disk]}</td>
- </tr>
- <tr>
- <td>Memory total</td>
- <td>{lab_info[total_memory]}</td>
- </tr>
- <tr>
- <td>Nodes count</td>
- <td>{lab_info[nodes_count]}</td>
- </tr>
- <tr>
- <td>CPU count</td>
- <td>{lab_info[processor_count]}</td>
- </tr>
- </table>
- </div>
-</div>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/report_templates/report.html b/report_templates/report.html
new file mode 100644
index 0000000..80cc1e2
--- /dev/null
+++ b/report_templates/report.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Report</title>
+ <link rel="stylesheet"
+ href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
+</head>
+
+<body>
+<div class="page-header text-center">
+ <h2>Performance Report</h2>
+</div>
+<div class="container">
+ <div class="row">
+ <table style="width: auto;" class="table table-bordered table-striped">
+ <tr>
+ <td>Test</td>
+ <td>Iops/BW</td>
+ <td>Test</td>
+ <td>Iops/BW</td>
+ </tr>
+ <tr>
+ <td>Rand read 4k direct max IOPS</td>
+ <td><div align="right">{direct_iops_r_max} IOPS</div></td>
+ <td>Rand write 4k sync IOPS 10ms lat</td>
+ <td><div align="right">{rws4k_10ms} IOPS</div></td>
+ </tr>
+ <tr>
+ <td>Rand write 4k direct max IOPS</td>
+ <td><div align="right">{direct_iops_w_max} IOPS</div></td>
+ <td>Rand write 4k sync IOPS 30ms lat</td>
+ <td><div align="right">{rws4k_30ms} IOPS</div></td>
+ </tr>
+ <tr>
+ <td>Direct sequantials read</td>
+ <td><div align="right">{bw_read_max} MiBps</div></td>
+ <td>Rand write 4k sync IOPS 100ms lat</td>
+ <td><div align="right">{rws4k_100ms} IOPS</div></td>
+ </tr>
+ <tr>
+ <td>Direct sequantials write</td>
+ <td><div align="right">{bw_write_max} MiBps</div></td>
+ <td></td><td></td>
+ </tr>
+ </table>
+ </div>
+ <div class="row">
+ <table>
+ <tr>
+ <td><img src="charts/rand_read_4k.png"></td>
+ <td width="10%"><H1> </H1></td>
+ <td><img src="charts/rand_write_4k.png"></td>
+ </tr>
+ </table>
+ </div>
+
+ <!--div class="row">
+ <table style="width: auto;" class="table table-bordered table-striped">
+ <tr>
+ <td>Disk total</td>
+ <td>{lab_info[total_disk]}</td>
+ </tr>
+ <tr>
+ <td>Memory total</td>
+ <td>{lab_info[total_memory]}</td>
+ </tr>
+ <tr>
+ <td>Nodes count</td>
+ <td>{lab_info[nodes_count]}</td>
+ </tr>
+ <tr>
+ <td>CPU count</td>
+ <td>{lab_info[processor_count]}</td>
+ </tr>
+ </table>
+ </div-->
+</div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/wally/config.py b/wally/config.py
index 7dba134..9626332 100644
--- a/wally/config.py
+++ b/wally/config.py
@@ -37,11 +37,11 @@
else:
run_uuid = str(uuid.uuid4())
results_dir = os.path.join(var_dir, run_uuid)
+ cfg_dict['run_uuid'] = run_uuid.replace('_', '-')
else:
results_dir = explicit_folder
cfg_dict['var_dir'] = results_dir
- cfg_dict['run_uuid'] = run_uuid.replace('_', '-')
mkdirs_if_unxists(cfg_dict['var_dir'])
in_var_dir = functools.partial(os.path.join, cfg_dict['var_dir'])
diff --git a/wally/report.py b/wally/report.py
index d191428..83cb457 100644
--- a/wally/report.py
+++ b/wally/report.py
@@ -1,17 +1,22 @@
import os
import sys
+import bisect
+import wally
from wally import charts
-from wally.statistic import med_dev, round_3_digit, round_deviation
from wally.utils import parse_creds
-from wally.suits.io.results_loader import filter_data
from wally.meta_info import total_lab_info, collect_lab_data
+from wally.suits.io.results_loader import process_disk_info
-def render_html(charts_urls, dest, lab_description, info):
- templ = open("report.html", 'r').read()
- open(dest, 'w').write(templ.format(urls=charts_urls,
- data=info, lab_info=lab_description))
+
+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,
@@ -41,6 +46,107 @@
return str(ch)
+def make_plots(processed_results, path):
+ name_filters = [
+ ('hdd_test_rrd4k', ('concurence', 'lat', 'iops'),
+ 'rand_read_4k', 'Random read 4k sync'),
+ ('hdd_test_rws4k', ('concurence', 'lat', 'iops'),
+ 'rand_write_4k', 'Random write 4k sync')
+ ]
+
+ 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]
+ bw = [x.bw for x in chart_data]
+ bw_dev = [x.dev * x.bw for x in chart_data]
+
+ io_chart(desc, concurence, lat, bw, bw_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
+
+
def make_io_report(results, path, lab_url=None, creds=None):
if lab_url is not None:
username, password, tenant_name = parse_creds(creds)
@@ -50,51 +156,17 @@
data = collect_lab_data(lab_url, creds)
lab_info = total_lab_info(data)
else:
- lab_info = ""
+ lab_info = {
+ "total_disk": "None",
+ "total_memory": "None",
+ "nodes_count": "None",
+ "processor_count": "None"
+ }
- for suite_type, test_suite_data in results:
- if suite_type != 'io' or test_suite_data is None:
- continue
-
- io_test_suite_res = test_suite_data['res']
-
- charts_url = []
- max_info = {}
-
- name_filters = [
- ('hdd_test_rrd4k', ('concurence', 'lat', 'iops'),
- 'rand_read_4k', 'random read 4k'),
- # ('hdd_test_swd1m', ('concurence', 'lat', 'bw'), 'seq_write_1m'),
- # ('hdd_test_srd1m', ('concurence', 'lat', 'bw'), 'seq_read_1m'),
- ('hdd_test_rws4k', ('concurence', 'lat', 'iops'),
- 'rand_write_4k', 'random write 4k')
- ]
-
- for name_filter, fields, fname, desc in name_filters:
- th_filter = filter_data(name_filter, fields)
-
- data = sorted(th_filter(io_test_suite_res.values()))
- if len(data) == 0:
- continue
-
- concurence, latv, iops_or_bw_v = zip(*data)
- iops_or_bw_v, iops_or_bw_dev_v = zip(*map(med_dev, iops_or_bw_v))
- latv, _ = zip(*map(med_dev, latv))
-
- url = io_chart(desc, concurence, latv, iops_or_bw_v,
- iops_or_bw_dev_v,
- fields[2], fname)
- max_lat = "%s msec" % round_3_digit(max(latv) / 1000)
- max_iops_or_bw = max(iops_or_bw_v)
- max_iops_or_bw_dev = iops_or_bw_dev_v[
- iops_or_bw_v.index(max_iops_or_bw)]
- r = round_deviation((max_iops_or_bw, max_iops_or_bw_dev))
- max_info[fname] = {fields[2]: r,
- "lat": max_lat}
- charts_url.append(url)
-
- if len(charts_url) != 0:
- render_html(charts_url, path, lab_info, max_info)
+ processed_results = process_disk_info(results)
+ make_plots(processed_results, path)
+ di = get_disk_info(processed_results)
+ render_html(path, di, lab_info)
def main(args):
diff --git a/wally/suits/io/results_loader.py b/wally/suits/io/results_loader.py
index 25721eb..9005450 100644
--- a/wally/suits/io/results_loader.py
+++ b/wally/suits/io/results_loader.py
@@ -1,10 +1,32 @@
import re
import json
+import collections
from wally.utils import ssize_to_b
from wally.statistic import med_dev
+PerfInfo = collections.namedtuple('PerfInfo',
+ ('name',
+ 'bw', 'iops', 'dev',
+ 'lat', 'lat_dev', 'raw'))
+
+
+def process_disk_info(test_output):
+ data = {}
+
+ for tp, pre_result in test_output:
+ if tp != 'io':
+ pass
+
+ for name, results in pre_result['res'].items():
+ bw, bw_dev = med_dev(results['bw'])
+ iops, iops_dev = med_dev(results['iops'])
+ lat, lat_dev = med_dev(results['lat'])
+ dev = bw_dev / float(bw)
+ data[name] = PerfInfo(name, bw, iops, dev, lat, lat_dev, results)
+ return data
+
def parse_output(out_err):
start_patt = r"(?ims)=+\s+RESULTS\(format=json\)\s+=+"
@@ -37,6 +59,21 @@
return closure
+def filter_processed_data(name_prefix, fields_to_select, **filters):
+ def closure(data):
+ for name, result in data.items():
+ if name_prefix is not None:
+ if not name.startswith(name_prefix):
+ continue
+
+ for k, v in filters.items():
+ if result.raw.get(k) != v:
+ break
+ else:
+ yield map(result.raw.get, fields_to_select)
+ return closure
+
+
def load_data(raw_data):
data = list(parse_output(raw_data))[0]