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 = '''
+
+
+
+
+
+
+
+
+
+
+'''
+
+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)