Step 1: Move vm scripts to the right place
[integration/test.git] / test / 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(float(row[self.end_time_index]) -
71                                             float(row[self.start_time_index]))
72                     self.used_ram_col.append(float(row[self.used_ram_index]))
73                     self.iowait_col.append(float(row[self.end_iowait_index]) -
74                                            float(row[self.start_iowait_index]))
75                     self.steal_time_col.append(
76                         float(row[self.end_steal_time_index]) -
77                         float(row[self.start_steal_time_index]))
78                     self.one_load_col.append(float(row[self.one_load_index]))
79                     self.five_load_col.append(float(row[self.five_load_index]))
80                     self.fifteen_load_col.append(
81                         float(row[self.fifteen_load_index]))
82                 except ValueError:
83                     # Skips header
84                     continue
85
86     def compute_avg_flow_stats(self):
87         """Compute CBench average flows/second stats."""
88         self.compute_generic_stats("flows", self.avg_flows_col)
89
90     def build_avg_flow_graph(self, total_gcount, graph_num):
91         """Plot average flows/sec data.
92
93         :param total_gcount: Total number of graphs to render.
94         :type total_gcount: int
95         :param graph_num: Number for this graph, <= total_gcount.
96         :type graph_num: int
97
98         """
99         self.build_generic_graph(total_gcount, graph_num,
100                                  "Average Flows per Second", self.avg_flows_col)
101
102     def compute_min_flow_stats(self):
103         """Compute CBench min flows/second stats."""
104         self.compute_generic_stats("min_flows", self.min_flows_col)
105
106     def build_min_flow_graph(self, total_gcount, graph_num):
107         """Plot min flows/sec data.
108
109         :param total_gcount: Total number of graphs to render.
110         :type total_gcount: int
111         :param graph_num: Number for this graph, <= total_gcount.
112         :type graph_num: int
113
114         """
115         self.build_generic_graph(total_gcount, graph_num,
116                                  "Minimum Flows per Second", self.min_flows_col)
117
118     def compute_max_flow_stats(self):
119         """Compute CBench max flows/second stats."""
120         self.compute_generic_stats("max_flows", self.max_flows_col)
121
122     def build_max_flow_graph(self, total_gcount, graph_num):
123         """Plot max flows/sec data.
124
125         :param total_gcount: Total number of graphs to render.
126         :type total_gcount: int
127         :param graph_num: Number for this graph, <= total_gcount.
128         :type graph_num: int
129
130         """
131         self.build_generic_graph(total_gcount, graph_num,
132                                  "Maximum Flows per Second", self.max_flows_col)
133
134     def compute_ram_stats(self):
135         """Compute used RAM stats."""
136         self.compute_generic_stats("used_ram", self.used_ram_col)
137
138     def build_ram_graph(self, total_gcount, graph_num):
139         """Plot used RAM data.
140
141         :param total_gcount: Total number of graphs to render.
142         :type total_gcount: int
143         :param graph_num: Number for this graph, <= total_gcount.
144         :type graph_num: int
145
146         """
147         self.build_generic_graph(total_gcount, graph_num,
148                                  "Used RAM (MB)", self.used_ram_col)
149
150     def compute_runtime_stats(self):
151         """Compute CBench runtime length stats."""
152         self.compute_generic_stats("runtime", self.runtime_col)
153
154     def build_runtime_graph(self, total_gcount, graph_num):
155         """Plot CBench runtime length data.
156
157         :param total_gcount: Total number of graphs to render.
158         :type total_gcount: int
159         :param graph_num: Number for this graph, <= total_gcount.
160         :type graph_num: int
161
162         """
163         self.build_generic_graph(total_gcount, graph_num,
164                                  "CBench Runtime (sec)", self.runtime_col)
165
166     def compute_iowait_stats(self):
167         """Compute iowait stats."""
168         self.compute_generic_stats("iowait", self.iowait_col)
169
170     def build_iowait_graph(self, total_gcount, graph_num):
171         """Plot iowait data.
172
173         :param total_gcount: Total number of graphs to render.
174         :type total_gcount: int
175         :param graph_num: Number for this graph, <= total_gcount.
176         :type graph_num: int
177
178         """
179         self.build_generic_graph(total_gcount, graph_num,
180                                  "IOWait Time (sec)", self.iowait_col)
181
182     def compute_steal_time_stats(self):
183         """Compute steal time stats."""
184         self.compute_generic_stats("steal_time", self.steal_time_col)
185
186     def build_steal_time_graph(self, total_gcount, graph_num):
187         """Plot steal time data.
188
189         :param total_gcount: Total number of graphs to render.
190         :type total_gcount: int
191         :param graph_num: Number for this graph, <= total_gcount.
192         :type graph_num: int
193
194         """
195         self.build_generic_graph(total_gcount, graph_num,
196                                  "Steal Time (sec)", self.steal_time_col)
197
198     def compute_one_load_stats(self):
199         """Compute one minute load stats."""
200         self.compute_generic_stats("one_load", self.one_load_col)
201
202     def build_one_load_graph(self, total_gcount, graph_num):
203         """Plot one minute load data.
204
205         :param total_gcount: Total number of graphs to render.
206         :type total_gcount: int
207         :param graph_num: Number for this graph, <= total_gcount.
208         :type graph_num: int
209
210         """
211         self.build_generic_graph(total_gcount, graph_num,
212                                  "One Minute Load", self.one_load_col)
213
214     def compute_five_load_stats(self):
215         """Compute five minute load stats."""
216         self.compute_generic_stats("five_load", self.five_load_col)
217
218     def build_five_load_graph(self, total_gcount, graph_num):
219         """Plot five minute load data.
220
221         :param total_gcount: Total number of graphs to render.
222         :type total_gcount: int
223         :param graph_num: Number for this graph, <= total_gcount.
224         :type graph_num: int
225
226         """
227         self.build_generic_graph(total_gcount, graph_num,
228                                  "Five Minute Load", self.five_load_col)
229
230     def compute_fifteen_load_stats(self):
231         """Compute fifteen minute load stats."""
232         self.compute_generic_stats("fifteen_load", self.fifteen_load_col)
233
234     def build_fifteen_load_graph(self, total_gcount, graph_num):
235         """Plot fifteen minute load data.
236
237         :param total_gcount: Total number of graphs to render.
238         :type total_gcount: int
239         :param graph_num: Number for this graph, <= total_gcount.
240         :type graph_num: int
241
242         """
243         self.build_generic_graph(total_gcount, graph_num,
244                                  "Fifteen Minute Load", self.fifteen_load_col)
245
246     def compute_generic_stats(self, stats_name, stats_col):
247         """Helper for computing generic stats."""
248         generic_stats = {}
249         generic_stats["min"] = int(numpy.amin(stats_col))
250         generic_stats["max"] = int(numpy.amax(stats_col))
251         generic_stats["mean"] = round(numpy.mean(stats_col), self.precision)
252         generic_stats["stddev"] = round(numpy.std(stats_col), self.precision)
253         try:
254             generic_stats["relstddev"] = round(generic_stats["stddev"] /
255                                                generic_stats["mean"] *
256                                                100, self.precision)
257         except ZeroDivisionError:
258             generic_stats["relstddev"] = 0.
259         self.results[stats_name] = generic_stats
260
261     def build_generic_graph(self, total_gcount, graph_num, y_label, data_col):
262         """Helper for plotting generic data.
263
264         :param total_gcount: Total number of graphs to render.
265         :type total_gcount: int
266         :param graph_num: Number for this graph, <= total_gcount.
267         :type graph_num: int
268         :param y_label: Lable of Y axis.
269         :type y_label: string
270         :param data_col: Data to graph.
271         :type data_col: list
272
273         """
274         # Pagenerics are numrows, numcols, fignum
275         pyplot.subplot(total_gcount, 1, graph_num)
276         # "go" means green O's
277         pyplot.plot(self.run_col, data_col, "go")
278         pyplot.xlabel("Run Number")
279         pyplot.ylabel(y_label)
280
281
282 # Build stats object
283 stats = Stats()
284
285 # Map of graph names to the Stats.fns that build them
286 graph_map = {"min_flows": stats.build_min_flow_graph,
287              "max_flows": stats.build_max_flow_graph,
288              "flows": stats.build_avg_flow_graph,
289              "runtime": stats.build_runtime_graph,
290              "iowait": stats.build_iowait_graph,
291              "steal_time": stats.build_steal_time_graph,
292              "one_load": stats.build_one_load_graph,
293              "five_load": stats.build_five_load_graph,
294              "fifteen_load": stats.build_fifteen_load_graph,
295              "ram": stats.build_ram_graph}
296 stats_map = {"min_flows": stats.compute_min_flow_stats,
297              "max_flows": stats.compute_max_flow_stats,
298              "flows": stats.compute_avg_flow_stats,
299              "runtime": stats.compute_runtime_stats,
300              "iowait": stats.compute_iowait_stats,
301              "steal_time": stats.compute_steal_time_stats,
302              "one_load": stats.compute_one_load_stats,
303              "five_load": stats.compute_five_load_stats,
304              "fifteen_load": stats.compute_fifteen_load_stats,
305              "ram": stats.compute_ram_stats}
306
307 # Build argument parser
308 parser = argparse.ArgumentParser(description="Compute stats about CBench data")
309 parser.add_argument("-S", "--all-stats", action="store_true",
310                     help="compute all stats")
311 parser.add_argument("-s", "--stats", choices=stats_map.keys(),
312                     help="compute stats on specified data", nargs="+")
313 parser.add_argument("-G", "--all-graphs", action="store_true",
314                     help="graph all data")
315 parser.add_argument("-g", "--graphs", choices=graph_map.keys(),
316                     help="graph specified data", nargs="+")
317
318
319 # Print help if no arguments are given
320 if len(sys.argv) == 1:
321     parser.print_help()
322     sys.exit(1)
323
324 # Parse the given args
325 args = parser.parse_args()
326
327 # Build graphs
328 if args.all_graphs:
329     graphs_to_build = graph_map.keys()
330 elif args.graphs:
331     graphs_to_build = args.graphs
332 else:
333     graphs_to_build = []
334 for graph, graph_num in zip(graphs_to_build, range(len(graphs_to_build))):
335     graph_map[graph](len(graphs_to_build), graph_num+1)
336
337 # Compute stats
338 if args.all_stats:
339     stats_to_compute = stats_map.keys()
340 elif args.stats:
341     stats_to_compute = args.stats
342 else:
343     stats_to_compute = []
344 for stat in stats_to_compute:
345     stats_map[stat]()
346
347 # Render graphs
348 if args.graphs or args.all_graphs:
349     # Attempt to adjust plot spacing, just a simple heuristic
350     if len(graphs_to_build) <= 3:
351         pyplot.subplots_adjust(hspace=.2)
352     elif len(graphs_to_build) <= 6:
353         pyplot.subplots_adjust(hspace=.4)
354     elif len(graphs_to_build) <= 9:
355         pyplot.subplots_adjust(hspace=.7)
356     else:
357         pyplot.subplots_adjust(hspace=.7)
358         print "WARNING: That's a lot of graphs. Add a second column?"
359     pyplot.show()
360
361 # Print stats
362 if args.stats or args.all_stats:
363     pprint.pprint(stats.results)