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:
parent
a8f140e031
commit
7478a3676e
@ -34,75 +34,126 @@ kb_html_tpl = "./kb_tpl.jinja"
|
|||||||
def get_formatted_num(value):
|
def get_formatted_num(value):
|
||||||
return '{:,}'.format(value)
|
return '{:,}'.format(value)
|
||||||
|
|
||||||
# List of fields to format with thousands separators
|
# table column names
|
||||||
fields_to_format = ['rps_max', 'rps', 'http_sock_err', 'total_server_vm',
|
col_names = ['<i class="glyphicon glyphicon-file"></i> File',
|
||||||
'http_total_req', 'total_connections']
|
'<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):
|
def get_new_latency_tuples():
|
||||||
for key in results.keys():
|
'''Returns a list of lists initializedas follows
|
||||||
if key in fields_to_format:
|
The latency tuples must be formatted like this:
|
||||||
print 'format:' + key
|
['Percentile', 'fileA', 'fileB'],
|
||||||
results[key] = get_formatted_num(results[key])
|
[2.0, 1, 3],
|
||||||
|
[4.0, 27, 38],
|
||||||
def get_progress_vars(name, cur_value, max_value):
|
etc, with the first column being calculated from the percentile
|
||||||
tpl_vars = {
|
using the formula (1/(1-percentile))
|
||||||
name: cur_value,
|
50% -> 1/0.5 = 2
|
||||||
name + "_max": max_value,
|
75% -> 4
|
||||||
name + "_percent": (cur_value * 100) / max_value,
|
etc...
|
||||||
}
|
This horizontal scaling is used to stretch the chart at the top end
|
||||||
return tpl_vars
|
(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):
|
class KbReport(object):
|
||||||
def __init__(self, results):
|
def __init__(self, data_list):
|
||||||
self.results = results
|
self.data_list = data_list
|
||||||
|
self.latency_tuples = get_new_latency_tuples()
|
||||||
|
self.common_stats = []
|
||||||
|
self.table = None
|
||||||
template_loader = FileSystemLoader(searchpath=".")
|
template_loader = FileSystemLoader(searchpath=".")
|
||||||
template_env = Environment(loader=template_loader)
|
template_env = Environment(loader=template_loader)
|
||||||
self.tpl = template_env.get_template(kb_html_tpl)
|
self.tpl = template_env.get_template(kb_html_tpl)
|
||||||
|
|
||||||
def get_template_vars(self):
|
def add_latency_stats(self, run_results):
|
||||||
rx_tp = float(self.results['http_throughput_kbytes'])
|
# init a column list
|
||||||
rx_tp = round(rx_tp * 8 / (1024 * 1024), 1)
|
column = [run_results['filename']]
|
||||||
tpl_vars = get_progress_vars('rx', rx_tp, 10)
|
for latency_pair in run_results['latency_stats']:
|
||||||
self.results.update(tpl_vars)
|
# convert from usec to msec
|
||||||
|
|
||||||
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
|
|
||||||
latency_ms = latency_pair[1] / 1000
|
latency_ms = latency_pair[1] / 1000
|
||||||
mod_latency_list.append([x_value, latency_ms])
|
column.append(latency_ms)
|
||||||
self.results['latency_stats'] = mod_latency_list
|
# and append that column to the latency list
|
||||||
return self.results
|
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):
|
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:
|
with open(dest_file, 'w') as dest:
|
||||||
print('Generating chart drawing code to ' + dest_file + '...')
|
print('Generating chart drawing code to ' + dest_file + '...')
|
||||||
tpl_vars = self.get_template_vars()
|
output = self.tpl.render(kbstats=kbstats)
|
||||||
format_numbers(tpl_vars)
|
|
||||||
output = self.tpl.render(tpl_vars)
|
|
||||||
dest.write(output)
|
dest.write(output)
|
||||||
|
|
||||||
def gen_chart(res_file, chart_dest, browser):
|
def get_display_file_name(filename):
|
||||||
if not os.path.isfile(res_file):
|
res = os.path.basename(filename)
|
||||||
print('Error: No such file %s: ' + res_file)
|
# remove extension
|
||||||
sys.exit(1)
|
res, _ = os.path.splitext(res)
|
||||||
with open(res_file) as data_file:
|
return res
|
||||||
results = json.load(data_file)
|
|
||||||
chart = KbReport(results)
|
def gen_chart(file_list, chart_dest, browser):
|
||||||
print('Generating report to ' + chart_dest + '...')
|
|
||||||
chart.plot(chart_dest)
|
data_list = []
|
||||||
if browser:
|
for res_file in file_list:
|
||||||
url = 'file://' + os.path.abspath(chart_dest)
|
print 'processing: ' + res_file
|
||||||
webbrowser.open(url, new=2)
|
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):
|
def get_absolute_path_for_file(file_name):
|
||||||
'''
|
'''
|
||||||
@ -131,8 +182,8 @@ if __name__ == '__main__':
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='print version of this script and exit')
|
help='print version of this script and exit')
|
||||||
|
|
||||||
parser.add_argument(dest='file',
|
parser.add_argument(dest='files',
|
||||||
help='KloudBuster json result file',
|
help='KloudBuster json result file', nargs="+",
|
||||||
metavar='<file>')
|
metavar='<file>')
|
||||||
|
|
||||||
opts = parser.parse_args()
|
opts = parser.parse_args()
|
||||||
@ -141,4 +192,4 @@ if __name__ == '__main__':
|
|||||||
print('Version ' + __version__)
|
print('Version ' + __version__)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
gen_chart(opts.file, opts.chart, opts.browser)
|
gen_chart(opts.files, opts.chart, opts.browser)
|
||||||
|
@ -4,6 +4,11 @@
|
|||||||
<script src="http://code.jquery.com/jquery.min.js"></script>
|
<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" />
|
<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://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">
|
<meta charset="utf-8">
|
||||||
<script type="text/javascript"
|
<script type="text/javascript"
|
||||||
src="https://www.google.com/jsapi?autoload={
|
src="https://www.google.com/jsapi?autoload={
|
||||||
@ -17,12 +22,9 @@
|
|||||||
google.setOnLoadCallback(drawChart);
|
google.setOnLoadCallback(drawChart);
|
||||||
|
|
||||||
function drawChart() {
|
function drawChart() {
|
||||||
var data = google.visualization.arrayToDataTable([
|
var data = google.visualization.arrayToDataTable(
|
||||||
['Percentile', 'Latency (msec)'],
|
{{kbstats.latency_tuples}}
|
||||||
{% for latency_list in latency_stats %}
|
);
|
||||||
{{latency_list}},
|
|
||||||
{% endfor %}
|
|
||||||
]);
|
|
||||||
var ticks =
|
var ticks =
|
||||||
[{v:2,f:'50%'},
|
[{v:2,f:'50%'},
|
||||||
{v:4,f:'75%'},
|
{v:4,f:'75%'},
|
||||||
@ -36,8 +38,11 @@
|
|||||||
title: 'HTTP Requests Latency Distribution',
|
title: 'HTTP Requests Latency Distribution',
|
||||||
curveType: 'function',
|
curveType: 'function',
|
||||||
hAxis: {title: 'Percentile', minValue: 0, logScale: true, ticks:ticks },
|
hAxis: {title: 'Percentile', minValue: 0, logScale: true, ticks:ticks },
|
||||||
vAxis: {title: 'Latency (ms)', minValue: 0, logScale: true, minValue: 0 },
|
vAxis: {title: 'Latency (ms)', minValue: 0, logScale: true,
|
||||||
legend: { position: 'none' }
|
gridlines: {count: 8},
|
||||||
|
minorGridlines: {count: 1},
|
||||||
|
minValue: 0 },
|
||||||
|
legend: { position: 'bottom' }
|
||||||
};
|
};
|
||||||
|
|
||||||
var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
|
var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
|
||||||
@ -54,7 +59,7 @@
|
|||||||
return function (dt, row) {
|
return function (dt, row) {
|
||||||
var percentile = 100.0 - (100.0/dt.getValue(row, 0));
|
var percentile = 100.0 - (100.0/dt.getValue(row, 0));
|
||||||
return dt.getColumnLabel(j) + ': ' +
|
return dt.getColumnLabel(j) + ': ' +
|
||||||
percentile.toPrecision(6) +
|
percentile +
|
||||||
'\%\'ile = ' + dt.getValue(row, j) + ' msec'
|
'\%\'ile = ' + dt.getValue(row, j) + ' msec'
|
||||||
}
|
}
|
||||||
})(i)
|
})(i)
|
||||||
@ -64,6 +69,17 @@
|
|||||||
view.setColumns(columns);
|
view.setColumns(columns);
|
||||||
chart.draw(view, options);
|
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>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -72,58 +88,42 @@
|
|||||||
<div class="panel panel-primary">
|
<div class="panel panel-primary">
|
||||||
<div class="panel-heading"><h3>HTTP Scale Results</h3></div>
|
<div class="panel-heading"><h3>HTTP Scale Results</h3></div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<h4>
|
|
||||||
|
{% if kbstats.table %}
|
||||||
<div class="row"><!-- ROW1 -->
|
<div class="row"><!-- ROW1 -->
|
||||||
<div class="col-md-6">
|
<table id="runs_table" class="table hover display compact" cellspacing="0" width="100%">
|
||||||
<ul class="list-group">
|
<thead>
|
||||||
<li class="list-group-item list-group-item-success">
|
<tr>
|
||||||
<span class="badge">{{ total_connections }}</span>
|
{% for col_name in kbstats.table.col_names %}
|
||||||
Total Concurrent Connections
|
<th>{{col_name}}</th>
|
||||||
</li>
|
{% endfor %}
|
||||||
<li class="list-group-item list-group-item-success">
|
</tr>
|
||||||
<span class="badge">{{ total_server_vms }}</span>
|
</thead>
|
||||||
Server VM count
|
|
||||||
</li>
|
<tbody>
|
||||||
<li class="list-group-item list-group-item-success">
|
{% for row in kbstats.table.rows %}
|
||||||
<span class="badge">{{ http_total_req }}</span>
|
<tr>
|
||||||
Total Requests/Responses
|
{% for cell in row.cells %}
|
||||||
</li>
|
<td>{{cell}}</td>
|
||||||
<li class="list-group-item list-group-item-success">
|
{% endfor %}
|
||||||
<span class="badge">{{ http_sock_err }}</span>
|
<td>
|
||||||
Total Socket Errors
|
<div class="progress-bar" role="progressbar" style="width:{{row.rx.percent}}%;min-width: 20px">
|
||||||
</li>
|
<span>{{ row.rx.value }}</span>
|
||||||
</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>
|
|
||||||
</div>
|
</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="row" align="center"><!-- LATENCY CHART ROW -->
|
||||||
<div class="well-sm">
|
<div id="curve_chart" style="width: 900px; height: 500px"></div>
|
||||||
<div class="progress">
|
</div><!-- LATENCY CHART ROW -->
|
||||||
<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><!--/panel-body-->
|
</div><!--/panel-body-->
|
||||||
</div><!--/panel-->
|
</div><!--/panel-->
|
||||||
</div><!--/container-->
|
</div><!--/container-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user