Added iperf executor with chart feature

Change-Id: I5dd6c56c615c86a4fc3400a951d9739205cff604
This commit is contained in:
Ilya Shakhat 2015-02-24 19:28:37 +03:00 committed by Ilya Shakhat
parent de78bbdcff
commit f5c694b9cd
7 changed files with 378 additions and 38 deletions

@ -17,8 +17,8 @@ To install:
3. ``shaker-image-builder`` - builds image for agent VMs inside OpenStack
Note: image builder is able to create Nova flavor optimized for the image and this requires
admin user privileges. However if the flavor is already exists then it can be provided via
``flavor-name`` config parameter and the tool executed from an ordinary user.
admin user privileges. However if the flavor is already exists then it can be provided via
``flavor-name`` config parameter and the tool executed from an ordinary user.
How to run
----------

@ -1,27 +1,25 @@
description:
This scenario launches pairs of VMs in the same private network
This scenario launches pairs of VMs in the same private network. Every VM is
hosted on a separate compute node.
deployment:
template: scenarios/networking/same_net.hot
template_parameters:
image: shaker-image
flavor: shaker-flavor
private_net_cidr: 10.0.0.0/24
vm_accommodation: [pair, single_room]
execution:
size: 'quadratic_progression'
tests:
-
title: iperf TCP test
class: iperf
title: Iperf TCP test
class: iperf_graph
time: 60
-
title: Iperf UDP 5 threads
class: iperf
udp: 1
mss: 1406
threads: 5
-
title: Netperf TCP_STREAM
class: netperf
method: TCP_STREAM
time: 60

@ -13,6 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import collections
import csv
from shaker.openstack.common import log as logging
@ -63,21 +66,66 @@ class IperfExecutor(BaseExecutor):
def get_command(self):
target = self.agent['slave']['ip']
mss = self.test_definition.get('mss')
interval = self.test_definition.get('interval')
return ('sudo nice -n -20 iperf --client %(target)s --format m'
'%(mss)s --len %(bs)s --print_mss --nodelay'
'%(udp)s --time %(time)s --parallel %(threads)s' %
'%(mss)s --len %(bs)s --nodelay'
'%(udp)s --time %(time)s --parallel %(threads)s'
'%(css)s %(interval)s' %
dict(target=self.test_definition.get('target') or target,
mss=mss and '--mss %s' % mss or '',
mss=mss and ' --mss %s' % mss or '',
bs=self.test_definition.get('buffer_size') or '8k',
udp=self.test_definition.get('udp') and '--udp' or '',
udp=self.test_definition.get('udp') and ' --udp' or '',
threads=self.test_definition.get('threads') or 1,
time=self.test_definition.get('time') or 60))
time=self.test_definition.get('time') or 60,
css=self.test_definition.get('css') and ' -y C' or '',
interval=interval and '--interval %s' % interval or ''))
class IperfGraphExecutor(IperfExecutor):
def get_command(self):
self.test_definition['css'] = True
self.test_definition['interval'] = '1'
return super(IperfGraphExecutor, self).get_command()
def process_reply(self, message):
result = super(IperfGraphExecutor, self).process_reply(message)
samples = collections.defaultdict(list)
for row in csv.reader(result['stdout'].split('\n')):
if row:
thread = row[5]
samples[thread].append(dict(
time=float(row[6].split('-')[1]),
transfer=int(row[7]),
bandwidth=int(row[8]),
))
result['samples'] = samples
# calc max, min, avg per thread
bandwidth_max = collections.defaultdict(float)
bandwidth_min = collections.defaultdict(float)
bandwidth_avg = collections.defaultdict(float)
for thread, data in samples.items():
arr = [s['bandwidth'] for s in samples[thread]]
bandwidth_max[thread] = max(arr)
bandwidth_min[thread] = min(arr)
bandwidth_avg[thread] = sum(arr) / len(arr)
result['bandwidth_max'] = bandwidth_max
result['bandwidth_min'] = bandwidth_min
result['bandwidth_avg'] = bandwidth_avg
return result
EXECUTORS = {
'shell': ShellExecutor,
'netperf': NetperfExecutor,
'iperf': IperfExecutor,
'iperf_graph': IperfGraphExecutor,
'netperf_wrapper': NetperfWrapperExecutor,
'_default': ShellExecutor,
}

