Change the result JSON schema to include metadata

1. Change the result JSON schema to include metadata;
2. Automatic adjust the timeout for initializing volumes;
3. Remove the old kb_gen_chart.py script;
4. Doc update for storage test on ephemeral disks;

Change-Id: Iac9e910efb722e949f4fe1c8d8f100a54dfc4ea5
This commit is contained in:
Yichen Wang 2016-03-24 14:29:31 -07:00
parent 3727636ac0
commit 8ca72c93f4
5 changed files with 26 additions and 365 deletions

View File

@ -314,14 +314,19 @@ This section controls how the Storage tests will be performed. All the fields
are self-explained, and you can create your own test case with customized
parameters.
* **client:volume_size**
* **client:storage_target**
This controls the size of the Cinder volume to be attached to each VM instance.
(in GB)
KloudBuster supports to test the storage performance on Cinder volumes or
ephemeral disks. Specify the testing target here.
* **client:disk_size**
This controls the size of the Cinder volume or ephemeral disk to be attached to
each VM instance. (in GB)
* **client:io_file_size**
This controls the size of the test file to be used for storage testing. (in GiB)
This controls the size of the test file to be used for storage testing. (in GB)
Advanced Features

View File

@ -1,227 +0,0 @@
#!/usr/bin/env python
# Copyright 2014 Cisco Systems, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# A tool that can represent KloudBuster json results in
# a nicer form using HTML5, bootstrap.js and the Google Charts Javascript library
#
import argparse
import json
import os
import os.path
import re
import sys
import webbrowser
from jinja2 import Environment
from jinja2 import FileSystemLoader
__version__ = '0.0.1'
kb_html_tpl = "./kb_tpl.jinja"
def get_formatted_num(value):
return '{:,}'.format(value)
# table column names
col_names = ['<i class="glyphicon glyphicon-file"></i> Description',
'<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 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, data_list, line_rate):
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)
self.line_rate = line_rate
def add_latency_stats(self, run_results):
# init a column list
column = [run_results['description']]
for latency_pair in run_results['latency_stats']:
# convert from usec to msec
latency_ms = latency_pair[1] / 1000
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']
rx_tp = float(run_res['http_throughput_kbytes'])
rx_tp = round(rx_tp * 8 / (1024 * 1024), 1)
cells = [run_res['description'],
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'] + run_res['http_sock_timeout']),
get_formatted_num(run_res['http_rps']),
get_formatted_num(rps_max)]
row = {'cells': cells,
'rx': {'value': rx_tp,
'max': self.line_rate,
'percent': (rx_tp * 100) / self.line_rate}}
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'
}
with open(dest_file, 'w') as dest:
print('Generating chart drawing code to ' + dest_file + '...')
output = self.tpl.render(kbstats=kbstats)
dest.write(output)
def get_display_file_name(filename):
res = os.path.basename(filename)
# remove extension
res, _ = os.path.splitext(res)
return res
def guess_line_rate(data_list):
max_tp_kb = 0
for data_list in data_list:
max_tp_kb = max(max_tp_kb, data_list['http_throughput_kbytes'])
max_tp_gb = (max_tp_kb * 8) / (1000 * 1000)
# typical GE line rates are 10, 40 and 100
if max_tp_gb < 10:
return 10
if max_tp_gb < 40:
return 40
return 100
def gen_chart(file_list, chart_dest, browser, line_rate):
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)
for run in results:
des_str = get_display_file_name(res_file)
if 'description' in run:
stage = re.search(r'Stage (\d+)\:', run['description']).group(1)
des_str += ': Stage ' + stage
run['description'] = str(des_str)
data_list.append(run)
if not line_rate:
line_rate = guess_line_rate(data_list)
print 'Guessed line rate: %s Gbps.' % line_rate
chart = KbReport(data_list, line_rate)
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):
'''
Return the filename in absolute path for any file
passed as relateive path.
'''
abs_file = os.path.dirname(os.path.abspath(__file__))
return abs_file + '/' + file_name
def main():
parser = argparse.ArgumentParser(description='KloudBuster Chart Generator V' + __version__)
parser.add_argument('-c', '--chart', dest='chart',
action='store', required=True,
help='create and save chart in html file',
metavar='<file>')
parser.add_argument('-b', '--browser', dest='browser',
action='store_true',
default=False,
help='display (-c) chart in the browser')
parser.add_argument('-v', '--version', dest='version',
default=False,
action='store_true',
help='print version of this script and exit')
parser.add_argument('-l', '--line-rate', dest='line_rate',
action='store',
default=0,
type=int,
help='line rate in Gbps (default=10)',
metavar='<rate-Gbps>')
parser.add_argument(dest='files',
help='KloudBuster json result file', nargs="+",
metavar='<file>')
opts = parser.parse_args()
if opts.version:
print('Version ' + __version__)
sys.exit(0)
gen_chart(opts.files, opts.chart, opts.browser, opts.line_rate)
if __name__ == '__main__':
main()

