diff --git a/genchart.py b/genchart.py new file mode 100755 index 0000000..9fe3141 --- /dev/null +++ b/genchart.py @@ -0,0 +1,273 @@ +# 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. +# + +# This is an example of tool that can represent VMTP json results in +# a nicer form using HTML and the Google Charts Javascript library +# + + +import argparse +import json +import os +import os.path +import sys +import webbrowser + +__version__ = '0.0.1' + +html_main_template = ''' + + + + + + + + +%s +
+ + +''' + +js_options_tpl = ''' + var options = { + chart: { + title: 'OpenStack Data Plane Performance (Gbps)', + subtitle: '%s', + } + }; +''' + +js_data_tpl = ''' + data = google.visualization.arrayToDataTable([ +%s + ]); + chart = new %s(document.getElementById('vmtp-%s%d')); + chart.draw(data, options); +''' + +div_tpl = ' ' * 6 + \ + '
' + \ + '
\n' + +# must be exact match +label_match = { + 'VM to VM same network fixed IP (inter-node)': 'L2', + 'VM to VM different network fixed IP (inter-node)': 'L3 fixed', + 'VM to VM different network floating IP (inter-node)': 'L3 floating', + # This one is special because it is bidirectional + 'External-VM': '' +} + +prop_match = { + 'cpu_info': 'CPU Info', + 'distro': 'Host Linux distribution', + 'date': 'Date', + 'nic_name': 'NIC name', + 'openstack_version': 'OpenStack release', + 'test_description': 'Description', + 'version': 'VMTP version', + 'encapsulation': 'Encapsulation', + 'l2agent_type': 'L2 agent type' +} + +# what goes in the subtitle +subtitle_match = ['test_description', 'openstack_version', 'distro', + 'encapsulation', 'l2agent_type'] + +class GoogleChartsBarChart: + def __init__(self, results, protocols): + self.results = results + if protocols not in ['udp', 'tcp']: + protocols = 'all' + self.show_udp = protocols in ['all', 'udp'] + self.show_tcp = protocols in ['all', 'tcp'] + + def _get_subtitle(self, res): + sub = 'inter-node' + for key in subtitle_match: + if key in res: + sub += ' ' + res[key].encode('ascii') + return sub + + def _get_categories(self, flow): + categories = ['Flow'] + # start with UDP first + if self.show_udp: + # iterate through all results in this flow to pick the sizes + for flow_res in flow['results']: + if flow_res['protocol'] == 'UDP': + categories.append('UDP ' + str(flow_res['pkt_size'])) + if self.show_tcp: + categories.append('TCP') + return categories + + def _get_flow_data(self, label, flow, reverse=False): + data = [label] + # start with UDP first + if self.show_udp: + # iterate through all results in this flow to pick the sizes + for flow_res in flow['results']: + reverse_flow = 'direction' in flow_res + if reverse_flow != reverse: + continue + if flow_res['protocol'] == 'UDP': + data.append(float(flow_res['throughput_kbps']) / (1024 * 1024)) + if self.show_tcp: + # TCP may have multiple samples - pick the average for now + res = [] + for flow_res in flow['results']: + if reverse and 'direction' not in flow_res: + continue + if flow_res['protocol'] == 'TCP': + res.append(float(flow_res['throughput_kbps']) / (1024 * 1024)) + break + if res: + total_tp = 0 + for tp in res: + total_tp += tp + data.append(total_tp / len(res)) + return data + + def _get_flows(self, flows): + res = [] + for flow in flows: + desc = flow['desc'] + if desc in label_match: + if label_match[desc]: + res.append(self._get_flow_data(label_match[desc], flow)) + else: + # upload/download + res.append(self._get_flow_data('Upload', flow, reverse=False)) + res.append(self._get_flow_data('Download', flow, reverse=True)) + return res + + def _get_js_options(self, res): + subtitle = self._get_subtitle(res) + return js_options_tpl % (subtitle) + + def _get_js_chart(self, chart_class, rows, chart_name, id): + data = '' + for row in rows: + data += ' ' * 12 + str(row) + ',\n' + return js_data_tpl % (data, chart_class, chart_name, id) + + def _get_js_data(self, flows, id): + rows = [self._get_categories(flows[0])] + rows.extend(self._get_flows(flows)) + return self._get_js_chart('google.charts.Bar', rows, 'chart', id) + + def _get_js_props(self, res, id): + rows = [['Property', 'Value']] + for key in prop_match: + if key in res: + rows.append([prop_match[key], res[key].encode('ascii', 'ignore')]) + return self._get_js_chart('google.visualization.Table', rows, 'table', id) + + def _get_js(self, res, id): + js = '' + js += self._get_js_options(res) + js += self._get_js_data(res['flows'], id) + # Add property table + js += self._get_js_props(res, id) + return js + + def _get_jss(self): + js = '' + id = 0 + for res in self.results: + js += self._get_js(res, id) + id += 1 + return js + + def _get_divs(self): + divs = '' + id = 0 + for _ in self.results: + divs += div_tpl % ('chart', id) + divs += div_tpl % ('table', id) + id += 1 + return divs + + def _plot(self, dest): + dest.write(html_main_template % (self._get_jss(), self._get_divs())) + + def plot(self, dest_file): + with open(dest_file, 'w') as dest: + print('Plotting bar chart to ' + dest_file + '...') + self._plot(dest) + +def gen_chart(files, chart_dest, browser, protocols=''): + results = [] + for ff in files: + if not os.path.isfile(ff): + print('Error: No such file %s: ' + ff) + sys.exit(1) + with open(ff) as data_file: + res = json.load(data_file) + results.append(res) + + chart = GoogleChartsBarChart(results, protocols.lower()) + chart.plot(chart_dest) + if browser: + url = 'file://' + os.path.abspath(opts.chart) + webbrowser.open(url, new=2) + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description='VMTP Chart Generator V' + __version__) + + parser.add_argument('-c', '--chart', dest='chart', + action='store', + 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('-p', '--protocol', dest='protocols', + action='store', + default='all', + help='select protocols:all, tcp, udp', + metavar='') + + parser.add_argument('-v', '--version', dest='version', + default=False, + action='store_true', + help='print version of this script and exit') + + parser.add_argument(dest='files', + help='vmtp 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.protocols)