CSIT-755: Presentation and analytics layer
[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     for suite_longname, suite in input_data.suites(job, build).iteritems():
71         # Generate data
72         suite_name = suite["name"]
73         table_lst = list()
74         for test in data[job][build].keys():
75             if data[job][build][test]["parent"] in suite_name:
76                 row_lst = list()
77                 for column in table["columns"]:
78                     try:
79                         col_data = str(data[job][build][test][column["data"].
80                                        split(" ")[1]]).replace('"', '""')
81                         if column["data"].split(" ")[1] in ("vat-history",
82                                                             "show-run"):
83                             col_data = replace(col_data, " |br| ", "",
84                                                maxreplace=1)
85                             col_data = " |prein| {0} |preout| ".\
86                                 format(col_data[:-5])
87                         row_lst.append('"{0}"'.format(col_data))
88                     except KeyError:
89                         row_lst.append("No data")
90                 table_lst.append(row_lst)
91
92         # Write the data to file
93         if table_lst:
94             file_name = "{0}_{1}{2}".format(table["output-file"], suite_name,
95                                             table["output-file-ext"])
96             logging.info("      Writing file: '{}'".format(file_name))
97             with open(file_name, "w") as file_handler:
98                 file_handler.write(",".join(header) + "\n")
99                 for item in table_lst:
100                     file_handler.write(",".join(item) + "\n")
101
102     logging.info("  Done.")
103
104
105 def table_performance_improvements(table, input_data):
106     """Generate the table(s) with algorithm: table_performance_improvements
107     specified in the specification file.
108
109     :param table: Table to generate.
110     :param input_data: Data to process.
111     :type table: pandas.Series
112     :type input_data: InputData
113     """
114
115     def _write_line_to_file(file_handler, data):
116         """Write a line to the .csv file.
117
118         :param file_handler: File handler for the csv file. It must be open for
119          writing text.
120         :param data: Item to be written to the file.
121         :type file_handler: BinaryIO
122         :type data: list
123         """
124
125         line_lst = list()
126         for item in data:
127             if isinstance(item["data"], str):
128                 line_lst.append(item["data"])
129             elif isinstance(item["data"], float):
130                 line_lst.append("{:.1f}".format(item["data"]))
131         file_handler.write(",".join(line_lst) + "\n")
132
133     logging.info("  Generating the table {0} ...".
134                  format(table.get("title", "")))
135
136     # Read the template
137     file_name = table.get("template", None)
138     if file_name:
139         try:
140             tmpl = _read_csv_template(file_name)
141         except PresentationError:
142             logging.error("  The template '{0}' does not exist. Skipping the "
143                           "table.".format(file_name))
144             return None
145     else:
146         logging.error("The template is not defined. Skipping the table.")
147         return None
148
149     # Transform the data
150     data = input_data.filter_data(table)
151
152     # Prepare the header of the tables
153     header = list()
154     for column in table["columns"]:
155         header.append(column["title"])
156
157     # Generate the data for the table according to the model in the table
158     # specification
159     tbl_lst = list()
160     for tmpl_item in tmpl:
161         tbl_item = list()
162         for column in table["columns"]:
163             cmd = column["data"].split(" ")[0]
164             args = column["data"].split(" ")[1:]
165             if cmd == "template":
166                 try:
167                     val = float(tmpl_item[int(args[0])])
168                 except ValueError:
169                     val = tmpl_item[int(args[0])]
170                 tbl_item.append({"data": val})
171             elif cmd == "data":
172                 job = args[0]
173                 operation = args[1]
174                 data_lst = list()
175                 for build in data[job]:
176                     try:
177                         data_lst.append(float(build[tmpl_item[0]]["throughput"]
178                                               ["value"]) / 1000000)
179                     except (KeyError, TypeError):
180                         # No data, ignore
181                         pass
182                 if data_lst:
183                     tbl_item.append({"data": eval(operation)(data_lst)})
184             elif cmd == "operation":
185                 operation = args[0]
186                 nr1 = tbl_item[int(args[1])]["data"]
187                 nr2 = tbl_item[int(args[2])]["data"]
188                 if nr1 and nr2:
189                     tbl_item.append({"data": eval(operation)(nr1, nr2)})
190                 else:
191                     tbl_item.append({"data": None})
192             else:
193                 logging.error("Not supported command {0}. Skipping the table.".
194                               format(cmd))
195                 return None
196         tbl_lst.append(tbl_item)
197
198     # Sort the table according to the relative change
199     tbl_lst.sort(key=lambda rel: rel[-1]["data"], reverse=True)
200
201     # Create the tables and write them to the files
202     file_names = [
203         "{0}_ndr_top{1}".format(table["output-file"], table["output-file-ext"]),
204         "{0}_pdr_top{1}".format(table["output-file"], table["output-file-ext"]),
205         "{0}_ndr_low{1}".format(table["output-file"], table["output-file-ext"]),
206         "{0}_pdr_low{1}".format(table["output-file"], table["output-file-ext"])
207     ]
208
209     for file_name in file_names:
210         logging.info("    Writing the file '{0}'".format(file_name))
211         with open(file_name, "w") as file_handler:
212             file_handler.write(",".join(header) + "\n")
213             for item in tbl_lst:
214                 if "ndr_top" in file_name \
215                         and "ndr" in item[1]["data"] \
216                         and item[-1]["data"] >= 10:
217                     _write_line_to_file(file_handler, item)
218                 elif "pdr_top" in file_name \
219                         and "pdr" in item[1]["data"] \
220                         and item[-1]["data"] >= 10:
221                     _write_line_to_file(file_handler, item)
222                 elif "ndr_low" in file_name \
223                         and "ndr" in item[1]["data"] \
224                         and item[-1]["data"] < 10:
225                     _write_line_to_file(file_handler, item)
226                 elif "pdr_low" in file_name \
227                         and "pdr" in item[1]["data"] \
228                         and item[-1]["data"] < 10:
229                     _write_line_to_file(file_handler, item)
230
231     logging.info("  Done.")
232
233
234 def _read_csv_template(file_name):
235     """Read the template from a .csv file.
236
237     :param file_name: Name / full path / relative path of the file to read.
238     :type file_name: str
239     :returns: Data from the template as list (lines) of lists (items on line).
240     :rtype: list
241     :raises: PresentationError if it is not possible to read the file.
242     """
243
244     try:
245         with open(file_name, 'r') as csv_file:
246             tmpl_data = list()
247             for line in csv_file:
248                 tmpl_data.append(line[:-1].split(","))
249         return tmpl_data
250     except IOError as err:
251         raise PresentationError(str(err), level="ERROR")