关键点
在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的方法
http://wuche.info/android-fps/