Android性能测试之fps获取

Posted Q博士

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android性能测试之fps获取相关的知识,希望对你有一定的参考价值。

关键点


testerhome看到一个好的帖子,说的是fps的获取方式,值得好好研究一下。

获取的方式是通过下面的命令获取


adb shell dumpsys SurfaceFlinger --latency <window_activity>

命令意义


上面的命令是做什么的?

可以看看老罗的关于SurfaceFlinger的详细讲解,那我这里只是简单的描述一下:

SurfaceFlinger是一个系统服务,管理android帧缓冲区,了解这些就足够啦,因为我们要获得的FPS值(Frames Per Second)中文翻译过来是每秒钟填充图像的帧数。

ok,那我们知道了这个服务的作用。


命令的结果


我们来看一下这个命令的结果,取android系统的主界面的帧数据


qianhuis-Mac-mini:app qianhui$ adb shell dumpsys SurfaceFlinger --latency com.android.launcher/com.android.launcher2.Launcher
16666666
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
0       0       0
53476438728     53483331194     53476438728
53774334579     53783331182     53774334579
53804473320     53833331180     53804473320
53821433876     53849997846     53821433876
54482172942     54499997820     54482172942
62828275267     62849997486     62828275267
77744212604     77749996890     77744212604
137676463526    137683327826    137676463526
197665365491    197683325426    197665365491
257656215141    257666656360    257656215141
317667889815    317666653960    317667889815
377658368227    377666651560    377658368227
437659404105    437666649160    437659404105
497680028798    497683313426    497680028798
557661828695    557666644360    557661828695
617669142813    617683308626    617669142813
677664261743    677683306226    677664261743
737664607859    737683303826    737664607859
797520577853    797549968098    797520577853
797663672552    797683301426    797663672552
813703318654    813749967450    813703318654
857696035562    857716632358    857696035562
917697480455    917716629958    917697480455
977667175775    977666627560    977667175775
1037666198546   1037666625160   1037666198546
1097679780732   1097699956092   1097679780732
1157680091691   1157699953692   1157680091691
1217681064721   1217699951292   1217681064721
1277681725100   1277699948892   1277681725100
1337665343758   1337683279826   1337665343758
1397664084052   1397683277426   1397664084052
1457665440087   1457683275026   1457665440087
1517669937332   1517666605960   1517669937332
1524620120922   1524649939014   1524620120922
1549500333658   1549533271352   1549500333658
1577676396539   1577699936892   1577676396539
1637681916267   1637699934492   1637681916267
1697678605745   1697699932092   1697678605745
1757681427632   1757699929692   1757681427632
1817680212373   1817699927292   1817680212373
1877681586834   1877699924892   1877681586834
1937702217412   1937733255824   1937702217412
1997665879256   1997683253426   1997665879256
2057664998469   2057683251026   2057664998469
2117667796048   2117683248626   2117667796048
2177667609969   2177683246226   2177667609969
2237666557333   2237666577160   2237666557333
2260655440820   2260683242906   2260655440820
2295708486334   2295733241504   2295708486334
2297667588171   2297666574760   2297667588171
2357668458964   2357666572360   2357668458964
2417677948705   2417699903292   2417677948705
2477680415203   2477699900892   2477680415203
2537681084888   2537699898492   2537681084888
2597682623610   2597699896092   2597682623610
2657662892357   2657683227026   2657662892357
2717663341559   2717683224626   2717663341559
2777684593156   2777683222226   2777684593156
2837677400623   2837699886492   2837677400623
2897718308856   2897733217424   2897718308856
2957669662475   2957683215026   2957669662475
3007072891033   3007099879716   3007072891033
3017678196794   3017699879292   3017678196794
3077679633292   3077683210226   3077679633292
3137681037968   3137699874492   3137681037968
3169623894137   3169666539880   3169623894137
3197683176766   3197699872092   3197683176766
3257684223564   3257699869692   3257684223564
3317680588767   3317733200624   3317680588767
3377665920385   3377683198226   3377665920385
3437676819013   3437683195826   3437676819013
3497666530549   3497683193426   3497666530549
3557665435190   3557666524360   3557665435190
3617697519980   3617716521958   3617697519980
3677680073314   3677699852892   3677680073314
3737679371848   3737699850492   3737679371848
3797700730719   3797733181424   3797700730719
3857682152646   3857699845692   3857682152646
3881320403971   3881349844746   3881320403971
3917683549768   3917699843292   3917683549768

