920c30a4a85a1aa929b356ad71ce7463e16299ec
[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                 job = args[0]
241                 operation = args[1]
242                 data_lst = list()
243                 for build in data[job]:
244                     try:
245                         data_lst.append(float(build[tmpl_item[0]]["throughput"]
246                                               ["value"]))
247                     except (KeyError, TypeError):
248                         # No data, ignore
249                         continue
250                 if data_lst:
251                     tbl_item.append({"data": (eval(operation)(data_lst)) /
252                                              1000000})
253                 else:
254                     tbl_item.append({"data": None})
255             elif cmd == "operation":
256                 operation = args[0]
257                 try:
258                     nr1 = float(tbl_item[int(args[1])]["data"])
259                     nr2 = float(tbl_item[int(args[2])]["data"])
260                     if nr1 and nr2:
261                         tbl_item.append({"data": eval(operation)(nr1, nr2)})
262                     else:
263                         tbl_item.append({"data": None})
264                 except (IndexError, ValueError, TypeError):
265                     logging.error("No data for {0}".format(tbl_item[1]["data"]))
266                     tbl_item.append({"data": None})
267                     continue
268             else:
269                 logging.error("Not supported command {0}. Skipping the table.".
270                               format(cmd))
271                 return None
272         tbl_lst.append(tbl_item)
273
274     # Sort the table according to the relative change
275     tbl_lst.sort(key=lambda rel: rel[-1]["data"], reverse=True)
276
277     # Create the tables and write them to the files
278     file_names = [
279         "{0}_ndr_top{1}".format(table["output-file"], table["output-file-ext"]),
280         "{0}_pdr_top{1}".format(table["output-file"], table["output-file-ext"]),
281         "{0}_ndr_low{1}".format(table["output-file"], table["output-file-ext"]),
282         "{0}_pdr_low{1}".format(table["output-file"], table["output-file-ext"])
283     ]
284
285     for file_name in file_names:
286         logging.info("    Writing the file '{0}'".format(file_name))
287         with open(file_name, "w") as file_handler:
288             file_handler.write(",".join(header) + "\n")
289             for item in tbl_lst:
290                 if isinstance(item[-1]["data"], float):
291                     rel_change = round(item[-1]["data"], 1)
292                 else:
293                     rel_change = item[-1]["data"]
294                 if "ndr_top" in file_name \
295                         and "ndr" in item[1]["data"] \
296                         and rel_change >= 10.0:
297                     _write_line_to_file(file_handler, item)
298                 elif "pdr_top" in file_name \
299                         and "pdr" in item[1]["data"] \
300                         and rel_change >= 10.0:
301                     _write_line_to_file(file_handler, item)
302                 elif "ndr_low" in file_name \
303                         and "ndr" in item[1]["data"] \
304                         and rel_change < 10.0:
305                     _write_line_to_file(file_handler, item)
306                 elif "pdr_low" in file_name \
307                         and "pdr" in item[1]["data"] \
308                         and rel_change < 10.0:
309                     _write_line_to_file(file_handler, item)
310
311     logging.info("  Done.")
312
313
314 def _read_csv_template(file_name):
315     """Read the template from a .csv file.
316
317     :param file_name: Name / full path / relative path of the file to read.
318     :type file_name: str
319     :returns: Data from the template as list (lines) of lists (items on line).
320     :rtype: list
321     :raises: PresentationError if it is not possible to read the file.
322     """
323
324     try:
325         with open(file_name, 'r') as csv_file:
326             tmpl_data = list()
327             for line in csv_file:
328                 tmpl_data.append(line[:-1].split(","))
329         return tmpl_data
330     except IOError as err:
331         raise PresentationError(str(err), level="ERROR")