Auto-generated patch by python-black
[integration/test.git] / tools / wcbench / stats.py
1 #!/usr/bin/env python
2 """Compute basic stats about CBench data."""
3
4 import csv
5 import numpy
6 import pprint
7 import matplotlib.pyplot as pyplot
8 import argparse
9 import sys
10
11
12 class Stats(object):
13
14     """Compute stats and/or graph data.
15
16     I know I could convert these fns that simply punt to a helper
17     to a dict/list data structure, but that would remove some of the
18     flexabilty I get by simply calling a graph/stat fn for each
19     graph/stat arg. All current fns just punt to helpers, but future
20     ones might not.
21
22     """
23
24     results_file = "results.csv"
25     log_file = "cbench.log"
26     precision = 3
27     run_index = 0
28     min_flow_index = 1
29     max_flow_index = 2
30     avg_flow_index = 3
31     start_time_index = 4
32     end_time_index = 5
33     start_steal_time_index = 12
34     end_steal_time_index = 13
35     used_ram_index = 15
36     one_load_index = 18
37     five_load_index = 19
38     fifteen_load_index = 20
39     start_iowait_index = 22
40     end_iowait_index = 23
41
42     def __init__(self):
43         """Setup some flags and data structures, kick off build_cols call."""
44         self.build_cols()
45         self.results = {}
46         self.results["sample_size"] = len(self.run_col)
47
48     def build_cols(self):
49         """Parse results file into lists of values, one per column."""
50         self.run_col = []
51         self.min_flows_col = []
52         self.max_flows_col = []
53         self.avg_flows_col = []
54         self.runtime_col = []
55         self.used_ram_col = []
56         self.iowait_col = []
57         self.steal_time_col = []
58         self.one_load_col = []
59         self.five_load_col = []
60         self.fifteen_load_col = []
61
62         with open(self.results_file, "rb") as results_fd:
63             results_reader = csv.reader(results_fd)
64             for row in results_reader:
65                 try:
66                     self.run_col.append(float(row[self.run_index]))
67                     self.min_flows_col.append(float(row[self.min_flow_index]))
68                     self.max_flows_col.append(float(row[self.max_flow_index]))
69                     self.avg_flows_col.append(float(row[self.avg_flow_index]))
70                     self.runtime_col.append(
71                         float(row[self.end_time_index])
72                         - float(row[self.start_time_index])
73                     )
74                     self.used_ram_col.append(float(row[self.used_ram_index]))
75                     self.iowait_col.append(
76                         float(row[self.end_iowait_index])
77                         - float(row[self.start_iowait_index])
78                     )
79                     self.steal_time_col.append(
80                         float(row[self.end_steal_time_index])
81                         - float(row[self.start_steal_time_index])
82                     )
83                     self.one_load_col.append(float(row[self.one_load_index]))
84                     self.five_load_col.append(float(row[self.five_load_index]))
85                     self.fifteen_load_col.append(float(row[self.fifteen_load_index]))
86                 except ValueError:
87                     # Skips header
88                     continue
89
90     def compute_avg_flow_stats(self):
91         """Compute CBench average flows/second stats."""
92         self.compute_generic_stats("flows", self.avg_flows_col)
93
94     def build_avg_flow_graph(self, total_gcount, graph_num):
95         """Plot average flows/sec data.
96
97         :param total_gcount: Total number of graphs to render.
98         :type total_gcount: int
99         :param graph_num: Number for this graph, <= total_gcount.
100         :type graph_num: int
101
102         """
103         self.build_generic_graph(
104             total_gcount, graph_num, "Average Flows per Second", self.avg_flows_col
105         )
106
107     def compute_min_flow_stats(self):
108         """Compute CBench min flows/second stats."""
109         self.compute_generic_stats("min_flows", self.min_flows_col)
110
111     def build_min_flow_graph(self, total_gcount, graph_num):
112         """Plot min flows/sec data.
113
114         :param total_gcount: Total number of graphs to render.
115         :type total_gcount: int
116         :param graph_num: Number for this graph, <= total_gcount.
117         :type graph_num: int
118
119         """
120         self.build_generic_graph(
121             total_gcount, graph_num, "Minimum Flows per Second", self.min_flows_col
122         )
123
124     def compute_max_flow_stats(self):
125         """Compute CBench max flows/second stats."""
126         self.compute_generic_stats("max_flows", self.max_flows_col)
127
128     def build_max_flow_graph(self, total_gcount, graph_num):
129         """Plot max flows/sec data.
130
131         :param total_gcount: Total number of graphs to render.
132         :type total_gcount: int
133         :param graph_num: Number for this graph, <= total_gcount.
134         :type graph_num: int
135
136         """
137         self.build_generic_graph(
138             total_gcount, graph_num, "Maximum Flows per Second", self.max_flows_col
139         )
140
141     def compute_ram_stats(self):
142         """Compute used RAM stats."""
143         self.compute_generic_stats("used_ram", self.used_ram_col)
144
145     def build_ram_graph(self, total_gcount, graph_num):
146         """Plot used RAM data.
147
148         :param total_gcount: Total number of graphs to render.
149         :type total_gcount: int
150         :param graph_num: Number for this graph, <= total_gcount.
151         :type graph_num: int
152
153         """
154         self.build_generic_graph(
155             total_gcount, graph_num, "Used RAM (MB)", self.used_ram_col
156         )
157
158     def compute_runtime_stats(self):
159         """Compute CBench runtime length stats."""
160         self.compute_generic_stats("runtime", self.runtime_col)
161
162     def build_runtime_graph(self, total_gcount, graph_num):
163         """Plot CBench runtime length data.
164
165         :param total_gcount: Total number of graphs to render.
166         :type total_gcount: int
167         :param graph_num: Number for this graph, <= total_gcount.
168         :type graph_num: int
169
170         """
171         self.build_generic_graph(
172             total_gcount, graph_num, "CBench Runtime (sec)", self.runtime_col
173         )
174
175     def compute_iowait_stats(self):
176         """Compute iowait stats."""
177         self.compute_generic_stats("iowait", self.iowait_col)
178
179     def build_iowait_graph(self, total_gcount, graph_num):
180         """Plot iowait data.
181
182         :param total_gcount: Total number of graphs to render.
183         :type total_gcount: int
184         :param graph_num: Number for this graph, <= total_gcount.
185         :type graph_num: int
186
187         """
188         self.build_generic_graph(
189             total_gcount, graph_num, "IOWait Time (sec)", self.iowait_col
190         )
191
192     def compute_steal_time_stats(self):
193         """Compute steal time stats."""
194         self.compute_generic_stats("steal_time", self.steal_time_col)
195
196     def build_steal_time_graph(self, total_gcount, graph_num):
197         """Plot steal time data.
198
199         :param total_gcount: Total number of graphs to render.
200         :type total_gcount: int
201         :param graph_num: Number for this graph, <= total_gcount.
202         :type graph_num: int
203
204         """
205         self.build_generic_graph(
206             total_gcount, graph_num, "Steal Time (sec)", self.steal_time_col
207         )
208
209     def compute_one_load_stats(self):
210         """Compute one minute load stats."""
211         self.compute_generic_stats("one_load", self.one_load_col)
212
213     def build_one_load_graph(self, total_gcount, graph_num):
214         """Plot one minute load data.
215
216         :param total_gcount: Total number of graphs to render.
217         :type total_gcount: int
218         :param graph_num: Number for this graph, <= total_gcount.
219         :type graph_num: int
220
221         """
222         self.build_generic_graph(
223             total_gcount, graph_num, "One Minute Load", self.one_load_col
224         )
225
226     def compute_five_load_stats(self):
227         """Compute five minute load stats."""
228         self.compute_generic_stats("five_load", self.five_load_col)
229
230     def build_five_load_graph(self, total_gcount, graph_num):
231         """Plot five minute load data.
232
233         :param total_gcount: Total number of graphs to render.
234         :type total_gcount: int
235         :param graph_num: Number for this graph, <= total_gcount.
236         :type graph_num: int
237
238         """
239         self.build_generic_graph(
240             total_gcount, graph_num, "Five Minute Load", self.five_load_col
241         )
242
243     def compute_fifteen_load_stats(self):
244         """Compute fifteen minute load stats."""
245         self.compute_generic_stats("fifteen_load", self.fifteen_load_col)
246
247     def build_fifteen_load_graph(self, total_gcount, graph_num):
248         """Plot fifteen minute load data.
249
250         :param total_gcount: Total number of graphs to render.
251         :type total_gcount: int
252         :param graph_num: Number for this graph, <= total_gcount.
253         :type graph_num: int
254
255         """
256         self.build_generic_graph(
257             total_gcount, graph_num, "Fifteen Minute Load", self.fifteen_load_col
258         )
259
260     def compute_generic_stats(self, stats_name, stats_col):
261         """Helper for computing generic stats."""
262         generic_stats = {}
263         generic_stats["min"] = int(numpy.amin(stats_col))
264         generic_stats["max"] = int(numpy.amax(stats_col))
265         generic_stats["mean"] = round(numpy.mean(stats_col), self.precision)
266         generic_stats["stddev"] = round(numpy.std(stats_col), self.precision)
267         try:
268             generic_stats["relstddev"] = round(
269                 generic_stats["stddev"] / generic_stats["mean"] * 100, self.precision
270             )
271         except ZeroDivisionError:
272             generic_stats["relstddev"] = 0.0
273         self.results[stats_name] = generic_stats
274
275     def build_generic_graph(self, total_gcount, graph_num, y_label, data_col):
276         """Helper for plotting generic data.
277
278         :param total_gcount: Total number of graphs to render.
279         :type total_gcount: int
280         :param graph_num: Number for this graph, <= total_gcount.
281         :type graph_num: int
282         :param y_label: Lable of Y axis.
283         :type y_label: string
284         :param data_col: Data to graph.
285         :type data_col: list
286
287         """
288         # Pagenerics are numrows, numcols, fignum
289         pyplot.subplot(total_gcount, 1, graph_num)
290         # "go" means green O's
291         pyplot.plot(self.run_col, data_col, "go")
292         pyplot.xlabel("Run Number")
293         pyplot.ylabel(y_label)
294
295
296 # Build stats object
297 stats = Stats()
298
299 # Map of graph names to the Stats.fns that build them
300 graph_map = {
301     "min_flows": stats.build_min_flow_graph,
302     "max_flows": stats.build_max_flow_graph,
303     "flows": stats.build_avg_flow_graph,
304     "runtime": stats.build_runtime_graph,
305     "iowait": stats.build_iowait_graph,
306     "steal_time": stats.build_steal_time_graph,
307     "one_load": stats.build_one_load_graph,
308     "five_load": stats.build_five_load_graph,
309     "fifteen_load": stats.build_fifteen_load_graph,
310     "ram": stats.build_ram_graph,
311 }
312 stats_map = {
313     "min_flows": stats.compute_min_flow_stats,
314     "max_flows": stats.compute_max_flow_stats,
315     "flows": stats.compute_avg_flow_stats,
316     "runtime": stats.compute_runtime_stats,
317     "iowait": stats.compute_iowait_stats,
318     "steal_time": stats.compute_steal_time_stats,
319     "one_load": stats.compute_one_load_stats,
320     "five_load": stats.compute_five_load_stats,
321     "fifteen_load": stats.compute_fifteen_load_stats,
322     "ram": stats.compute_ram_stats,
323 }
324
325 # Build argument parser
326 parser = argparse.ArgumentParser(description="Compute stats about CBench data")
327 parser.add_argument("-S", "--all-stats", action="store_true", help="compute all stats")
328 parser.add_argument(
329     "-s",
330     "--stats",
331     choices=stats_map.keys(),
332     help="compute stats on specified data",
333     nargs="+",
334 )
335 parser.add_argument("-G", "--all-graphs", action="store_true", help="graph all data")
336 parser.add_argument(
337     "-g", "--graphs", choices=graph_map.keys(), help="graph specified data", nargs="+"
338 )
339
340
341 # Print help if no arguments are given
342 if len(sys.argv) == 1:
343     parser.print_help()
344     sys.exit(1)
345
346 # Parse the given args
347 args = parser.parse_args()
348
349 # Build graphs
350 if args.all_graphs:
351     graphs_to_build = graph_map.keys()
352 elif args.graphs:
353     graphs_to_build = args.graphs
354 else:
355     graphs_to_build = []
356 for graph, graph_num in zip(graphs_to_build, range(len(graphs_to_build))):
357     graph_map[graph](len(graphs_to_build), graph_num + 1)
358
359 # Compute stats
360 if args.all_stats:
361     stats_to_compute = stats_map.keys()
362 elif args.stats:
363     stats_to_compute = args.stats
364 else:
365     stats_to_compute = []
366 for stat in stats_to_compute:
367     stats_map[stat]()
368
369 # Render graphs
370 if args.graphs or args.all_graphs:
371     # Attempt to adjust plot spacing, just a simple heuristic
372     if len(graphs_to_build) <= 3:
373         pyplot.subplots_adjust(hspace=0.2)
374     elif len(graphs_to_build) <= 6:
375         pyplot.subplots_adjust(hspace=0.4)
376     elif len(graphs_to_build) <= 9:
377         pyplot.subplots_adjust(hspace=0.7)
378     else:
379         pyplot.subplots_adjust(hspace=0.7)
380         print("WARNING: That's a lot of graphs. Add a second column?")
381     pyplot.show()
382
383 # Print stats
384 if args.stats or args.all_stats:
385     pprint.pprint(stats.results)