#! /apps/prod/bin/python2.4 import sys from optparse import OptionParser # (log)levels MUCHSLOWER = 0x0001 SLOWER = 0x0002 MUCHFASTER = 0x0010 FASTER = 0x0020 EQUAL = 0x0100 ALLSLOWER = MUCHSLOWER|SLOWER ALLFASTER = MUCHFASTER|FASTER ALLDIFF = ALLSLOWER|ALLFASTER ALL = ALLDIFF|EQUAL class BenchCompare(object): DictClass = dict # maybe provide an ordered dictionary, if necessary # FIXME: make this sortable, e.g. slowest to fastest or s.th. class CompResult(dict): val_type = tuple val_len = 5 def __setitem__(self, key, value): if isinstance(value, self.val_type): if len(value) == self.val_len: dict.__setitem__(self, key, value) dumpformat = "%50s: %15.8f %s %15.8f %s (%+8f) %s\n" signs = { MUCHSLOWER: "!!!", SLOWER: "", MUCHFASTER: "+++", FASTER: "+", EQUAL: "=", } op_chars = { MUCHSLOWER: "<<<", SLOWER: " < ", MUCHFASTER: ">>>", FASTER: " > ", EQUAL: " = ", } @staticmethod def benchlog2dict(fname, libname="lxe", pos=5): """Return a benchmark dictionary containing unique benchmark names as keys and the best-of runs results as values. Expects the file lines to be in lxml benchmark log format. Parameters: libname: libname (identifies how test lines start) pos: position of the actual comparison value """ benchlog = open(fname, "r") benchDict = {} for line in benchlog: # be very lazy about recognizing relevant lines and rely on line starts # and finding a number value at try: if line.startswith(libname): singleBenchTuple = line.split() benchID = ' '.join(singleBenchTuple[:pos]) benchVal = float(singleBenchTuple[pos]) benchUnit = singleBenchTuple[pos+1].strip(",") if benchDict.has_key(benchID): print "WARNING: benchmark names are not unique: %s" % benchID else: benchDict[benchID] = (benchVal, benchUnit) except ValueError: pass benchlog.close() return benchDict def __init__(self, fnameA, fnameB, libname="lxe", pos=5): """Set up the comparison structure for two lxml benchmark files. """ self.libname = libname self.pos = pos self.comparables = self.DictClass() benchDictA = self.benchlog2dict(fnameA, self.libname, self.pos) benchDictB = self.benchlog2dict(fnameB, self.libname, self.pos) for bench in benchDictA: if benchDictB.has_key(bench): valA, unitA = benchDictA[bench] valB, unitB = benchDictB[bench] if unitA != unitB: print "ERROR: Can not compare benchmark units:" \ " %s %s <==> %s %s" % (bench, valA, unitA, valB, unitB) else: unit = unitA self.comparables[bench] = (valA, valB, unit) def _compare(self, tolerance): # normalize tolerance tolerance = tolerance / 100.0 compResult = {} for (bench, (valA, valB, unit)) in self.comparables.items(): deviation = self._cmp(valA, valB) if deviation > tolerance: level = MUCHSLOWER elif deviation > 0: level = SLOWER elif deviation < -tolerance: level = MUCHFASTER elif deviation < 0: level = FASTER else: # ignore floating point comparison issues level = EQUAL compResult[bench] = (deviation, level, valA, valB, unit) return compResult def __call__(self, tolerance, loglevel=MUCHSLOWER, output=sys.stdout): self.dump(tolerance, loglevel, output) def dump(self, tolerance, loglevel=MUCHSLOWER, output=sys.stdout): """Dump comparison output to . Parameters: tolerance: tolerance in %, to categorize speed gains/losses loglevel: control output generation output: output file or filename (default: stdout) """ if not isinstance(output, file): output = open(output, "w") compResult = self._compare(tolerance) for (bench, (deviation, level, valA, valB, unit)) in compResult.items(): if loglevel & level: op_char = self.op_chars[level] sign = self.signs[level] self._writeCmpLine(output, bench, valA, op_char, valB, unit, deviation, sign) def _cmp(self, x, y): return (y / x - 1) def _writeCmpLine(self, output, *args): output.write(self.dumpformat % args) output.flush() if __name__ == "__main__": # Parse command Config.line options op = OptionParser( usage="%prog [options] val(A) + tolerance;" " SLOWER: log val(A) < values(B) < val(A) + tolerance;" " MUCHFASTER: log val(B) < val(A) - tolerance;" " FASTER: log val(A) - tolerance < val(B) < val(A);" " ALLSLOWER: log all speed losses;" " ALLFASTER: log all speed gains;" " ALLDIFF: log all speed differrences;" " ALL: log all comparisons") options, args = op.parse_args() if not args: op.print_help() sys.exit(1) loglevel = eval(options.loglevel) benchcmp = BenchCompare(args[0], args[1]) benchcmp(options.tolerance, loglevel)