diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 2df9221..7c62b1b 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -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 diff --git a/kloudbuster/kb_gen_chart.py b/kloudbuster/kb_gen_chart.py deleted file mode 100755 index 92091d3..0000000 --- a/kloudbuster/kb_gen_chart.py +++ /dev/null @@ -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 = [' Description', - ' Connections', - ' Server VMs', - ' Requests', - ' Socket errors', - ' RPS measured', - ' RPS requested', - ' 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='') - - 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='') - - parser.add_argument(dest='files', - help='KloudBuster json result file', nargs="+", - metavar='') - - 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() diff --git a/kloudbuster/kb_runner_storage.py b/kloudbuster/kb_runner_storage.py index 03a4dde..25ca45e 100644 --- a/kloudbuster/kb_runner_storage.py +++ b/kloudbuster/kb_runner_storage.py @@ -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: diff --git a/kloudbuster/kb_tpl.jinja b/kloudbuster/kb_tpl.jinja deleted file mode 100644 index 23009fd..0000000 --- a/kloudbuster/kb_tpl.jinja +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - -
-

KloudBuster Report

-
-

HTTP Scale Results

-
- -{% if kbstats.table %} -
- - - -{% for col_name in kbstats.table.col_names %} - -{% endfor %} - - - - -{% for row in kbstats.table.rows %} - -{% for cell in row.cells %} - -{% endfor %} - - -{% endfor %} - -
{{col_name}}
{{cell}} -
- {{ row.rx.value }} -
-
-
-{% endif %} - - -
-
-
-
-
-
- - diff --git a/kloudbuster/kloudbuster.py b/kloudbuster/kloudbuster.py index 9f518c4..230d410 100755 --- a/kloudbuster/kloudbuster.py +++ b/kloudbuster/kloudbuster.py @@ -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):