PAL: Perf improvements - more data sources
[csit.git] / resources / tools / presentation / generator_tables.py
1 # Copyright (c) 2017 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Algorithms to generate tables.
15 """
16
17
18 import logging
19 from string import replace
20
21 from errors import PresentationError
22 from utils import mean, stdev, relative_change
23
24
25 def generate_tables(spec, data):
26     """Generate all tables specified in the specification file.
27
28     :param spec: Specification read from the specification file.
29     :param data: Data to process.
30     :type spec: Specification
31     :type data: InputData
32     """
33
34     logging.info("Generating the tables ...")
35     for table in spec.tables:
36         try:
37             eval(table["algorithm"])(table, data)
38         except NameError:
39             logging.error("The algorithm '{0}' is not defined.".
40                           format(table["algorithm"]))
41     logging.info("Done.")
42
43
44 def table_details(table, input_data):
45     """Generate the table(s) with algorithm: table_detailed_test_results
46     specified in the specification file.
47
48     :param table: Table to generate.
49     :param input_data: Data to process.
50     :type table: pandas.Series
51     :type input_data: InputData
52     """
53
54     logging.info("  Generating the table {0} ...".
55                  format(table.get("title", "")))
56
57     # Transform the data
58     data = input_data.filter_data(table)
59
60     # Prepare the header of the tables
61     header = list()
62     for column in table["columns"]:
63         header.append('"{0}"'.format(str(column["title"]).replace('"', '""')))
64
65     # Generate the data for the table according to the model in the table
66     # specification
67
68     job = table["data"].keys()[0]
69     build = str(table["data"][job][0])
70     try:
71         suites = input_data.suites(job, build)
72     except KeyError:
73         logging.error("    No data available. The table will not be generated.")
74         return
75
76     for suite_longname, suite in suites.iteritems():
77         # Generate data
78         suite_name = suite["name"]
79         table_lst = list()
80         for test in data[job][build].keys():
81             if data[job][build][test]["parent"] in suite_name:
82                 row_lst = list()
83                 for column in table["columns"]:
84                     try:
85                         col_data = str(data[job][build][test][column["data"].
86                                        split(" ")[1]]).replace('"', '""')
87                         if column["data"].split(" ")[1] in ("vat-history",
88                                                             "show-run"):
89                             col_data = replace(col_data, " |br| ", "",
90                                                maxreplace=1)
91                             col_data = " |prein| {0} |preout| ".\
92                                 format(col_data[:-5])
93                         row_lst.append('"{0}"'.format(col_data))
94                     except KeyError:
95                         row_lst.append("No data")
96                 table_lst.append(row_lst)
97
98         # Write the data to file
99         if table_lst:
100             file_name = "{0}_{1}{2}".format(table["output-file"], suite_name,
101                                             table["output-file-ext"])
102             logging.info("      Writing file: '{}'".format(file_name))
103             with open(file_name, "w") as file_handler:
104                 file_handler.write(",".join(header) + "\n")
105                 for item in table_lst:
106                     file_handler.write(",".join(item) + "\n")
107
108     logging.info("  Done.")
109
110
111 def table_merged_details(table, input_data):
112     """Generate the table(s) with algorithm: table_merged_details
113     specified in the specification file.
114
115     :param table: Table to generate.
116     :param input_data: Data to process.
117     :type table: pandas.Series
118     :type input_data: InputData
119     """
120
121     logging.info("  Generating the table {0} ...".
122                  format(table.get("title", "")))
123
124     # Transform the data
125     data = input_data.filter_data(table)
126     data = input_data.merge_data(data)
127
128     suites = input_data.filter_data(table, data_set="suites")
129     suites = input_data.merge_data(suites)
130
131     # Prepare the header of the tables
132     header = list()
133     for column in table["columns"]:
134         header.append('"{0}"'.format(str(column["title"]).replace('"', '""')))
135
136     for _, suite in suites.iteritems():
137         # Generate data
138         suite_name = suite["name"]
139         table_lst = list()
140         for test in data.keys():
141             if data[test]["parent"] in suite_name:
142                 row_lst = list()
143                 for column in table["columns"]:
144                     try:
145                         col_data = str(data[test][column["data"].
146                                        split(" ")[1]]).replace('"', '""')
147                         if column["data"].split(" ")[1] in ("vat-history",
148                                                             "show-run"):
149                             col_data = replace(col_data, " |br| ", "",
150                                                maxreplace=1)
151                             col_data = " |prein| {0} |preout| ".\
152                                 format(col_data[:-5])
153                         row_lst.append('"{0}"'.format(col_data))
154                     except KeyError:
155                         row_lst.append("No data")
156                 table_lst.append(row_lst)
157
158         # Write the data to file
159         if table_lst:
160             file_name = "{0}_{1}{2}".format(table["output-file"], suite_name,
161                                             table["output-file-ext"])
162             logging.info("      Writing file: '{}'".format(file_name))
163             with open(file_name, "w") as file_handler:
164                 file_handler.write(",".join(header) + "\n")
165                 for item in table_lst:
166                     file_handler.write(",".join(item) + "\n")
167
168     logging.info("  Done.")
169
170
171 def table_performance_improvements(table, input_data):
172     """Generate the table(s) with algorithm: table_performance_improvements
173     specified in the specification file.
174
175     :param table: Table to generate.
176     :param input_data: Data to process.
177     :type table: pandas.Series
178     :type input_data: InputData
179     """
180
181     def _write_line_to_file(file_handler, data):
182         """Write a line to the .csv file.
183
184         :param file_handler: File handler for the csv file. It must be open for
185          writing text.
186         :param data: Item to be written to the file.
187         :type file_handler: BinaryIO
188         :type data: list
189         """
190
191         line_lst = list()
192         for item in data:
193             if isinstance(item["data"], str):
194                 line_lst.append(item["data"])
195             elif isinstance(item["data"], float):
196                 line_lst.append("{:.1f}".format(item["data"]))
197             elif item["data"] is None:
198                 line_lst.append("")
199         file_handler.write(",".join(line_lst) + "\n")
200
201     logging.info("  Generating the table {0} ...".
202                  format(table.get("title", "")))
203
204     # Read the template
205     file_name = table.get("template", None)
206     if file_name:
207         try:
208             tmpl = _read_csv_template(file_name)
209         except PresentationError:
210             logging.error("  The template '{0}' does not exist. Skipping the "
211                           "table.".format(file_name))
212             return None
213     else:
214         logging.error("The template is not defined. Skipping the table.")
215         return None
216
217     # Transform the data
218     data = input_data.filter_data(table)
219
220     # Prepare the header of the tables
221     header = list()
222     for column in table["columns"]:
223         header.append(column["title"])
224
225     # Generate the data for the table according to the model in the table
226     # specification
227     tbl_lst = list()
228     for tmpl_item in tmpl:
229         tbl_item = list()
230         for column in table["columns"]:
231             cmd = column["data"].split(" ")[0]
232             args = column["data"].split(" ")[1:]
233             if cmd == "template":
234                 try:
235                     val = float(tmpl_item[int(args[0])])
236                 except ValueError:
237                     val = tmpl_item[int(args[0])]
238                 tbl_item.append({"data": val})
239             elif cmd == "data":
240                 jobs = args[0:-1]
241                 operation = args[-1]
242                 data_lst = list()
243                 for job in jobs:
244                     for build in data[job]:
245                         try:
246                             data_lst.append(float(build[tmpl_item[0]]
247                                                   ["throughput"]["value"]))
248                         except (KeyError, TypeError):
249                             # No data, ignore
250                             continue
251                 if data_lst:
252                     tbl_item.append({"data": (eval(operation)(data_lst)) /
253                                              1000000})
254                 else:
255                     tbl_item.append({"data": None})
256             elif cmd == "operation":
257                 operation = args[0]
258                 try:
259                     nr1 = float(tbl_item[int(args[1])]["data"])
260                     nr2 = float(tbl_item[int(args[2])]["data"])
261                     if nr1 and nr2:
262                         tbl_item.append({"data": eval(operation)(nr1, nr2)})
263                     else:
264                         tbl_item.append({"data": None})
265                 except (IndexError, ValueError, TypeError):
266                     logging.error("No data for {0}".format(tbl_item[1]["data"]))
267                     tbl_item.append({"data": None})
268                     continue
269             else:
270                 logging.error("Not supported command {0}. Skipping the table.".
271                               format(cmd))
272                 return None
273         tbl_lst.append(tbl_item)
274
275     # Sort the table according to the relative change
276     tbl_lst.sort(key=lambda rel: rel[-1]["data"], reverse=True)
277
278     # Create the tables and write them to the files
279     file_names = [
280         "{0}_ndr_top{1}".format(table["output-file"], table["output-file-ext"]),
281         "{0}_pdr_top{1}".format(table["output-file"], table["output-file-ext"]),
282         "{0}_ndr_low{1}".format(table["output-file"], table["output-file-ext"]),
283         "{0}_pdr_low{1}".format(table["output-file"], table["output-file-ext"])
284     ]
285
286     for file_name in file_names:
287         logging.info("    Writing the file '{0}'".format(file_name))
288         with open(file_name, "w") as file_handler:
289             file_handler.write(",".join(header) + "\n")
290             for item in tbl_lst:
291                 if isinstance(item[-1]["data"], float):
292                     rel_change = round(item[-1]["data"], 1)
293                 else:
294                     rel_change = item[-1]["data"]
295                 if "ndr_top" in file_name \
296                         and "ndr" in item[1]["data"] \
297                         and rel_change >= 10.0:
298                     _write_line_to_file(file_handler, item)
299                 elif "pdr_top" in file_name \
300                         and "pdr" in item[1]["data"] \
301                         and rel_change >= 10.0:
302                     _write_line_to_file(file_handler, item)
303                 elif "ndr_low" in file_name \
304                         and "ndr" in item[1]["data"] \
305                         and rel_change < 10.0:
306                     _write_line_to_file(file_handler, item)
307                 elif "pdr_low" in file_name \
308                         and "pdr" in item[1]["data"] \
309                         and rel_change < 10.0:
310                     _write_line_to_file(file_handler, item)
311
312     logging.info("  Done.")
313
314
315 def _read_csv_template(file_name):
316     """Read the template from a .csv file.
317
318     :param file_name: Name / full path / relative path of the file to read.
319     :type file_name: str
320     :returns: Data from the template as list (lines) of lists (items on line).
321     :rtype: list
322     :raises: PresentationError if it is not possible to read the file.
323     """
324
325     try:
326         with open(file_name, 'r') as csv_file:
327             tmpl_data = list()
328             for line in csv_file:
329                 tmpl_data.append(line[:-1].split(","))
330         return tmpl_data
331     except IOError as err:
332         raise PresentationError(str(err), level="ERROR")