是不是有点晕,这些数字都是啥跟啥啊。其实testerhome上面的那篇文章提供了获取fps方法具体解释


用到了一个第三方的库:pylib

# adb shell dumpsys SurfaceFlinger --latency <window name>
    # prints some information about the last 128 frames displayed in
    # that window.只打印128行的帧数据
    # The data returned looks like this:
    # 16954612
    # 7657467895508   7657482691352   7657493499756
    # 7657484466553   7657499645964   7657511077881
    # 7657500793457   7657516600576   7657527404785
    # (...)
    #
    # The first line is the refresh period (here 16.95 ms), it is followed
    # by 128 lines w/ 3 timestamps in nanosecond each:
    # A) when the app started to draw
    # B) the vsync immediately preceding SF submitting the frame to the h/w
    # C) timestamp immediately after SF submitted that frame to the h/w
    #
    # The difference between the 1st and 3rd timestamp is the frame-latency.
    # An interesting data is when the frame latency crosses a refresh period
    # boundary, this can be calculated this way:
    #
    # ceil((C - A) / refresh-period)
    #
    # (each time the number above changes, we have a "jank").
    # If this happens a lot during an animation, the animation appears
    # janky, even if it runs at 60 fps in average.
    #
    # We use the special "SurfaceView" window name because the statistics for
    # the activity's main window are not updated when the main web content is
    # composited into a SurfaceView.

上面的解释信息有如下主要信息:

数据的单位是纳秒,时间是以开机时间为起始点。

每一次的命令都会得到128行的帧相关的数据。


第一行数据,表示刷新的时间间隔refresh_period,我的机器打印出来的间隔期是:


16666666/1000/1000 = 16.67ms(毫秒)

那么剩下来127行的数据分为3部分,每一行的数据的每一列都代表一部分。


第一部分


这一部分的数据表示应用程序绘制图像的时间点。


第二部分


在SF(软件)将帧提交给H/W(硬件)绘制之前的垂直同步时间


第三部分


在SF将帧提交给H/W的时间点,算是H/W接受完SF发来数据的时间点,绘制完成的时间点。


那么可以看出第一部分和第三部分类似,那么差异在于哪里?差异在于帧有延迟时间,从准备好绘制完成绘制的时间间隔就是帧延迟。

何为jank,即掉帧 。每一行都可以通过下面的公式得到一个值,该值是一个标准,我们称为jankflag,如果当前行的jankflag与上一行的jankflag发生改变,那么就叫掉帧。


ceil((C - A) / refresh-period)

所以掉帧是一个状态。


FPS计算



那么我们要用到上面的哪些数据了?那么我们去库里面一步一步去按照方法来找到最后的答案,首先它是调用了下面方法:

collector = surface_stats_collector.SurfaceStatsCollector(adb, activityName,0.5)
results = collector.SampleResults()


 

那么我们首先找到SurfaceStatsCollector这个类,在pylib库中的perl包下

SampleResults方法:

def SampleResults(self):
self._StorePerfResults()
results = self.GetResults()
self._results = []
return results

首先调用了 _StorePerfResults方法




从上面看出计算方法有两种方式,一个是支持legacy方法,一个不支持legacy方法。


第一种情况


支持legacy方法。判断是否支持legacy方法,可以通过执行dumpsys SurfaceFlinger --latency-clear SurfaceView来判断。



然后我们进入


def _GetSurfaceStatsLegacy(self):
"""Legacy method (before JellyBean), returns the current Surface index
and timestamp.
Calculate FPS by measuring the difference of Surface index returned by
SurfaceFlinger in a period of time.
Returns:
Dict of page_flip_count (or 0 if there was an error), timestamp.
"""
results = self._adb.RunShellCommand('service call SurfaceFlinger 1013')
assert len(results) == 1
match = re.search('^Result: Parcel\\((\\w+)', results[0])
cur_surface = 0
if match:
try:
cur_surface = int(match.group(1), 16)
except Exception:
logging.error('Failed to parse current surface from ' + match.group(1))
else:
logging.warning('Failed to call SurfaceFlinger surface ' + results[0])
return 
'page_flip_count': cur_surface,
'timestamp': datetime.datetime.now(),

这个_GetSurfaceStatsLegacy方法会调用"service call SurfaceFlinger 1013"这个命令,结果是如下形式:


