CSIT-1110: Fix dashboard anomaly count range
[csit.git] / resources / tools / presentation / new / generator_tables.py
1 # Copyright (c) 2018 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 import csv
20 import prettytable
21 import pandas as pd
22
23 from string import replace
24 from collections import OrderedDict
25 from numpy import nan, isnan
26 from xml.etree import ElementTree as ET
27
28 from errors import PresentationError
29 from utils import mean, stdev, relative_change, classify_anomalies
30
31
32 def generate_tables(spec, data):
33     """Generate all tables specified in the specification file.
34
35     :param spec: Specification read from the specification file.
36     :param data: Data to process.
37     :type spec: Specification
38     :type data: InputData
39     """
40
41     logging.info("Generating the tables ...")
42     for table in spec.tables:
43         try:
44             eval(table["algorithm"])(table, data)
45         except NameError as err:
46             logging.error("Probably algorithm '{alg}' is not defined: {err}".
47                           format(alg=table["algorithm"], err=repr(err)))
48     logging.info("Done.")
49
50
51 def table_details(table, input_data):
52     """Generate the table(s) with algorithm: table_detailed_test_results
53     specified in the specification file.
54
55     :param table: Table to generate.
56     :param input_data: Data to process.
57     :type table: pandas.Series
58     :type input_data: InputData
59     """
60
61     logging.info("  Generating the table {0} ...".
62                  format(table.get("title", "")))
63
64     # Transform the data
65     logging.info("    Creating the data set for the {0} '{1}'.".
66                  format(table.get("type", ""), table.get("title", "")))
67     data = input_data.filter_data(table)
68
69     # Prepare the header of the tables
70     header = list()
71     for column in table["columns"]:
72         header.append('"{0}"'.format(str(column["title"]).replace('"', '""')))
73
74     # Generate the data for the table according to the model in the table
75     # specification
76     job = table["data"].keys()[0]
77     build = str(table["data"][job][0])
78     try:
79         suites = input_data.suites(job, build)
80     except KeyError:
81         logging.error("    No data available. The table will not be generated.")
82         return
83
84     for suite_longname, suite in suites.iteritems():
85         # Generate data
86         suite_name = suite["name"]
87         table_lst = list()
88         for test in data[job][build].keys():
89             if data[job][build][test]["parent"] in suite_name:
90                 row_lst = list()
91                 for column in table["columns"]:
92                     try:
93                         col_data = str(data[job][build][test][column["data"].
94                                        split(" ")[1]]).replace('"', '""')
95                         if column["data"].split(" ")[1] in ("vat-history",
96                                                             "show-run"):
97                             col_data = replace(col_data, " |br| ", "",
98                                                maxreplace=1)
99                             col_data = " |prein| {0} |preout| ".\
100                                 format(col_data[:-5])
101                         row_lst.append('"{0}"'.format(col_data))
102                     except KeyError:
103                         row_lst.append("No data")
104                 table_lst.append(row_lst)
105
106         # Write the data to file
107         if table_lst:
108             file_name = "{0}_{1}{2}".format(table["output-file"], suite_name,
109                                             table["output-file-ext"])
110             logging.info("      Writing file: '{}'".format(file_name))
111             with open(file_name, "w") as file_handler:
112                 file_handler.write(",".join(header) + "\n")
113                 for item in table_lst:
114                     file_handler.write(",".join(item) + "\n")
115
116     logging.info("  Done.")
117
118
119 def table_merged_details(table, input_data):
120     """Generate the table(s) with algorithm: table_merged_details
121     specified in the specification file.
122
123     :param table: Table to generate.
124     :param input_data: Data to process.
125     :type table: pandas.Series
126     :type input_data: InputData
127     """
128
129     logging.info("  Generating the table {0} ...".
130                  format(table.get("title", "")))
131
132     # Transform the data
133     logging.info("    Creating the data set for the {0} '{1}'.".
134                  format(table.get("type", ""), table.get("title", "")))
135     data = input_data.filter_data(table)
136     data = input_data.merge_data(data)
137     data.sort_index(inplace=True)
138
139     logging.info("    Creating the data set for the {0} '{1}'.".
140                  format(table.get("type", ""), table.get("title", "")))
141     suites = input_data.filter_data(table, data_set="suites")
142     suites = input_data.merge_data(suites)
143
144     # Prepare the header of the tables
145     header = list()
146     for column in table["columns"]:
147         header.append('"{0}"'.format(str(column["title"]).replace('"', '""')))
148
149     for _, suite in suites.iteritems():
150         # Generate data
151         suite_name = suite["name"]
152         table_lst = list()
153         for test in data.keys():
154             if data[test]["parent"] in suite_name:
155                 row_lst = list()
156                 for column in table["columns"]:
157                     try:
158                         col_data = str(data[test][column["data"].
159                                        split(" ")[1]]).replace('"', '""')
160                         if column["data"].split(" ")[1] in ("vat-history",
161                                                             "show-run"):
162                             col_data = replace(col_data, " |br| ", "",
163                                                maxreplace=1)
164                             col_data = " |prein| {0} |preout| ".\
165                                 format(col_data[:-5])
166                         row_lst.append('"{0}"'.format(col_data))
167                     except KeyError:
168                         row_lst.append("No data")
169                 table_lst.append(row_lst)
170
171         # Write the data to file
172         if table_lst:
173             file_name = "{0}_{1}{2}".format(table["output-file"], suite_name,
174                                             table["output-file-ext"])
175             logging.info("      Writing file: '{}'".format(file_name))
176             with open(file_name, "w") as file_handler:
177                 file_handler.write(",".join(header) + "\n")
178                 for item in table_lst:
179                     file_handler.write(",".join(item) + "\n")
180
181     logging.info("  Done.")
182
183
184 def table_performance_improvements(table, input_data):
185     """Generate the table(s) with algorithm: table_performance_improvements
186     specified in the specification file.
187
188     :param table: Table to generate.
189     :param input_data: Data to process.
190     :type table: pandas.Series
191     :type input_data: InputData
192     """
193
194     def _write_line_to_file(file_handler, data):
195         """Write a line to the .csv file.
196
197         :param file_handler: File handler for the csv file. It must be open for
198          writing text.
199         :param data: Item to be written to the file.
200         :type file_handler: BinaryIO
201         :type data: list
202         """
203
204         line_lst = list()
205         for item in data:
206             if isinstance(item["data"], str):
207                 # Remove -?drdisc from the end
208                 if item["data"].endswith("drdisc"):
209                     item["data"] = item["data"][:-8]
210                 line_lst.append(item["data"])
211             elif isinstance(item["data"], float):
212                 line_lst.append("{:.1f}".format(item["data"]))
213             elif item["data"] is None:
214                 line_lst.append("")
215         file_handler.write(",".join(line_lst) + "\n")
216
217     logging.info("  Generating the table {0} ...".
218                  format(table.get("title", "")))
219
220     # Read the template
221     file_name = table.get("template", None)
222     if file_name:
223         try:
224             tmpl = _read_csv_template(file_name)
225         except PresentationError:
226             logging.error("  The template '{0}' does not exist. Skipping the "
227                           "table.".format(file_name))
228             return None
229     else:
230         logging.error("The template is not defined. Skipping the table.")
231         return None
232
233     # Transform the data
234     logging.info("    Creating the data set for the {0} '{1}'.".
235                  format(table.get("type", ""), table.get("title", "")))
236     data = input_data.filter_data(table)
237
238     # Prepare the header of the tables
239     header = list()
240     for column in table["columns"]:
241         header.append(column["title"])
242
243     # Generate the data for the table according to the model in the table
244     # specification
245     tbl_lst = list()
246     for tmpl_item in tmpl:
247         tbl_item = list()
248         for column in table["columns"]:
249             cmd = column["data"].split(" ")[0]
250             args = column["data"].split(" ")[1:]
251             if cmd == "template":
252                 try:
253                     val = float(tmpl_item[int(args[0])])
254                 except ValueError:
255                     val = tmpl_item[int(args[0])]
256                 tbl_item.append({"data": val})
257             elif cmd == "data":
258                 jobs = args[0:-1]
259                 operation = args[-1]
260                 data_lst = list()
261                 for job in jobs:
262                     for build in data[job]:
263                         try:
264                             data_lst.append(float(build[tmpl_item[0]]
265                                                   ["throughput"]["value"]))
266                         except (KeyError, TypeError):
267                             # No data, ignore
268                             continue
269                 if data_lst:
270                     tbl_item.append({"data": (eval(operation)(data_lst)) /
271                                              1000000})
272                 else:
273                     tbl_item.append({"data": None})
274             elif cmd == "operation":
275                 operation = args[0]
276                 try:
277                     nr1 = float(tbl_item[int(args[1])]["data"])
278                     nr2 = float(tbl_item[int(args[2])]["data"])
279                     if nr1 and nr2:
280                         tbl_item.append({"data": eval(operation)(nr1, nr2)})
281                     else:
282                         tbl_item.append({"data": None})
283                 except (IndexError, ValueError, TypeError):
284                     logging.error("No data for {0}".format(tbl_item[0]["data"]))
285                     tbl_item.append({"data": None})
286                     continue
287             else:
288                 logging.error("Not supported command {0}. Skipping the table.".
289                               format(cmd))
290                 return None
291         tbl_lst.append(tbl_item)
292
293     # Sort the table according to the relative change
294     tbl_lst.sort(key=lambda rel: rel[-1]["data"], reverse=True)
295
296     # Create the tables and write them to the files
297     file_names = [
298         "{0}_ndr_top{1}".format(table["output-file"], table["output-file-ext"]),
299         "{0}_pdr_top{1}".format(table["output-file"], table["output-file-ext"]),
300         "{0}_ndr_low{1}".format(table["output-file"], table["output-file-ext"]),
301         "{0}_pdr_low{1}".format(table["output-file"], table["output-file-ext"])
302     ]
303
304     for file_name in file_names:
305         logging.info("    Writing the file '{0}'".format(file_name))
306         with open(file_name, "w") as file_handler:
307             file_handler.write(",".join(header) + "\n")
308             for item in tbl_lst:
309                 if isinstance(item[-1]["data"], float):
310                     rel_change = round(item[-1]["data"], 1)
311                 else:
312                     rel_change = item[-1]["data"]
313                 if "ndr_top" in file_name \
314                         and "ndr" in item[0]["data"] \
315                         and rel_change >= 10.0:
316                     _write_line_to_file(file_handler, item)
317                 elif "pdr_top" in file_name \
318                         and "pdr" in item[0]["data"] \
319                         and rel_change >= 10.0:
320                     _write_line_to_file(file_handler, item)
321                 elif "ndr_low" in file_name \
322                         and "ndr" in item[0]["data"] \
323                         and rel_change < 10.0:
324                     _write_line_to_file(file_handler, item)
325                 elif "pdr_low" in file_name \
326                         and "pdr" in item[0]["data"] \
327                         and rel_change < 10.0:
328                     _write_line_to_file(file_handler, item)
329
330     logging.info("  Done.")
331
332
333 def _read_csv_template(file_name):
334     """Read the template from a .csv file.
335
336     :param file_name: Name / full path / relative path of the file to read.
337     :type file_name: str
338     :returns: Data from the template as list (lines) of lists (items on line).
339     :rtype: list
340     :raises: PresentationError if it is not possible to read the file.
341     """
342
343     try:
344         with open(file_name, 'r') as csv_file:
345             tmpl_data = list()
346             for line in csv_file:
347                 tmpl_data.append(line[:-1].split(","))
348         return tmpl_data
349     except IOError as err:
350         raise PresentationError(str(err), level="ERROR")
351
352
353 def table_performance_comparison(table, input_data):
354     """Generate the table(s) with algorithm: table_performance_comparison
355     specified in the specification file.
356
357     :param table: Table to generate.
358     :param input_data: Data to process.
359     :type table: pandas.Series
360     :type input_data: InputData
361     """
362
363     logging.info("  Generating the table {0} ...".
364                  format(table.get("title", "")))
365
366     # Transform the data
367     logging.info("    Creating the data set for the {0} '{1}'.".
368                  format(table.get("type", ""), table.get("title", "")))
369     data = input_data.filter_data(table, continue_on_error=True)
370
371     # Prepare the header of the tables
372     try:
373         header = ["Test case", ]
374
375         history = table.get("history", None)
376         if history:
377             for item in history:
378                 header.extend(
379                     ["{0} Throughput [Mpps]".format(item["title"]),
380                      "{0} Stdev [Mpps]".format(item["title"])])
381         header.extend(
382             ["{0} Throughput [Mpps]".format(table["reference"]["title"]),
383              "{0} Stdev [Mpps]".format(table["reference"]["title"]),
384              "{0} Throughput [Mpps]".format(table["compare"]["title"]),
385              "{0} Stdev [Mpps]".format(table["compare"]["title"]),
386              "Change [%]"])
387         header_str = ",".join(header) + "\n"
388     except (AttributeError, KeyError) as err:
389         logging.error("The model is invalid, missing parameter: {0}".
390                       format(err))
391         return
392
393     # Prepare data to the table:
394     tbl_dict = dict()
395     for job, builds in table["reference"]["data"].items():
396         for build in builds:
397             for tst_name, tst_data in data[job][str(build)].iteritems():
398                 if tbl_dict.get(tst_name, None) is None:
399                     name = "{0}-{1}".format(tst_data["parent"].split("-")[0],
400                                             "-".join(tst_data["name"].
401                                                      split("-")[1:]))
402                     tbl_dict[tst_name] = {"name": name,
403                                           "ref-data": list(),
404                                           "cmp-data": list()}
405                 try:
406                     tbl_dict[tst_name]["ref-data"].\
407                         append(tst_data["throughput"]["value"])
408                 except TypeError:
409                     pass  # No data in output.xml for this test
410
411     for job, builds in table["compare"]["data"].items():
412         for build in builds:
413             for tst_name, tst_data in data[job][str(build)].iteritems():
414                 try:
415                     tbl_dict[tst_name]["cmp-data"].\
416                         append(tst_data["throughput"]["value"])
417                 except KeyError:
418                     pass
419                 except TypeError:
420                     tbl_dict.pop(tst_name, None)
421     if history:
422         for item in history:
423             for job, builds in item["data"].items():
424                 for build in builds:
425                     for tst_name, tst_data in data[job][str(build)].iteritems():
426                         if tbl_dict.get(tst_name, None) is None:
427                             continue
428                         if tbl_dict[tst_name].get("history", None) is None:
429                             tbl_dict[tst_name]["history"] = OrderedDict()
430                         if tbl_dict[tst_name]["history"].get(item["title"],
431                                                              None) is None:
432                             tbl_dict[tst_name]["history"][item["title"]] = \
433                                 list()
434                         try:
435                             tbl_dict[tst_name]["history"][item["title"]].\
436                                 append(tst_data["throughput"]["value"])
437                         except (TypeError, KeyError):
438                             pass
439
440     tbl_lst = list()
441     for tst_name in tbl_dict.keys():
442         item = [tbl_dict[tst_name]["name"], ]
443         if history:
444             if tbl_dict[tst_name].get("history", None) is not None:
445                 for hist_data in tbl_dict[tst_name]["history"].values():
446                     if hist_data:
447                         item.append(round(mean(hist_data) / 1000000, 2))
448                         item.append(round(stdev(hist_data) / 1000000, 2))
449                     else:
450                         item.extend([None, None])
451             else:
452                 item.extend([None, None])
453         data_t = tbl_dict[tst_name]["ref-data"]
454         if data_t:
455             item.append(round(mean(data_t) / 1000000, 2))
456             item.append(round(stdev(data_t) / 1000000, 2))
457         else:
458             item.extend([None, None])
459         data_t = tbl_dict[tst_name]["cmp-data"]
460         if data_t:
461             item.append(round(mean(data_t) / 1000000, 2))
462             item.append(round(stdev(data_t) / 1000000, 2))
463         else:
464             item.extend([None, None])
465         if item[-4] is not None and item[-2] is not None and item[-4] != 0:
466             item.append(int(relative_change(float(item[-4]), float(item[-2]))))
467         if len(item) == len(header):
468             tbl_lst.append(item)
469
470     # Sort the table according to the relative change
471     tbl_lst.sort(key=lambda rel: rel[-1], reverse=True)
472
473     # Generate tables:
474     # All tests in csv:
475     tbl_names = ["{0}-ndr-1t1c-full{1}".format(table["output-file"],
476                                                table["output-file-ext"]),
477                  "{0}-ndr-2t2c-full{1}".format(table["output-file"],
478                                                table["output-file-ext"]),
479                  "{0}-ndr-4t4c-full{1}".format(table["output-file"],
480                                                table["output-file-ext"]),
481                  "{0}-pdr-1t1c-full{1}".format(table["output-file"],
482                                                table["output-file-ext"]),
483                  "{0}-pdr-2t2c-full{1}".format(table["output-file"],
484                                                table["output-file-ext"]),
485                  "{0}-pdr-4t4c-full{1}".format(table["output-file"],
486                                                table["output-file-ext"])
487                  ]
488     for file_name in tbl_names:
489         logging.info("      Writing file: '{0}'".format(file_name))
490         with open(file_name, "w") as file_handler:
491             file_handler.write(header_str)
492             for test in tbl_lst:
493                 if (file_name.split("-")[-3] in test[0] and    # NDR vs PDR
494                         file_name.split("-")[-2] in test[0]):  # cores
495                     test[0] = "-".join(test[0].split("-")[:-1])
496                     file_handler.write(",".join([str(item) for item in test]) +
497                                        "\n")
498
499     # All tests in txt:
500     tbl_names_txt = ["{0}-ndr-1t1c-full.txt".format(table["output-file"]),
501                      "{0}-ndr-2t2c-full.txt".format(table["output-file"]),
502                      "{0}-ndr-4t4c-full.txt".format(table["output-file"]),
503                      "{0}-pdr-1t1c-full.txt".format(table["output-file"]),
504                      "{0}-pdr-2t2c-full.txt".format(table["output-file"]),
505                      "{0}-pdr-4t4c-full.txt".format(table["output-file"])
506                      ]
507
508     for i, txt_name in enumerate(tbl_names_txt):
509         txt_table = None
510         logging.info("      Writing file: '{0}'".format(txt_name))
511         with open(tbl_names[i], 'rb') as csv_file:
512             csv_content = csv.reader(csv_file, delimiter=',', quotechar='"')
513             for row in csv_content:
514                 if txt_table is None:
515                     txt_table = prettytable.PrettyTable(row)
516                 else:
517                     txt_table.add_row(row)
518             txt_table.align["Test case"] = "l"
519         with open(txt_name, "w") as txt_file:
520             txt_file.write(str(txt_table))
521
522     # Selected tests in csv:
523     input_file = "{0}-ndr-1t1c-full{1}".format(table["output-file"],
524                                                table["output-file-ext"])
525     with open(input_file, "r") as in_file:
526         lines = list()
527         for line in in_file:
528             lines.append(line)
529
530     output_file = "{0}-ndr-1t1c-top{1}".format(table["output-file"],
531                                                table["output-file-ext"])
532     logging.info("      Writing file: '{0}'".format(output_file))
533     with open(output_file, "w") as out_file:
534         out_file.write(header_str)
535         for i, line in enumerate(lines[1:]):
536             if i == table["nr-of-tests-shown"]:
537                 break
538             out_file.write(line)
539
540     output_file = "{0}-ndr-1t1c-bottom{1}".format(table["output-file"],
541                                                   table["output-file-ext"])
542     logging.info("      Writing file: '{0}'".format(output_file))
543     with open(output_file, "w") as out_file:
544         out_file.write(header_str)
545         for i, line in enumerate(lines[-1:0:-1]):
546             if i == table["nr-of-tests-shown"]:
547                 break
548             out_file.write(line)
549
550     input_file = "{0}-pdr-1t1c-full{1}".format(table["output-file"],
551                                                table["output-file-ext"])
552     with open(input_file, "r") as in_file:
553         lines = list()
554         for line in in_file:
555             lines.append(line)
556
557     output_file = "{0}-pdr-1t1c-top{1}".format(table["output-file"],
558                                                table["output-file-ext"])
559     logging.info("      Writing file: '{0}'".format(output_file))
560     with open(output_file, "w") as out_file:
561         out_file.write(header_str)
562         for i, line in enumerate(lines[1:]):
563             if i == table["nr-of-tests-shown"]:
564                 break
565             out_file.write(line)
566
567     output_file = "{0}-pdr-1t1c-bottom{1}".format(table["output-file"],
568                                                   table["output-file-ext"])
569     logging.info("      Writing file: '{0}'".format(output_file))
570     with open(output_file, "w") as out_file:
571         out_file.write(header_str)
572         for i, line in enumerate(lines[-1:0:-1]):
573             if i == table["nr-of-tests-shown"]:
574                 break
575             out_file.write(line)
576
577
578 def table_performance_comparison_mrr(table, input_data):
579     """Generate the table(s) with algorithm: table_performance_comparison_mrr
580     specified in the specification file.
581
582     :param table: Table to generate.
583     :param input_data: Data to process.
584     :type table: pandas.Series
585     :type input_data: InputData
586     """
587
588     logging.info("  Generating the table {0} ...".
589                  format(table.get("title", "")))
590
591     # Transform the data
592     logging.info("    Creating the data set for the {0} '{1}'.".
593                  format(table.get("type", ""), table.get("title", "")))
594     data = input_data.filter_data(table, continue_on_error=True)
595
596     # Prepare the header of the tables
597     try:
598         header = ["Test case",
599                   "{0} Throughput [Mpps]".format(table["reference"]["title"]),
600                   "{0} stdev [Mpps]".format(table["reference"]["title"]),
601                   "{0} Throughput [Mpps]".format(table["compare"]["title"]),
602                   "{0} stdev [Mpps]".format(table["compare"]["title"]),
603                   "Change [%]"]
604         header_str = ",".join(header) + "\n"
605     except (AttributeError, KeyError) as err:
606         logging.error("The model is invalid, missing parameter: {0}".
607                       format(err))
608         return
609
610     # Prepare data to the table:
611     tbl_dict = dict()
612     for job, builds in table["reference"]["data"].items():
613         for build in builds:
614             for tst_name, tst_data in data[job][str(build)].iteritems():
615                 if tbl_dict.get(tst_name, None) is None:
616                     name = "{0}-{1}".format(tst_data["parent"].split("-")[0],
617                                             "-".join(tst_data["name"].
618                                                      split("-")[1:]))
619                     tbl_dict[tst_name] = {"name": name,
620                                           "ref-data": list(),
621                                           "cmp-data": list()}
622                 try:
623                     tbl_dict[tst_name]["ref-data"].\
624                         append(tst_data["result"]["throughput"])
625                 except TypeError:
626                     pass  # No data in output.xml for this test
627
628     for job, builds in table["compare"]["data"].items():
629         for build in builds:
630             for tst_name, tst_data in data[job][str(build)].iteritems():
631                 try:
632                     tbl_dict[tst_name]["cmp-data"].\
633                         append(tst_data["result"]["throughput"])
634                 except KeyError:
635                     pass
636                 except TypeError:
637                     tbl_dict.pop(tst_name, None)
638
639     tbl_lst = list()
640     for tst_name in tbl_dict.keys():
641         item = [tbl_dict[tst_name]["name"], ]
642         data_t = tbl_dict[tst_name]["ref-data"]
643         if data_t:
644             item.append(round(mean(data_t) / 1000000, 2))
645             item.append(round(stdev(data_t) / 1000000, 2))
646         else:
647             item.extend([None, None])
648         data_t = tbl_dict[tst_name]["cmp-data"]
649         if data_t:
650             item.append(round(mean(data_t) / 1000000, 2))
651             item.append(round(stdev(data_t) / 1000000, 2))
652         else:
653             item.extend([None, None])
654         if item[1] is not None and item[3] is not None and item[1] != 0:
655             item.append(int(relative_change(float(item[1]), float(item[3]))))
656         if len(item) == 6:
657             tbl_lst.append(item)
658
659     # Sort the table according to the relative change
660     tbl_lst.sort(key=lambda rel: rel[-1], reverse=True)
661
662     # Generate tables:
663     # All tests in csv:
664     tbl_names = ["{0}-1t1c-full{1}".format(table["output-file"],
665                                            table["output-file-ext"]),
666                  "{0}-2t2c-full{1}".format(table["output-file"],
667                                            table["output-file-ext"]),
668                  "{0}-4t4c-full{1}".format(table["output-file"],
669                                            table["output-file-ext"])
670                  ]
671     for file_name in tbl_names:
672         logging.info("      Writing file: '{0}'".format(file_name))
673         with open(file_name, "w") as file_handler:
674             file_handler.write(header_str)
675             for test in tbl_lst:
676                 if file_name.split("-")[-2] in test[0]:  # cores
677                     test[0] = "-".join(test[0].split("-")[:-1])
678                     file_handler.write(",".join([str(item) for item in test]) +
679                                        "\n")
680
681     # All tests in txt:
682     tbl_names_txt = ["{0}-1t1c-full.txt".format(table["output-file"]),
683                      "{0}-2t2c-full.txt".format(table["output-file"]),
684                      "{0}-4t4c-full.txt".format(table["output-file"])
685                      ]
686
687     for i, txt_name in enumerate(tbl_names_txt):
688         txt_table = None
689         logging.info("      Writing file: '{0}'".format(txt_name))
690         with open(tbl_names[i], 'rb') as csv_file:
691             csv_content = csv.reader(csv_file, delimiter=',', quotechar='"')
692             for row in csv_content:
693                 if txt_table is None:
694                     txt_table = prettytable.PrettyTable(row)
695                 else:
696                     txt_table.add_row(row)
697             txt_table.align["Test case"] = "l"
698         with open(txt_name, "w") as txt_file:
699             txt_file.write(str(txt_table))
700
701
702 def table_performance_trending_dashboard(table, input_data):
703     """Generate the table(s) with algorithm:
704     table_performance_trending_dashboard
705     specified in the specification file.
706
707     :param table: Table to generate.
708     :param input_data: Data to process.
709     :type table: pandas.Series
710     :type input_data: InputData
711     """
712
713     logging.info("  Generating the table {0} ...".
714                  format(table.get("title", "")))
715
716     # Transform the data
717     logging.info("    Creating the data set for the {0} '{1}'.".
718                  format(table.get("type", ""), table.get("title", "")))
719     data = input_data.filter_data(table, continue_on_error=True)
720
721     # Prepare the header of the tables
722     header = ["Test Case",
723               "Trend [Mpps]",
724               "Short-Term Change [%]",
725               "Long-Term Change [%]",
726               "Regressions [#]",
727               "Progressions [#]"
728               ]
729     header_str = ",".join(header) + "\n"
730
731     # Prepare data to the table:
732     tbl_dict = dict()
733     for job, builds in table["data"].items():
734         for build in builds:
735             for tst_name, tst_data in data[job][str(build)].iteritems():
736                 if tst_name.lower() in table["ignore-list"]:
737                     continue
738                 if tbl_dict.get(tst_name, None) is None:
739                     name = "{0}-{1}".format(tst_data["parent"].split("-")[0],
740                                             "-".join(tst_data["name"].
741                                                      split("-")[1:]))
742                     tbl_dict[tst_name] = {"name": name,
743                                           "data": OrderedDict()}
744                 try:
745                     tbl_dict[tst_name]["data"][str(build)] =  \
746                         tst_data["result"]["throughput"]
747                 except (TypeError, KeyError):
748                     pass  # No data in output.xml for this test
749
750     tbl_lst = list()
751     for tst_name in tbl_dict.keys():
752         if len(tbl_dict[tst_name]["data"]) < 2:
753             continue
754
755         data_t = pd.Series(tbl_dict[tst_name]["data"])
756
757         classification_lst, avgs = classify_anomalies(data_t)
758
759         win_size = min(data_t.size, table["window"])
760         long_win_size = min(data_t.size, table["long-trend-window"])
761         try:
762             max_long_avg = max(
763                 [x for x in avgs[-long_win_size:-win_size]
764                  if not isnan(x)])
765         except ValueError:
766             max_long_avg = nan
767         last_avg = avgs[-1]
768         avg_week_ago = avgs[max(-win_size, -len(avgs))]
769
770         if isnan(last_avg) or isnan(avg_week_ago) or avg_week_ago == 0.0:
771             rel_change_last = nan
772         else:
773             rel_change_last = round(
774                 ((last_avg - avg_week_ago) / avg_week_ago) * 100, 2)
775
776         if isnan(max_long_avg) or isnan(last_avg) or max_long_avg == 0.0:
777             rel_change_long = nan
778         else:
779             rel_change_long = round(
780                 ((last_avg - max_long_avg) / max_long_avg) * 100, 2)
781
782         if classification_lst:
783             if isnan(rel_change_last) and isnan(rel_change_long):
784                 continue
785             tbl_lst.append(
786                 [tbl_dict[tst_name]["name"],
787                  '-' if isnan(last_avg) else
788                  round(last_avg / 1000000, 2),
789                  '-' if isnan(rel_change_last) else rel_change_last,
790                  '-' if isnan(rel_change_long) else rel_change_long,
791                  classification_lst[-win_size:].count("regression"),
792                  classification_lst[-win_size:].count("progression")])
793
794     tbl_lst.sort(key=lambda rel: rel[0])
795
796     tbl_sorted = list()
797     for nrr in range(table["window"], -1, -1):
798         tbl_reg = [item for item in tbl_lst if item[4] == nrr]
799         for nrp in range(table["window"], -1, -1):
800             tbl_out = [item for item in tbl_reg if item[5] == nrp]
801             tbl_out.sort(key=lambda rel: rel[2])
802             tbl_sorted.extend(tbl_out)
803
804     file_name = "{0}{1}".format(table["output-file"], table["output-file-ext"])
805
806     logging.info("    Writing file: '{0}'".format(file_name))
807     with open(file_name, "w") as file_handler:
808         file_handler.write(header_str)
809         for test in tbl_sorted:
810             file_handler.write(",".join([str(item) for item in test]) + '\n')
811
812     txt_file_name = "{0}.txt".format(table["output-file"])
813     txt_table = None
814     logging.info("    Writing file: '{0}'".format(txt_file_name))
815     with open(file_name, 'rb') as csv_file:
816         csv_content = csv.reader(csv_file, delimiter=',', quotechar='"')
817         for row in csv_content:
818             if txt_table is None:
819                 txt_table = prettytable.PrettyTable(row)
820             else:
821                 txt_table.add_row(row)
822         txt_table.align["Test case"] = "l"
823     with open(txt_file_name, "w") as txt_file:
824         txt_file.write(str(txt_table))
825
826
827 def table_performance_trending_dashboard_html(table, input_data):
828     """Generate the table(s) with algorithm:
829     table_performance_trending_dashboard_html specified in the specification
830     file.
831
832     :param table: Table to generate.
833     :param input_data: Data to process.
834     :type table: pandas.Series
835     :type input_data: InputData
836     """
837
838     logging.info("  Generating the table {0} ...".
839                  format(table.get("title", "")))
840
841     try:
842         with open(table["input-file"], 'rb') as csv_file:
843             csv_content = csv.reader(csv_file, delimiter=',', quotechar='"')
844             csv_lst = [item for item in csv_content]
845     except KeyError:
846         logging.warning("The input file is not defined.")
847         return
848     except csv.Error as err:
849         logging.warning("Not possible to process the file '{0}'.\n{1}".
850                         format(table["input-file"], err))
851         return
852
853     # Table:
854     dashboard = ET.Element("table", attrib=dict(width="100%", border='0'))
855
856     # Table header:
857     tr = ET.SubElement(dashboard, "tr", attrib=dict(bgcolor="#7eade7"))
858     for idx, item in enumerate(csv_lst[0]):
859         alignment = "left" if idx == 0 else "center"
860         th = ET.SubElement(tr, "th", attrib=dict(align=alignment))
861         th.text = item
862
863     # Rows:
864     colors = {"regression": ("#ffcccc", "#ff9999"),
865               "progression": ("#c6ecc6", "#9fdf9f"),
866               "normal": ("#e9f1fb", "#d4e4f7")}
867     for r_idx, row in enumerate(csv_lst[1:]):
868         if int(row[4]):
869             color = "regression"
870         elif int(row[5]):
871             color = "progression"
872         else:
873             color = "normal"
874         background = colors[color][r_idx % 2]
875         tr = ET.SubElement(dashboard, "tr", attrib=dict(bgcolor=background))
876
877         # Columns:
878         for c_idx, item in enumerate(row):
879             alignment = "left" if c_idx == 0 else "center"
880             td = ET.SubElement(tr, "td", attrib=dict(align=alignment))
881             # Name:
882             url = "../trending/"
883             file_name = ""
884             anchor = "#"
885             feature = ""
886             if c_idx == 0:
887                 if "lbdpdk" in item or "lbvpp" in item:
888                     file_name = "link_bonding.html"
889
890                 elif "testpmd" in item or "l3fwd" in item:
891                     file_name = "dpdk.html"
892
893                 elif "memif" in item:
894                     file_name = "container_memif.html"
895
896                 elif "srv6" in item:
897                     file_name = "srv6.html"
898
899                 elif "vhost" in item:
900                     if "l2xcbase" in item or "l2bdbasemaclrn" in item:
901                         file_name = "vm_vhost_l2.html"
902                     elif "ip4base" in item:
903                         file_name = "vm_vhost_ip4.html"
904
905                 elif "ipsec" in item:
906                     file_name = "ipsec.html"
907
908                 elif "ethip4lispip" in item or "ethip4vxlan" in item:
909                     file_name = "ip4_tunnels.html"
910
911                 elif "ip4base" in item or "ip4scale" in item:
912                     file_name = "ip4.html"
913                     if "iacl" in item or "snat" in item or "cop" in item:
914                         feature = "-features"
915
916                 elif "ip6base" in item or "ip6scale" in item:
917                     file_name = "ip6.html"
918
919                 elif "l2xcbase" in item or "l2xcscale" in item \
920                         or "l2bdbasemaclrn" in item or "l2bdscale" in item \
921                         or "l2dbbasemaclrn" in item or "l2dbscale" in item:
922                     file_name = "l2.html"
923                     if "iacl" in item:
924                         feature = "-features"
925
926                 if "x520" in item:
927                     anchor += "x520-"
928                 elif "x710" in item:
929                     anchor += "x710-"
930                 elif "xl710" in item:
931                     anchor += "xl710-"
932
933                 if "64b" in item:
934                     anchor += "64b-"
935                 elif "78b" in item:
936                     anchor += "78b-"
937                 elif "imix" in item:
938                     anchor += "imix-"
939                 elif "9000b" in item:
940                     anchor += "9000b-"
941                 elif "1518" in item:
942                     anchor += "1518b-"
943
944                 if "1t1c" in item:
945                     anchor += "1t1c"
946                 elif "2t2c" in item:
947                     anchor += "2t2c"
948                 elif "4t4c" in item:
949                     anchor += "4t4c"
950
951                 url = url + file_name + anchor + feature
952
953                 ref = ET.SubElement(td, "a", attrib=dict(href=url))
954                 ref.text = item
955
956             else:
957                 td.text = item
958
959     try:
960         with open(table["output-file"], 'w') as html_file:
961             logging.info("    Writing file: '{0}'".format(table["output-file"]))
962             html_file.write(".. raw:: html\n\n\t")
963             html_file.write(ET.tostring(dashboard))
964             html_file.write("\n\t<p><br><br></p>\n")
965     except KeyError:
966         logging.warning("The output file is not defined.")
967         return