在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
用到了一个第三方的库: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)
那么我们要用到上面的哪些数据了?那么我们去库里面一步一步去按照方法来找到最后的答案,首先它是调用了下面方法:
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
从上面看出计算方法有两种方式,一个是支持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(), }
10|root@generic_x86:/ # service call SurfaceFlinger 1013 Result: Parcel(00000b5e '^...')
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索引值的差值,得到的值就是在seconds时间内产生surface的个数。赋值给frame_count。
然后用frame_count/seconds公式计算,做4舍5入,最后转化为整形,追加到results数组中,该方法就返回了。就到了SampleResults方法中:
results = self.GetResults() self._results = []
有人看完上面的内容,有蒙的感觉么?其实我也蒙了,我们说了那么多的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)
如果尝试的次数没到3次,再执行一遍dumpsys命令。
如果超过了3次,我们要判断上一次获取的timestamps数组中最后一个值是否为0。因为last_timestamp的赋值语句如下:
last_timestamp = timestamps[-1]
如果为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方法。
上面的方法中,首先得到帧的数量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)
int(round((frame_count - 1) / seconds))
这样我们就得到了fps和jank,然后fps-jank 就是我们要得到的数。
这个地方是帮助你去头去尾后的数据。
当无法使用--latency--clear方法的时候,我们需要计算fps和jank的值,
fps的值计算公司变为int(round((frame_count-1)/ seconds)),
而且还可以得到吊帧的个数
testerhome_小A帮助我分析python相关源码
kasi前辈的补充
android开发中计算
百度经验
http://wuche.info/android-fps/