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:
parent
3727636ac0
commit
8ca72c93f4
@ -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
|
are self-explained, and you can create your own test case with customized
|
||||||
parameters.
|
parameters.
|
||||||
|
|
||||||
* **client:volume_size**
|
* **client:storage_target**
|
||||||
|
|
||||||
This controls the size of the Cinder volume to be attached to each VM instance.
|
KloudBuster supports to test the storage performance on Cinder volumes or
|
||||||
(in GB)
|
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**
|
* **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
|
Advanced Features
|
||||||
|
@ -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()
|
|
@ -70,7 +70,9 @@ class KBRunner_Storage(KBRunner):
|
|||||||
msg = "Stage %d: %d VM(s)%s%s" % (stage, vm_count, iops_str, tp_str)
|
msg = "Stage %d: %d VM(s)%s%s" % (stage, vm_count, iops_str, tp_str)
|
||||||
return msg
|
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 = {'size': str(self.config.io_file_size) + 'GiB'}
|
||||||
parameter['mkfs'] = True if self.config.storage_target == 'volume' else False
|
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':
|
if self.config.storage_target == 'volume':
|
||||||
LOG.info("Initializing volume and setting up filesystem...")
|
LOG.info("Initializing volume and setting up filesystem...")
|
||||||
else:
|
else:
|
||||||
LOG.info("Initializing ephermeral disk...")
|
LOG.info("Initializing ephemeral disk...")
|
||||||
self.init_volume(active_range)
|
self.init_volume(active_range)
|
||||||
|
|
||||||
if self.config.prompt_before_run:
|
if self.config.prompt_before_run:
|
||||||
|
@ -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>
|
|
@ -14,6 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@ -257,7 +258,7 @@ class KloudBuster(object):
|
|||||||
LOG.info('Automatically setting "use_floatingip" to True for server cloud...')
|
LOG.info('Automatically setting "use_floatingip" to True for server cloud...')
|
||||||
|
|
||||||
self.kb_proxy = None
|
self.kb_proxy = None
|
||||||
self.final_result = []
|
self.final_result = {}
|
||||||
self.server_vm_create_thread = None
|
self.server_vm_create_thread = None
|
||||||
self.client_vm_create_thread = None
|
self.client_vm_create_thread = None
|
||||||
self.kb_runner = None
|
self.kb_runner = None
|
||||||
@ -414,6 +415,15 @@ class KloudBuster(object):
|
|||||||
not self.tenants_list['client'] else self.testing_kloud.flavor_to_use
|
not self.tenants_list['client'] else self.testing_kloud.flavor_to_use
|
||||||
ins.boot_info['user_data'] = str(ins.user_data)
|
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):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
self.stage()
|
self.stage()
|
||||||
@ -528,11 +538,11 @@ class KloudBuster(object):
|
|||||||
self.print_provision_info()
|
self.print_provision_info()
|
||||||
|
|
||||||
def run_test(self, test_only=False):
|
def run_test(self, test_only=False):
|
||||||
self.final_result = []
|
self.gen_metadata()
|
||||||
self.kb_runner.config = self.client_cfg
|
self.kb_runner.config = self.client_cfg
|
||||||
# Run the runner to perform benchmarkings
|
# Run the runner to perform benchmarkings
|
||||||
for run_result in self.kb_runner.run(test_only):
|
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)
|
LOG.info('SUMMARY: %s' % self.final_result)
|
||||||
|
|
||||||
def stop_test(self):
|
def stop_test(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user