Skip to content

Commit

Permalink
Merge pull request #222 from huangwei1024/master
Browse files Browse the repository at this point in the history
SurfaceView FPS统计优化
  • Loading branch information
rafa0128 authored Sep 19, 2023
2 parents 0ea7755 + f4a6e0d commit f322da7
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 84 deletions.
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

0 comments on commit f322da7

Please sign in to comment.