Index: tools/skpbench/skpbench.py |
diff --git a/tools/skpbench/skpbench.py b/tools/skpbench/skpbench.py |
index 94d68b28e401129a1608a3bae1b5b9645a2153e6..b84b0703a00ebc584c7b00f6ce20a27e992c1948 100755 |
--- a/tools/skpbench/skpbench.py |
+++ b/tools/skpbench/skpbench.py |
@@ -6,57 +6,60 @@ |
# found in the LICENSE file. |
from __future__ import print_function |
+from _adb import Adb |
from _benchresult import BenchResult |
+from _hardware import HardwareException, Hardware |
from argparse import ArgumentParser |
from queue import Queue |
-from threading import Thread |
+from threading import Thread, Timer |
import collections |
import glob |
import math |
import re |
import subprocess |
import sys |
+import time |
-__argparse = ArgumentParser(description=""" |
+__argparse = ArgumentParser(description=''' |
Executes the skpbench binary with various configs and skps. |
Also monitors the output in order to filter out and re-run results that have an |
unacceptable stddev. |
-""") |
+''') |
__argparse.add_argument('--adb', |
- action='store_true', help='execute skpbench over adb') |
+ action='store_true', help="execute skpbench over adb") |
__argparse.add_argument('-s', '--device-serial', |
- help='if using adb, id of the specific device to target') |
+ help="if using adb, id of the specific device to target") |
__argparse.add_argument('-p', '--path', |
- help='directory to execute ./skpbench from') |
+ help="directory to execute ./skpbench from") |
__argparse.add_argument('-m', '--max-stddev', |
type=float, default=4, |
- help='initial max allowable relative standard deviation') |
+ help="initial max allowable relative standard deviation") |
__argparse.add_argument('-x', '--suffix', |
- help='suffix to append on config (e.g. "_before", "_after")') |
+ help="suffix to append on config (e.g. '_before', '_after')") |
__argparse.add_argument('-w','--write-path', |
- help='directory to save .png proofs to disk.') |
+ help="directory to save .png proofs to disk.") |
__argparse.add_argument('-v','--verbosity', |
- type=int, default=0, help='level of verbosity (0=none to 5=debug)') |
+ type=int, default=1, help="level of verbosity (0=none to 5=debug)") |
__argparse.add_argument('-n', '--samples', |
- type=int, help='number of samples to collect for each bench') |
+ type=int, help="number of samples to collect for each bench") |
__argparse.add_argument('-d', '--sample-ms', |
- type=int, help='duration of each sample') |
+ type=int, help="duration of each sample") |
__argparse.add_argument('--fps', |
- action='store_true', help='use fps instead of ms') |
+ action='store_true', help="use fps instead of ms") |
__argparse.add_argument('-c', '--config', |
- default='gpu', help='comma- or space-separated list of GPU configs') |
+ default='gpu', help="comma- or space-separated list of GPU configs") |
__argparse.add_argument('skps', |
nargs='+', |
- help='.skp files or directories to expand for .skp files') |
+ help=".skp files or directories to expand for .skp files") |
FLAGS = __argparse.parse_args() |
if FLAGS.adb: |
import _adb_path as _path |
- _path.set_device_serial(FLAGS.device_serial) |
+ _path.init(FLAGS.device_serial) |
else: |
import _os_path as _path |
@@ -66,12 +69,25 @@ class StddevException(Exception): |
class Message: |
READLINE = 0, |
- EXIT = 1 |
+ POLL_HARDWARE = 1, |
+ EXIT = 2 |
def __init__(self, message, value=None): |
self.message = message |
self.value = value |
-class SKPBench(Thread): |
+class SubprocessMonitor(Thread): |
+ def __init__(self, queue, proc): |
+ self._queue = queue |
+ self._proc = proc |
+ Thread.__init__(self) |
+ |
+ def run(self): |
+ '''Runs on the background thread.''' |
+ for line in iter(self._proc.stdout.readline, b''): |
+ self._queue.put(Message(Message.READLINE, line.decode('utf-8').rstrip())) |
+ self._queue.put(Message(Message.EXIT)) |
+ |
+class SKPBench: |
ARGV = ['skpbench', '--verbosity', str(FLAGS.verbosity)] |
if FLAGS.samples: |
ARGV.extend(['--samples', str(FLAGS.samples)]) |
@@ -97,86 +113,152 @@ class SKPBench(Thread): |
self.max_stddev = max_stddev |
self.best_result = best_result |
self._queue = Queue() |
- Thread.__init__(self) |
+ self._proc = None |
+ self._monitor = None |
+ self._hw_poll_timer = None |
+ |
+ def __enter__(self): |
+ return self |
+ |
+ def __exit__(self, exception_type, exception_value, traceback): |
+ if self._proc: |
+ self.terminate() |
+ if self._hw_poll_timer: |
+ self._hw_poll_timer.cancel() |
+ |
+ def execute(self, hardware): |
+ hardware.sanity_check() |
+ self._schedule_hardware_poll() |
+ |
+ commandline = self.ARGV + ['--config', self.config, |
+ '--skp', self.skp, |
+ '--suppressHeader', 'true'] |
+ if FLAGS.write_path: |
+ pngfile = _path.join(FLAGS.write_path, self.config, |
+ _path.basename(self.skp) + '.png') |
+ commandline.extend(['--png', pngfile]) |
+ if (FLAGS.verbosity >= 4): |
+ quoted = ['\'%s\'' % re.sub(r'([\\\'])', r'\\\1', x) for x in commandline] |
+ print(' '.join(quoted), file=sys.stderr) |
+ self._proc = subprocess.Popen(commandline, stdout=subprocess.PIPE) |
+ self._monitor = SubprocessMonitor(self._queue, self._proc) |
+ self._monitor.start() |
- def execute(self): |
- self.start() |
while True: |
message = self._queue.get() |
if message.message == Message.READLINE: |
result = BenchResult.match(message.value) |
if result: |
- self.__process_result(result) |
+ hardware.sanity_check() |
+ self._process_result(result) |
else: |
print(message.value) |
sys.stdout.flush() |
continue |
+ if message.message == Message.POLL_HARDWARE: |
+ hardware.sanity_check() |
+ self._schedule_hardware_poll() |
+ continue |
if message.message == Message.EXIT: |
- self.join() |
+ self._monitor.join() |
+ self._proc.wait() |
+ if self._proc.returncode != 0: |
+ raise Exception("skpbench exited with nonzero exit code %i" % |
+ self._proc.returncode) |
+ self._proc = None |
break |
- def __process_result(self, result): |
+ def _schedule_hardware_poll(self): |
+ if self._hw_poll_timer: |
+ self._hw_poll_timer.cancel() |
+ self._hw_poll_timer = \ |
+ Timer(1, lambda: self._queue.put(Message(Message.POLL_HARDWARE))) |
+ self._hw_poll_timer.start() |
+ |
+ def _process_result(self, result): |
if not self.best_result or result.stddev <= self.best_result.stddev: |
self.best_result = result |
- elif FLAGS.verbosity >= 1: |
- print('NOTE: reusing previous result for %s/%s with lower stddev ' |
- '(%s%% instead of %s%%).' % |
+ elif FLAGS.verbosity >= 2: |
+ print("reusing previous result for %s/%s with lower stddev " |
+ "(%s%% instead of %s%%)." % |
(result.config, result.bench, self.best_result.stddev, |
result.stddev), file=sys.stderr) |
if self.max_stddev and self.best_result.stddev > self.max_stddev: |
raise StddevException() |
- self.best_result.print_values(config_suffix=FLAGS.suffix) |
- def run(self): |
- """Called on the background thread. |
- |
- Launches and reads output from an skpbench process. |
- |
- """ |
- commandline = self.ARGV + ['--config', self.config, |
- '--skp', self.skp, |
- '--suppressHeader', 'true'] |
- if (FLAGS.write_path): |
- pngfile = _path.join(FLAGS.write_path, self.config, |
- _path.basename(self.skp) + '.png') |
- commandline.extend(['--png', pngfile]) |
- if (FLAGS.verbosity >= 3): |
- print(' '.join(commandline), file=sys.stderr) |
- proc = subprocess.Popen(commandline, stdout=subprocess.PIPE) |
- for line in iter(proc.stdout.readline, b''): |
- self._queue.put(Message(Message.READLINE, line.decode('utf-8').rstrip())) |
- proc.wait() |
- self._queue.put(Message(Message.EXIT, proc.returncode)) |
+ def terminate(self): |
+ if self._proc: |
+ self._proc.kill() |
+ self._monitor.join() |
+ self._proc.wait() |
+ self._proc = None |
-def main(): |
+def run_benchmarks(configs, skps, hardware): |
SKPBench.print_header() |
- # Delimiter is "," or " ", skip if nested inside parens (e.g. gpu(a=b,c=d)). |
- DELIMITER = r'[, ](?!(?:[^(]*\([^)]*\))*[^()]*\))' |
- configs = re.split(DELIMITER, FLAGS.config) |
- skps = _path.find_skps(FLAGS.skps) |
- |
benches = collections.deque([(skp, config, FLAGS.max_stddev) |
for skp in skps |
for config in configs]) |
while benches: |
benchargs = benches.popleft() |
- skpbench = SKPBench(*benchargs) |
- try: |
- skpbench.execute() |
- |
- except StddevException: |
- retry_max_stddev = skpbench.max_stddev * math.sqrt(2) |
- if FLAGS.verbosity >= 1: |
- print('NOTE: stddev too high for %s/%s (%s%%; max=%.2f%%). ' |
- 'Re-queuing with max=%.2f%%.' % |
- (skpbench.best_result.config, skpbench.best_result.bench, |
- skpbench.best_result.stddev, skpbench.max_stddev, |
- retry_max_stddev), |
- file=sys.stderr) |
- benches.append((skpbench.skp, skpbench.config, retry_max_stddev, |
- skpbench.best_result)) |
+ with SKPBench(*benchargs) as skpbench: |
+ try: |
+ skpbench.execute(hardware) |
+ if skpbench.best_result: |
+ skpbench.best_result.print_values(config_suffix=FLAGS.suffix) |
+ else: |
+ print("WARNING: no result for %s with config %s" % |
+ (skpbench.skp, skpbench.config), file=sys.stderr) |
+ |
+ except StddevException: |
+ retry_max_stddev = skpbench.max_stddev * math.sqrt(2) |
+ if FLAGS.verbosity >= 2: |
+ print("stddev is too high for %s/%s (%s%%, max=%.2f%%), " |
+ "re-queuing with max=%.2f%%." % |
+ (skpbench.best_result.config, skpbench.best_result.bench, |
+ skpbench.best_result.stddev, skpbench.max_stddev, |
+ retry_max_stddev), |
+ file=sys.stderr) |
+ benches.append((skpbench.skp, skpbench.config, retry_max_stddev, |
+ skpbench.best_result)) |
+ |
+ except HardwareException as exception: |
+ skpbench.terminate() |
+ naptime = max(hardware.kick_in_time, exception.sleeptime) |
+ if FLAGS.verbosity >= 1: |
+ print("%s; taking a %i second nap..." % |
+ (exception.message, naptime), file=sys.stderr) |
+ benches.appendleft(benchargs) # retry the same bench next time. |
+ hardware.sleep(naptime - hardware.kick_in_time) |
+ time.sleep(hardware.kick_in_time) |
+ |
+ |
+def main(): |
+ # Delimiter is ',' or ' ', skip if nested inside parens (e.g. gpu(a=b,c=d)). |
+ DELIMITER = r'[, ](?!(?:[^(]*\([^)]*\))*[^()]*\))' |
+ configs = re.split(DELIMITER, FLAGS.config) |
+ skps = _path.find_skps(FLAGS.skps) |
+ |
+ if FLAGS.adb: |
+ adb = Adb(FLAGS.device_serial) |
+ model = adb.get_device_model() |
+ if False: |
+ pass # TODO: unique subclasses tailored to individual platforms. |
+ else: |
+ from _hardware_android import HardwareAndroid |
+ print("WARNING: %s: don't know how to monitor this hardware; results " |
+ "may be unreliable." % model, file=sys.stderr) |
+ hardware = HardwareAndroid(adb) |
+ else: |
+ hardware = Hardware() |
+ |
+ with hardware: |
+ if hardware.kick_in_time: |
+ print("sleeping %i seconds to allow hardware settings to kick in..." % |
+ hardware.kick_in_time, file=sys.stderr) |
+ time.sleep(hardware.kick_in_time) |
+ run_benchmarks(configs, skps, hardware) |
if __name__ == '__main__': |