Fix: table_performance_improvements
[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_performance_improvements(table, input_data):
112     """Generate the table(s) with algorithm: table_performance_improvements
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     def _write_line_to_file(file_handler, data):
122         """Write a line to the .csv file.
123
124         :param file_handler: File handler for the csv file. It must be open for
125          writing text.
126         :param data: Item to be written to the file.
127         :type file_handler: BinaryIO
128         :type data: list
129         """
130
131         line_lst = list()
132         for item in data:
133             if isinstance(item["data"], str):
134                 line_lst.append(item["data"])
135             elif isinstance(item["data"], float):
136                 line_lst.append("{:.1f}".format(item["data"]))
137         file_handler.write(",".join(line_lst) + "\n")
138
139     logging.info("  Generating the table {0} ...".
140                  format(table.get("title", "")))
141
142     # Read the template
143     file_name = table.get("template", None)
144     if file_name:
145         try:
146             tmpl = _read_csv_template(file_name)
147         except PresentationError:
148             logging.error("  The template '{0}' does not exist. Skipping the "
149                           "table.".format(file_name))
150             return None
151     else:
152         logging.error("The template is not defined. Skipping the table.")
153         return None
154
155     # Transform the data
156     data = input_data.filter_data(table)
157
158     # Prepare the header of the tables
159     header = list()
160     for column in table["columns"]:
161         header.append(column["title"])
162
163     # Generate the data for the table according to the model in the table
164     # specification
165     tbl_lst = list()
166     for tmpl_item in tmpl:
167         tbl_item = list()
168         for column in table["columns"]:
169             cmd = column["data"].split(" ")[0]
170             args = column["data"].split(" ")[1:]
171             if cmd == "template":
172                 try:
173                     val = float(tmpl_item[int(args[0])])
174                 except ValueError:
175                     val = tmpl_item[int(args[0])]
176                 tbl_item.append({"data": val})
177             elif cmd == "data":
178                 job = args[0]
179                 operation = args[1]
180                 data_lst = list()
181                 for build in data[job]:
182                     try:
183                         data_lst.append(float(build[tmpl_item[0]]["throughput"]
184                                               ["value"]) / 1000000)
185                     except (KeyError, TypeError):
186                         # No data, ignore
187                         pass
188                 if data_lst:
189                     tbl_item.append({"data": eval(operation)(data_lst)})
190             elif cmd == "operation":
191                 operation = args[0]
192                 try:
193                     nr1 = tbl_item[int(args[1])]["data"]
194                     nr2 = tbl_item[int(args[2])]["data"]
195                     if nr1 and nr2:
196                         tbl_item.append({"data": eval(operation)(nr1, nr2)})
197                     else:
198                         tbl_item.append({"data": None})
199                 except IndexError:
200                     logging.error("No data for {0}".format(tbl_item[0]))
201                     tbl_item.append({"data": None})
202                     continue
203             else:
204                 logging.error("Not supported command {0}. Skipping the table.".
205                               format(cmd))
206                 return None
207         tbl_lst.append(tbl_item)
208
209     # Sort the table according to the relative change
210     tbl_lst.sort(key=lambda rel: rel[-1]["data"], reverse=True)
211
212     # Create the tables and write them to the files
213     file_names = [
214         "{0}_ndr_top{1}".format(table["output-file"], table["output-file-ext"]),
215         "{0}_pdr_top{1}".format(table["output-file"], table["output-file-ext"]),
216         "{0}_ndr_low{1}".format(table["output-file"], table["output-file-ext"]),
217         "{0}_pdr_low{1}".format(table["output-file"], table["output-file-ext"])
218     ]
219
220     for file_name in file_names:
221         logging.info("    Writing the file '{0}'".format(file_name))
222         with open(file_name, "w") as file_handler:
223             file_handler.write(",".join(header) + "\n")
224             for item in tbl_lst:
225                 if "ndr_top" in file_name \
226                         and "ndr" in item[1]["data"] \
227                         and item[-1]["data"] >= 10:
228                     _write_line_to_file(file_handler, item)
229                 elif "pdr_top" in file_name \
230                         and "pdr" in item[1]["data"] \
231                         and item[-1]["data"] >= 10:
232                     _write_line_to_file(file_handler, item)
233                 elif "ndr_low" in file_name \
234                         and "ndr" in item[1]["data"] \
235                         and item[-1]["data"] < 10:
236                     _write_line_to_file(file_handler, item)
237                 elif "pdr_low" in file_name \
238                         and "pdr" in item[1]["data"] \
239                         and item[-1]["data"] < 10:
240                     _write_line_to_file(file_handler, item)
241
242     logging.info("  Done.")
243
244
245 def _read_csv_template(file_name):
246     """Read the template from a .csv file.
247
248     :param file_name: Name / full path / relative path of the file to read.
249     :type file_name: str
250     :returns: Data from the template as list (lines) of lists (items on line).
251     :rtype: list
252     :raises: PresentationError if it is not possible to read the file.
253     """
254
255     try:
256         with open(file_name, 'r') as csv_file:
257             tmpl_data = list()
258             for line in csv_file:
259                 tmpl_data.append(line[:-1].split(","))
260         return tmpl_data
261     except IOError as err:
262         raise PresentationError(str(err), level="ERROR")