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