Path: blob/master/AndroidRunner/Plugins/frametimes/Frametimes.py
630 views
import os.path as op1import os2import time3import timeit4import threading5import csv67from AndroidRunner.Plugins.Profiler import Profiler8910class ConfigError(Exception):11pass121314class Frametimes(Profiler):15def __init__(self, config, paths):16super(Frametimes, self).__init__(config, paths)17self.output_dir = ''18self.paths = paths19self.profile = False20self.interval = float(self.is_integer(config.get('sample_interval', 0))) / 100021self.data = set()2223def get_frame_times(self, device, app):24result = device.shell(25'dumpsys gfxinfo {} framestats | sed -n /--PROFILEDATA---/,/--PROFILEDATA---/p'.format(app))2627if 'No process found' in result:28raise Exception('FrameTimes Profiler: {}'.format(result))2930filteredResult = filter(lambda row: not row.startswith('Flags') and row != '---PROFILEDATA---', result.split())3132return map(lambda stats: self.extract_frame_start_end(stats.split(',')), filteredResult)3334def extract_frame_start_end(self, frame_times):35return [int(frame_times[1]), int(frame_times[13])]3637def start_profiling(self, device, **kwargs):38self.profile = True39self.data = set()40app = kwargs.get('app', None)41self.get_data(device, app)4243def get_data(self, device, app):44"""Runs the profiling methods every self.interval seconds in a separate thread"""45start = timeit.default_timer()46for frame in self.get_frame_times(device, app):47self.data.add(tuple(frame))48end = timeit.default_timer()49# timer results could be negative50interval = max(float(0), self.interval - max(0, end - start))51if self.profile:52threading.Timer(interval, self.get_data, args=(device, app)).start()5354def stop_profiling(self, device, **kwargs):55self.profile = False5657def collect_results(self, device, path=None):58times_filename = 'frame_times_{}_{}.csv'.format(device.id, time.strftime('%Y.%m.%d_%H%M%S'))59delayed_filename = 'delayed_{}_{}.csv'.format(device.id, time.strftime('%Y.%m.%d_%H%M%S'))60delayed_count = 06162with open(op.join(self.output_dir, times_filename), 'w+') as f:63writer = csv.writer(f)64writer.writerow(['frame_start', 'frame_end', 'frame_time', 'is_delayed'])65for row in self.data:66frame_time = row[1] - row[0]67# https://developer.android.com/topic/performance/vitals/render68# TL;DR; A frame is considered as delayed whenever it took more than 16m to render69if row[1] - row[0] > 16000000:70writer.writerow([row[0], row[1], frame_time, True])71delayed_count += 172else:73writer.writerow([row[0], row[1], frame_time, False])74self.data = set()7576with open(op.join(self.output_dir, delayed_filename), 'w+') as f:77writer = csv.writer(f)78writer.writerow(['delayed_frames_count'])79writer.writerow([delayed_count])8081def set_output(self, output_dir):82self.output_dir = output_dir8384def dependencies(self):85return []8687def load(self, device):88return8990def unload(self, device):91return9293def aggregate_subject(self):94self.aggregate_delayed_frames()95self.aggregate_frame_times()9697def aggregate_delayed_frames(self):98with open(op.join(self.output_dir, 'all_delayed_frame_counts.csv'), 'w+') as output:99writer = csv.writer(output)100writer.writerow(['delayed_frames'])101for output_file in os.listdir(self.output_dir):102if output_file.startswith("delayed_"):103writer.writerow([int(open(op.join(self.output_dir, output_file)).readlines()[1])])104105def aggregate_frame_times(self):106with open(op.join(self.output_dir, 'all_frame_times.csv'), 'w+') as output:107writer = csv.writer(output)108writer.writerow(['frame_time'])109for output_file in os.listdir(self.output_dir):110if output_file.startswith("frame_times_"):111for row in open(op.join(self.output_dir, output_file)).readlines()[1:]:112writer.writerow([int(row.split(',')[2])])113114def aggregate_end(self, data_dir, output_file):115return116117def aggregate_final(self, data_dir):118return119120def is_integer(self, number, minimum=0):121if not isinstance(number, int):122raise ConfigError('%s is not an integer' % number)123if number < minimum:124raise ConfigError('%s should be equal or larger than %i' % (number, minimum))125return number126127128