Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SurfaceView FPS统计优化 #222

Merged
merged 2 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 37 additions & 20 deletions solox/public/apm.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def getSysCpuStat(self):
ileCpu = float(toks[4])
sysCpu = self.getTotalCpuStat() - ileCpu
return sysCpu

def getAndroidCpuRate(self, noLog=False):
"""get the Android cpu rate of a process"""
try:
Expand All @@ -94,7 +94,7 @@ def getAndroidCpuRate(self, noLog=False):
if len(d.getPid(self.deviceId, self.pkgName)) == 0:
logger.error('[CPU] {} : No process found'.format(self.pkgName))
else:
logger.exception(e)
logger.exception(e)
return appCpuRate, sysCpuRate

def getiOSCpuRate(self, noLog=False):
Expand Down Expand Up @@ -138,7 +138,7 @@ def getAndroidMem(self):
if len(d.getPid(self.deviceId, self.pkgName)) == 0:
logger.error('[Memory] {} : No process found'.format(self.pkgName))
else:
logger.exception(e)
logger.exception(e)
return totalPass, nativePass, dalvikPass

def getiOSMem(self):
Expand All @@ -152,7 +152,7 @@ def getiOSMem(self):
def getProcessMem(self, noLog=False):
"""Get the app memory"""
totalPass, nativePass, dalvikPass = self.getAndroidMem() if self.platform == Platform.Android else self.getiOSMem()
if noLog is False:
if noLog is False:
apm_time = datetime.datetime.now().strftime('%H:%M:%S.%f')
f.add_log(os.path.join(f.report_dir,'mem_total.log'), apm_time, totalPass)
if self.platform == Platform.Android:
Expand All @@ -164,15 +164,15 @@ class Battery(object):
def __init__(self, deviceId, platform=Platform.Android):
self.deviceId = deviceId
self.platform = platform

def getBattery(self, noLog=False):
if self.platform == Platform.Android:
level, temperature = self.getAndroidBattery(noLog)
return level, temperature
else:
temperature, current, voltage, power = self.getiOSBattery(noLog)
return temperature, current, voltage, power

def getAndroidBattery(self, noLog=False):
"""Get android battery info, unit:%"""
# Switch mobile phone battery to non-charging state
Expand All @@ -189,7 +189,7 @@ def getAndroidBattery(self, noLog=False):
f.add_log(os.path.join(f.report_dir,'battery_level.log'), apm_time, level)
f.add_log(os.path.join(f.report_dir,'battery_tem.log'), apm_time, temperature)
return level, temperature

def getiOSBattery(self, noLog=False):
"""Get ios battery info, unit:%"""
d = tidevice.Device(udid=self.deviceId)
Expand Down Expand Up @@ -242,9 +242,9 @@ def getAndroidNet(self, wifi=True):
if len(d.getPid(self.deviceId, self.pkgName)) == 0:
logger.error('[Network] {} : No process found'.format(self.pkgName))
else:
logger.exception(e)
logger.exception(e)
return sendNum, recNum

def setAndroidNet(self, wifi=True):
try:
net = 'wlan0' if wifi else 'rmnet0'
Expand All @@ -258,7 +258,7 @@ def setAndroidNet(self, wifi=True):
if len(d.getPid(self.deviceId, self.pkgName)) == 0:
logger.error('[Network] {} : No process found'.format(self.pkgName))
else:
logger.exception(e)
logger.exception(e)
return sendNum, recNum


Expand All @@ -280,21 +280,38 @@ def getNetWorkData(self, wifi=True, noLog=False):
return sendNum, recNum

class FPS(object):
AndroidFPS = None

@classmethod
def getObject(cls, *args, **kwargs):
if kwargs['platform'] == Platform.Android:
if cls.AndroidFPS is None:
cls.AndroidFPS = FPS(*args, **kwargs)
return cls.AndroidFPS
return FPS(*args, **kwargs)

@classmethod
def clear(cls):
cls.AndroidFPS = None

def __init__(self, pkgName, deviceId, platform=Platform.Android, surfaceview=True):
self.pkgName = pkgName
self.deviceId = deviceId
self.platform = platform
self.surfaceview = surfaceview
self.apm_time = datetime.datetime.now().strftime('%H:%M:%S.%f')
self.monitors = None

