#!/usr/bin/env python """Compute basic stats about CBench data.""" import csv import numpy import pprint import matplotlib.pyplot as pyplot import argparse import sys class Stats(object): """Compute stats and/or graph data. I know I could convert these fns that simply punt to a helper to a dict/list data structure, but that would remove some of the flexabilty I get by simply calling a graph/stat fn for each graph/stat arg. All current fns just punt to helpers, but future ones might not. """ results_file = "results.csv" log_file = "cbench.log" precision = 3 run_index = 0 flow_index = 1 start_time_index = 2 end_time_index = 3 start_steal_time_index = 10 end_steal_time_index = 11 used_ram_index = 13 one_load_index = 16 five_load_index = 17 fifteen_load_index = 18 start_iowait_index = 20 end_iowait_index = 21 def __init__(self): """Setup some flags and data structures, kick off build_cols call.""" self.build_cols() self.results = {} self.results["sample_size"] = len(self.run_col) def build_cols(self): """Parse results file into lists of values, one per column.""" self.run_col = [] self.flows_col = [] self.runtime_col = [] self.used_ram_col = [] self.iowait_col = [] self.steal_time_col = [] self.one_load_col = [] self.five_load_col = [] self.fifteen_load_col = [] with open(self.results_file, "rb") as results_fd: results_reader = csv.reader(results_fd) for row in results_reader: try: self.run_col.append(float(row[self.run_index])) self.flows_col.append(float(row[self.flow_index])) self.runtime_col.append(float(row[self.end_time_index]) - float(row[self.start_time_index])) self.used_ram_col.append(float(row[self.used_ram_index])) self.iowait_col.append(float(row[self.end_iowait_index]) - float(row[self.start_iowait_index])) self.steal_time_col.append( float(row[self.end_steal_time_index]) - float(row[self.start_steal_time_index])) self.one_load_col.append(float(row[self.one_load_index])) self.five_load_col.append(float(row[self.five_load_index])) self.fifteen_load_col.append( float(row[self.fifteen_load_index])) except ValueError: # Skips header continue def compute_flow_stats(self): """Compute CBench flows/second stats.""" self.compute_generic_stats("flows", self.flows_col) def build_flow_graph(self, total_gcount, graph_num): """Plot flows/sec data. :param total_gcount: Total number of graphs to render. :type total_gcount: int :param graph_num: Number for this graph, <= total_gcount. :type graph_num: int """ self.build_generic_graph(total_gcount, graph_num, "Flows per Second", self.flows_col) def compute_ram_stats(self): """Compute used RAM stats.""" self.compute_generic_stats("used_ram", self.used_ram_col) def build_ram_graph(self, total_gcount, graph_num): """Plot used RAM data. :param total_gcount: Total number of graphs to render. :type total_gcount: int :param graph_num: Number for this graph, <= total_gcount. :type graph_num: int """ self.build_generic_graph(total_gcount, graph_num, "Used RAM (MB)", self.used_ram_col) def compute_runtime_stats(self): """Compute CBench runtime length stats.""" self.compute_generic_stats("runtime", self.runtime_col) def build_runtime_graph(self, total_gcount, graph_num): """Plot CBench runtime length data. :paruntime total_gcount: Total number of graphs to render. :type total_gcount: int :paruntime graph_num: Number for this graph, <= total_gcount. :type graph_num: int """ self.build_generic_graph(total_gcount, graph_num, "CBench Runtime (sec)", self.runtime_col) def compute_iowait_stats(self): """Compute iowait stats.""" self.compute_generic_stats("iowait", self.iowait_col) def build_iowait_graph(self, total_gcount, graph_num): """Plot iowait data. :paiowait total_gcount: Total number of graphs to render. :type total_gcount: int :paiowait graph_num: Number for this graph, <= total_gcount. :type graph_num: int """ self.build_generic_graph(total_gcount, graph_num, "IOWait Time (sec)", self.iowait_col) def compute_steal_time_stats(self): """Compute steal time stats.""" self.compute_generic_stats("steal_time", self.steal_time_col) def build_steal_time_graph(self, total_gcount, graph_num): """Plot steal time data. :pasteal_time total_gcount: Total number of graphs to render. :type total_gcount: int :pasteal_time graph_num: Number for this graph, <= total_gcount. :type graph_num: int """ self.build_generic_graph(total_gcount, graph_num, "Steal Time (sec)", self.steal_time_col) def compute_one_load_stats(self): """Compute one minute load stats.""" self.compute_generic_stats("one_load", self.one_load_col) def build_one_load_graph(self, total_gcount, graph_num): """Plot one minute load data. :paone_load total_gcount: Total number of graphs to render. :type total_gcount: int :paone_load graph_num: Number for this graph, <= total_gcount. :type graph_num: int """ self.build_generic_graph(total_gcount, graph_num, "One Minute Load", self.one_load_col) def compute_five_load_stats(self): """Compute five minute load stats.""" self.compute_generic_stats("five_load", self.five_load_col) def build_five_load_graph(self, total_gcount, graph_num): """Plot five minute load data. :pafive_load total_gcount: Total number of graphs to render. :type total_gcount: int :pafive_load graph_num: Number for this graph, <= total_gcount. :type graph_num: int """ self.build_generic_graph(total_gcount, graph_num, "Five Minute Load", self.five_load_col) def compute_fifteen_load_stats(self): """Compute fifteen minute load stats.""" self.compute_generic_stats("fifteen_load", self.fifteen_load_col) def build_fifteen_load_graph(self, total_gcount, graph_num): """Plot fifteen minute load data. :pafifteen_load total_gcount: Total number of graphs to render. :type total_gcount: int :pafifteen_load graph_num: Number for this graph, <= total_gcount. :type graph_num: int """ self.build_generic_graph(total_gcount, graph_num, "Fifteen Minute Load", self.fifteen_load_col) def compute_generic_stats(self, stats_name, stats_col): """Helper for computing generic stats.""" generic_stats = {} generic_stats["min"] = int(numpy.amin(stats_col)) generic_stats["max"] = int(numpy.amax(stats_col)) generic_stats["mean"] = round(numpy.mean(stats_col), self.precision) generic_stats["stddev"] = round(numpy.std(stats_col), self.precision) try: generic_stats["relstddev"] = round(generic_stats["stddev"] / generic_stats["mean"] * 100, self.precision) except ZeroDivisionError: generic_stats["relstddev"] = 0. self.results[stats_name] = generic_stats def build_generic_graph(self, total_gcount, graph_num, y_label, data_col): """Helper for plotting generic data. :pageneric total_gcount: Total number of graphs to render. :type total_gcount: int :pageneric graph_num: Number for this graph, <= total_gcount. :type graph_num: int :param y_label: Lable of Y axis. :type y_label: string :param data_col: Data to graph. :type data_col: list """ # Pagenerics are numrows, numcols, fignum pyplot.subplot(total_gcount, 1, graph_num) # "go" means green O's pyplot.plot(self.run_col, data_col, "go") pyplot.xlabel("Run Number") pyplot.ylabel(y_label) # Build stats object stats = Stats() # Map of graph names to the Stats.fns that build them graph_map = {"flows": stats.build_flow_graph, "runtime": stats.build_runtime_graph, "iowait": stats.build_iowait_graph, "steal_time": stats.build_steal_time_graph, "one_load": stats.build_one_load_graph, "five_load": stats.build_five_load_graph, "fifteen_load": stats.build_fifteen_load_graph, "ram": stats.build_ram_graph} stats_map = {"flows": stats.compute_flow_stats, "runtime": stats.compute_runtime_stats, "iowait": stats.compute_iowait_stats, "steal_time": stats.compute_steal_time_stats, "one_load": stats.compute_one_load_stats, "five_load": stats.compute_five_load_stats, "fifteen_load": stats.compute_fifteen_load_stats, "ram": stats.compute_ram_stats} # Build argument parser parser = argparse.ArgumentParser(description="Compute stats about CBench data") parser.add_argument("-S", "--all-stats", action="store_true", help="compute all stats") parser.add_argument("-s", "--stats", choices=stats_map.keys(), help="compute stats on specified data", nargs="+") parser.add_argument("-G", "--all-graphs", action="store_true", help="graph all data") parser.add_argument("-g", "--graphs", choices=graph_map.keys(), help="graph specified data", nargs="+") # Print help if no arguments are given if len(sys.argv) == 1: parser.print_help() sys.exit(1) # Parse the given args args = parser.parse_args() # Build graphs if args.all_graphs: graphs_to_build = graph_map.keys() elif args.graphs: graphs_to_build = args.graphs else: graphs_to_build = [] for graph, graph_num in zip(graphs_to_build, range(len(graphs_to_build))): graph_map[graph](len(graphs_to_build), graph_num+1) # Compute stats if args.all_stats: stats_to_compute = stats_map.keys() elif args.stats: stats_to_compute = args.stats else: stats_to_compute = [] for stat in stats_to_compute: stats_map[stat]() # Render graphs if args.graphs or args.all_graphs: # Attempt to adjust plot spacing, just a simple heuristic if len(graphs_to_build) <= 3: pyplot.subplots_adjust(hspace=.2) elif len(graphs_to_build) <= 6: pyplot.subplots_adjust(hspace=.4) elif len(graphs_to_build) <= 9: pyplot.subplots_adjust(hspace=.7) else: pyplot.subplots_adjust(hspace=.7) print "WARNING: That's a lot of graphs. Add a second column?" pyplot.show() # Print stats if args.stats or args.all_stats: pprint.pprint(stats.results)