Chart generation supports multiple json

Add multiple curves to latency chart - 1 per json file
Switch to jQuery dataTable to represent 1 row per json file

Change-Id: Ie10cfbda5604444a655e9bb71b6a8f4d9edd2d5b
This commit is contained in:
ahothan 2015-05-11 15:36:24 -07:00
parent a8f140e031
commit 7478a3676e
2 changed files with 166 additions and 115 deletions

View File

@ -34,75 +34,126 @@ kb_html_tpl = "./kb_tpl.jinja"
def get_formatted_num(value):
return '{:,}'.format(value)
# List of fields to format with thousands separators
fields_to_format = ['rps_max', 'rps', 'http_sock_err', 'total_server_vm',
'http_total_req', 'total_connections']
# table column names
col_names = ['<i class="glyphicon glyphicon-file"></i> File',
'<i class="glyphicon glyphicon-random"></i> Connections',
'<i class="glyphicon glyphicon-book"></i> Server VMs',
'<i class="glyphicon glyphicon-transfer"></i> Requests',
'<i class="glyphicon glyphicon-fire"></i> Socket errors',
'<i class="glyphicon glyphicon-time"></i> RPS measured',
'<i class="glyphicon glyphicon-pencil"></i> RPS requested',
'<i class="glyphicon glyphicon-cloud-download"></i> RX throughput (Gbps)']
def format_numbers(results):
for key in results.keys():
if key in fields_to_format:
print 'format:' + key
results[key] = get_formatted_num(results[key])
def get_progress_vars(name, cur_value, max_value):
tpl_vars = {
name: cur_value,
name + "_max": max_value,
name + "_percent": (cur_value * 100) / max_value,
}
return tpl_vars
def get_new_latency_tuples():
'''Returns a list of lists initializedas follows
The latency tuples must be formatted like this:
['Percentile', 'fileA', 'fileB'],
[2.0, 1, 3],
[4.0, 27, 38],
etc, with the first column being calculated from the percentile
using the formula (1/(1-percentile))
50% -> 1/0.5 = 2
75% -> 4
etc...
This horizontal scaling is used to stretch the chart at the top end
(towards 100%)
'''
return [
['Percentile'], # add run file name
[2], # add run 50% latency
[4], # add run 75% latency
[10],
[100],
[1000],
[10000],
[100000] # add run 99.999% latency
]
class KbReport(object):
def __init__(self, results):
self.results = results
def __init__(self, data_list):
self.data_list = data_list
self.latency_tuples = get_new_latency_tuples()
self.common_stats = []
self.table = None
template_loader = FileSystemLoader(searchpath=".")
template_env = Environment(loader=template_loader)
self.tpl = template_env.get_template(kb_html_tpl)
def get_template_vars(self):
rx_tp = float(self.results['http_throughput_kbytes'])
rx_tp = round(rx_tp * 8 / (1024 * 1024), 1)
tpl_vars = get_progress_vars('rx', rx_tp, 10)
self.results.update(tpl_vars)
rps = self.results['http_rps']
rps_max = self.results['http_rate_limit'] * self.results['total_client_vms']
tpl_vars = get_progress_vars('rps', rps, rps_max)
self.results.update(tpl_vars)
# tweak the latency percentile information so that the X value
# maps to the modified log scale
# The X value to use is 1/(1 - percentile)
latency_list = self.results['latency_stats']
mod_latency_list = []
for latency_pair in latency_list:
x_value = round(100 / (100 - latency_pair[0]), 1)
# conert from usec to msec
def add_latency_stats(self, run_results):
# init a column list
column = [run_results['filename']]
for latency_pair in run_results['latency_stats']:
# convert from usec to msec
latency_ms = latency_pair[1] / 1000
mod_latency_list.append([x_value, latency_ms])
self.results['latency_stats'] = mod_latency_list
return self.results
column.append(latency_ms)
# and append that column to the latency list
for pct_list, colval in zip(self.latency_tuples, column):
pct_list.append(colval)
def prepare_table(self):
table = {}
table['col_names'] = col_names
# add values for each row
rows = []
for run_res in self.data_list:
rps_max = run_res['http_rate_limit'] * run_res['total_client_vms']
rx_tp = float(run_res['http_throughput_kbytes'])
rx_tp = round(rx_tp * 8 / (1024 * 1024), 1)
cells = [run_res['filename'],
get_formatted_num(run_res['total_connections']),
get_formatted_num(run_res['total_server_vms']),
get_formatted_num(run_res['http_total_req']),
get_formatted_num(run_res['http_sock_err']),
get_formatted_num(run_res['http_rps']),
get_formatted_num(rps_max)]
row = {'cells': cells,
'rx': {'value': rx_tp,
'max': 10,
'percent': rx_tp * 10}}
rows.append(row)
table['rows'] = rows
self.table = table
def plot(self, dest_file):
for run_results in self.data_list:
self.add_latency_stats(run_results)
self.prepare_table()
kbstats = {
'table': self.table,
'latency_tuples': self.latency_tuples,
'search_page': 'true' if len(self.data_list) > 10 else 'false'
}
print kbstats
with open(dest_file, 'w') as dest:
print('Generating chart drawing code to ' + dest_file + '...')
tpl_vars = self.get_template_vars()
format_numbers(tpl_vars)
output = self.tpl.render(tpl_vars)
output = self.tpl.render(kbstats=kbstats)
dest.write(output)
def gen_chart(res_file, chart_dest, browser):
if not os.path.isfile(res_file):
print('Error: No such file %s: ' + res_file)
sys.exit(1)
with open(res_file) as data_file:
results = json.load(data_file)
chart = KbReport(results)
print('Generating report to ' + chart_dest + '...')
chart.plot(chart_dest)
if browser:
url = 'file://' + os.path.abspath(chart_dest)
webbrowser.open(url, new=2)
def get_display_file_name(filename):
res = os.path.basename(filename)
# remove extension
res, _ = os.path.splitext(res)
return res
def gen_chart(file_list, chart_dest, browser):
data_list = []
for res_file in file_list:
print 'processing: ' + res_file
if not os.path.isfile(res_file):
print('Error: No such file %s: ' + res_file)
sys.exit(1)
with open(res_file) as data_file:
results = json.load(data_file)
results['filename'] = get_display_file_name(res_file)
data_list.append(results)
chart = KbReport(data_list)
print('Generating report to ' + chart_dest + '...')
chart.plot(chart_dest)
if browser:
url = 'file://' + os.path.abspath(chart_dest)
webbrowser.open(url, new=2)
def get_absolute_path_for_file(file_name):
'''
@ -131,8 +182,8 @@ if __name__ == '__main__':
action='store_true',
help='print version of this script and exit')
parser.add_argument(dest='file',
help='KloudBuster json result file',
parser.add_argument(dest='files',
help='KloudBuster json result file', nargs="+",
metavar='<file>')
opts = parser.parse_args()
@ -141,4 +192,4 @@ if __name__ == '__main__':
print('Version ' + __version__)
sys.exit(0)
gen_chart(opts.file, opts.chart, opts.browser)
gen_chart(opts.files, opts.chart, opts.browser)

View File

@ -4,6 +4,11 @@
<script src="http://code.jquery.com/jquery.min.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script src="http://cdn.datatables.net/1.10.7/js/jquery.dataTables.min.js"></script>
<link href="http://cdn.datatables.net/1.10.7/css/jquery.dataTables.min.css" rel="stylesheet" type="text/css" />
<style media="all" type="text/css">
.alignRight { text-align: right; }
</style>
<meta charset="utf-8">
<script type="text/javascript"
src="https://www.google.com/jsapi?autoload={
@ -17,12 +22,9 @@
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Percentile', 'Latency (msec)'],
{% for latency_list in latency_stats %}
{{latency_list}},
{% endfor %}
]);
var data = google.visualization.arrayToDataTable(
{{kbstats.latency_tuples}}
);
var ticks =
[{v:2,f:'50%'},
{v:4,f:'75%'},
@ -36,8 +38,11 @@
title: 'HTTP Requests Latency Distribution',
curveType: 'function',
hAxis: {title: 'Percentile', minValue: 0, logScale: true, ticks:ticks },
vAxis: {title: 'Latency (ms)', minValue: 0, logScale: true, minValue: 0 },
legend: { position: 'none' }
vAxis: {title: 'Latency (ms)', minValue: 0, logScale: true,
gridlines: {count: 8},
minorGridlines: {count: 1},
minValue: 0 },
legend: { position: 'bottom' }
};
var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
@ -54,7 +59,7 @@
return function (dt, row) {
var percentile = 100.0 - (100.0/dt.getValue(row, 0));
return dt.getColumnLabel(j) + ': ' +
percentile.toPrecision(6) +
percentile +
'\%\'ile = ' + dt.getValue(row, j) + ' msec'
}
})(i)
@ -64,6 +69,17 @@
view.setColumns(columns);
chart.draw(view, options);
}
// For jquery dataTable
$(document).ready(function() {
$('#runs_table').dataTable({
"searching": {{kbstats.search_page}},
"paging": {{kbstats.search_page}},
"bInfo" : {{kbstats.search_page}},
"aoColumnDefs": [
{ "sClass": "alignRight", "aTargets": [ 1, 2, 3, 4, 5, 6 ] }
]
});
} );
</script>
</head>
<body>
@ -72,58 +88,42 @@
<div class="panel panel-primary">
<div class="panel-heading"><h3>HTTP Scale Results</h3></div>
<div class="panel-body">
<h4>
{% if kbstats.table %}
<div class="row"><!-- ROW1 -->
<div class="col-md-6">
<ul class="list-group">
<li class="list-group-item list-group-item-success">
<span class="badge">{{ total_connections }}</span>
Total Concurrent Connections
</li>
<li class="list-group-item list-group-item-success">
<span class="badge">{{ total_server_vms }}</span>
Server VM count
</li>
<li class="list-group-item list-group-item-success">
<span class="badge">{{ http_total_req }}</span>
Total Requests/Responses
</li>
<li class="list-group-item list-group-item-success">
<span class="badge">{{ http_sock_err }}</span>
Total Socket Errors
</li>
</ul>
</div>
<div class="col-md-6">
<div class="panel">
<i class="glyphicon glyphicon-refresh"></i>
Requests/sec <span class="label label-default pull-right">{{ rps }} / {{ rps_max }}</span>
<div class="well-sm">
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="{{ rps }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ rps_percent }}%">
<span">{{ rps_percent }}%</span>
</div>
<table id="runs_table" class="table hover display compact" cellspacing="0" width="100%">
<thead>
<tr>
{% for col_name in kbstats.table.col_names %}
<th>{{col_name}}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in kbstats.table.rows %}
<tr>
{% for cell in row.cells %}
<td>{{cell}}</td>
{% endfor %}
<td>
<div class="progress-bar" role="progressbar" style="width:{{row.rx.percent}}%;min-width: 20px">
<span>{{ row.rx.value }}</span>
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div><!-- ROW1 -->
{% endif %}
<i class="glyphicon glyphicon-cloud-download"></i>
Reception Throughput (Gbps)<span class="label label-default pull-right">{{ rx }} / {{ rx_max }}</span>
<div class="well-sm">
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="{{ rx }}" aria-valuemin="0" aria-valuemax="10" style="width: {{ rx_percent }}%">
<span>{{ rx_percent }}%</span>
</div>
</div>
</div> </div>
</div>
</div><!-- ROW1 -->
</h4>
<div class="row" align="center"><!-- ROW2 -->
<div id="curve_chart" style="width: 1200px; height: 500px"></div>
</div><!-- ROW2 -->
<div class="row" align="center"><!-- LATENCY CHART ROW -->
<div id="curve_chart" style="width: 900px; height: 500px"></div>
</div><!-- LATENCY CHART ROW -->
</div><!--/panel-body-->
</div><!--/panel-->
</div><!--/container-->
</body>
</html>
</html>