10|root@generic_x86:/ # service call SurfaceFlinger 1013                       
Result: Parcel(00000b5e    '^...')

然后我们会提取里面的00000b5e这个16进制的数,然后通过int('str',16)方法将16进制数转化为10进制的数,这个值就是当前surface的索引值。可以通过2个不同索引值的surface之间的间隔时间获得。然后返回一个字典,里面包含了2个内容:当前surface索引,以及当前时间戳。这个方法就结束了。该字典将赋值给surface_after。我们就回到了_StorePerfResults方法中,看下一行要执行的代码:


td = surface_after['timestamp'] - self._surface_before['timestamp']
seconds =td.seconds +td.microseconds/1e6
frame_count = (surface_after['page_flip_count'] -self._surface_before['page_flip_count']

上面的代码,是将这次的时间-上次的时间,得到的值就是2次获取surface索引的时间间隔,赋值给td,然后取得td的秒数,但是精确到微妙级别。赋值给seconds。

然后计算2次surface索引值的差值,得到的值就是在seconds时间内产生surface的个数。赋值给frame_count。

然后用frame_count/seconds公式计算,做4舍5入,最后转化为整形,追加到results数组中,该方法就返回了。就到了SampleResults方法中:


results = self.GetResults()
self._results = []

由于上面的第二行可以看出来,每次的results的数组都会重新设置为空,那么当前results的值就只有一个值,就是我们这次所获得值,所以返回的results数组里就只有一个数值。就是我们的fps的值,然后至于你获得多少次这样的值,以及间隔时间,那是你自己决定的事啦。


what?


有人看完上面的内容,有蒙的感觉么?其实我也蒙了,我们说了那么多的dumpsys SurfaceFlinger --latency,但是经过我们一分析,居然真正的计算是没有用到这个里面的数据的,是不是很奔溃。但是要想到的一点是,在调用collector.SampleResults()方法前,是需要启动_CollectorThread(self)的,尝试执行dumpsys命令3次获得需要的值。该线程中的就是调用了

_GetSurfaceFlingerFrameData()里的方法,具体实现细节如下代码所示。


def _CollectorThread(self):
last_timestamp = 0
timestamps = []
retries = 0
while not self._stop_event.is_set():
self._get_data_event.wait(1)
try:
refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData()
if refresh_period is None or timestamps is None:
retries += 1
if retries < 3:
continue
if last_timestamp:
# Some data has already been collected, but either the app
# was closed or there's no new data. Signal the main thread and
# wait.
self._data_queue.put((None, None))
self._stop_event.wait()
break
raise Exception('Unable to get surface flinger latency data')
timestamps += [timestamp for timestamp in new_timestamps
if timestamp > last_timestamp]
if len(timestamps):
last_timestamp = timestamps[-1]
if self._get_data_event.is_set():
self._get_data_event.clear()
self._data_queue.put((refresh_period, timestamps))
timestamps = []
except Exception as e:
# On any error, before aborting, put the exception into _data_queue to
# prevent the main thread from waiting at _data_queue.get() infinitely.
self._data_queue.put(e)
raise


_GetSurfaceFlingerFrameData方法实现细节如下:


def _GetSurfaceFlingerFrameData(self):

results = self._adb.RunShellCommand(
'dumpsys SurfaceFlinger --latency SurfaceView',
log_result=logging.getLogger().isEnabledFor(logging.DEBUG))
if not len(results):
return (None, None)
timestamps = []
nanoseconds_per_second = 1e9
refresh_period = long(results[0]) / nanoseconds_per_second

pending_fence_timestamp = (1 << 63) - 1
for line in results[1:]:
fields = line.split()
if len(fields) != 3:
continue
timestamp = long(fields[1])
if timestamp == pending_fence_timestamp:
continue
timestamp /= nanoseconds_per_second
timestamps.append(timestamp)
return (refresh_period, timestamps)

上面的具体执行流程,是先运行dumpsys SurfaceFlinger --latency 命令,从得到的128行数据中,提取第一行的刷新时间,然后得到每一行的中第二部分数据,获取其中秒数,保存到timestamps数组中。然后方法返回包含刷新时间refresh_period和timestamps的数组。然后回到线程的主方法中。


1.判断获取到的数据为NONE


如果尝试的次数没到3次,再执行一遍dumpsys命令。

如果超过了3次,我们要判断上一次获取的timestamps数组中最后一个值是否为0。因为last_timestamp的赋值语句如下:


last_timestamp = timestamps[-1]

如果不为0,说明我们已经收集过了,直接在队列中加上NONE值,让线程等待,跳出循环了。否则抛出异常


2.不为NONE


如果为0,说明我们fps的数据还没有开始收集,我们会将_GetSurfaceFlingerFrameData方法返回的数组中元素一个一个赋值到我们本地数组中。然后给last_timestamp赋值。

然后我们会向_data_queue队列中添加refresh_period, timestamps数据。


总结


从上面的分析可知,要想获得获取fps值,需要3步:

1.adb shell dumpsys SurfaceFlinger --latency命令产生fps数据

2.通过service call SurfaceFlinger 1013 来得到当前帧的索引以及时间戳,设置为A = indexA,timeA

3.公式:

设上一次的数据为B = indexB,timeB

FPS = (indexA-indexB)/(timeA-timeB)


第二种情况


当--latency-clear不能使用,也就是`service call SurfaceFlinger 1013`命令不能使用,那自然上面的方法就不起作用了。这个时候我们就要从下面的代码进行分析了:


# Non-legacy method.
assert self._collector_thread
(refresh_period, timestamps) = self._GetDataFromThread()
if not refresh_period or not len(timestamps) >= 3:
if self._warn_about_empty_data:
logging.warning('Surface stat data is empty')
return
self._results.append(SurfaceStatsCollector.Result(
'refresh_period', refresh_period, 'seconds'))
self._results += self._CalculateResults(refresh_period, timestamps, '')
self._results += self._CalculateBuckets(refresh_period, timestamps)

首先我们从线程中获得dumpsys命令得到的值,然后创建Result对象,该对象中含有属性名和属性值,以及单位。


然后我们要进入_CalculateResults_CalculateBuckets方法。


_CalculateResults



上面的方法中,首先得到帧的数量frame_count,然后得到产生frame_count所用的时间seconds。然后调用_GetNormalizedDeltas方法


上面巧妙的使用zip来计算各个数据之间的差值。由于_MIN_NORMALIZED_FRAME_LENGTH =0.5,所以要执行后续的语句,filter函数中,从deltas数组中取出元素除以refresh_period,判断杯除后的值是否大于0.5,这个函数作用过滤掉被除后小于0.5的值。那么我们最终返回的值就是这个数组deltas,以及数组中每个元素除以refresh_period后的生成的新的数组。然后回到_CalculateResults方法中,差值数组赋值给frame_lengths,新的数组赋值给normalized_frame_lengths。然后对frame_lengths的个数进行判断。然后我们再调用一次_GetNormalizedDeltas,这个时候传入的min_normalized_delta是空的,所以不会执行filter函数。直接求出frame_lengths数组中各个元素的差值保存到数组deltas中。然后再计算deltas的值与refresh_period比值,这是为了求jank(掉帧)。然后方法返回,将deltas值赋给length_changes,将比值赋给normalized_changes。

为了求出jank,我们需要求出normalized_changes数组中比0大的数。下面的代码就是求出jank_count代码块。


jankiness = [max(0, round(change)) for change in normalized_changes]
pause_threshold = 20
jank_count = sum(1 for change in jankiness
if change > 0 and change < pause_threshold)

jank_count初始值设为,然后遍历jankiness,求出大于0,小于20的数的个数。这个值就是jank的值,掉帧值。而fps的值是通过下面公式得到的:


int(round((frame_count - 1) / seconds))

这样我们就得到了fps和jank,然后fps-jank 就是我们要得到的数。


_CalculateBuckets




这个地方是帮助你去头去尾后的数据。


总结


当无法使用--latency--clear方法的时候,我们需要计算fps和jank的值,

fps的值计算公司变为int(round((frame_count-1)/ seconds)),

而且还可以得到吊帧的个数


感谢



testerhome_小A帮助我分析python相关源码

kasi前辈的补充



其他计算fps的方法


android开发中计算

百度经验

http://wuche.info/android-fps/



以上是关于Android性能测试之fps获取的主要内容,如果未能解决你的问题,请参考以下文章

Android性能测试(内存、cpu、fps、流量、GPU、电量)——adb篇

Android远程桌面助手之性能监测篇

性能测试 Android APP 帧数FPS实战

Android性能专项FPS测试实践

Android性能指标FPS获取的JAVA实现

Android下获取FPS的几种方法