6b4953c00bceb24b5b84312ee2de967a68508a8f
[csit.git] / resources / tools / report_gen / run_improvments_tables.py
1 #!/usr/bin/python
2
3 # Copyright (c) 2017 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """Generate csv files for the chapter "CSIT Release Notes" from json files
17 generated by Jenkins' jobs.
18 """
19
20 from sys import exit as sys_exit
21 from os import walk
22 from os.path import join
23 from math import sqrt
24 from argparse import ArgumentParser, RawDescriptionHelpFormatter
25 from json import load
26
27
28 EXT_JSON = ".json"
29 EXT_TMPL = ".template"
30 EXT_CSV = ".csv"
31
32
33 def get_files(path, extension):
34     """Generates the list of files to process.
35
36     :param path: Path to files.
37     :param extension: Extension of files to process. If it is the empty string,
38     all files will be processed.
39     :type path: str
40     :type extension: str
41     :returns: List of files to process.
42     :rtype: list
43     """
44
45     file_list = list()
46     for root, _, files in walk(path):
47         for filename in files:
48             if extension:
49                 if filename.endswith(extension):
50                     file_list.append(join(root, filename))
51             else:
52                 file_list.append(join(root, filename))
53
54     return file_list
55
56
57 def parse_args():
58     """Parse arguments from cmd line.
59
60     :returns: Parsed arguments.
61     :rtype ArgumentParser
62     """
63
64     parser = ArgumentParser(description=__doc__,
65                             formatter_class=RawDescriptionHelpFormatter)
66     parser.add_argument("-i", "--input",
67                         required=True,
68                         help="Input folder with data files.")
69     parser.add_argument("-o", "--output",
70                         required=True,
71                         help="Output folder with csv files and templates for "
72                              "csv files.")
73     return parser.parse_args()
74
75
76 def calculate_stats(data):
77     """Calculate statistics:
78     - average,
79     - standard deviation.
80
81     :param data: Data to process.
82     :type data: list
83     :returns: Average and standard deviation.
84     :rtype: tuple
85     """
86
87     if len(data) == 0:
88         return None, None
89
90     def average(items):
91         """Calculate average from the items.
92
93         :param items: Average is calculated from these items.
94         :type items: list
95         :returns: Average.
96         :rtype: float
97         """
98         return float(sum(items)) / len(items)
99
100     avg = average(data)
101     variance = [(x - avg) ** 2 for x in data]
102     stdev = sqrt(average(variance))
103
104     return avg, stdev
105
106
107 def write_line_to_file(file_handler, item):
108     """Write a line to the csv file.
109
110     :param file_handler: File handler for the csv file. It must be open for
111      writing text.
112     :param item: Item to be written to the file.
113     :type file_handler: BinaryIO
114     :type item: dict
115     """
116
117     mean = "" if item["mean"] is None else "{:.1f}".format(item["mean"])
118     stdev = "" if item["stdev"] is None else "{:.1f}".format(item["stdev"])
119     change = "" if item["change"] is None else "{:.0f}%".format(item["change"])
120     file_handler.write("{},{},{},{}\n".format(item["old"], mean, stdev, change))
121
122
123 def main():
124     """Main function to generate csv files for the chapter "CSIT Release Notes"
125     from json files generated by Jenkins' jobs.
126     """
127
128     args = parse_args()
129
130     json_files = get_files(args.input, EXT_JSON)
131     tmpl_files = get_files(args.output, EXT_TMPL)
132
133     if len(json_files) == 0:
134         print("No json data to process.")
135         exit(1)
136
137     if len(tmpl_files) == 0:
138         print("No template files to process.")
139         exit(1)
140
141     # Get information from template files
142     csv_data = list()
143     for tmpl_file in tmpl_files:
144         with open(tmpl_file, mode='r') as file_handler:
145             for line in file_handler:
146                 line_list = line.split(',')
147                 try:
148                     csv_data.append({
149                         "ID": line_list[0],
150                         "type": line_list[0].rsplit("-", 1)[-1],
151                         "old": ",".join(line_list[1:])[:-1],
152                         "last_old": line_list[-1][:-1],
153                         "rates": list(),
154                         "mean": None,
155                         "stdev": None,
156                         "change": None})
157                 except IndexError:
158                     pass
159
160     # Update existing data with the new information from json files
161     for json_file in json_files:
162         with open(json_file) as file_handler:
163             tests_data = load(file_handler)
164             for item in csv_data:
165                 try:
166                     rate = tests_data["data"][item["ID"]]["throughput"]["value"]
167                     item["rates"].append(rate)
168                 except KeyError:
169                     pass
170
171     # Add statistics
172     for item in csv_data:
173         mean, stdev = calculate_stats(item["rates"])
174         if mean is not None:
175             mean = float(mean) / 1000000
176             old = float(item["last_old"])
177             item["mean"] = mean
178             item["change"] = ((round(mean, 1) - round(old, 1)) / round(old, 1))\
179                              * 100
180             item["stdev"] = stdev / 1000000
181
182     # Sort the list, key = change
183     csv_data.sort(key=lambda data: data["change"], reverse=True)
184
185     # Write csv files
186     for tmpl_file in tmpl_files:
187         csv_file = tmpl_file.replace(EXT_TMPL, EXT_CSV)
188         with open(csv_file, "w") as file_handler:
189             for item in csv_data:
190                 if "pdr_" in csv_file \
191                         and "_others" not in csv_file \
192                         and item["type"] == "pdrdisc" \
193                         and item["change"] >= 9.5:
194                     write_line_to_file(file_handler, item)
195                 elif "pdr_" in csv_file \
196                         and "_others" in csv_file \
197                         and item["type"] == "pdrdisc" \
198                         and item["change"] < 9.5:
199                     write_line_to_file(file_handler, item)
200                 elif "ndr_" in csv_file \
201                         and "_others" not in csv_file \
202                         and item["type"] == "ndrdisc" \
203                         and item["change"] >= 9.5:
204                     write_line_to_file(file_handler, item)
205                 elif "ndr_" in csv_file \
206                         and "_others" in csv_file \
207                         and item["type"] == "ndrdisc" \
208                         and item["change"] < 9.5:
209                     write_line_to_file(file_handler, item)
210
211
212 if __name__ == "__main__":
213     sys_exit(main())