背景
性能优化,经常用到一些指标,诸如帧率、功耗等。对于普通app来讲, 之前一直使用gfxinfo指令获取丢帧率。但是这个指令无法获取游戏的帧率,查阅资料,发现SurfaceFlinger可以获取游戏帧率。
帧率获取原理
-
获取当前focused layer
指令:adb shell ‘dumpsys SurfaceFlinger | grep -i Explicit -A 30’
这一步看到focused layer name可能会不全,有省略号。可使用下一步指令查看完整layer name。focused的图层会有*号标志。示例如下:
PS D:\work> adb shell 'dumpsys SurfaceFlinger | grep -i Explicit -A 30'Z | Window Type | Layer Class | Comp Type | Transform | Disp Frame (LTRB) | Source Crop (LTRB) | Frame Rate (Explicit) (Seamlessness) [Focused]
---------------------------------------------------------------------------------------------------------------------------------------------------------------SurfaceView[com.netease.l22.nearme.g[...]tyPlugin.AndroidPlugin](BLAST)#142209 | 0 | 2 | DEVICE | 0 | 0 0 1080 2376 | 0.0 0.0 1080.0 2376.0 | [*]
-
获取SurfaceFlinger的layer列表
指令:adb shell ‘dumpsys SurfaceFlinger --list | grep xxx’
经过上一步获取到的focused layer name部分字符串xxx过来匹配,过滤掉不相关的数据。 -
获取具体图层的帧信息
指令:adb shell ‘dumpsys SurfaceFlinger --latency <layer_name>’
terminal输出格式大致如下:
共有128行。第一行是刷新率。剩余的数据127行,分为3列,分别是:
desiredPresentTime:应用期望提交的时间
actualPresentTime:实际提交的时间
frameReadyTime:帧准备好的时间
每个数字,代表一个帧的时间戳,单位是ns。计算帧率,使用第二列数据,通过数时间戳,可以确定1秒有多少帧。
> adb shell dumpsys SurfaceFlinger --latency "xxxxx"
16666666
59069638658663 59069678041684 59069654158298
59069653090955 59069695022100 59069670894236
59069671034444 59069711403455 59069687949861
59069688421840 59069728057361 59069704415121
59069705420850 59069744773350 59069720767830
59069719818975 59069761378975 59069737416007
59069736702673 59069778060955 59069754568663
59069753361528 59069794716007 59069770761632
59069768766371 59069811380486 59069787649600
......
帧率获取脚本
通过上述原理,将计算逻辑封装成python脚本。计算原理如下:
import subprocess
import time
from threading import Threadnanoseconds_per_second = 1e9class SurfaceFlingerFPS():def __init__(self, view, ip):self.view = viewself.ip = ipself.refresh_period, self.base_timestamp, self.timestamps = self.__init_frame_data__(self.view)self.recent_timestamps = self.timestamps[-2]self.fps = 0def __init_frame_data__(self, view):# print('__init_frame_data__()')out = ''try:out = subprocess.check_output(['adb', '-s', self.ip, 'shell', 'dumpsys', 'SurfaceFlinger', '--latency-clear', view])except subprocess.CalledProcessError as e:print(e.output)out = out.decode('utf-8')if out.strip() != '':raise RuntimeError("Not supported.")time.sleep(0.1)(refresh_period, timestamps) = self.__frame_data__(view)base_timestamp = 0base_index = 0for timestamp in timestamps:if timestamp != 0:base_timestamp = timestampbreakbase_index += 1if base_timestamp == 0:raise RuntimeError("Initial frame collect failed")# print('refresh_period={} base_timestamp={}\ntimestamps=\n{}'.format(refresh_period, base_timestamp, str(timestamps[base_index:])))return (refresh_period, base_timestamp, timestamps[base_index:])def __frame_data__(self, view):out = subprocess.check_output(['adb', '-s', self.ip, 'shell', 'dumpsys', 'SurfaceFlinger', '--latency', view])out = out.decode('utf-8')results = out.splitlines()refresh_period = int(results[0]) / nanoseconds_per_secondtimestamps = []for line in results[1:]:fields = line.split()if len(fields) != 3:continue(start, submitting, submitted) = map(int, fields)if submitting == 0:continuetimestamp = submitting / nanoseconds_per_secondtimestamps.append(timestamp)return (refresh_period, timestamps)def collect_frame_data(self, view):if view is None:raise RuntimeError("Fail to get current SurfaceFlinger view")self.refresh_period, self.timestamps = self.__frame_data__(view)# print("\ncollect_frame_data()\ntimestamps=\n" + str(self.timestamps))time.sleep(1)self.refresh_period, tss = self.__frame_data__(view)# print("tss=\n" + str(tss))self.last_index = 0if self.timestamps:self.recent_timestamp = self.timestamps[-2]if self.recent_timestamp not in tss:self.recent_timestamp = self.timestamps[-3]self.last_index = tss.index(self.recent_timestamp)self.timestamps = self.timestamps[:-2] + tss[self.last_index:]# time.sleep(1)ajusted_timestamps = []for seconds in self.timestamps[:]:seconds -= self.base_timestampif seconds > 1e6: # too large, just ignorecontinueajusted_timestamps.append(seconds)# print('ajusted_timestamps=\n' + str(ajusted_timestamps))from_time = ajusted_timestamps[-1] - 1.0fps_count = 0for seconds in ajusted_timestamps:if seconds > from_time:fps_count += 1self.fps = fps_countdef start(self):th = Thread(target=self.collect_frame_data, args=(self.view,))th.start()def getFPS(self):self.collect_frame_data(self.view)return self.fps