@ -14,31 +14,16 @@
# limitations under the License.
import sys
import uuid
import jinja2
from shaker.engine import utils
def _add_uuids(d):
for k, v in d.items():
if isinstance(v, dict):
_add_uuids(v)
elif isinstance(v, list):
for i in v:
if isinstance(i, dict):
_add_uuids(i)
else:
d['uuid'] = uuid.uuid4()
def generate_report(report_template, report_filename, data):
template = utils.read_file(report_template)
compiled_template = jinja2.Template(template)
_add_uuids(data)
rendered_template = compiled_template.render(dict(report=data))
if report_filename:

@ -15,6 +15,11 @@
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.9/c3.min.js"></script>
<style type="text/css">
body {
@ -79,6 +84,224 @@
font-weight: bold;
}
/*-- Chart --*/
.c3 svg {
font: 10px sans-serif;
}
.c3 path, .c3 line {
fill: none;
stroke: #000;
}
.c3 text {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.c3-legend-item-tile,
.c3-xgrid-focus,
.c3-ygrid,
.c3-event-rect,
.c3-bars path {
shape-rendering: crispEdges;
}
.c3-chart-arc path {
stroke: #fff;
}
.c3-chart-arc text {
fill: #fff;
font-size: 13px;
}
/*-- Axis --*/
.c3-axis-x .tick {
}
.c3-axis-x-label {
}
.c3-axis-y .tick {
}
.c3-axis-y-label {
}
.c3-axis-y2 .tick {
}
.c3-axis-y2-label {
}
/*-- Grid --*/
.c3-grid line {
stroke: #aaa;
}
.c3-grid text {
fill: #aaa;
}
.c3-xgrid, .c3-ygrid {
stroke-dasharray: 3 3;
}
.c3-xgrid-focus {
}
/*-- Text on Chart --*/
.c3-text {
}
.c3-text.c3-empty {
fill: #808080;
font-size: 2em;
}
/*-- Line --*/
.c3-line {
stroke-width: 1px;
}
/*-- Point --*/
.c3-circle._expanded_ {
stroke-width: 1px;
stroke: white;
}
.c3-selected-circle {
fill: white;
stroke-width: 2px;
}
/*-- Bar --*/
.c3-bar {
stroke-width: 0;
}
.c3-bar._expanded_ {
fill-opacity: 0.75;
}
/*-- Arc --*/
.c3-chart-arcs-title {
font-size: 1.3em;
}
/*-- Focus --*/
.c3-target.c3-focused {
opacity: 1;
}
.c3-target.c3-focused path.c3-line, .c3-target.c3-focused path.c3-step {
stroke-width: 2px;
}
.c3-target.c3-defocused {
opacity: 0.3 !important;
}
/*-- Region --*/
.c3-region {
fill: steelblue;
fill-opacity: .1;
}
/*-- Brush --*/
.c3-brush .extent {
fill-opacity: .1;
}
/*-- Select - Drag --*/
.c3-dragarea {
}
/*-- Legend --*/
.c3-legend-item {
font-size: 12px;
}
.c3-legend-item-hidden {
opacity: 0.15;
}
.c3-legend-background {
opacity: 0.75;
fill: white;
stroke: lightgray;
stroke-width: 1
}
/*-- Tooltip --*/
.c3-tooltip-container {
z-index: 10;
}
.c3-tooltip {
border-collapse:collapse;
border-spacing:0;
background-color:#fff;
empty-cells:show;
-webkit-box-shadow: 7px 7px 12px -9px rgb(119,119,119);
-moz-box-shadow: 7px 7px 12px -9px rgb(119,119,119);
box-shadow: 7px 7px 12px -9px rgb(119,119,119);
opacity: 0.9;
}
.c3-tooltip tr {
border:1px solid #CCC;
}
.c3-tooltip th {
background-color: #aaa;
font-size:14px;
padding:2px 5px;
text-align:left;
color:#FFF;
}
.c3-tooltip td {
font-size:13px;
padding: 3px 6px;
background-color:#fff;
border-left:1px dotted #999;
}
.c3-tooltip td > span {
display: inline-block;
width: 10px;
height: 10px;
margin-right: 6px;
}
.c3-tooltip td.value{
text-align: right;
}
.c3-area {
stroke-width: 0;
opacity: 0.2;
}
.c3-chart-arcs .c3-chart-arcs-background {
fill: #e0e0e0;
stroke: none;
}
.c3-chart-arcs .c3-chart-arcs-gauge-unit {
fill: #000;
font-size: 16px;
}
.c3-chart-arcs .c3-chart-arcs-gauge-max {
fill: #777;
}
.c3-chart-arcs .c3-chart-arcs-gauge-min {
fill: #777;
}
.c3-chart-arc .c3-gauge-value {
fill: #000;
/* font-size: 28px !important;*/
}
</style>
</head>
@ -116,7 +339,7 @@
{% for test in tests %}
<li><a href="#test-{{ test.definition.uuid }}" data-toggle="tab">
{% if test.definition.title %}
{{ test.definition.title|title }}
{{ test.definition.title }}
{% else %}
Test {{ test.definition }}
{% endif %}
@ -165,6 +388,33 @@
<h4>Agent {{ result_per_agent.agent.id }}
({{ result_per_agent.agent.ip }})</h4>
{% if result_per_agent.samples %}
{# <div>{{ result_per_agent.samples }}</div>#}
<div id="chart-{{ result_per_agent.uuid }}"></div>
<script type="application/javascript">
var chart = c3.generate({
bindto: '#chart-{{ result_per_agent.uuid }}',
data: {
x: 'x',
columns: [
{# {% set first_row = first(result_per_agent.samples) %}#}
{# ['x', {{ first_row|map(attribute='time')|join(', ') }}],#}
{% for thread, array in result_per_agent.samples.items() %}
['x', {{ array|map(attribute='time')|join(', ') }}],
['thread{{ thread }}', {{ array|map(attribute='bandwidth')|join(', ') }}]
{% endfor %}
],
types: { thread3: 'area' }
},
axis: {
x: { label: 'time' },
y: { label: 'Bandwidth, bps' }
}
});
</script>
{% endif %}
{% if result_per_agent.command %}
<h5>Command:</h5>
<pre>{{ result_per_agent.command }}</pre>
@ -188,11 +438,5 @@
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
</body>
</html>

@ -14,6 +14,7 @@
# limitations under the License.
import time
import uuid
from oslo_config import cfg
import yaml
@ -175,11 +176,15 @@ def execute(execution, agents):
for a in selected_agents)
test_case_result = quorum.run_test_case(executors)
values = test_case_result.values()
for v in values:
v['uuid'] = uuid.uuid4()
results_per_iteration.append({
'agents': selected_agents,
'results_per_agent': test_case_result.values(),
'results_per_agent': values,
})
test['uuid'] = uuid.uuid4()
result.append({
'results_per_iteration': results_per_iteration,
'definition': test,

@ -0,0 +1,60 @@
# Copyright (c) 2015 Mirantis Inc.
#
# 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.
import testtools
from shaker.engine import executors
IP = '10.0.0.10'
AGENT = {'slave': {'ip': IP}}
class TestIperfGraphExecutor(testtools.TestCase):
def test_get_command(self):
executor = executors.IperfGraphExecutor({}, AGENT)
expected = ('sudo nice -n -20 iperf --client %s --format m '
'--len 8k --nodelay --time 60 --parallel 1 '
'-y C --interval 1') % IP
self.assertEqual(expected, executor.get_command())
def test_process_reply(self):
executor = executors.IperfGraphExecutor({}, AGENT)
message = {
'stdout': """
20150224134955,172.1.7.77,47351,172.1.76.77,5001,3,0.0-1.0,500686848,4005494784
20150224134956,172.1.7.77,47351,172.1.76.77,5001,3,1.0-2.0,516055040,4128440320
20150224134957,172.1.7.77,47351,172.1.76.77,5001,3,2.0-3.0,508436480,4067491840
"""
}
expected = {
'samples': {
'3': [
dict(time=1.0, transfer=500686848, bandwidth=4005494784),
dict(time=2.0, transfer=516055040, bandwidth=4128440320),
dict(time=3.0, transfer=508436480, bandwidth=4067491840),
]
},
'bandwidth_max': {'3': 4128440320},
'bandwidth_min': {'3': 4005494784},
'bandwidth_avg': {'3': (4005494784 + 4128440320 + 4067491840) / 3},
}
reply = executor.process_reply(message)
self.assertEqual(expected['samples'], reply['samples'])
self.assertEqual(expected['bandwidth_max'], reply['bandwidth_max'])
self.assertEqual(expected['bandwidth_min'], reply['bandwidth_min'])
self.assertEqual(expected['bandwidth_avg'], reply['bandwidth_avg'])