def getAndroidFps(self, noLog=False):
"""get Android Fps, unit:HZ"""
try:
monitors = FPSMonitor(device_id=self.deviceId, package_name=self.pkgName, frequency=1,
if self.monitors is None:
self.monitors = FPSMonitor(device_id=self.deviceId, package_name=self.pkgName, frequency=1,
surfaceview=self.surfaceview, start_time=TimeUtils.getCurrentTimeUnderline())
monitors.start()
fps, jank = monitors.stop()
self.monitors.start()

# fps, jank = monitors.stop()
fps, jank = self.monitors.get_fps()
if noLog is False:
apm_time = datetime.datetime.now().strftime('%H:%M:%S.%f')
f.add_log(os.path.join(f.report_dir,'fps.log'), apm_time, fps)
Expand All @@ -304,7 +321,7 @@ def getAndroidFps(self, noLog=False):
if len(d.getPid(self.deviceId, self.pkgName)) == 0:
logger.error('[FPS] {} : No process found'.format(self.pkgName))
else:
logger.exception(e)
logger.exception(e)
return fps, jank

def getiOSFps(self, noLog=False):
Expand Down Expand Up @@ -332,7 +349,7 @@ def getGPU(self, noLog=False):
if noLog is False:
apm_time = datetime.datetime.now().strftime('%H:%M:%S.%f')
f.add_log(os.path.join(f.report_dir,'gpu.log'), apm_time, gpu)
return gpu
return gpu

class iosAPM(object):

Expand Down Expand Up @@ -446,7 +463,7 @@ def collectFps(self):
if time.time() > self.end_time:
break
return result

def collectGpu(self):
_gpu = GPU(self.pkgName)
result = {}
Expand All @@ -459,7 +476,7 @@ def collectGpu(self):
if time.time() > self.end_time:
break
return result

def setPerfs(self):
match(self.platform):
case Platform.Android:
Expand Down Expand Up @@ -490,7 +507,7 @@ def setPerfs(self):
summary_dict['jank_charts'] = f.getFpsLog(Platform.Android, scene)['jank']
f.make_android_html(scene=scene, summary=summary_dict)
case Platform.iOS:
scene = f.make_report(app=self.pkgName, devices=self.deviceId,
scene = f.make_report(app=self.pkgName, devices=self.deviceId,
video=0, platform=self.platform, model='normal')
summary = f._setiOSPerfs(scene)
summary_dict = {}
Expand All @@ -513,7 +530,7 @@ def setPerfs(self):
summary_dict['gpu_charts'] = f.getGpuLog(Platform.iOS, scene)
f.make_ios_html(scene=scene, summary=summary_dict)
case _:
raise Exception('platfrom is invalid')
raise Exception('platfrom is invalid')

def collectAll(self):
try:
Expand All @@ -530,7 +547,7 @@ def collectAll(self):
pool.apply_async(Scrcpy.start_record, (self.deviceId))
pool.close()
pool.join()
self.setPerfs()
self.setPerfs()
except KeyboardInterrupt:
Scrcpy.stop_record()
self.setPerfs()
Expand Down
88 changes: 77 additions & 11 deletions solox/public/fps.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def __init__(self, device, frequency, package_name, fps_queue, jank_threshold, s
self.device = device
self.frequency = frequency
self.package_name = package_name
self.jank_threshold = jank_threshold / 1000.0
self.jank_threshold = jank_threshold / 1000.0
self.use_legacy_method = use_legacy
self.surface_before = 0
self.last_timestamp = 0
Expand All @@ -30,6 +30,11 @@ def __init__(self, device, frequency, package_name, fps_queue, jank_threshold, s
self.surfaceview = surfaceview
self.fps_queue = fps_queue

self.persistent_mode = True
self.last_timestamp_calc = 0
self.last_calc_result = (0, 0)
self.timestamps_calc_count = 0

def start(self, start_time):
if not self.use_legacy_method:
try:
Expand All @@ -56,22 +61,43 @@ def stop(self):
self.collector_thread = None
if self.fps_queue:
self.fps_queue.task_done()


def get_surfaceview(self):
activity_name = ''
activity_line = ''
try:
dumpsys_result = adb.shell(cmd='dumpsys SurfaceFlinger --list | {} {}'.format(d.filterType(), self.package_name), deviceId=self.device)
dumpsys_result_list = dumpsys_result.split('\n')
for line in dumpsys_result_list:
if line.startswith('SurfaceView') and line.find(self.package_name) != -1:
activity_line = line.strip()
return activity_line

activity_name = dumpsys_result_list[len(dumpsys_result_list) - 1]
if not activity_name.__contains__(self.package_name):
logger.error('get activity name failed, Please provide SurfaceFlinger --list information to the author')
logger.info('dumpsys SurfaceFlinger --list info: {}'.format(dumpsys_result))
except Exception:
traceback.print_exc()
logger.error('get activity name failed, Please provide SurfaceFlinger --list information to the author')
logger.info('dumpsys SurfaceFlinger --list info: {}'.format(dumpsys_result))
return activity_name

def get_surfaceview_activity(self):
activity_name = ''
activity_line = ''
try:
dumpsys_result = adb.shell(cmd='dumpsys SurfaceFlinger --list | {} {}'.format(d.filterType(), self.package_name), deviceId=self.device)
dumpsys_result_list = dumpsys_result.split('\n')
dumpsys_result_list = dumpsys_result.split('\n')
for line in dumpsys_result_list:
if line.startswith('SurfaceView') and line.find(self.package_name) != -1:
activity_line = line.strip()
break
if activity_line:
if activity_line.find(' ') != -1:
if activity_line.find(' ') != -1:
activity_name = activity_line.split(' ')[2]
else:
activity_name = activity_line.replace('SurfaceView','').replace('[','').replace(']','')
activity_name = activity_line.replace('SurfaceView','').replace('[','').replace(']','')
else:
activity_name = dumpsys_result_list[len(dumpsys_result_list) - 1]
if not activity_name.__contains__(self.package_name):
Expand All @@ -82,10 +108,11 @@ def get_surfaceview_activity(self):
logger.error('get activity name failed, Please provide SurfaceFlinger --list information to the author')
logger.info('dumpsys SurfaceFlinger --list info: {}'.format(dumpsys_result))
return activity_name

def get_focus_activity(self):
activity_name = ''
activity_line = ''
activity_line_split = []
dumpsys_result = adb.shell(cmd='dumpsys window windows', deviceId=self.device)
dumpsys_result_list = dumpsys_result.split('\n')
for line in dumpsys_result_list:
Expand All @@ -94,14 +121,15 @@ def get_focus_activity(self):
if activity_line:
activity_line_split = activity_line.split(' ')
else:
return activity_name
if activity_name:
return activity_name
if len(activity_line_split) > 1:
if activity_line_split[1] == 'u0':
activity_name = activity_line_split[2].rstrip('}')
else:
activity_name = activity_line_split[1]
if not activity_name:
activity_name = self.get_surfaceview_activity()
activity_name = self.get_surfaceview_activity()
return activity_name

def get_foreground_process(self):
Expand Down Expand Up @@ -181,6 +209,29 @@ def _calculate_jankey_new(self, timestamps):
jank = jank + 1
return jank

def _calculate_results_persistent(self, refresh_period, timestamps):
if len(timestamps) == 0:
return 0, 0
if self.last_timestamp_calc == 0:
self.last_timestamp_calc = timestamps[0][1]
self.timestamps_calc_count = len(timestamps)
return

ts = timestamps[-1][1] # s
self.timestamps_calc_count += len(timestamps)

# logger.debug('elapse: %.2f frams: %d'% (ts - self.last_timestamp_calc, self.timestamps_calc_count))
if ts - self.last_timestamp_calc < 1.0:
return self.last_calc_result

fps = 1.0 * self.timestamps_calc_count / (ts - self.last_timestamp_calc)
jank = self._calculate_janky(timestamps)
self.last_timestamp_calc = ts
self.timestamps_calc_count = 0
self.last_calc_result = (fps, jank)
return self.last_calc_result


def _calculate_janky(self, timestamps):
tempstamp = 0
jank = 0
Expand Down Expand Up @@ -219,7 +270,10 @@ def _calculator_thread(self, start_time):
timestamps = data[1]
collect_time = data[2]
# fps,jank = self._calculate_results(refresh_period, timestamps)
fps, jank = self._calculate_results_new(refresh_period, timestamps)
if self.persistent_mode:
fps, jank = self._calculate_results_persistent(refresh_period, timestamps)
else:
fps, jank = self._calculate_results_new(refresh_period, timestamps)
# logger.debug('FPS:%2s Jank:%s'%(fps,jank))
collect_fps = fps
collect_jank = jank
Expand Down Expand Up @@ -375,6 +429,10 @@ def _get_surfaceflinger_frame_data(self):
fields = []
fields = line.split(",")
if fields and '0' == fields[0]:
# https://www.cnblogs.com/zhengna/p/10032078.html
# 1 INTENDED_VSYNC
# 2 VSYNC
# 13 FRAME_COMPLETED
timestamp = [int(fields[1]), int(fields[2]), int(fields[13])]
if timestamp[1] == pending_fence_timestamp:
continue
Expand All @@ -383,9 +441,10 @@ def _get_surfaceflinger_frame_data(self):
if 2 == PROFILEDATA_line:
break
else:
self.focus_window = self.get_surfaceview_activity()
# self.focus_window = self.get_surfaceview_activity()
self.focus_window = self.get_surfaceview()
results = adb.shell(
cmd='dumpsys SurfaceFlinger --latency %s' % self.focus_window, deviceId=self.device)
cmd='dumpsys SurfaceFlinger --latency \\"%s\\"' % self.focus_window, deviceId=self.device)
results = results.replace("\r\n", "\n").splitlines()
if not len(results):
return (None, None)
Expand Down Expand Up @@ -429,6 +488,8 @@ def _get_surface_stats_legacy(self):
return None
match = re.search('^Result: Parcel\((\w+)', ret)
if match:
if match.group().find('Error') != -1:
return None
cur_surface = int(match.group(1), 16)
return {'page_flip_count': cur_surface, 'timestamp': timestamp}
return None
Expand Down Expand Up @@ -496,3 +557,8 @@ def parse(self, file_path):

def get_fps_collector(self):
return self.fpscollector

def get_fps(self):
global collect_fps
global collect_jank
return collect_fps, collect_jank
Loading