Report generation script updates
[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)+'. TC'] * 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                     ))
204                 elif args.plot == 'scatter':
205                     traces.append(plgo.Scatter(
206                         x=ydata[suite][0::2],
207                         y=ydata[suite][1::2],
208                         mode='lines+markers',
209                         name=str(i+1)+'. '+suite.lower().replace('-ndrdisc',''),
210                     ))
211                 else:
212                     pass
213
214         # Add plot layout
215         layout = plgo.Layout(
216             title='{0}'.format(args.title),
217             xaxis=dict(
218                 autorange=True,
219                 autotick=False,
220                 fixedrange=False,
221                 gridcolor='rgb(238, 238, 238)',
222                 linecolor='rgb(238, 238, 238)',
223                 linewidth=1,
224                 showgrid=True,
225                 showline=True,
226                 showticklabels=True,
227                 tickcolor='rgb(238, 238, 238)',
228                 tickmode='linear',
229                 title='Indexed Test Cases' if args.plot == 'box'\
230                     else '',
231                 zeroline=False,
232             ),
233             yaxis=dict(
234                 gridcolor='rgb(238, 238, 238)',
235                 hoverformat='' if args.latency else '.4s',
236                 linecolor='rgb(238, 238, 238)',
237                 linewidth=1,
238                 range=[args.lower, args.upper] if args.lower and args.upper\
239                     else [],
240                 showgrid=True,
241                 showline=True,
242                 showticklabels=True,
243                 tickcolor='rgb(238, 238, 238)',
244                 title='Latency min/avg/max [uSec]' if args.latency\
245                     else 'Packets Per Second [pps]',
246                 zeroline=False,
247             ),
248             boxmode='group',
249             boxgroupgap=0.5,
250             autosize=False,
251             margin=dict(
252                 autoexpand=False,
253                 b=200,
254                 l=50,
255                 r=50,
256             ),
257             showlegend=True,
258             legend=dict(
259                 orientation='h',
260                 y=-1,
261                 yanchor='bottom',
262             ),
263             width=700,
264             height=700,
265         )
266         # Create plot
267         plpl = plgo.Figure(data=traces, layout=layout)
268         # Export Plot
269         ploff.plot(plpl,
270                    show_link=False, auto_open=False,
271                    filename='{0}.html'.format(args.output))
272     else:
273         sys.stderr.write('No data found!\n')
274
275
276 if __name__ == "__main__":
277     sys.exit(main())