aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--setup.py6
-rw-r--r--yardstick/plot/__init__.py0
-rw-r--r--yardstick/plot/plotter.py311
3 files changed, 316 insertions, 1 deletions
diff --git a/setup.py b/setup.py
index a346f5765..f73094ac1 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@ setup(
'benchmark/scenarios/networking/*.bash',
'benchmark/scenarios/storage/*.bash',
'resources/files/*'
- ]
+ ]
},
url="https://www.opnfv.org",
install_requires=["backport_ipaddress", # remove with python3
@@ -30,9 +30,13 @@ setup(
"paramiko",
"six"
],
+ extras_require={
+ 'plot': ["matplotlib>=1.4.2"]
+ },
entry_points={
'console_scripts': [
'yardstick=yardstick.main:main',
+ 'yardstick-plot=yardstick.plot.plotter:main [plot]'
],
},
scripts=['tools/yardstick-img-modify']
diff --git a/yardstick/plot/__init__.py b/yardstick/plot/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/yardstick/plot/__init__.py
diff --git a/yardstick/plot/plotter.py b/yardstick/plot/plotter.py
new file mode 100644
index 000000000..f3fb75d3e
--- /dev/null
+++ b/yardstick/plot/plotter.py
@@ -0,0 +1,311 @@
+#!/usr/bin/env python
+
+##############################################################################
+# Copyright (c) 2015 Ericsson AB and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+''' yardstick-plot - a command line tool for visualizing results from the
+ output file of yardstick framework.
+
+ Example invocation:
+ $ yardstick-plot -i /tmp/yardstick.out -o /tmp/plots/
+'''
+
+import argparse
+import json
+import os
+import sys
+import time
+import matplotlib.pyplot as plt
+import matplotlib.lines as mlines
+
+
+class Parser(object):
+ ''' Command-line argument and input file parser for yardstick-plot tool'''
+
+ def __init__(self):
+ self.data = {
+ 'ping': [],
+ 'pktgen': [],
+ 'iperf3': [],
+ 'fio': []
+ }
+ self.default_input_loc = "/tmp/yardstick.out"
+
+ def _get_parser(self):
+ '''get a command-line parser'''
+ parser = argparse.ArgumentParser(
+ prog='yardstick-plot',
+ description="A tool for visualizing results from yardstick. "
+ "Currently supports plotting graphs for output files "
+ "from tests: " + str(self.data.keys())
+ )
+ parser.add_argument(
+ '-i', '--input',
+ help="The input file name. If left unspecified then "
+ "it defaults to %s" % self.default_input_loc
+ )
+ parser.add_argument(
+ '-o', '--output-folder',
+ help="The output folder location. If left unspecified then "
+ "it defaults to <script_directory>/plots/"
+ )
+ return parser
+
+ def _add_record(self, record):
+ '''add record to the relevant scenario'''
+ runner_object = record['sargs']['runner']['object']
+ for test_type in self.data.keys():
+ if test_type in runner_object:
+ self.data[test_type].append(record)
+
+ def parse_args(self):
+ '''parse command-line arguments'''
+ parser = self._get_parser()
+ self.args = parser.parse_args()
+ return self.args
+
+ def parse_input_file(self):
+ '''parse the input test results file'''
+ if self.args.input:
+ input_file = self.args.input
+ else:
+ print("No input file specified, reading from %s"
+ % self.default_input_loc)
+ input_file = self.default_input_loc
+
+ try:
+ with open(input_file) as f:
+ for line in f:
+ record = json.loads(line)
+ self._add_record(record)
+ except IOError as e:
+ print(os.strerror(e.errno))
+ sys.exit(1)
+
+
+class Plotter(object):
+ '''Graph plotter for scenario-specific results from yardstick framework'''
+
+ def __init__(self, data, output_folder):
+ self.data = data
+ self.output_folder = output_folder
+ self.fig_counter = 1
+ self.colors = ['g', 'b', 'c', 'm', 'y']
+
+ def plot(self):
+ '''plot the graph(s)'''
+ for test_type in self.data.keys():
+ if self.data[test_type]:
+ plt.figure(self.fig_counter)
+ self.fig_counter += 1
+
+ plt.title(test_type, loc="left")
+ method_name = "_plot_" + test_type
+ getattr(self, method_name)(self.data[test_type])
+ self._save_plot(test_type)
+
+ def _save_plot(self, test_type):
+ '''save the graph to output folder'''
+ timestr = time.strftime("%Y%m%d-%H%M%S")
+ file_name = test_type + "_" + timestr + ".png"
+ if not self.output_folder:
+ curr_path = os.path.dirname(os.path.abspath(__file__))
+ self.output_folder = os.path.join(curr_path, "plots")
+ if not os.path.isdir(self.output_folder):
+ os.makedirs(self.output_folder)
+ new_file = os.path.join(self.output_folder, file_name)
+ plt.savefig(new_file)
+ print("Saved graph to " + new_file)
+
+ def _plot_ping(self, records):
+ '''ping test result interpretation and visualization on the graph'''
+ rtts = [r['benchmark']['data'] for r in records]
+ seqs = [r['benchmark']['sequence'] for r in records]
+
+ for i in range(0, len(rtts)):
+ # If SLA failed
+ if not rtts[i]:
+ rtts[i] = 0.0
+ plt.axvline(seqs[i], color='r')
+
+ # If there is a single data-point then display a bar-chart
+ if len(rtts) == 1:
+ plt.bar(1, rtts[0], 0.35, color=self.colors[0])
+ else:
+ plt.plot(seqs, rtts, self.colors[0]+'-')
+
+ self._construct_legend(['rtt'])
+ plt.xlabel("sequence number")
+ plt.xticks(seqs, seqs)
+ plt.ylabel("round trip time in milliseconds (rtt)")
+
+ def _plot_pktgen(self, records):
+ '''pktgen test result interpretation and visualization on the graph'''
+ flows = [r['benchmark']['data']['flows'] for r in records]
+ sent = [r['benchmark']['data']['packets_sent'] for r in records]
+ received = [int(r['benchmark']['data']['packets_received'])
+ for r in records]
+
+ for i in range(0, len(sent)):
+ # If SLA failed
+ if not sent[i] or not received[i]:
+ sent[i] = 0.0
+ received[i] = 0.0
+ plt.axvline(flows[i], color='r')
+
+ ppm = [1000000.0*(i - j)/i for i, j in zip(sent, received)]
+
+ # If there is a single data-point then display a bar-chart
+ if len(ppm) == 1:
+ plt.bar(1, ppm[0], 0.35, color=self.colors[0])
+ else:
+ plt.plot(flows, ppm, self.colors[0]+'-')
+
+ self._construct_legend(['ppm'])
+ plt.xlabel("number of flows")
+ plt.ylabel("lost packets per million packets (ppm)")
+
+ def _plot_iperf3(self, records):
+ '''iperf3 test result interpretation and visualization on the graph'''
+ intervals = []
+ for r in records:
+ # If did not fail the SLA
+ if r['benchmark']['data']:
+ intervals.append(r['benchmark']['data']['intervals'])
+ else:
+ intervals.append(None)
+
+ kbps = [0]
+ seconds = [0]
+ for i, val in enumerate(intervals):
+ if val:
+ for j, _ in enumerate(intervals):
+ kbps.append(val[j]['sum']['bits_per_second']/1000)
+ seconds.append(seconds[-1] + val[j]['sum']['seconds'])
+ else:
+ kbps.append(0.0)
+ # Don't know how long the failed test took, add 1 second
+ # TODO more accurate solution or replace x-axis from seconds
+ # to measurement nr
+ seconds.append(seconds[-1] + 1)
+ plt.axvline(seconds[-1], color='r')
+
+ self._construct_legend(['bandwidth'])
+ plt.plot(seconds[1:], kbps[1:], self.colors[0]+'-')
+ plt.xlabel("time in seconds")
+ plt.ylabel("bandwidth in Kb/s")
+
+ def _plot_fio(self, records):
+ '''fio test result interpretation and visualization on the graph'''
+ rw_types = [r['sargs']['options']['rw'] for r in records]
+ seqs = [x for x in range(1, len(records) + 1)]
+ data = {}
+
+ for i in range(0, len(records)):
+ is_r_type = rw_types[i] == "read" or rw_types[i] == "randread"
+ is_w_type = rw_types[i] == "write" or rw_types[i] == "randwrite"
+ is_rw_type = rw_types[i] == "rw" or rw_types[i] == "randrw"
+
+ if is_r_type or is_rw_type:
+ # Remove trailing 'usec' and convert to float
+ data['read_lat'] = \
+ [r['benchmark']['data']['read_lat'][:-4] for r in records]
+ data['read_lat'] = \
+ [float(i) for i in data['read_lat']]
+ # Remove trailing 'KB/s' and convert to float
+ data['read_bw'] = \
+ [r['benchmark']['data']['read_bw'][:-4] for r in records]
+ data['read_bw'] = \
+ [float(i) for i in data['read_bw']]
+ # Convert to int
+ data['read_iops'] = \
+ [r['benchmark']['data']['read_iops'] for r in records]
+ data['read_iops'] = \
+ [int(i) for i in data['read_iops']]
+
+ if is_w_type or is_rw_type:
+ data['write_lat'] = \
+ [r['benchmark']['data']['write_lat'][:-4] for r in records]
+ data['write_lat'] = \
+ [float(i) for i in data['write_lat']]
+
+ data['write_bw'] = \
+ [r['benchmark']['data']['write_bw'][:-4] for r in records]
+ data['write_bw'] = \
+ [float(i) for i in data['write_bw']]
+
+ data['write_iops'] = \
+ [r['benchmark']['data']['write_iops'] for r in records]
+ data['write_iops'] = \
+ [int(i) for i in data['write_iops']]
+
+ # Divide the area into 3 subplots, sharing a common x-axis
+ fig, axl = plt.subplots(3, sharex=True)
+ axl[0].set_title("fio", loc="left")
+
+ self._plot_fio_helper(data, seqs, 'read_bw', self.colors[0], axl[0])
+ self._plot_fio_helper(data, seqs, 'write_bw', self.colors[1], axl[0])
+ axl[0].set_ylabel("Bandwidth in KB/s")
+
+ self._plot_fio_helper(data, seqs, 'read_iops', self.colors[0], axl[1])
+ self._plot_fio_helper(data, seqs, 'write_iops', self.colors[1], axl[1])
+ axl[1].set_ylabel("IOPS")
+
+ self._plot_fio_helper(data, seqs, 'read_lat', self.colors[0], axl[2])
+ self._plot_fio_helper(data, seqs, 'write_lat', self.colors[1], axl[2])
+ axl[2].set_ylabel("Latency in " + u"\u00B5s")
+
+ self._construct_legend(['read', 'write'], obj=axl[0])
+ plt.xlabel("Sequence number")
+ plt.xticks(seqs, seqs)
+
+ def _plot_fio_helper(self, data, seqs, key, bar_color, axl):
+ '''check if measurements exist for a key and then plot the
+ data to a given subplot'''
+ if key in data:
+ if len(data[key]) == 1:
+ axl.bar(0.1, data[key], 0.35, color=bar_color)
+ else:
+ line_style = bar_color + '-'
+ axl.plot(seqs, data[key], line_style)
+
+ def _construct_legend(self, legend_texts, obj=plt):
+ '''construct legend for the plot or subplot'''
+ ci = 0
+ lines = []
+
+ for text in legend_texts:
+ line = mlines.Line2D([], [], color=self.colors[ci], label=text)
+ lines.append(line)
+ ci += 1
+
+ lines.append(mlines.Line2D([], [], color='r', label="SLA failed"))
+
+ getattr(obj, "legend")(
+ bbox_to_anchor=(0.25, 1.02, 0.75, .102),
+ loc=3,
+ borderaxespad=0.0,
+ ncol=len(lines),
+ mode="expand",
+ handles=lines
+ )
+
+
+def main():
+ parser = Parser()
+ args = parser.parse_args()
+ print("Parsing input file")
+ parser.parse_input_file()
+ print("Initializing plotter")
+ plotter = Plotter(parser.data, args.output_folder)
+ print("Plotting graph(s)")
+ plotter.plot()
+
+if __name__ == '__main__':
+ main()