View File

@ -70,7 +70,9 @@ class KBRunner_Storage(KBRunner):
msg = "Stage %d: %d VM(s)%s%s" % (stage, vm_count, iops_str, tp_str)
return msg
def init_volume(self, active_range, timeout=30):
def init_volume(self, active_range):
# timeout is calculated as 30s/GB
timeout = 30 * self.config.io_file_size
parameter = {'size': str(self.config.io_file_size) + 'GiB'}
parameter['mkfs'] = True if self.config.storage_target == 'volume' else False
@ -102,7 +104,7 @@ class KBRunner_Storage(KBRunner):
if self.config.storage_target == 'volume':
LOG.info("Initializing volume and setting up filesystem...")
else:
LOG.info("Initializing ephermeral disk...")
LOG.info("Initializing ephemeral disk...")
self.init_volume(active_range)
if self.config.prompt_before_run:

View File

@ -1,129 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<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={
'modules':[{
'name':'visualization',
'version':'1',
'packages':['corechart']
}]
}"></script>
<script type="text/javascript">
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable(
{{kbstats.latency_tuples}}
);
var ticks =
[{v:2,f:'50%'},
{v:4,f:'75%'},
{v:10,f:'90%'},
{v:100,f:'99%'},
{v:1000,f:'99.9%'},
{v:10000,f:'99.99%'},
{v:100000,f:'99.999%'},
];
var options = {
title: 'HTTP Requests Latency Distribution',
curveType: 'function',
hAxis: {title: 'Percentile', minValue: 0, logScale: true, ticks:ticks },
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'));
// add tooptips with correct percentile text to data:
var columns = [0];
for (var i = 1; i < data.getNumberOfColumns(); i++) {
columns.push(i);
columns.push({
type: 'string',
properties: {
role: 'tooltip'
},
calc: (function (j) {
return function (dt, row) {
var percentile = 100.0 - (100.0/dt.getValue(row, 0));
return dt.getColumnLabel(j) + ': ' +
percentile +
'\%\'ile = ' + dt.getValue(row, j) + ' msec'
}
})(i)
});
}
var view = new google.visualization.DataView(data);
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>
<div class="container-fluid">
<h2><i class="glyphicon glyphicon-dashboard"></i> KloudBuster Report</h2>
<div class="panel panel-primary">
<div class="panel-heading"><h3>HTTP Scale Results</h3></div>
<div class="panel-body">
{% if kbstats.table %}
<div class="row"><!-- ROW1 -->
<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>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div><!-- ROW1 -->
{% endif %}
<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>

View File

@ -14,6 +14,7 @@
# under the License.
from concurrent.futures import ThreadPoolExecutor
import datetime
import json
import os
import sys
@ -257,7 +258,7 @@ class KloudBuster(object):
LOG.info('Automatically setting "use_floatingip" to True for server cloud...')
self.kb_proxy = None
self.final_result = []
self.final_result = {}
self.server_vm_create_thread = None
self.client_vm_create_thread = None
self.kb_runner = None
@ -414,6 +415,15 @@ class KloudBuster(object):
not self.tenants_list['client'] else self.testing_kloud.flavor_to_use
ins.boot_info['user_data'] = str(ins.user_data)
def gen_metadata(self):
self.final_result = {}
self.final_result['time'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.final_result['test_mode'] = 'storage' if self.storage_mode else 'http'
if self.storage_mode:
self.final_result['storage_target'] = self.client_cfg.storage_target
self.final_result['version'] = __version__
self.final_result['kb_result'] = []
def run(self):
try:
self.stage()
@ -528,11 +538,11 @@ class KloudBuster(object):
self.print_provision_info()
def run_test(self, test_only=False):
self.final_result = []
self.gen_metadata()
self.kb_runner.config = self.client_cfg
# Run the runner to perform benchmarkings
for run_result in self.kb_runner.run(test_only):
self.final_result.append(self.kb_runner.tool_result)
self.final_result['kb_result'].append(self.kb_runner.tool_result)
LOG.info('SUMMARY: %s' % self.final_result)
def stop_test(self):