tools/perf.py (view raw)
1#!/usr/bin/env python
2from __future__ import print_function
3import argparse
4import csv
5import os
6import signal
7import subprocess
8import sys
9import time
10
11class PerfTest(object):
12 EXECUTABLE = 'mgba-perf'
13
14 def __init__(self, rom, renderer='software'):
15 self.rom = rom
16 self.renderer = renderer
17 self.results = None
18 self.name = 'Perf Test: {}'.format(rom)
19
20 def get_args(self):
21 return []
22
23 def wait(self, proc):
24 pass
25
26 def run(self, cwd):
27 args = [os.path.join(os.getcwd(), self.EXECUTABLE), '-P']
28 args.extend(self.get_args())
29 if self.renderer != 'software':
30 args.append('-N')
31 args.append(self.rom)
32 env = {}
33 if 'LD_LIBRARY_PATH' in os.environ:
34 env['LD_LIBRARY_PATH'] = os.path.abspath(os.environ['LD_LIBRARY_PATH'])
35 env['DYLD_LIBRARY_PATH'] = env['LD_LIBRARY_PATH'] # Fake it on OS X
36 proc = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd, universal_newlines=True, env=env)
37 try:
38 self.wait(proc)
39 proc.wait()
40 except:
41 proc.kill()
42 raise
43 if proc.returncode:
44 print('Game crashed!', file=sys.stderr)
45 return
46 reader = csv.DictReader(proc.stdout)
47 self.results = next(reader)
48
49class WallClockTest(PerfTest):
50 def __init__(self, rom, duration, renderer='software'):
51 super(WallClockTest, self).__init__(rom, renderer)
52 self.duration = duration
53 self.name = 'Wall-Clock Test ({} seconds, {} renderer): {}'.format(duration, renderer, rom)
54
55 def wait(self, proc):
56 time.sleep(self.duration)
57 proc.send_signal(signal.SIGINT)
58
59class GameClockTest(PerfTest):
60 def __init__(self, rom, frames, renderer='software'):
61 super(GameClockTest, self).__init__(rom, renderer)
62 self.frames = frames
63 self.name = 'Game-Clock Test ({} frames, {} renderer): {}'.format(frames, renderer, rom)
64
65 def get_args(self):
66 return ['-F', str(self.frames)]
67
68class Suite(object):
69 def __init__(self, cwd, wall=None, game=None, renderer='software'):
70 self.cwd = cwd
71 self.tests = []
72 self.wall = wall
73 self.game = game
74 self.renderer = renderer
75
76 def collect_tests(self):
77 roms = []
78 for f in os.listdir(self.cwd):
79 if f.endswith('.gba'):
80 roms.append(f)
81 roms.sort()
82 for rom in roms:
83 self.add_tests(rom)
84
85 def add_tests(self, rom):
86 if self.wall:
87 self.tests.append(WallClockTest(rom, self.wall, renderer=self.renderer))
88 if self.game:
89 self.tests.append(GameClockTest(rom, self.game, renderer=self.renderer))
90
91 def run(self):
92 results = []
93 for test in self.tests:
94 print('Running test {}'.format(test.name), file=sys.stderr)
95 try:
96 test.run(self.cwd)
97 except KeyboardInterrupt:
98 print('Interrupted, returning early...', file=sys.stderr)
99 return results
100 if test.results:
101 results.append(test.results)
102 return results
103
104if __name__ == '__main__':
105 parser = argparse.ArgumentParser()
106 parser.add_argument('-w', '--wall-time', type=float, default=0, metavar='TIME', help='wall-clock time')
107 parser.add_argument('-g', '--game-frames', type=int, default=0, metavar='FRAMES', help='game-clock frames')
108 parser.add_argument('-N', '--disable-renderer', action='store_const', const=True, help='disable video rendering')
109 parser.add_argument('-o', '--out', metavar='FILE', help='output file path')
110 parser.add_argument('directory', help='directory containing ROM files')
111 args = parser.parse_args()
112
113 s = Suite(args.directory, wall=args.wall_time, game=args.game_frames, renderer=None if args.disable_renderer else 'software')
114 s.collect_tests()
115 results = s.run()
116 fout = sys.stdout
117 if args.out:
118 fout = open(args.out, 'w')
119 writer = csv.DictWriter(fout, results[0].keys())
120 writer.writeheader()
121 writer.writerows(results)
122 if fout is not sys.stdout:
123 fout.close()