84 lines
3.9 KiB
Python
84 lines
3.9 KiB
Python
# coding: utf-8
|
|
"""Run opt-in single-section render isolation on a connected debug Android APK.
|
|
Requires its Client resources to be synced. Writes CSVs/screenshots/summary locally.
|
|
Normal startup is restored afterwards; device system settings are not changed.
|
|
"""
|
|
import argparse
|
|
import csv
|
|
import json
|
|
import shlex
|
|
import statistics
|
|
import subprocess
|
|
import time
|
|
from pathlib import Path
|
|
|
|
PKG = 'org.metin2port.client'
|
|
PERF = '/sdcard/Android/data/' + PKG + '/files/perf/'
|
|
|
|
def adb(*args):
|
|
return subprocess.check_output(['adb', *args], stderr=subprocess.STDOUT)
|
|
|
|
def shell(command):
|
|
return adb('shell', command).decode(errors='replace').strip()
|
|
|
|
def files():
|
|
return set(shell('ls ' + PERF + 'perf-*.csv 2>/dev/null || true').splitlines())
|
|
|
|
def parse(text):
|
|
return list(csv.DictReader(line for line in text.splitlines() if line and not line.startswith('#')))
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('output', type=Path)
|
|
parser.add_argument('--sections', default='baseline,terrain,characters,effects,touch,scene,baseline_repeat')
|
|
parser.add_argument('--windows', type=int, default=6)
|
|
parser.add_argument('--render-scale', type=float, default=0.6)
|
|
parser.add_argument('--msaa', type=int, choices=[1,2,4,8], default=1)
|
|
parser.add_argument('--fps-cap', type=int, default=30)
|
|
args = parser.parse_args()
|
|
args.output.mkdir(parents=True, exist_ok=True)
|
|
results = []
|
|
try:
|
|
for section in args.sections.split(','):
|
|
before = files()
|
|
launch = ('--interactive --fake-mobs 24 --perf-log --show-fps --fps-cap %d --msaa %d --render-scale %g'
|
|
% (args.fps_cap, args.msaa, args.render_scale))
|
|
if not section.startswith('baseline'):
|
|
launch += ' --diagnostic-skip-render ' + section
|
|
shell('am force-stop ' + PKG)
|
|
shell('am start -n ' + PKG + '/.MainActivity --es args ' + shlex.quote(launch))
|
|
print('START ' + section, flush=True)
|
|
deadline = time.monotonic() + 100
|
|
path = None
|
|
rows = []
|
|
while time.monotonic() < deadline:
|
|
time.sleep(2)
|
|
candidates = files() - before
|
|
if candidates:
|
|
path = sorted(candidates)[-1]
|
|
raw = adb('exec-out', 'cat', path).decode(errors='replace')
|
|
rows = [r for r in parse(raw) if r.get('map') == 'metin2_map_a1' and r.get('actors') == '28']
|
|
# Exclude the first interval after loading, then collect the requested count.
|
|
if len(rows) >= args.windows + 1:
|
|
break
|
|
if len(rows) < args.windows + 1:
|
|
raise RuntimeError('insufficient valid game samples: ' + section)
|
|
rows = rows[1:args.windows+1]
|
|
(args.output/(section+'.csv')).write_bytes(adb('exec-out', 'cat', path))
|
|
(args.output/(section+'.png')).write_bytes(adb('exec-out','screencap','-p'))
|
|
(args.output/(section+'-memory.txt')).write_text(shell('dumpsys meminfo '+PKG))
|
|
result = {'section':section,'args':launch,'device_csv':path,'windows':len(rows)}
|
|
for field in ['fps','gpu_ms','app_ms','draws','vertices','frame_p95_ms','rss_mb','batt_c']:
|
|
values = [float(r[field]) for r in rows]
|
|
result[field] = {'mean':statistics.mean(values),'min':min(values),'max':max(values)}
|
|
results.append(result)
|
|
(args.output/'summary.json').write_text(json.dumps(results,indent=2)+'\n')
|
|
print('DONE %s fps=%.2f gpu=%.1fms draws=%.0f batt=%.1fC' %
|
|
(section,result['fps']['mean'],result['gpu_ms']['mean'],result['draws']['mean'],result['batt_c']['mean']),flush=True)
|
|
finally:
|
|
shell('am force-stop ' + PKG)
|
|
shell('am start -n ' + PKG + '/.MainActivity')
|
|
|
|
if __name__ == '__main__':
|
|
main()
|