2014-10-09 07:08:46 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
|
|
import csv
|
|
|
|
import os
|
2016-03-03 09:05:55 +00:00
|
|
|
import shlex
|
2014-10-09 07:08:46 +00:00
|
|
|
import signal
|
2016-03-03 09:05:55 +00:00
|
|
|
import socket
|
2014-10-09 07:08:46 +00:00
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
|
|
|
|
class PerfTest(object):
|
|
|
|
EXECUTABLE = 'mgba-perf'
|
|
|
|
|
|
|
|
def __init__(self, rom, renderer='software'):
|
|
|
|
self.rom = rom
|
|
|
|
self.renderer = renderer
|
|
|
|
self.results = None
|
|
|
|
self.name = 'Perf Test: {}'.format(rom)
|
|
|
|
|
|
|
|
def get_args(self):
|
|
|
|
return []
|
|
|
|
|
|
|
|
def wait(self, proc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def run(self, cwd):
|
|
|
|
args = [os.path.join(os.getcwd(), self.EXECUTABLE), '-P']
|
|
|
|
args.extend(self.get_args())
|
2020-01-03 02:38:11 +00:00
|
|
|
if not self.renderer:
|
2014-10-09 07:08:46 +00:00
|
|
|
args.append('-N')
|
2020-01-03 02:38:11 +00:00
|
|
|
elif self.renderer == 'threaded-software':
|
|
|
|
args.append('-T')
|
2014-10-09 07:08:46 +00:00
|
|
|
args.append(self.rom)
|
2014-10-09 08:36:08 +00:00
|
|
|
env = {}
|
|
|
|
if 'LD_LIBRARY_PATH' in os.environ:
|
2014-10-09 08:53:47 +00:00
|
|
|
env['LD_LIBRARY_PATH'] = os.path.abspath(os.environ['LD_LIBRARY_PATH'])
|
|
|
|
env['DYLD_LIBRARY_PATH'] = env['LD_LIBRARY_PATH'] # Fake it on OS X
|
2014-10-09 08:36:08 +00:00
|
|
|
proc = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd, universal_newlines=True, env=env)
|
2014-10-09 07:08:46 +00:00
|
|
|
try:
|
|
|
|
self.wait(proc)
|
|
|
|
proc.wait()
|
|
|
|
except:
|
|
|
|
proc.kill()
|
|
|
|
raise
|
2015-01-10 12:00:22 +00:00
|
|
|
if proc.returncode:
|
2014-10-11 05:54:04 +00:00
|
|
|
print('Game crashed!', file=sys.stderr)
|
2014-10-09 07:08:46 +00:00
|
|
|
return
|
|
|
|
reader = csv.DictReader(proc.stdout)
|
|
|
|
self.results = next(reader)
|
|
|
|
|
|
|
|
class WallClockTest(PerfTest):
|
|
|
|
def __init__(self, rom, duration, renderer='software'):
|
|
|
|
super(WallClockTest, self).__init__(rom, renderer)
|
|
|
|
self.duration = duration
|
|
|
|
self.name = 'Wall-Clock Test ({} seconds, {} renderer): {}'.format(duration, renderer, rom)
|
|
|
|
|
|
|
|
def wait(self, proc):
|
|
|
|
time.sleep(self.duration)
|
|
|
|
proc.send_signal(signal.SIGINT)
|
|
|
|
|
|
|
|
class GameClockTest(PerfTest):
|
|
|
|
def __init__(self, rom, frames, renderer='software'):
|
|
|
|
super(GameClockTest, self).__init__(rom, renderer)
|
|
|
|
self.frames = frames
|
|
|
|
self.name = 'Game-Clock Test ({} frames, {} renderer): {}'.format(frames, renderer, rom)
|
|
|
|
|
|
|
|
def get_args(self):
|
|
|
|
return ['-F', str(self.frames)]
|
|
|
|
|
2016-03-03 09:05:55 +00:00
|
|
|
class PerfServer(object):
|
|
|
|
ITERATIONS_PER_INSTANCE = 50
|
2022-06-06 23:56:18 +00:00
|
|
|
RETRIES = 4
|
2016-03-03 09:05:55 +00:00
|
|
|
|
2020-08-19 04:26:35 +00:00
|
|
|
def __init__(self, address, root='/', command=None):
|
2016-03-03 09:05:55 +00:00
|
|
|
s = address.rsplit(':', 1)
|
|
|
|
if len(s) == 1:
|
|
|
|
self.address = (s[0], 7216)
|
|
|
|
else:
|
|
|
|
self.address = (s[0], s[1])
|
2020-08-19 04:26:35 +00:00
|
|
|
self.command = None
|
2016-03-03 09:05:55 +00:00
|
|
|
if command:
|
|
|
|
self.command = shlex.split(command)
|
|
|
|
self.iterations = self.ITERATIONS_PER_INSTANCE
|
|
|
|
self.socket = None
|
|
|
|
self.results = []
|
|
|
|
self.reader = None
|
2020-08-19 04:26:35 +00:00
|
|
|
self.root = root
|
2016-03-03 09:05:55 +00:00
|
|
|
|
|
|
|
def _start(self, test):
|
|
|
|
if self.command:
|
|
|
|
server_command = list(self.command)
|
|
|
|
else:
|
|
|
|
server_command = [os.path.join(os.getcwd(), PerfTest.EXECUTABLE)]
|
2020-08-06 00:56:03 +00:00
|
|
|
server_command.extend(['-PD'])
|
2018-04-02 20:21:58 +00:00
|
|
|
if hasattr(test, "frames"):
|
|
|
|
server_command.extend(['-F', str(test.frames)])
|
2020-01-03 02:38:11 +00:00
|
|
|
if not test.renderer:
|
2018-04-02 20:21:58 +00:00
|
|
|
server_command.append('-N')
|
2020-01-03 02:38:11 +00:00
|
|
|
elif test.renderer == 'threaded-software':
|
|
|
|
server_command.append('-T')
|
2022-06-06 23:56:18 +00:00
|
|
|
for backoff in range(self.RETRIES):
|
|
|
|
try:
|
|
|
|
subprocess.check_call(server_command)
|
|
|
|
break
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
print("Failed to start server:", e, file=sys.stderr)
|
|
|
|
if backoff == self.RETRIES - 1:
|
|
|
|
raise
|
|
|
|
time.sleep(2 ** backoff)
|
2021-03-10 05:39:18 +00:00
|
|
|
time.sleep(3)
|
2021-03-10 05:01:42 +00:00
|
|
|
for backoff in range(self.RETRIES):
|
|
|
|
try:
|
|
|
|
self.socket = socket.create_connection(self.address, timeout=1000)
|
|
|
|
break
|
|
|
|
except OSError as e:
|
2021-03-10 05:29:45 +00:00
|
|
|
print("Failed to connect:", e, file=sys.stderr)
|
2022-06-06 23:56:18 +00:00
|
|
|
if backoff == self.RETRIES - 1:
|
2021-03-10 05:01:42 +00:00
|
|
|
raise
|
2022-06-06 23:56:18 +00:00
|
|
|
time.sleep(2 ** backoff)
|
2019-06-07 19:46:38 +00:00
|
|
|
kwargs = {}
|
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
kwargs["encoding"] = "utf-8"
|
|
|
|
self.reader = csv.DictReader(self.socket.makefile(**kwargs))
|
2016-03-03 09:05:55 +00:00
|
|
|
|
|
|
|
def run(self, test):
|
|
|
|
if not self.socket:
|
|
|
|
self._start(test)
|
2020-08-19 04:26:35 +00:00
|
|
|
self.socket.send(os.path.join(self.root, test.rom).encode("utf-8"))
|
2016-03-03 09:05:55 +00:00
|
|
|
self.results.append(next(self.reader))
|
|
|
|
self.iterations -= 1
|
|
|
|
if self.iterations == 0:
|
2016-03-06 02:11:00 +00:00
|
|
|
self.finish()
|
2016-03-03 09:05:55 +00:00
|
|
|
self.iterations = self.ITERATIONS_PER_INSTANCE
|
|
|
|
|
2016-03-06 02:11:00 +00:00
|
|
|
def finish(self):
|
2019-06-07 19:46:38 +00:00
|
|
|
self.socket.send(b"\n");
|
2016-03-06 02:11:00 +00:00
|
|
|
self.reader = None
|
|
|
|
self.socket.close()
|
|
|
|
time.sleep(5)
|
|
|
|
self.socket = None
|
|
|
|
|
2014-10-09 07:08:46 +00:00
|
|
|
class Suite(object):
|
2014-10-11 05:52:49 +00:00
|
|
|
def __init__(self, cwd, wall=None, game=None, renderer='software'):
|
2014-10-09 07:08:46 +00:00
|
|
|
self.cwd = cwd
|
|
|
|
self.tests = []
|
|
|
|
self.wall = wall
|
|
|
|
self.game = game
|
2014-10-11 05:52:49 +00:00
|
|
|
self.renderer = renderer
|
2016-03-03 09:05:55 +00:00
|
|
|
self.server = None
|
|
|
|
|
|
|
|
def set_server(self, server):
|
|
|
|
self.server = server
|
2014-10-09 07:08:46 +00:00
|
|
|
|
|
|
|
def collect_tests(self):
|
|
|
|
roms = []
|
|
|
|
for f in os.listdir(self.cwd):
|
2016-03-03 09:05:55 +00:00
|
|
|
if f.endswith('.gba') or f.endswith('.zip') or f.endswith('.gbc') or f.endswith('.gb'):
|
2014-10-09 07:08:46 +00:00
|
|
|
roms.append(f)
|
|
|
|
roms.sort()
|
|
|
|
for rom in roms:
|
|
|
|
self.add_tests(rom)
|
|
|
|
|
|
|
|
def add_tests(self, rom):
|
|
|
|
if self.wall:
|
2014-10-11 05:52:49 +00:00
|
|
|
self.tests.append(WallClockTest(rom, self.wall, renderer=self.renderer))
|
2014-10-09 07:08:46 +00:00
|
|
|
if self.game:
|
2014-10-11 05:52:49 +00:00
|
|
|
self.tests.append(GameClockTest(rom, self.game, renderer=self.renderer))
|
2014-10-09 07:08:46 +00:00
|
|
|
|
|
|
|
def run(self):
|
|
|
|
results = []
|
2016-03-03 09:05:55 +00:00
|
|
|
sock = None
|
2014-10-09 07:08:46 +00:00
|
|
|
for test in self.tests:
|
|
|
|
print('Running test {}'.format(test.name), file=sys.stderr)
|
2021-03-10 05:29:45 +00:00
|
|
|
last_result = None
|
2016-03-03 09:05:55 +00:00
|
|
|
if self.server:
|
|
|
|
self.server.run(test)
|
2021-03-10 05:29:45 +00:00
|
|
|
last_result = self.server.results[-1]
|
2016-03-03 09:05:55 +00:00
|
|
|
else:
|
|
|
|
try:
|
|
|
|
test.run(self.cwd)
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
print('Interrupted, returning early...', file=sys.stderr)
|
|
|
|
return results
|
|
|
|
if test.results:
|
|
|
|
results.append(test.results)
|
2021-03-10 05:29:45 +00:00
|
|
|
last_result = results[-1]
|
|
|
|
if last_result:
|
2021-03-10 05:39:18 +00:00
|
|
|
print('{:.2f} fps'.format(int(last_result['frames']) * 1000000 / float(last_result['duration'])), file=sys.stderr)
|
2016-03-03 09:05:55 +00:00
|
|
|
if self.server:
|
2016-03-06 02:11:00 +00:00
|
|
|
self.server.finish()
|
2016-03-03 09:05:55 +00:00
|
|
|
results.extend(self.server.results)
|
2014-10-09 07:08:46 +00:00
|
|
|
return results
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = argparse.ArgumentParser()
|
2014-10-11 05:52:49 +00:00
|
|
|
parser.add_argument('-w', '--wall-time', type=float, default=0, metavar='TIME', help='wall-clock time')
|
|
|
|
parser.add_argument('-g', '--game-frames', type=int, default=0, metavar='FRAMES', help='game-clock frames')
|
|
|
|
parser.add_argument('-N', '--disable-renderer', action='store_const', const=True, help='disable video rendering')
|
2020-01-03 02:38:11 +00:00
|
|
|
parser.add_argument('-T', '--threaded-renderer', action='store_const', const=True, help='threaded video rendering')
|
2016-03-03 09:05:55 +00:00
|
|
|
parser.add_argument('-s', '--server', metavar='ADDRESS', help='run on server')
|
|
|
|
parser.add_argument('-S', '--server-command', metavar='COMMAND', help='command to launch server')
|
2014-10-09 07:08:46 +00:00
|
|
|
parser.add_argument('-o', '--out', metavar='FILE', help='output file path')
|
2020-08-19 04:26:35 +00:00
|
|
|
parser.add_argument('-r', '--root', metavar='PATH', type=str, default='/perfroms', help='root path for server mode')
|
2014-10-09 07:08:46 +00:00
|
|
|
parser.add_argument('directory', help='directory containing ROM files')
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
2020-01-03 02:38:11 +00:00
|
|
|
renderer = 'software'
|
|
|
|
if args.disable_renderer:
|
|
|
|
renderer = None
|
|
|
|
elif args.threaded_renderer:
|
|
|
|
renderer = 'threaded-software'
|
|
|
|
s = Suite(args.directory, wall=args.wall_time, game=args.game_frames, renderer=renderer)
|
2016-03-03 09:05:55 +00:00
|
|
|
if args.server:
|
|
|
|
if args.server_command:
|
2020-08-19 04:26:35 +00:00
|
|
|
server = PerfServer(args.server, args.root, args.server_command)
|
2016-03-03 09:05:55 +00:00
|
|
|
else:
|
2020-08-19 04:26:35 +00:00
|
|
|
server = PerfServer(args.server, args.root)
|
2016-03-03 09:05:55 +00:00
|
|
|
s.set_server(server)
|
2014-10-09 07:08:46 +00:00
|
|
|
s.collect_tests()
|
|
|
|
results = s.run()
|
|
|
|
fout = sys.stdout
|
|
|
|
if args.out:
|
|
|
|
fout = open(args.out, 'w')
|
|
|
|
writer = csv.DictWriter(fout, results[0].keys())
|
|
|
|
writer.writeheader()
|
|
|
|
writer.writerows(results)
|
|
|
|
if fout is not sys.stdout:
|
|
|
|
fout.close()
|