CSIT-755: Presentation and analytics layer
[csit.git] / resources / tools / report_gen / run_plot.py
1 #!/usr/bin/python
2
3 # Copyright (c) 2016 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """Plot the performance data"""
17
18 import argparse
19 import operator
20 import os
21 import sys
22 import math
23
24 import plotly.offline as ploff
25 import plotly.graph_objs as plgo
26 from lxml import etree
27
28
29 def select_files_in_subfolders(directory, ext='xml'):
30     """Get all files in folder and its subfolders.
31
32     :param dir: Input folder.
33     :param ext: File extension.
34     :type dir: str
35     :type ext: str
36     :return: List of filex matching the parameters.
37     :rtype list
38     """
39     for _, _, files in os.walk(directory):
40         for file in files:
41             if file.endswith('.%s' % ext):
42                 yield os.path.join(directory, file)
43
44
45 def select_files_in_folder(directory, ext='xml'):
46     """Get all files in folder.
47
48     :param dir: Input folder.
49     :param ext: File extension.
50     :type dir: str
51     :type ext: str
52     :return: List of filex matching the parameters.
53     :rtype list
54     """
55     for file in os.listdir(directory):
56         if file.endswith('.%s' % ext):
57             yield os.path.join(directory, file)
58
59
60 def combine_dicts(first, second, oper=operator.add):
61     """Combine two dictionaries.
62
63     :param first: First dict.
64     :param second: Second dict.
65     :param oper: Operator.
66     :type first: dict
67     :type second: dict
68     :type oper: operator
69     :return: Combined dictionary.
70     :rtype dict
71     """
72
73     return dict(first.items() + second.items() +\
74         [(k, oper(first[k], second[k])) for k in set(second) & set(first)])
75
76
77 def parse_data_pps(args):
78     """Get PPS data out of XML file into array.
79
80     :param args: Command line parameters.
81     :type suite: ArgumentParser
82     :return: X-data and Y-data dictionaries.
83     :rtype tuple of dict
84     """
85     xdata = []
86     ydata_pps = {}
87
88     for i, file in enumerate(sorted(select_files_in_folder(args.input))):
89         xml_tree = etree.parse(file)
90         sel = xml_tree.xpath(args.xpath)
91         if sel:
92             ydata_pps = combine_dicts(ydata_pps, dict((elem.attrib['name'],\
93                 (i, float(elem.text))) for elem in sel))
94             xdata.append(xml_tree.getroot().attrib['vdevice'])
95     return xdata, ydata_pps
96
97
98 def parse_data_lat(args):
99     """Get latency data out of XML file into array.
100
101     :param args: Command line parameters.
102     :type suite: ArgumentParser
103     :return: X-data and Y-data dictionaries.
104     :rtype tuple of dict
105     """
106     xdata = []
107     ydata_lat = {}
108
109     for i, file in enumerate(sorted(select_files_in_folder(args.input))):
110         xml_tree = etree.parse(file)
111         sel = xml_tree.xpath(args.xpath)
112         if sel:
113             try:
114                 ydata_lat = combine_dicts(ydata_lat, dict((elem.attrib['name'],\
115                     (i, elem.attrib[args.latency])) for elem in sel))
116             except KeyError:
117                 raise RuntimeError('Retrieving latency data error (PDR?)')
118             xdata.append(xml_tree.getroot().attrib['vdevice'])
119     return xdata, ydata_lat
120
121
122 def parse_args():
123     """Parse arguments from cmd line.
124
125     :return: Parsed arguments.
126     :rtype ArgumentParser
127     """
128
129     parser = argparse.ArgumentParser()
130     parser.add_argument("-x", "--xpath", required=True,
131                         help="Xpath filter")
132     parser.add_argument("-t", "--title", required=True,
133                         help="Plot title")
134     parser.add_argument("-l", "--lower",
135                         default=False,
136                         help="Lower boudary of Y-axis")
137     parser.add_argument("-u", "--upper",
138                         default=False,
139                         help="Upper boudary of Y-axis")
140     parser.add_argument("-e", "--errorbar",
141                         default=False,
142                         help="Errorbar for Y-axis")
143     parser.add_argument("-d", "--latency",
144                         choices=['lat_10', 'lat_50', 'lat_100'],
145                         help="Latency to draw")
146     parser.add_argument("-p", "--plot",
147                         choices=['box', 'scatter'],
148                         default='box',
149                         help="Throughput plot type")
150     parser.add_argument("-i", "--input",
151                         help="Input folder")
152     parser.add_argument("-o", "--output", required=True,
153                         help="Output image file name")
154     return parser.parse_args()
155
156
157 def main():
158     """Main function."""
159
160     args = parse_args()
161     if args.latency:
162         xdata, ydata = parse_data_lat(args)
163     else:
164         xdata, ydata = parse_data_pps(args)
165
166     # Print data into console for debug
167     print args.title
168     for data in ydata:
169         print data + ";" + ";".join(str(val) for val in ydata[data][1::2])
170
171     if xdata and ydata:
172         traces = []
173         # Add plot traces
174         for i, suite in enumerate(ydata):
175             if args.latency:
176                 y_extract = []
177                 _ = [y_extract.extend([l, l]) for l in ydata[suite][1::2][0].split('/')]
178                 traces.append(plgo.Box(
179                     x=['TGint1-to-SUT1-to-SUT2-to-TGint2',
180                        'TGint1-to-SUT1-to-SUT2-to-TGint2',
181                        'TGint1-to-SUT1-to-SUT2-to-TGint2',
182                        'TGint1-to-SUT1-to-SUT2-to-TGint2',
183                        'TGint1-to-SUT1-to-SUT2-to-TGint2',
184                        'TGint1-to-SUT1-to-SUT2-to-TGint2',
185                        'TGint2-to-SUT2-to-SUT1-to-TGint1',
186                        'TGint2-to-SUT2-to-SUT1-to-TGint1',
187                        'TGint2-to-SUT2-to-SUT1-to-TGint1',
188                        'TGint2-to-SUT2-to-SUT1-to-TGint1',
189                        'TGint2-to-SUT2-to-SUT1-to-TGint1',
190                        'TGint2-to-SUT2-to-SUT1-to-TGint1'],
191                     y=y_extract,
192                     name=str(i+1)+'. '+suite.lower().replace('-ndrdisc',''),
193                     boxmean=False,
194                 ))
195             else:
196                 if args.plot == 'box':
197                     traces.append(plgo.Box(
198                         x=[str(i+1)+'.'] * len(ydata[suite][1::2]),
199                         y=ydata[suite][1::2],
200                         name=str(i+1)+'. '+suite.lower().replace('-ndrdisc',''),
201                         hoverinfo='x+y',
202                         boxpoints='outliers',
203                         whiskerwidth=0,
204                     ))
205                 elif args.plot == 'scatter':
206                     traces.append(plgo.Scatter(
207                         x=ydata[suite][0::2],
208                         y=ydata[suite][1::2],
209                         mode='lines+markers',
210                         name=str(i+1)+'. '+suite.lower().replace('-ndrdisc',''),
211                     ))
212                 else:
213                     pass
214
215         # Add plot layout
216         layout = plgo.Layout(
217             title='{0}'.format(args.title),
218             xaxis=dict(
219                 autorange=True,
220                 autotick=False,
221                 fixedrange=False,
222                 gridcolor='rgb(238, 238, 238)',
223                 linecolor='rgb(238, 238, 238)',
224                 linewidth=1,
225                 showgrid=True,
226                 showline=True,
227                 showticklabels=True,
228                 tickcolor='rgb(238, 238, 238)',
229                 tickmode='linear',
230                 title='Indexed Test Cases' if args.plot == 'box'\
231                     else '',
232                 zeroline=False,
233             ),
234             yaxis=dict(
235                 gridcolor='rgb(238, 238, 238)',
236                 hoverformat='' if args.latency else '.4s',
237                 linecolor='rgb(238, 238, 238)',
238                 linewidth=1,
239                 range=[args.lower, args.upper] if args.lower and args.upper\
240                     else [],
241                 showgrid=True,
242                 showline=True,
243                 showticklabels=True,
244                 tickcolor='rgb(238, 238, 238)',
245                 title='Latency min/avg/max [uSec]' if args.latency\
246                     else 'Packets Per Second [pps]',
247                 zeroline=False,
248             ),
249             boxmode='group',
250             boxgroupgap=0.5,
251             autosize=False,
252             margin=dict(
253                 t=50,
254                 b=20,
255                 l=50,
256                 r=20,
257             ),
258             showlegend=True,
259             legend=dict(
260                 orientation='h',
261             ),
262             width=700,
263             height=1000,
264         )
265         # Create plot
266         plpl = plgo.Figure(data=traces, layout=layout)
267         # Export Plot
268         ploff.plot(plpl,
269                    show_link=False, auto_open=False,
270                    filename='{0}.html'.format(args.output))
271     else:
272         sys.stderr.write('No data found!\n')
273
274
275 if __name__ == "__main__":
276     sys.exit(main())