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()