(點擊上方公眾號,可快速關注)
作者:淘寶前端團隊(FED)- 冬萌
網址:http://taobaofed.org/blog/2016/01/13/measuring-fps/
時間回到幾周前,這天,女神突然來找我,「我這裡有幾個頁面想測量下頁面滾動的順暢性,你有啥辦法不?」。Are you kidding me?這麼簡單,簡直是道送分題啊,於是當著女神面,打開 Chrome 開發者工具,勾選上 Show FPS meter,醒目的 FPS 監控面板就出來了,滑動頁面時 FPS 的曲線就基本反映出了頁面滾動的順暢性,坐等女神的誇獎~~
「這個我知道啊,但是我有好多頁面,難道要一個一個人工看嗎?而且這個也沒有一個記錄的導出,如果能有個方法幫我自動的測量,有問題再通知我,我再仔細排查就好了」。
這個需求開始有點技術含量了,不過應該也難不倒我,頁面都接入了 UITest,在頁面做 UI 測試的時候,跑一下測量 FPS 的測試用例就 ok 了,那如何測量呢?女神等我~~
mozPaintCount
mozPaintCount 變量是 Mozilla 提供的方法,其返回的是當前文檔 paint 到屏幕上的數量,通過計算單位時間 paint 數量變化,即可計算出頁面的 FPS,so easy。
等等,這個變量目前好像只有 Firefox 支持,Chrome 上並沒有一個 webkitPaintCount 或者 paintCount 變量,而我們的 UITest 是跑在 ChromeDriver 或者 PhantomJS 上,並沒有 Firefox 環境,好吧,這個可以做備選方案,依賴於 UITest 支持 Firefox 環境。為了完成女神的需求,我們還要考慮其他方案了。
requestAnimationFrame
在頁面重繪前,瀏覽器會執行傳入 requestAnimationFrame 的入參函數,一般多用來實現連貫的逐幀動畫。那我們基於 requestAnimationFrame 不就可以獲得頁面的繪製頻率,計算出 FPS,而且瀏覽器支持情況也不錯,說幹就幹,示例代碼如下(簡單示例,沒做兼容等處理):
var lastTime = performance.now();
var frame = 0;
var lastFameTime = performance.now();
var loop = function(time) {
var now = performance.now();
var fs = (now - lastFameTime);
lastFameTime = now;
var fps = Math.round(1000/fs);
frame++;
if (now > 1000 + lastTime) {
var fps = Math.round( ( frame * 1000 ) / ( now - lastTime ) );
frame = 0;
lastTime = now;
};
window.requestAnimFrame(loop);
}
用例結果如下:
大功告成,可以去找女神答覆了,等等,這樣是不是太簡單了點,無法讓女神刮目相看啊(在女神面前裝逼),我們是不是再深入點。
Chrome 瀏覽器渲染頁面時,涉及了兩個線程,Render 主線程和 Compositor 合成線程,且兩個線程通過名為 Commit 的消息來保持同步,而每一幀消耗時間應該是包含兩部分,Render 主線程消耗的時間和 Compositor 線程消耗的時間。典型的狀態如下(以下三張圖片來自 frame-timing-polyfill):
主線程 Commit 消息提交給合成線程,任務都在 16.66ms 內完成。當然,對於一些輸入事件,比如滾動,是先轉移給合成線程進行處理,然後通知給主線程,這樣可以保證對用戶的輸入操作做及時的響應,同時,對於一些頁面更新,如 CSS 動畫和 CSS 濾鏡,只需合成線程處理,而無需請求主線程,如下所示:
當然,也有可能,主線程處理耗時較多,導致提交給合成線程的時間推遲到了下一幀,如下所示:
針對以上三種情況,那流暢性如何定義呢?就需要因地制宜,不同環境分別分析了。
那是否有方法可以讓我們分別取到 Render 主線程和 Compositor 合成線程的數據呢?答案是 Frame Timing。
Frame Timing
Frame Timing API 目前還只是草案,暫時還沒發現有瀏覽器支持,不過我們可以先實現,萬一瀏覽器支持了呢~~,目前的 API 如下:
var rendererEvents = window.performance.getEntriesByType("renderer");
var compositeEvents = window.performance.getEntriesByType("composite");
獲取 Render 主線程和合成線程的記錄,每條記錄包含的信息基本如下:
{
sourceFrameNumber: 120,
startTime: 1342.549374253,
duration: 10.654313323
}
每個記錄都包括唯一的 Frame Number、Frame 開始時間以及持續時間。根據 duration 就可以知道該幀是否達到 16.66ms 的標準,同時根據單位時間記錄數(Frame)的個數就能算出主線程或者合成線程每秒的幀率。
同時,對於主線程 Commit 給合成線程繪製的情況,可以根據唯一的 sourceFrameNumber 將 renderEvents 的記錄和 compositeEvents 的記錄做關聯,得出每個主線程 Commit 所對應的合成線程繪製的次數,如前所說,這也是判斷 JS 動畫流程性的一個可檢測指標~~(具體實現代碼較多且比較簡單,就不貼了,小夥伴們動動腦筋,分分鐘就寫出來了~)
至此,終於可以向女神交差了,想想女神崇拜的目光,還有點小激動呢~~
再等等,既然 Chrome 能打開 FPS meter,而且我們的 UI 測試也是跑在 ChromDriver 中的,那是不是可以通過配置打開 Chrome 的 FPS meter 獲取到 FPS 呢,女神,再等我下~
Show FPS Counter or Performance Log
果不其然,查詢 ChromeDriver 的配置設置
–show-fps-counter: Draws a heads-up-display showing Frames Per Second as well as GPU memory usage. If you also use –vmodule=」head*=1」 then FPS will also be output to the console log.
只要能輸出到 console log 裡,我們就能方便的取到了,於是果斷在 UITest 的 ChromeDriver 配置裡加上裡這兩項:
chromeOptions["args"] = [
// 其他配置省略
'--show-fps-counter',
'--vmodule="head*=1"'
];
滿心歡喜的一試,FPS meter 是出來的,但是說好的 console log 並沒有,具體的討論可以參見這個 issue。
難道沒有別的辦法了嗎?既然 Chrome 的 Timeline 那麼強大,其中也包含了每一幀的耗時,那是不是可以取到 Timeline 的數據?當然沒問題,我們使用 selenium-webdriver就能方便的獲取到頁面的 Performance Log,示例如下所示:
var webdriver = require('selenium-webdriver');
var chrome = require('selenium-webdriver/chrome');
// ...
// 配置需要跟蹤記錄的數據
var options = new chrome.Options();
var traceCategories = [
'blink.console',
'devtools.timeline',
'toplevel',
'disabled-by-default-devtools.timeline',
'disabled-by-default-d.evtools.timeline.frame'
];
options.setLoggingPrefs({ performance: 'ALL' });
options.setPerfLoggingPrefs({
'traceCategories': traceCategories.join(',')
});
// ...
// 傳入 chromedriver 實例
function getTrace(browser) {
return (new webdriver.WebDriver.Logs(browser))
.get('performance')
.then(function(logs) {
// performance log
});
}
其中,traceCategories 的配置和 chrome://tracing/ 的配置一樣,打開 Chrome,地址欄輸入 chrome://tracing/,點擊 record 按鈕,會出現如下所示的配置項:
其中的配置就是我們可以獲取的。獲取到 Performance Log 後導出成 JSON 文件,導入到 Chrome 的 Timeline 裡,你會驚奇的發現,這和直接用 Timeline 效果是一樣一樣的,如下所示:
但是接下來,我發現真正頭疼的問題來了,Performance Log 是一堆密密麻麻的數據,而且還沒找到相關文檔,目前只是可以取出平均的 FPS,計算方法如下,首先解析數據,取出類型為 DrawFrame 的記錄個數,然後除以整個統計的持續時間,即可大體得出整體平均的 FPS,如何解析出更具體的數據,還在持續研究中,希望這下可以讓女神滿意,嘿嘿嘿~
參考文檔
【今日微信公號推薦↓】