1. 程式人生 > >Android效能測試之fps獲取

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|[email protected]_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)),

而且還可以得到吊幀的個數

感謝

kasi前輩的補充

其他計算fps的方法

百度經驗