3 ##############################################################################
4 # Copyright (c) 2015 Ericsson AB and others.
6 # All rights reserved. This program and the accompanying materials
7 # are made available under the terms of the Apache License, Version 2.0
8 # which accompanies this distribution, and is available at
9 # http://www.apache.org/licenses/LICENSE-2.0
10 ##############################################################################
12 """ yardstick-plot - a command line tool for visualizing results from the
13 output file of yardstick framework.
16 $ yardstick-plot -i /tmp/yardstick.out -o /tmp/plots/
19 from __future__ import absolute_import
20 from __future__ import print_function
27 import matplotlib.lines as mlines
28 import matplotlib.pyplot as plt
29 from oslo_serialization import jsonutils
30 from six.moves import range
31 from six.moves import zip
35 """ Command-line argument and input file parser for yardstick-plot tool"""
44 self.default_input_loc = "/tmp/yardstick.out"
47 def _get_parser(self):
48 """get a command-line parser"""
49 parser = argparse.ArgumentParser(
50 prog='yardstick-plot',
51 description="A tool for visualizing results from yardstick. "
52 "Currently supports plotting graphs for output files "
53 "from tests: " + str(list(self.data.keys()))
57 help="The input file name. If left unspecified then "
58 "it defaults to %s" % self.default_input_loc
61 '-o', '--output-folder',
62 help="The output folder location. If left unspecified then "
63 "it defaults to <script_directory>/plots/"
67 def _add_record(self, record):
68 """add record to the relevant scenario"""
69 if "runner_id" in record and "benchmark" not in record:
70 obj_name = record["scenario_cfg"]["runner"]["object"]
71 self.scenarios[record["runner_id"]] = obj_name
73 runner_object = self.scenarios[record["runner_id"]]
74 for test_type in self.data:
75 if test_type in runner_object:
76 self.data[test_type].append(record)
79 """parse command-line arguments"""
80 parser = self._get_parser()
81 self.args = parser.parse_args()
84 def parse_input_file(self):
85 """parse the input test results file"""
87 input_file = self.args.input
89 print(("No input file specified, reading from %s"
90 % self.default_input_loc))
91 input_file = self.default_input_loc
94 with open(input_file) as f:
96 record = jsonutils.loads(line)
97 self._add_record(record)
99 print((os.strerror(e.errno)))
103 class Plotter(object):
104 """Graph plotter for scenario-specific results from yardstick framework"""
106 def __init__(self, data, output_folder):
108 self.output_folder = output_folder
110 self.colors = ['g', 'b', 'c', 'm', 'y']
113 """plot the graph(s)"""
114 for test_type in self.data.keys():
115 if self.data[test_type]:
116 plt.figure(self.fig_counter)
117 self.fig_counter += 1
119 plt.title(test_type, loc="left")
120 method_name = "_plot_" + test_type
121 getattr(self, method_name)(self.data[test_type])
122 self._save_plot(test_type)
124 def _save_plot(self, test_type):
125 """save the graph to output folder"""
126 timestr = time.strftime("%Y%m%d-%H%M%S")
127 file_name = test_type + "_" + timestr + ".png"
128 if not self.output_folder:
129 curr_path = os.path.dirname(os.path.abspath(__file__))
130 self.output_folder = os.path.join(curr_path, "plots")
131 if not os.path.isdir(self.output_folder):
132 os.makedirs(self.output_folder)
133 new_file = os.path.join(self.output_folder, file_name)
134 plt.savefig(new_file)
135 print(("Saved graph to " + new_file))
137 def _plot_ping(self, records):
138 """ping test result interpretation and visualization on the graph"""
139 rtts = [r['benchmark']['data']['rtt'] for r in records]
140 seqs = [r['benchmark']['sequence'] for r in records]
142 for i in range(0, len(rtts)):
146 plt.axvline(seqs[i], color='r')
148 # If there is a single data-point then display a bar-chart
150 plt.bar(1, rtts[0], 0.35, color=self.colors[0])
152 plt.plot(seqs, rtts, self.colors[0] + '-')
154 self._construct_legend(['rtt'])
155 plt.xlabel("sequence number")
156 plt.xticks(seqs, seqs)
157 plt.ylabel("round trip time in milliseconds (rtt)")
159 def _plot_pktgen(self, records):
160 """pktgen test result interpretation and visualization on the graph"""
161 flows = [r['benchmark']['data']['flows'] for r in records]
162 sent = [r['benchmark']['data']['packets_sent'] for r in records]
163 received = [int(r['benchmark']['data']['packets_received'])
166 for i in range(0, len(sent)):
168 if not sent[i] or not received[i]:
171 plt.axvline(flows[i], color='r')
173 ppm = [1000000.0 * (i - j) / i for i, j in zip(sent, received)]
175 # If there is a single data-point then display a bar-chart
177 plt.bar(1, ppm[0], 0.35, color=self.colors[0])
179 plt.plot(flows, ppm, self.colors[0] + '-')
181 self._construct_legend(['ppm'])
182 plt.xlabel("number of flows")
183 plt.ylabel("lost packets per million packets (ppm)")
185 def _plot_iperf3(self, records):
186 """iperf3 test result interpretation and visualization on the graph"""
189 # If did not fail the SLA
190 if r['benchmark']['data']:
191 intervals.append(r['benchmark']['data']['intervals'])
193 intervals.append(None)
197 for i, val in enumerate(intervals):
199 for j, _ in enumerate(intervals):
200 kbps.append(val[j]['sum']['bits_per_second'] / 1000)
201 seconds.append(seconds[-1] + val[j]['sum']['seconds'])
204 # Don't know how long the failed test took, add 1 second
205 # TODO more accurate solution or replace x-axis from seconds
207 seconds.append(seconds[-1] + 1)
208 plt.axvline(seconds[-1], color='r')
210 self._construct_legend(['bandwidth'])
211 plt.plot(seconds[1:], kbps[1:], self.colors[0] + '-')
212 plt.xlabel("time in seconds")
213 plt.ylabel("bandwidth in Kb/s")
215 def _plot_fio(self, records):
216 """fio test result interpretation and visualization on the graph"""
217 rw_types = [r['sargs']['options']['rw'] for r in records]
218 seqs = [x for x in range(1, len(records) + 1)]
221 for i in range(0, len(records)):
222 is_r_type = rw_types[i] == "read" or rw_types[i] == "randread"
223 is_w_type = rw_types[i] == "write" or rw_types[i] == "randwrite"
224 is_rw_type = rw_types[i] == "rw" or rw_types[i] == "randrw"
226 if is_r_type or is_rw_type:
229 [r['benchmark']['data']['read_lat'] for r in records]
231 [float(i) for i in data['read_lat']]
234 [r['benchmark']['data']['read_bw'] for r in records]
236 [int(i) for i in data['read_bw']]
238 data['read_iops'] = \
239 [r['benchmark']['data']['read_iops'] for r in records]
240 data['read_iops'] = \
241 [int(i) for i in data['read_iops']]
243 if is_w_type or is_rw_type:
244 data['write_lat'] = \
245 [r['benchmark']['data']['write_lat'] for r in records]
246 data['write_lat'] = \
247 [float(i) for i in data['write_lat']]
250 [r['benchmark']['data']['write_bw'] for r in records]
252 [int(i) for i in data['write_bw']]
254 data['write_iops'] = \
255 [r['benchmark']['data']['write_iops'] for r in records]
256 data['write_iops'] = \
257 [int(i) for i in data['write_iops']]
259 # Divide the area into 3 subplots, sharing a common x-axis
260 fig, axl = plt.subplots(3, sharex=True)
261 axl[0].set_title("fio", loc="left")
263 self._plot_fio_helper(data, seqs, 'read_bw', self.colors[0], axl[0])
264 self._plot_fio_helper(data, seqs, 'write_bw', self.colors[1], axl[0])
265 axl[0].set_ylabel("Bandwidth in KB/s")
267 self._plot_fio_helper(data, seqs, 'read_iops', self.colors[0], axl[1])
268 self._plot_fio_helper(data, seqs, 'write_iops', self.colors[1], axl[1])
269 axl[1].set_ylabel("IOPS")
271 self._plot_fio_helper(data, seqs, 'read_lat', self.colors[0], axl[2])
272 self._plot_fio_helper(data, seqs, 'write_lat', self.colors[1], axl[2])
273 axl[2].set_ylabel("Latency in " + u"\u00B5s")
275 self._construct_legend(['read', 'write'], obj=axl[0])
276 plt.xlabel("Sequence number")
277 plt.xticks(seqs, seqs)
279 def _plot_fio_helper(self, data, seqs, key, bar_color, axl):
280 """check if measurements exist for a key and then plot the
281 data to a given subplot"""
283 if len(data[key]) == 1:
284 axl.bar(0.1, data[key], 0.35, color=bar_color)
286 line_style = bar_color + '-'
287 axl.plot(seqs, data[key], line_style)
289 def _construct_legend(self, legend_texts, obj=plt):
290 """construct legend for the plot or subplot"""
294 for text in legend_texts:
295 line = mlines.Line2D([], [], color=self.colors[ci], label=text)
299 lines.append(mlines.Line2D([], [], color='r', label="SLA failed"))
301 getattr(obj, "legend")(
302 bbox_to_anchor=(0.25, 1.02, 0.75, .102),
313 args = parser.parse_args()
314 print("Parsing input file")
315 parser.parse_input_file()
316 print("Initializing plotter")
317 plotter = Plotter(parser.data, args.output_folder)
318 print("Plotting graph(s)")
322 if __name__ == '__main__':