123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- #!/usr/bin/python
- import sys
- import os
- import argparse
- import subprocess
- import multiprocessing
- import json
- import pprint
- def take_first(value):
- if type(value) is tuple or type(value) is list:
- return value[0]
- return value
- def take_order(value, order):
- for i in range(len(order)):
- if value == order[i]:
- return i
- return len(order)
- def take_map(value, keys, vals):
- if len(keys) != len(vals):
- raise RuntimeError("Length of keys and vals must match")
- for i in range(len(keys)):
- if value == keys[i]:
- return vals[i]
- return value
- def take_best(values, key, compare, reverse=False):
- if 0 == len(values):
- return []
- values = sorted(values, key=key, reverse=reverse)
- for i in range(1, len(values)):
- if not compare(values[0], values[i]):
- return values[0:i]
- return values
- def take_plural(count, base, suffix):
- if 1 == abs(count):
- return base
- return base + suffix
- def take_threads_variants():
- # single thread
- variants = [1]
- cpus = multiprocessing.cpu_count()
- if 1 > cpus:
- cpus = 1
- # load all CPUs
- if 1 < cpus:
- variants.append(cpus)
- # overcommit
- variants.append(2 * cpus)
- return variants
- def _cmp_percentage(p, key, a, b):
- if key(a) == key(b):
- return True
- if p >= abs((key(a) - key(b)) / float(key(a))):
- return True
- return False
- def cmp_percentage(p, key):
- return lambda a, b: _cmp_percentage(p, key, a, b)
- def translate_test(test):
- name = take_first(test)
- if "call_site_size.str" == name:
- return 1000, "Call site size: string"
- if "call_site_size.fmti" == name:
- return 2000, "Call site size: 3 integers"
- if "executable_size.m1" == name:
- return 3000, "Executable size: 1 module"
- if "compile_time" == name:
- return 4000, "Module compile time"
- if "link_time" == name:
- return 5000, "Executable link time"
- if "speed" == name:
- threads = test[1]
- mode = test[2]
- mode_keys = ["str", "fmti", "str-off", "slowf-off"]
- mode_vals = ["string", "3 integers", "string, off", "slow function, off"]
- tr_mode = take_map(mode, mode_keys, mode_vals)
- order = 10 * threads + take_order(mode, mode_keys)
- return 6000 + order, "Speed: %i %s, %s" % (threads, take_plural(threads, "thread", "s"), tr_mode)
- if type(test) is tuple or type(test) is list:
- return 31416, ", ".join(test)
- return 27183, test
- def translate_subj(subj):
- if "zf_log_n" == subj:
- return 31416, "zf_log"
- if "easylog" == subj:
- return 31416, "Easylogging++"
- return 31416, subj
- def translation_sort_key(v):
- return "[%04i] %s" % (v[0], v[1])
- def translation_value(v):
- return v[1]
- class data_cell:
- def __init__(self):
- self.best = False
- def set_best(self, best=True):
- self.best = best
- def ifbest(self, a, b):
- if hasattr(self, "best") and self.best:
- return a
- return b
- class data_str(data_cell):
- def __init__(self, value):
- if type(value) is not str:
- raise RuntimeError("Not a string")
- self.value = value
- def __str__(self):
- return self.value
- def __repr__(self):
- return repr(self.value)
- class data_bytes(data_cell):
- def __init__(self, value):
- if type(value) is not int:
- raise RuntimeError("Not an int")
- self.value = value
- def __str__(self):
- if self.value < 1024:
- return "%i B" % (self.value)
- if self.value < 1024 * 1024:
- return "%.2f KB" % (self.value / 1024.0)
- return "%.2f MB" % (self.value / 1024.0 / 1024.0)
- def __repr__(self):
- return repr(self.value)
- class data_seconds(data_cell):
- def __init__(self, value):
- if type(value) is not int and type(value) is not float:
- raise RuntimeError("Not an int or float")
- self.value = value
- def __str__(self):
- return "%.3f sec" % (self.value)
- def __repr__(self):
- return repr(self.value)
- class data_freq(data_cell):
- def __init__(self, count, seconds):
- if type(count) is not int and type(count) is not float:
- raise RuntimeError("Not an int or float")
- if type(seconds) is not int and type(seconds) is not float:
- raise RuntimeError("Not an int or float")
- self.count = count
- self.seconds = seconds
- def __str__(self):
- return "{:,}".format(self.count / self.seconds)
- def __repr__(self):
- return repr((self.count, self.seconds))
- def freq(self):
- return self.count / self.seconds
- def get_table_data(result):
- # collect all tests
- tests = result.keys()
- tests = sorted(tests, key=lambda x: translation_sort_key(translate_test(x)))
- # collect all subjects
- subjs = set()
- for test in tests:
- subjs.update(result[test].keys())
- subjs = sorted(subjs, key=lambda x: translation_sort_key(translate_subj(x)))
- # create table
- rows = len(tests) + 1
- cols = len(subjs) + 1
- table = [[None for _ in range(cols)] for _ in range(rows)]
- # put names and captions
- for i in range(1, rows):
- table[i][0] = data_str(translation_value(translate_test(tests[i - 1])))
- for j in range(1, cols):
- table[0][j] = data_str(translation_value(translate_subj(subjs[j - 1])))
- # put data
- for i in range(1, rows):
- for j in range(1, cols):
- test = tests[i - 1]
- subj = subjs[j - 1]
- if subj in result[test]:
- table[i][j] = result[test][subj]
- # gen cells content
- for i in range(0, rows):
- for j in range(0, cols):
- value = table[i][j]
- if value is None:
- value = data_str("")
- elif not isinstance(value, data_cell):
- raise RuntimeError("Value \"%s\" is of unsupported type \"%s\"" % (value, type(value)))
- table[i][j] = value
- # find cols width
- widths = [0 for _ in range(cols)]
- for j in range(0, cols):
- for i in range(0, rows):
- s = str(table[i][j])
- if widths[j] < len(s):
- widths[j] = len(s)
- return table, rows, cols, widths
- def gen_table_ascii(result):
- table, rows, cols, widths = get_table_data(result)
- # apply cell format
- margin_norm = (" ", " ")
- margin_best = ("*", " ")
- margins = max(map(len, margin_norm)) + max(map(len, margin_norm))
- for i in range(1, rows):
- table[i][0] = str(table[i][0]).ljust(widths[0]).join(margin_norm)
- for j in range(0, cols):
- table[0][j] = str(table[0][j]).center(widths[j]).join(margin_norm)
- for i in range(1, rows):
- for j in range(1, cols):
- data = table[i][j]
- margin = data.ifbest(margin_best, margin_norm)
- table[i][j] = str(data).rjust(widths[j]).join(margin)
- # draw chart
- line = "+" + "-" * (sum(widths) + (margins + 1) * len(widths) - 1) + "+\n"
- chart = line
- for row in table:
- chart += "|"
- for cell in row:
- chart += "%s|" % (cell)
- chart += "\n" + line
- return chart
- def gen_table_markdown(result):
- table, rows, cols, widths = get_table_data(result)
- # apply cell format
- margin_norm = (" ", " ")
- margin_best = ("**", "**")
- margins = max(map(len, margin_norm)) + max(map(len, margin_norm))
- for i in range(1, rows):
- table[i][0] = str(table[i][0]).ljust(widths[0]).join(margin_norm)
- for j in range(0, cols):
- table[0][j] = str(table[0][j]).center(widths[j]).join(margin_norm)
- for i in range(1, rows):
- for j in range(1, cols):
- data = table[i][j]
- margin = data.ifbest(margin_best, margin_norm)
- table[i][j] = str(data).join(margin).rjust(widths[j] + margins)
- # draw chart
- chart = ""
- if 0 == rows:
- return chart
- chart += "|"
- for cell in table[0]:
- chart += "%s|" % (cell)
- chart += "\n"
- chart += "| " + "-" * (margins + widths[0] - 2) + " |"
- for i in range(1, cols):
- chart += " " + "-" * (margins + widths[i] - 2) + ":|"
- chart += "\n"
- for i in range(1, rows):
- chart += "|"
- for j in range(0, cols):
- chart += "%s|" % (table[i][j])
- chart += "\n"
- return chart
- def run_call_site_size(params, result):
- if type(result) is not dict:
- raise RuntimeError("Not a dictionary")
- id = "call_site_size"
- params = params[id]
- for mode in params:
- name = "%s.%s" % (id, mode)
- values = dict()
- for subj in params[mode]:
- data = params[mode][subj]
- sz1 = os.path.getsize(data["1"])
- sz2 = os.path.getsize(data["2"])
- data = data_bytes(sz2 - sz1)
- values[subj] = data
- result[name] = values
- for best in take_best(values.values(),
- key=lambda x: x.value,
- compare=cmp_percentage(0.0, key=lambda x: x.value)):
- best.set_best()
- def run_executable_size(params, result):
- if type(result) is not dict:
- raise RuntimeError("Not a dictionary")
- id = "executable_size"
- params = params[id]
- for mode in params:
- name = "%s.%s" % (id, mode)
- values = dict()
- for subj in params[mode]:
- sz = os.path.getsize(params[mode][subj])
- values[subj] = data_bytes(sz)
- result[name] = values
- for best in take_best(values.values(),
- key=lambda x: x.value,
- compare=cmp_percentage(0.0, key=lambda x: x.value)):
- best.set_best()
- def run_build_time(params, result, id, optional=False):
- if type(result) is not dict:
- raise RuntimeError("Not a dictionary")
- if optional and id not in params:
- return
- params = params[id]
- values = dict()
- for subj in params:
- with open(params[subj], "r") as f:
- dt = json.load(f)["dt"]
- values[subj] = data_seconds(dt)
- result[id] = values
- for best in take_best(values.values(),
- key=lambda x: x.value,
- compare=cmp_percentage(0.2, key=lambda x: x.value)):
- best.set_best()
- def run_speed(params, result, threads_variants, seconds=1):
- if type(result) is not dict:
- raise RuntimeError("Not a dictionary")
- id = "speed"
- params = params[id]
- for threads in threads_variants:
- for mode in params:
- name = (id, threads, mode)
- values = dict()
- for subj in params[mode]:
- path = params[mode][subj]
- p = subprocess.Popen([path, str(threads), str(seconds)], stdout=subprocess.PIPE)
- stdout, stderr = p.communicate()
- values[subj] = data_freq(int(stdout), seconds)
- result[name] = values
- for best in take_best(values.values(),
- key=lambda x: x.freq(), reverse=True,
- compare=cmp_percentage(0.1, key=lambda x: x.freq())):
- best.set_best()
- def run_tests(params):
- result = dict()
- run_call_site_size(params, result)
- run_executable_size(params, result)
- run_build_time(params, result, "compile_time", optional=True)
- run_build_time(params, result, "link_time", optional=True)
- run_speed(params, result, take_threads_variants())
- return result
- def main(argv):
- parser = argparse.ArgumentParser()
- parser.add_argument("-t", "--text", metavar="PATH", default=None,
- help="Text output file path")
- parser.add_argument("-m", "--markdown", metavar="PATH", default=None,
- help="Markdown output file path")
- parser.add_argument("-p", "--parameter", metavar="VALUE", action="append", default=[],
- help="Input parameter")
- parser.add_argument("-b", "--build", metavar="TYPE",
- help="Input parameter")
- parser.add_argument("-v", "--verbose", action="store_true",
- help="Verbose output")
- args = parser.parse_args(argv[1:])
- # process parameters
- params = dict()
- for p in args.parameter:
- d = params
- vs = p.split(":")
- key = None
- for i in range(len(vs)):
- if i == len(vs) - 1:
- d[key] = vs[i]
- break
- if key is not None:
- d = d[key]
- key = vs[i]
- if key not in d:
- d[key] = dict()
- if args.verbose:
- sys.stderr.write(pprint.pformat(params, indent=4))
- sys.stderr.write("\n")
- # run, run, run!
- result = run_tests(params)
- if args.verbose:
- sys.stderr.write(pprint.pformat(result, indent=4))
- sys.stderr.write("\n")
- if args.text is not None:
- with open(args.text, "w") as f:
- table = gen_table_ascii(result)
- f.write(table)
- if args.markdown is not None:
- with open(args.markdown, "w") as f:
- table = gen_table_markdown(result)
- f.write(table)
- return 0
- if __name__ == "__main__":
- sys.exit(main(sys.argv))
|