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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Algorithms to generate tables.
23 from string import replace
24 from math import isnan
25 from xml.etree import ElementTree as ET
27 from errors import PresentationError
28 from utils import mean, stdev, relative_change, remove_outliers, split_outliers
31 def generate_tables(spec, data):
32 """Generate all tables specified in the specification file.
34 :param spec: Specification read from the specification file.
35 :param data: Data to process.
36 :type spec: Specification
40 logging.info("Generating the tables ...")
41 for table in spec.tables:
43 eval(table["algorithm"])(table, data)
45 logging.error("The algorithm '{0}' is not defined.".
46 format(table["algorithm"]))
50 def table_details(table, input_data):
51 """Generate the table(s) with algorithm: table_detailed_test_results
52 specified in the specification file.
54 :param table: Table to generate.
55 :param input_data: Data to process.
56 :type table: pandas.Series
57 :type input_data: InputData
60 logging.info(" Generating the table {0} ...".
61 format(table.get("title", "")))
64 data = input_data.filter_data(table)
66 # Prepare the header of the tables
68 for column in table["columns"]:
69 header.append('"{0}"'.format(str(column["title"]).replace('"', '""')))
71 # Generate the data for the table according to the model in the table
73 job = table["data"].keys()[0]
74 build = str(table["data"][job][0])
76 suites = input_data.suites(job, build)
78 logging.error(" No data available. The table will not be generated.")
81 for suite_longname, suite in suites.iteritems():
83 suite_name = suite["name"]
85 for test in data[job][build].keys():
86 if data[job][build][test]["parent"] in suite_name:
88 for column in table["columns"]:
90 col_data = str(data[job][build][test][column["data"].
91 split(" ")[1]]).replace('"', '""')
92 if column["data"].split(" ")[1] in ("vat-history",
94 col_data = replace(col_data, " |br| ", "",
96 col_data = " |prein| {0} |preout| ".\
98 row_lst.append('"{0}"'.format(col_data))
100 row_lst.append("No data")
101 table_lst.append(row_lst)
103 # Write the data to file
105 file_name = "{0}_{1}{2}".format(table["output-file"], suite_name,
106 table["output-file-ext"])
107 logging.info(" Writing file: '{}'".format(file_name))
108 with open(file_name, "w") as file_handler:
109 file_handler.write(",".join(header) + "\n")
110 for item in table_lst:
111 file_handler.write(",".join(item) + "\n")
113 logging.info(" Done.")
116 def table_merged_details(table, input_data):
117 """Generate the table(s) with algorithm: table_merged_details
118 specified in the specification file.
120 :param table: Table to generate.
121 :param input_data: Data to process.
122 :type table: pandas.Series
123 :type input_data: InputData
126 logging.info(" Generating the table {0} ...".
127 format(table.get("title", "")))
130 data = input_data.filter_data(table)
131 data = input_data.merge_data(data)
132 data.sort_index(inplace=True)
134 suites = input_data.filter_data(table, data_set="suites")
135 suites = input_data.merge_data(suites)
137 # Prepare the header of the tables
139 for column in table["columns"]:
140 header.append('"{0}"'.format(str(column["title"]).replace('"', '""')))
142 for _, suite in suites.iteritems():
144 suite_name = suite["name"]
146 for test in data.keys():
147 if data[test]["parent"] in suite_name:
149 for column in table["columns"]:
151 col_data = str(data[test][column["data"].
152 split(" ")[1]]).replace('"', '""')
153 if column["data"].split(" ")[1] in ("vat-history",
155 col_data = replace(col_data, " |br| ", "",
157 col_data = " |prein| {0} |preout| ".\
158 format(col_data[:-5])
159 row_lst.append('"{0}"'.format(col_data))
161 row_lst.append("No data")
162 table_lst.append(row_lst)
164 # Write the data to file
166 file_name = "{0}_{1}{2}".format(table["output-file"], suite_name,
167 table["output-file-ext"])
168 logging.info(" Writing file: '{}'".format(file_name))
169 with open(file_name, "w") as file_handler:
170 file_handler.write(",".join(header) + "\n")
171 for item in table_lst:
172 file_handler.write(",".join(item) + "\n")
174 logging.info(" Done.")
177 def table_performance_improvements(table, input_data):
178 """Generate the table(s) with algorithm: table_performance_improvements
179 specified in the specification file.
181 :param table: Table to generate.
182 :param input_data: Data to process.
183 :type table: pandas.Series
184 :type input_data: InputData
187 def _write_line_to_file(file_handler, data):
188 """Write a line to the .csv file.
190 :param file_handler: File handler for the csv file. It must be open for
192 :param data: Item to be written to the file.
193 :type file_handler: BinaryIO
199 if isinstance(item["data"], str):
200 # Remove -?drdisc from the end
201 if item["data"].endswith("drdisc"):
202 item["data"] = item["data"][:-8]
203 line_lst.append(item["data"])
204 elif isinstance(item["data"], float):
205 line_lst.append("{:.1f}".format(item["data"]))
206 elif item["data"] is None:
208 file_handler.write(",".join(line_lst) + "\n")
210 logging.info(" Generating the table {0} ...".
211 format(table.get("title", "")))
214 file_name = table.get("template", None)
217 tmpl = _read_csv_template(file_name)
218 except PresentationError:
219 logging.error(" The template '{0}' does not exist. Skipping the "
220 "table.".format(file_name))
223 logging.error("The template is not defined. Skipping the table.")
227 data = input_data.filter_data(table)
229 # Prepare the header of the tables
231 for column in table["columns"]:
232 header.append(column["title"])
234 # Generate the data for the table according to the model in the table
237 for tmpl_item in tmpl:
239 for column in table["columns"]:
240 cmd = column["data"].split(" ")[0]
241 args = column["data"].split(" ")[1:]
242 if cmd == "template":
244 val = float(tmpl_item[int(args[0])])
246 val = tmpl_item[int(args[0])]
247 tbl_item.append({"data": val})
253 for build in data[job]:
255 data_lst.append(float(build[tmpl_item[0]]
256 ["throughput"]["value"]))
257 except (KeyError, TypeError):
261 tbl_item.append({"data": (eval(operation)(data_lst)) /
264 tbl_item.append({"data": None})
265 elif cmd == "operation":
268 nr1 = float(tbl_item[int(args[1])]["data"])
269 nr2 = float(tbl_item[int(args[2])]["data"])
271 tbl_item.append({"data": eval(operation)(nr1, nr2)})
273 tbl_item.append({"data": None})
274 except (IndexError, ValueError, TypeError):
275 logging.error("No data for {0}".format(tbl_item[0]["data"]))
276 tbl_item.append({"data": None})
279 logging.error("Not supported command {0}. Skipping the table.".
282 tbl_lst.append(tbl_item)
284 # Sort the table according to the relative change
285 tbl_lst.sort(key=lambda rel: rel[-1]["data"], reverse=True)
287 # Create the tables and write them to the files
289 "{0}_ndr_top{1}".format(table["output-file"], table["output-file-ext"]),
290 "{0}_pdr_top{1}".format(table["output-file"], table["output-file-ext"]),
291 "{0}_ndr_low{1}".format(table["output-file"], table["output-file-ext"]),
292 "{0}_pdr_low{1}".format(table["output-file"], table["output-file-ext"])
295 for file_name in file_names:
296 logging.info(" Writing the file '{0}'".format(file_name))
297 with open(file_name, "w") as file_handler:
298 file_handler.write(",".join(header) + "\n")
300 if isinstance(item[-1]["data"], float):
301 rel_change = round(item[-1]["data"], 1)
303 rel_change = item[-1]["data"]
304 if "ndr_top" in file_name \
305 and "ndr" in item[0]["data"] \
306 and rel_change >= 10.0:
307 _write_line_to_file(file_handler, item)
308 elif "pdr_top" in file_name \
309 and "pdr" in item[0]["data"] \
310 and rel_change >= 10.0:
311 _write_line_to_file(file_handler, item)
312 elif "ndr_low" in file_name \
313 and "ndr" in item[0]["data"] \
314 and rel_change < 10.0:
315 _write_line_to_file(file_handler, item)
316 elif "pdr_low" in file_name \
317 and "pdr" in item[0]["data"] \
318 and rel_change < 10.0:
319 _write_line_to_file(file_handler, item)
321 logging.info(" Done.")
324 def _read_csv_template(file_name):
325 """Read the template from a .csv file.
327 :param file_name: Name / full path / relative path of the file to read.
329 :returns: Data from the template as list (lines) of lists (items on line).
331 :raises: PresentationError if it is not possible to read the file.
335 with open(file_name, 'r') as csv_file:
337 for line in csv_file:
338 tmpl_data.append(line[:-1].split(","))
340 except IOError as err:
341 raise PresentationError(str(err), level="ERROR")
344 def table_performance_comparison(table, input_data):
345 """Generate the table(s) with algorithm: table_performance_comparison
346 specified in the specification file.
348 :param table: Table to generate.
349 :param input_data: Data to process.
350 :type table: pandas.Series
351 :type input_data: InputData
354 logging.info(" Generating the table {0} ...".
355 format(table.get("title", "")))
358 data = input_data.filter_data(table, continue_on_error=True)
360 # Prepare the header of the tables
362 header = ["Test case",
363 "{0} Throughput [Mpps]".format(table["reference"]["title"]),
364 "{0} stdev [Mpps]".format(table["reference"]["title"]),
365 "{0} Throughput [Mpps]".format(table["compare"]["title"]),
366 "{0} stdev [Mpps]".format(table["compare"]["title"]),
368 header_str = ",".join(header) + "\n"
369 except (AttributeError, KeyError) as err:
370 logging.error("The model is invalid, missing parameter: {0}".
374 # Prepare data to the table:
376 for job, builds in table["reference"]["data"].items():
378 for tst_name, tst_data in data[job][str(build)].iteritems():
379 if tbl_dict.get(tst_name, None) is None:
380 name = "{0}-{1}".format(tst_data["parent"].split("-")[0],
381 "-".join(tst_data["name"].
383 tbl_dict[tst_name] = {"name": name,
387 tbl_dict[tst_name]["ref-data"].\
388 append(tst_data["throughput"]["value"])
390 pass # No data in output.xml for this test
392 for job, builds in table["compare"]["data"].items():
394 for tst_name, tst_data in data[job][str(build)].iteritems():
396 tbl_dict[tst_name]["cmp-data"].\
397 append(tst_data["throughput"]["value"])
401 tbl_dict.pop(tst_name, None)
404 for tst_name in tbl_dict.keys():
405 item = [tbl_dict[tst_name]["name"], ]
406 if tbl_dict[tst_name]["ref-data"]:
407 data_t = remove_outliers(tbl_dict[tst_name]["ref-data"],
408 outlier_const=table["outlier-const"])
409 # TODO: Specify window size.
411 item.append(round(mean(data_t) / 1000000, 2))
412 item.append(round(stdev(data_t) / 1000000, 2))
414 item.extend([None, None])
416 item.extend([None, None])
417 if tbl_dict[tst_name]["cmp-data"]:
418 data_t = remove_outliers(tbl_dict[tst_name]["cmp-data"],
419 outlier_const=table["outlier-const"])
420 # TODO: Specify window size.
422 item.append(round(mean(data_t) / 1000000, 2))
423 item.append(round(stdev(data_t) / 1000000, 2))
425 item.extend([None, None])
427 item.extend([None, None])
428 if item[1] is not None and item[3] is not None:
429 item.append(int(relative_change(float(item[1]), float(item[3]))))
433 # Sort the table according to the relative change
434 tbl_lst.sort(key=lambda rel: rel[-1], reverse=True)
438 tbl_names = ["{0}-ndr-1t1c-full{1}".format(table["output-file"],
439 table["output-file-ext"]),
440 "{0}-ndr-2t2c-full{1}".format(table["output-file"],
441 table["output-file-ext"]),
442 "{0}-ndr-4t4c-full{1}".format(table["output-file"],
443 table["output-file-ext"]),
444 "{0}-pdr-1t1c-full{1}".format(table["output-file"],
445 table["output-file-ext"]),
446 "{0}-pdr-2t2c-full{1}".format(table["output-file"],
447 table["output-file-ext"]),
448 "{0}-pdr-4t4c-full{1}".format(table["output-file"],
449 table["output-file-ext"])
451 for file_name in tbl_names:
452 logging.info(" Writing file: '{0}'".format(file_name))
453 with open(file_name, "w") as file_handler:
454 file_handler.write(header_str)
456 if (file_name.split("-")[-3] in test[0] and # NDR vs PDR
457 file_name.split("-")[-2] in test[0]): # cores
458 test[0] = "-".join(test[0].split("-")[:-1])
459 file_handler.write(",".join([str(item) for item in test]) +
463 tbl_names_txt = ["{0}-ndr-1t1c-full.txt".format(table["output-file"]),
464 "{0}-ndr-2t2c-full.txt".format(table["output-file"]),
465 "{0}-ndr-4t4c-full.txt".format(table["output-file"]),
466 "{0}-pdr-1t1c-full.txt".format(table["output-file"]),
467 "{0}-pdr-2t2c-full.txt".format(table["output-file"]),
468 "{0}-pdr-4t4c-full.txt".format(table["output-file"])
471 for i, txt_name in enumerate(tbl_names_txt):
473 logging.info(" Writing file: '{0}'".format(txt_name))
474 with open(tbl_names[i], 'rb') as csv_file:
475 csv_content = csv.reader(csv_file, delimiter=',', quotechar='"')
476 for row in csv_content:
477 if txt_table is None:
478 txt_table = prettytable.PrettyTable(row)
480 txt_table.add_row(row)
481 txt_table.align["Test case"] = "l"
482 with open(txt_name, "w") as txt_file:
483 txt_file.write(str(txt_table))
485 # Selected tests in csv:
486 input_file = "{0}-ndr-1t1c-full{1}".format(table["output-file"],
487 table["output-file-ext"])
488 with open(input_file, "r") as in_file:
493 output_file = "{0}-ndr-1t1c-top{1}".format(table["output-file"],
494 table["output-file-ext"])
495 logging.info(" Writing file: '{0}'".format(output_file))
496 with open(output_file, "w") as out_file:
497 out_file.write(header_str)
498 for i, line in enumerate(lines[1:]):
499 if i == table["nr-of-tests-shown"]:
503 output_file = "{0}-ndr-1t1c-bottom{1}".format(table["output-file"],
504 table["output-file-ext"])
505 logging.info(" Writing file: '{0}'".format(output_file))
506 with open(output_file, "w") as out_file:
507 out_file.write(header_str)
508 for i, line in enumerate(lines[-1:0:-1]):
509 if i == table["nr-of-tests-shown"]:
513 input_file = "{0}-pdr-1t1c-full{1}".format(table["output-file"],
514 table["output-file-ext"])
515 with open(input_file, "r") as in_file:
520 output_file = "{0}-pdr-1t1c-top{1}".format(table["output-file"],
521 table["output-file-ext"])
522 logging.info(" Writing file: '{0}'".format(output_file))
523 with open(output_file, "w") as out_file:
524 out_file.write(header_str)
525 for i, line in enumerate(lines[1:]):
526 if i == table["nr-of-tests-shown"]:
530 output_file = "{0}-pdr-1t1c-bottom{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:0:-1]):
536 if i == table["nr-of-tests-shown"]:
541 def table_performance_comparison_mrr(table, input_data):
542 """Generate the table(s) with algorithm: table_performance_comparison_mrr
543 specified in the specification file.
545 :param table: Table to generate.
546 :param input_data: Data to process.
547 :type table: pandas.Series
548 :type input_data: InputData
551 logging.info(" Generating the table {0} ...".
552 format(table.get("title", "")))
555 data = input_data.filter_data(table, continue_on_error=True)
557 # Prepare the header of the tables
559 header = ["Test case",
560 "{0} Throughput [Mpps]".format(table["reference"]["title"]),
561 "{0} stdev [Mpps]".format(table["reference"]["title"]),
562 "{0} Throughput [Mpps]".format(table["compare"]["title"]),
563 "{0} stdev [Mpps]".format(table["compare"]["title"]),
565 header_str = ",".join(header) + "\n"
566 except (AttributeError, KeyError) as err:
567 logging.error("The model is invalid, missing parameter: {0}".
571 # Prepare data to the table:
573 for job, builds in table["reference"]["data"].items():
575 for tst_name, tst_data in data[job][str(build)].iteritems():
576 if tbl_dict.get(tst_name, None) is None:
577 name = "{0}-{1}".format(tst_data["parent"].split("-")[0],
578 "-".join(tst_data["name"].
580 tbl_dict[tst_name] = {"name": name,
584 tbl_dict[tst_name]["ref-data"].\
585 append(tst_data["result"]["throughput"])
587 pass # No data in output.xml for this test
589 for job, builds in table["compare"]["data"].items():
591 for tst_name, tst_data in data[job][str(build)].iteritems():
593 tbl_dict[tst_name]["cmp-data"].\
594 append(tst_data["result"]["throughput"])
598 tbl_dict.pop(tst_name, None)
601 for tst_name in tbl_dict.keys():
602 item = [tbl_dict[tst_name]["name"], ]
603 if tbl_dict[tst_name]["ref-data"]:
604 data_t = remove_outliers(tbl_dict[tst_name]["ref-data"],
605 outlier_const=table["outlier-const"])
606 # TODO: Specify window size.
608 item.append(round(mean(data_t) / 1000000, 2))
609 item.append(round(stdev(data_t) / 1000000, 2))
611 item.extend([None, None])
613 item.extend([None, None])
614 if tbl_dict[tst_name]["cmp-data"]:
615 data_t = remove_outliers(tbl_dict[tst_name]["cmp-data"],
616 outlier_const=table["outlier-const"])
617 # TODO: Specify window size.
619 item.append(round(mean(data_t) / 1000000, 2))
620 item.append(round(stdev(data_t) / 1000000, 2))
622 item.extend([None, None])
624 item.extend([None, None])
625 if item[1] is not None and item[3] is not None and item[1] != 0:
626 item.append(int(relative_change(float(item[1]), float(item[3]))))
630 # Sort the table according to the relative change
631 tbl_lst.sort(key=lambda rel: rel[-1], reverse=True)
635 tbl_names = ["{0}-1t1c-full{1}".format(table["output-file"],
636 table["output-file-ext"]),
637 "{0}-2t2c-full{1}".format(table["output-file"],
638 table["output-file-ext"]),
639 "{0}-4t4c-full{1}".format(table["output-file"],
640 table["output-file-ext"])
642 for file_name in tbl_names:
643 logging.info(" Writing file: '{0}'".format(file_name))
644 with open(file_name, "w") as file_handler:
645 file_handler.write(header_str)
647 if file_name.split("-")[-2] in test[0]: # cores
648 test[0] = "-".join(test[0].split("-")[:-1])
649 file_handler.write(",".join([str(item) for item in test]) +
653 tbl_names_txt = ["{0}-1t1c-full.txt".format(table["output-file"]),
654 "{0}-2t2c-full.txt".format(table["output-file"]),
655 "{0}-4t4c-full.txt".format(table["output-file"])
658 for i, txt_name in enumerate(tbl_names_txt):
660 logging.info(" Writing file: '{0}'".format(txt_name))
661 with open(tbl_names[i], 'rb') as csv_file:
662 csv_content = csv.reader(csv_file, delimiter=',', quotechar='"')
663 for row in csv_content:
664 if txt_table is None:
665 txt_table = prettytable.PrettyTable(row)
667 txt_table.add_row(row)
668 txt_table.align["Test case"] = "l"
669 with open(txt_name, "w") as txt_file:
670 txt_file.write(str(txt_table))
673 def table_performance_trending_dashboard(table, input_data):
674 """Generate the table(s) with algorithm: table_performance_comparison
675 specified in the specification file.
677 :param table: Table to generate.
678 :param input_data: Data to process.
679 :type table: pandas.Series
680 :type input_data: InputData
683 logging.info(" Generating the table {0} ...".
684 format(table.get("title", "")))
687 data = input_data.filter_data(table, continue_on_error=True)
689 # Prepare the header of the tables
690 header = ["Test Case",
691 "Throughput Trend [Mpps]",
692 "Long Trend Compliance",
694 "Top Anomaly [Mpps]",
698 header_str = ",".join(header) + "\n"
700 # Prepare data to the table:
702 for job, builds in table["data"].items():
704 for tst_name, tst_data in data[job][str(build)].iteritems():
705 if tbl_dict.get(tst_name, None) is None:
706 name = "{0}-{1}".format(tst_data["parent"].split("-")[0],
707 "-".join(tst_data["name"].
709 tbl_dict[tst_name] = {"name": name,
712 tbl_dict[tst_name]["data"][str(build)] = \
713 tst_data["result"]["throughput"]
714 except (TypeError, KeyError):
715 pass # No data in output.xml for this test
718 for tst_name in tbl_dict.keys():
719 if len(tbl_dict[tst_name]["data"]) > 2:
721 pd_data = pd.Series(tbl_dict[tst_name]["data"])
722 win_size = min(pd_data.size, table["window"])
724 name = tbl_dict[tst_name]["name"]
726 median = pd_data.rolling(window=win_size, min_periods=2).median()
727 median_idx = pd_data.size - table["long-trend-window"]
728 median_idx = 0 if median_idx < 0 else median_idx
730 max_median = max([x for x in median.values[median_idx:]
734 trimmed_data, _ = split_outliers(pd_data, outlier_const=1.5,
736 stdev_t = pd_data.rolling(window=win_size, min_periods=2).std()
738 rel_change_lst = [None, ]
739 classification_lst = [None, ]
740 median_lst = [None, ]
741 sample_lst = [None, ]
743 for build_nr, value in pd_data.iteritems():
747 # Relative changes list:
748 if not isnan(value) \
749 and not isnan(median[build_nr]) \
750 and median[build_nr] != 0:
751 rel_change_lst.append(round(
752 relative_change(float(median[build_nr]), float(value)),
755 rel_change_lst.append(None)
757 # Classification list:
758 if isnan(trimmed_data[build_nr]) \
759 or isnan(median[build_nr]) \
760 or isnan(stdev_t[build_nr]) \
762 classification_lst.append("outlier")
763 elif value < (median[build_nr] - 3 * stdev_t[build_nr]):
764 classification_lst.append("regression")
765 elif value > (median[build_nr] + 3 * stdev_t[build_nr]):
766 classification_lst.append("progression")
768 classification_lst.append("normal")
769 sample_lst.append(value)
770 median_lst.append(median[build_nr])
772 last_idx = len(classification_lst) - 1
773 first_idx = last_idx - int(table["evaluated-window"])
778 consecutive_outliers = 0
780 for item in classification_lst[first_idx:]:
781 if item == "outlier":
783 consecutive_outliers += 1
784 if consecutive_outliers == 3:
787 consecutive_outliers = 0
790 classification = "failure"
791 elif "regression" in classification_lst[first_idx:]:
792 classification = "regression"
793 elif "progression" in classification_lst[first_idx:]:
794 classification = "progression"
796 classification = "normal"
798 if classification == "normal":
799 index = len(classification_lst) - 1
801 tmp_classification = "outlier" if classification == "failure" \
804 for idx in range(first_idx, len(classification_lst)):
805 if classification_lst[idx] == tmp_classification:
806 if rel_change_lst[idx]:
811 for idx in range(index+1, len(classification_lst)):
812 if classification_lst[idx] == tmp_classification:
813 if rel_change_lst[idx]:
814 if (abs(rel_change_lst[idx]) >
815 abs(rel_change_lst[index])):
818 logging.debug("{}".format(name))
819 logging.debug("sample_lst: {} - {}".
820 format(len(sample_lst), sample_lst))
821 logging.debug("median_lst: {} - {}".
822 format(len(median_lst), median_lst))
823 logging.debug("rel_change: {} - {}".
824 format(len(rel_change_lst), rel_change_lst))
825 logging.debug("classn_lst: {} - {}".
826 format(len(classification_lst), classification_lst))
827 logging.debug("index: {}".format(index))
828 logging.debug("classifica: {}".format(classification))
831 trend = round(float(median_lst[-1]) / 1000000, 2) \
832 if not isnan(median_lst[-1]) else '-'
833 sample = round(float(sample_lst[index]) / 1000000, 2) \
834 if not isnan(sample_lst[index]) else '-'
835 rel_change = rel_change_lst[index] \
836 if rel_change_lst[index] is not None else '-'
837 if max_median is not None:
838 if not isnan(sample_lst[index]):
839 long_trend_threshold = \
840 max_median * (table["long-trend-threshold"] / 100)
841 if sample_lst[index] < long_trend_threshold:
842 long_trend_classification = "failure"
844 long_trend_classification = 'normal'
846 long_trend_classification = "failure"
848 long_trend_classification = '-'
849 tbl_lst.append([name,
851 long_trend_classification,
853 '-' if classification == "normal" else sample,
854 '-' if classification == "normal" else
857 except IndexError as err:
858 logging.error("{}".format(err))
861 # Sort the table according to the classification
863 for long_trend_class in ("failure", 'normal', '-'):
864 tbl_long = [item for item in tbl_lst if item[2] == long_trend_class]
865 for classification in \
866 ("failure", "regression", "progression", "normal"):
867 tbl_tmp = [item for item in tbl_long if item[3] == classification]
868 tbl_tmp.sort(key=lambda rel: rel[0])
869 tbl_sorted.extend(tbl_tmp)
871 file_name = "{0}{1}".format(table["output-file"], table["output-file-ext"])
873 logging.info(" Writing file: '{0}'".format(file_name))
874 with open(file_name, "w") as file_handler:
875 file_handler.write(header_str)
876 for test in tbl_sorted:
877 file_handler.write(",".join([str(item) for item in test]) + '\n')
879 txt_file_name = "{0}.txt".format(table["output-file"])
881 logging.info(" Writing file: '{0}'".format(txt_file_name))
882 with open(file_name, 'rb') as csv_file:
883 csv_content = csv.reader(csv_file, delimiter=',', quotechar='"')
884 for row in csv_content:
885 if txt_table is None:
886 txt_table = prettytable.PrettyTable(row)
888 txt_table.add_row(row)
889 txt_table.align["Test case"] = "l"
890 with open(txt_file_name, "w") as txt_file:
891 txt_file.write(str(txt_table))
894 def table_performance_trending_dashboard_html(table, input_data):
895 """Generate the table(s) with algorithm:
896 table_performance_trending_dashboard_html specified in the specification
899 :param table: Table to generate.
900 :param input_data: Data to process.
901 :type table: pandas.Series
902 :type input_data: InputData
905 logging.info(" Generating the table {0} ...".
906 format(table.get("title", "")))
909 with open(table["input-file"], 'rb') as csv_file:
910 csv_content = csv.reader(csv_file, delimiter=',', quotechar='"')
911 csv_lst = [item for item in csv_content]
913 logging.warning("The input file is not defined.")
915 except csv.Error as err:
916 logging.warning("Not possible to process the file '{0}'.\n{1}".
917 format(table["input-file"], err))
921 dashboard = ET.Element("table", attrib=dict(width="100%", border='0'))
924 tr = ET.SubElement(dashboard, "tr", attrib=dict(bgcolor="#7eade7"))
925 for idx, item in enumerate(csv_lst[0]):
926 alignment = "left" if idx == 0 else "center"
927 th = ET.SubElement(tr, "th", attrib=dict(align=alignment))
931 for r_idx, row in enumerate(csv_lst[1:]):
932 background = "#D4E4F7" if r_idx % 2 else "white"
933 tr = ET.SubElement(dashboard, "tr", attrib=dict(bgcolor=background))
936 for c_idx, item in enumerate(row):
937 alignment = "left" if c_idx == 0 else "center"
938 td = ET.SubElement(tr, "td", attrib=dict(align=alignment))
946 file_name = "container_memif.html"
948 elif "vhost" in item:
949 if "l2xcbase" in item or "l2bdbasemaclrn" in item:
950 file_name = "vm_vhost_l2.html"
951 elif "ip4base" in item:
952 file_name = "vm_vhost_ip4.html"
954 elif "ipsec" in item:
955 file_name = "ipsec.html"
957 elif "ethip4lispip" in item or "ethip4vxlan" in item:
958 file_name = "ip4_tunnels.html"
960 elif "ip4base" in item or "ip4scale" in item:
961 file_name = "ip4.html"
962 if "iacl" in item or "snat" in item or "cop" in item:
963 feature = "-features"
965 elif "ip6base" in item or "ip6scale" in item:
966 file_name = "ip6.html"
968 elif "l2xcbase" in item or "l2xcscale" in item \
969 or "l2bdbasemaclrn" in item or "l2bdscale" in item \
970 or "l2dbbasemaclrn" in item or "l2dbscale" in item:
971 file_name = "l2.html"
973 feature = "-features"
979 elif "xl710" in item:
988 elif "9000b" in item:
1000 url = url + file_name + anchor + feature
1002 ref = ET.SubElement(td, "a", attrib=dict(href=url))
1006 if item == "regression":
1007 td.set("bgcolor", "#eca1a6")
1008 elif item == "failure":
1009 td.set("bgcolor", "#d6cbd3")
1010 elif item == "progression":
1011 td.set("bgcolor", "#bdcebe")
1016 with open(table["output-file"], 'w') as html_file:
1017 logging.info(" Writing file: '{0}'".
1018 format(table["output-file"]))
1019 html_file.write(".. raw:: html\n\n\t")
1020 html_file.write(ET.tostring(dashboard))
1021 html_file.write("\n\t<p><br><br></p>\n")
1023 logging.warning("The output file is not defined.")