blob: 5d6fd0a670df7ec8a31d1d116119394955516d2e [file] [log] [blame]
#!/usr/bin/python
# Copyright 2018 the V8 project authors. All rights reserved.
'''
python %prog -c <command> [options]
Local benchmark runner.
The -c option is mandatory.
'''
# for py2/py3 compatibility
from __future__ import print_function
import math
from optparse import OptionParser
import os
import re
import subprocess
import sys
import time
def GeometricMean(numbers):
log = sum([math.log(n) for n in numbers])
return math.pow(math.e, log / len(numbers))
class BenchmarkSuite(object):
def __init__(self, name):
self.name = name
self.results = {}
self.tests = []
self.avgresult = {}
self.sigmaresult = {}
self.numresult = {}
self.kClassicScoreSuites = ["SunSpider", "Kraken"]
self.kGeometricScoreSuites = ["Octane"]
def RecordResult(self, test, result):
if test not in self.tests:
self.tests += [test]
self.results[test] = []
self.results[test] += [int(result)]
def ThrowAwayWorstResult(self, results):
if len(results) <= 1: return
if self.name in self.kClassicScoreSuites:
results.pop()
elif self.name in self.kGeometricScoreSuites:
del results[0]
def ProcessResults(self, opts):
for test in self.tests:
results = self.results[test]
results.sort()
self.ThrowAwayWorstResult(results)
mean = sum(results) * 1.0 / len(results)
self.avgresult[test] = mean
sigma_divisor = len(results) - 1
if sigma_divisor == 0:
sigma_divisor = 1
self.sigmaresult[test] = math.sqrt(
sum((x - mean) ** 2 for x in results) / sigma_divisor)
self.numresult[test] = len(results)
if opts.verbose:
if not test in ["Octane"]:
print("%s,%.1f,%.2f,%d" %
(test, self.avgresult[test],
self.sigmaresult[test], self.numresult[test]))
def ComputeScoreGeneric(self):
self.score = 0
self.sigma = 0
for test in self.tests:
self.score += self.avgresult[test]
self.sigma += self.sigmaresult[test]
self.num = self.numresult[test]
def ComputeScoreV8Octane(self, name):
# The score for the run is stored with the form
# "Octane-octane2.1(Score): <score>"
found_name = ''
for s in self.avgresult.keys():
if re.search("^Octane", s):
found_name = s
break
self.score = self.avgresult[found_name]
self.sigma = 0
for test in self.tests:
self.sigma += self.sigmaresult[test]
self.num = self.numresult[test]
self.sigma /= len(self.tests)
def ComputeScore(self):
if self.name in self.kClassicScoreSuites:
self.ComputeScoreGeneric()
elif self.name in self.kGeometricScoreSuites:
self.ComputeScoreV8Octane(self.name)
else:
print("Don't know how to compute score for suite: '%s'" % self.name)
def IsBetterThan(self, other):
if self.name in self.kClassicScoreSuites:
return self.score < other.score
elif self.name in self.kGeometricScoreSuites:
return self.score > other.score
else:
print("Don't know how to compare score for suite: '%s'" % self.name)
class BenchmarkRunner(object):
def __init__(self, args, current_directory, opts):
self.best = {}
self.second_best = {}
self.args = args
self.opts = opts
self.current_directory = current_directory
self.outdir = os.path.join(opts.cachedir, "_benchmark_runner_data")
def Run(self):
if not os.path.exists(self.outdir):
os.mkdir(self.outdir)
self.RunCommand()
# Figure out the suite from the command line (heuristic) or the current
# working directory.
teststr = opts.command.lower() + " " + self.current_directory.lower()
if teststr.find('octane') >= 0:
suite = 'Octane'
elif teststr.find('sunspider') >= 0:
suite = 'SunSpider'
elif teststr.find('kraken') >= 0:
suite = 'Kraken'
else:
suite = 'Generic'
self.ProcessOutput(suite)
def RunCommand(self):
for i in range(self.opts.runs):
outfile = "%s/out.%d.txt" % (self.outdir, i)
if os.path.exists(outfile) and not self.opts.force:
continue
print("run #%d" % i)
cmdline = "%s > %s" % (self.opts.command, outfile)
subprocess.call(cmdline, shell=True)
time.sleep(self.opts.sleep)
def ProcessLine(self, line):
# Octane puts this line in before score.
if line == "----":
return (None, None)
# Kraken or Sunspider?
g = re.match("(?P<test_name>\w+(-\w+)*)\(RunTime\): (?P<score>\d+) ms\.", \
line)
if g == None:
# Octane?
g = re.match("(?P<test_name>\w+): (?P<score>\d+)", line)
if g == None:
g = re.match("Score \(version [0-9]+\): (?P<score>\d+)", line)
if g != None:
return ('Octane', g.group('score'))
else:
# Generic?
g = re.match("(?P<test_name>\w+)\W+(?P<score>\d+)", line)
if g == None:
return (None, None)
return (g.group('test_name'), g.group('score'))
def ProcessOutput(self, suitename):
suite = BenchmarkSuite(suitename)
for i in range(self.opts.runs):
outfile = "%s/out.%d.txt" % (self.outdir, i)
with open(outfile, 'r') as f:
for line in f:
(test, result) = self.ProcessLine(line)
if test != None:
suite.RecordResult(test, result)
suite.ProcessResults(self.opts)
suite.ComputeScore()
print(("%s,%.1f,%.2f,%d " %
(suite.name, suite.score, suite.sigma, suite.num)), end='')
if self.opts.verbose:
print("")
print("")
if __name__ == '__main__':
parser = OptionParser(usage=__doc__)
parser.add_option("-c", "--command", dest="command",
help="Command to run the test suite.")
parser.add_option("-r", "--runs", dest="runs", default=4,
help="Number of runs")
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
default=False, help="Print results for each test")
parser.add_option("-f", "--force", dest="force", action="store_true",
default=False,
help="Force re-run even if output files exist")
parser.add_option("-z", "--sleep", dest="sleep", default=0,
help="Number of seconds to sleep between runs")
parser.add_option("-d", "--run-directory", dest="cachedir",
help="Directory where a cache directory will be created")
(opts, args) = parser.parse_args()
opts.runs = int(opts.runs)
opts.sleep = int(opts.sleep)
if not opts.command:
print("You must specify the command to run (-c). Aborting.")
sys.exit(1)
cachedir = os.path.abspath(os.getcwd())
if not opts.cachedir:
opts.cachedir = cachedir
if not os.path.exists(opts.cachedir):
print("Directory " + opts.cachedir + " is not valid. Aborting.")
sys.exit(1)
br = BenchmarkRunner(args, os.getcwd(), opts)
br.Run()