all repos — mgba @ cf3c275daf08fd89095302cff18d4c3e3b09bd67

mGBA Game Boy Advance Emulator

tools/perf.py (view raw)

  1#!/usr/bin/env python
  2from __future__ import print_function
  3import argparse
  4import csv
  5import os
  6import shlex
  7import signal
  8import socket
  9import subprocess
 10import sys
 11import time
 12
 13class PerfTest(object):
 14    EXECUTABLE = 'mgba-perf'
 15
 16    def __init__(self, rom, renderer='software'):
 17        self.rom = rom
 18        self.renderer = renderer
 19        self.results = None
 20        self.name = 'Perf Test: {}'.format(rom)
 21
 22    def get_args(self):
 23        return []
 24
 25    def wait(self, proc):
 26        pass
 27
 28    def run(self, cwd):
 29        args = [os.path.join(os.getcwd(), self.EXECUTABLE), '-P']
 30        args.extend(self.get_args())
 31        if self.renderer != 'software':
 32            args.append('-N')
 33        args.append(self.rom)
 34        env = {}
 35        if 'LD_LIBRARY_PATH' in os.environ:
 36            env['LD_LIBRARY_PATH'] = os.path.abspath(os.environ['LD_LIBRARY_PATH'])
 37            env['DYLD_LIBRARY_PATH'] = env['LD_LIBRARY_PATH'] # Fake it on OS X
 38        proc = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd, universal_newlines=True, env=env)
 39        try:
 40            self.wait(proc)
 41            proc.wait()
 42        except:
 43            proc.kill()
 44            raise
 45        if proc.returncode:
 46            print('Game crashed!', file=sys.stderr)
 47            return
 48        reader = csv.DictReader(proc.stdout)
 49        self.results = next(reader)
 50
 51class WallClockTest(PerfTest):
 52    def __init__(self, rom, duration, renderer='software'):
 53        super(WallClockTest, self).__init__(rom, renderer)
 54        self.duration = duration
 55        self.name = 'Wall-Clock Test ({} seconds, {} renderer): {}'.format(duration, renderer, rom)
 56
 57    def wait(self, proc):
 58        time.sleep(self.duration)
 59        proc.send_signal(signal.SIGINT)
 60
 61class GameClockTest(PerfTest):
 62    def __init__(self, rom, frames, renderer='software'):
 63        super(GameClockTest, self).__init__(rom, renderer)
 64        self.frames = frames
 65        self.name = 'Game-Clock Test ({} frames, {} renderer): {}'.format(frames, renderer, rom)
 66
 67    def get_args(self):
 68        return ['-F', str(self.frames)]
 69
 70class PerfServer(object):
 71    ITERATIONS_PER_INSTANCE = 50
 72
 73    def __init__(self, address, command=None):
 74        s = address.rsplit(':', 1)
 75        if len(s) == 1:
 76            self.address = (s[0], 7216)
 77        else:
 78            self.address = (s[0], s[1])
 79        if command:
 80            self.command = shlex.split(command)
 81        self.iterations = self.ITERATIONS_PER_INSTANCE
 82        self.socket = None
 83        self.results = []
 84        self.reader = None
 85
 86    def _start(self, test):
 87        if self.command:
 88            server_command = list(self.command)
 89        else:
 90            server_command = [os.path.join(os.getcwd(), PerfTest.EXECUTABLE)]
 91        server_command.extend(['--', '-PD', '0'])
 92        if hasattr(test, "frames"):
 93            server_command.extend(['-F', str(test.frames)])
 94        if test.renderer != "software":
 95            server_command.append('-N')
 96        subprocess.check_call(server_command)
 97        time.sleep(4)
 98        self.socket = socket.create_connection(self.address, timeout=1000)
 99        self.reader = csv.DictReader(self.socket.makefile())
100
101    def run(self, test):
102        if not self.socket:
103            self._start(test)
104        self.socket.send(os.path.join("/perfroms", test.rom))
105        self.results.append(next(self.reader))
106        self.iterations -= 1
107        if self.iterations == 0:
108            self.finish()
109            self.iterations = self.ITERATIONS_PER_INSTANCE
110
111    def finish(self):
112        self.socket.send("\n");
113        self.reader = None
114        self.socket.close()
115        time.sleep(5)
116        self.socket = None
117
118class Suite(object):
119    def __init__(self, cwd, wall=None, game=None, renderer='software'):
120        self.cwd = cwd
121        self.tests = []
122        self.wall = wall
123        self.game = game
124        self.renderer = renderer
125        self.server = None
126
127    def set_server(self, server):
128        self.server = server
129
130    def collect_tests(self):
131        roms = []
132        for f in os.listdir(self.cwd):
133            if f.endswith('.gba') or f.endswith('.zip') or f.endswith('.gbc') or f.endswith('.gb'):
134                roms.append(f)
135        roms.sort()
136        for rom in roms:
137            self.add_tests(rom)
138
139    def add_tests(self, rom):
140        if self.wall:
141            self.tests.append(WallClockTest(rom, self.wall, renderer=self.renderer))
142        if self.game:
143            self.tests.append(GameClockTest(rom, self.game, renderer=self.renderer))
144
145    def run(self):
146        results = []
147        sock = None
148        for test in self.tests:
149            print('Running test {}'.format(test.name), file=sys.stderr)
150            if self.server:
151                self.server.run(test)
152            else:
153                try:
154                    test.run(self.cwd)
155                except KeyboardInterrupt:
156                    print('Interrupted, returning early...', file=sys.stderr)
157                    return results
158                if test.results:
159                    results.append(test.results)
160        if self.server:
161            self.server.finish()
162            results.extend(self.server.results)
163        return results
164
165if __name__ == '__main__':
166    parser = argparse.ArgumentParser()
167    parser.add_argument('-w', '--wall-time', type=float, default=0, metavar='TIME', help='wall-clock time')
168    parser.add_argument('-g', '--game-frames', type=int, default=0, metavar='FRAMES', help='game-clock frames')
169    parser.add_argument('-N', '--disable-renderer', action='store_const', const=True, help='disable video rendering')
170    parser.add_argument('-s', '--server', metavar='ADDRESS', help='run on server')
171    parser.add_argument('-S', '--server-command', metavar='COMMAND', help='command to launch server')
172    parser.add_argument('-o', '--out', metavar='FILE', help='output file path')
173    parser.add_argument('directory', help='directory containing ROM files')
174    args = parser.parse_args()
175
176    s = Suite(args.directory, wall=args.wall_time, game=args.game_frames, renderer=None if args.disable_renderer else 'software')
177    if args.server:
178        if args.server_command:
179            server = PerfServer(args.server, args.server_command)
180        else:
181            server = PerfServer(args.server)
182        s.set_server(server)
183    s.collect_tests()
184    results = s.run()
185    fout = sys.stdout
186    if args.out:
187        fout = open(args.out, 'w')
188    writer = csv.DictWriter(fout, results[0].keys())
189    writer.writeheader()
190    writer.writerows(results)
191    if fout is not sys.stdout:
192        fout.close()