1. 我們是如何在CI流水線統(tǒng)計web前端FPS的?

        共 7394字,需瀏覽 15分鐘

         ·

        2021-06-11 03:44

        1. 背景

        1.1 FPS 統(tǒng)計意義

        FPS(幀率)是圖像領域中的定義,是指畫面每秒渲染幀數(shù),F(xiàn)PS 一般在 0-60 之間,低于 30 時人眼能明顯感覺到卡頓。頁面交互過程中頁面展示是否流暢,頁面中的動畫是否存在卡頓等,都需要通過 FPS 的統(tǒng)計指標作為頁面性能的參考依據(jù)。


        1.2 現(xiàn)有 web 前端 FPS 統(tǒng)計方式

        1.2.1 Chrome devtools

        如下圖,通過 Chrome devtools 右側(cè)菜單 -> more tools -> Rendering -> 勾選 Frame Rendering Stats,則會在頁面左上角顯示實時 Frame Rate(FPS)和 GPU 內(nèi)存使用情況的小窗。


        缺點 :生產(chǎn)環(huán)境數(shù)據(jù)無法收集上報,需要人工實時觀測;比較適合在開發(fā)階段進行自測

        1.2.2 requestAnimationFrame API

        window.requestAnimationFrame() 告訴瀏覽器你希望執(zhí)行一個動畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動畫。該方法需要傳入一個回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會在瀏覽器下一次重繪之前執(zhí)行回調(diào)?;卣{(diào)函數(shù)執(zhí)行次數(shù)通常與瀏覽器屏幕刷新次數(shù)相匹配,一般是每秒 60 次。

        那么正好可以利用 requestAnimationFrame API 的特性來計算統(tǒng)計 FPS ,原理如下:

        假設動畫在時間 A 開始執(zhí)行,在時間 B 結(jié)束,耗時 (B-A) s,這期間 requestAnimationFrame 一共執(zhí)行了 n 次,則此段動畫的 FPS = n / (B-A)。

        requestAnimationFrame 在不掉幀的情況下一秒內(nèi)會執(zhí)行 60 次,即 FPS = 60 / 1。

        統(tǒng)計 FPS 核心代碼如下:

        let lastTime = performance.now();let frames = 0;
        const loop = () => { const currentTime = performance.now(); frames += 1; if (currentTime > 1000 + lastTime) { fps = Math.round((frames * 1000) / (currentTime - lastTime)); frames = 0; lastTime = currentTime; console.log(`fps:${fps}`); } window.requestAnimationFrame(loop);}
        loop();

        在生產(chǎn)環(huán)境,只需要通過 requestAnimationFrame 統(tǒng)計出監(jiān)控階段的回調(diào)調(diào)用次數(shù),即可計算出對應 FPS,對 FPS 也比較方便進行收集和上報,是目前使用最多的 FPS 統(tǒng)計方式。

        缺點:

        1. 對業(yè)務代碼 侵入性較強 ,需要引入腳本且實現(xiàn)代碼指定統(tǒng)計階段

        2. 統(tǒng)計的 FPS** 結(jié)果不夠準確**,因為它是將每兩次主線程執(zhí)行的時間間隔當成一幀,而非主線程加合成線程所消耗的時間為一幀。js 執(zhí)行屬于主線程,主線程很容易遭到阻塞(例如:js 執(zhí)行耗時較長),而此時合成器線程基本上是空閑的,合成器能夠自己運行某些動畫(合成滾動和加速 CSS 動畫),它可以在不等待 JS 的情況下運行這些動畫。例如這個 demo 頁面:https://xdevilj136.github.io//main-thread-block.html,主線程被 js 執(zhí)行完全阻塞,requestAnimationFrame 無法正常統(tǒng)計 FPS,這種情況下實際頁面還是可以正常滾動的。

        1.3 痛點

        現(xiàn)有的前端 FPS 統(tǒng)計方式存在一些痛點,解決痛點希望滿足以下方面:

        1. 不侵入業(yè)務代碼,對 web 頁面進行 FPS 統(tǒng)計

        2. 具有一定的通用性,適用于前端大部分動畫、交互場景

        3. 統(tǒng)計 FPS 結(jié)果數(shù)據(jù)相對準確

        4. 可以在 CI 階段進行 FPS 統(tǒng)計,生成性能報告

        目前 alloyperf 的 FPS 統(tǒng)計工具模塊,已經(jīng)實現(xiàn)并滿足以上要求,在 CI 流水線定時統(tǒng)計騰訊文檔頁面 FPS 數(shù)據(jù)并定時生成性能報告。后面章節(jié),將介紹 alloyperf FPS 統(tǒng)計的實現(xiàn)原理。

        2. alloyperf FPS 統(tǒng)計工具介紹

        2.1 alloyperf FPS 統(tǒng)計工具

        alloyperf FPS 統(tǒng)計工具實現(xiàn)主要利用 Selenium WebDriver 和 chrominum:

        • Selenium WebDriver 驅(qū)動 chrome 瀏覽器打開測試頁面,并通過 API 模擬頁面交互操作,以測試頁面不同的交互場景;

        • chromnium 內(nèi)部的 Chrome tracing,記錄了 chrome 瀏覽器打開、展示頁面整個過程中各個進程不同階段的 tracing 記錄,通過獲取并分析 Chrome tracing 的記錄 logs, 即可計算統(tǒng)計出頁面對應測試階段的 FPS 指標。

        2.2 Selenium WebDriver 介紹

        Selenium 是 ThoughtWorks 提供的一個強大的基于瀏覽器的開源自動化測試工具集,Selenium WebDriver 是工具集其中一個子工具,主要用于在各種瀏覽器上自動化測試 web 應用。

        它對瀏覽器提供的原生 API 進行了封裝,使其成為一套更加面向?qū)ο蟮?Selenium WebDriver API,使用這套 API 可以操控瀏覽器的開啟、關閉,打開網(wǎng)頁,操作界面元素,還可以操作瀏覽器 devtools 等,由于使用的原生 API,其速度與穩(wěn)定性都會好很多。

        Selenium WebDriver 通過 JsonWireProtocol 協(xié)議與各瀏覽器的 driver 進行通信(例如:ChromeDriver 即為 Chromium 實現(xiàn)了 JsonWireProtocol 協(xié)議),Selenium 對不同廠商的各個 driver 進行了封裝,如:selenium-chrome-driver、selenium-edge-driver、selenium-firefox-driver 等,可支持各種主流瀏覽器的自動化測試。

        Selenium WebDriver 架構(gòu)如下圖所示:

        2.3 Chrome tracing 介紹

        對于 FPS 的統(tǒng)計,Chrome tracing 是核心也是本文的重點,下面重點介紹。

        2.3.1 Tracing ecosystem

        Tracing ecosystem 即 tracing 的生態(tài)系統(tǒng),tracing 即跟蹤應用運行過程并生成記錄的行為。Tracing ecosystem 的運行基于"trace 文件",trace 文件包含所有的跟蹤記錄數(shù)據(jù),Tracing ecosystem 包含兩種工具:

        1. 記錄并生成 trace 文件的工具

        2. 解析展示 trace 文件的工具

        記錄并生成 trace 文件的工具有很多,比如:Android 的 systrace 命令行工具、開源的 adb_trace 等,web 前端常用的有 chrome devtools 中 performance record 功能、chrome tracing 的 record 功能。

        解析展示 trace 文件的工具,web 前端常用的 chrome devtools performance、chrome tracing 同樣具有這樣的強大能力,chrome tracing 相對展示的信息更加詳細。

        chrome devtools performance 圖示


        chrome tracing 圖示

        2.3.2 Trace viewer

        chrome tracing 是內(nèi)置在 chrome 中的工具,可用來收集和解析展示非常詳細的性能跟蹤數(shù)據(jù),在 devtools 無法滿足需求時,可使用此工具來進行更加復雜或具體的性能分析。

        通過 chrome tracing 的 record 按鈕進行記錄后即可生成對應的跟蹤數(shù)據(jù),chrome tracing 內(nèi)部通過 trace viewer 可直接對產(chǎn)生的數(shù)據(jù)進行解析和展示:

        Trace viewer 結(jié)果展示


        Trace viewer 可以對 record 產(chǎn)生的 trace 數(shù)據(jù)直接進行展示,也可以 load 對應的 trace json 文件并進行解析展示。展示結(jié)果如上圖,時序按從左到右排列,通過左側(cè)的 Processes 和 Threads 進行細分,右側(cè)每一個小色塊對應一個 TRACEEVENT(即 Chromium 內(nèi)部 tracing 庫生成的單個記錄事件點)。

        在 trace viewer 中點選對應的 TRACEEVENT 色塊,甚至可以直接點擊下方的詳情跳轉(zhuǎn)到相關的 Chromnium 源碼:

        跳轉(zhuǎn) Chromium 源碼展示


        Chromnium 通過 TRACE_EVENT0 函數(shù)將對應的 EVENT 記錄到對應的 category,例如上圖將 ProxyImpl::NotifyReadyToCommitOnImpl 記錄到 cc(即 Chrome Compositor 合成器)。

        同時,Trace viewer 結(jié)果展示圖中,還可以通過菜單選擇對應的 flow 展示某個 event 流的軌跡走向,例如單幀在渲染進程中的 flow 大致是經(jīng)歷如下階段:

        1. 輸入事件來自于瀏覽器進程,并被傳遞給合成器線程,對應的 TRACE_EVENT 為 "InputEventFilter::ForwardToHandler"

        2. 輸入事件從合成器線程到主線程,啟動了 Blink 的輸入事件處理

        3. Blink 生成一個新的動畫幀,并在 "WebViewImpl::animate "中調(diào)用 requestAnimationFrame 回調(diào)

        4. 如果在 RAF 回調(diào)或輸入事件處理程序中 JavaScript 修改了頁面,觸發(fā)了一個重新布局,首先是樣式的重新計算,對應于"Document::updateStyle"

        5. Blink 重新繪制覆蓋失效區(qū)域,對應 TRACE_EVENT "Picture::Record",layer 屬性(如 transform、opacity)也在 Blink 的 layer tree 副本中被更新

        6. 通過"ThreadProxy::BeginMainFrame::Commit",新的記錄和更新后的 layer tree 從 Blink 線程傳遞到合成器線程,在這期間主線程被合成器線程阻塞

        7. 之后合成器進行柵格化處理,然后傳遞給瀏覽器合成器并交換幀緩存"DelegatingRenderer:SwapBuffers",最終完成繪制

        所以通過 TRACE_EVENT 的 flow 軌跡,即可以非常精細地看到頁面每一幀的具體渲染流程。

        2.3.3 trace 文件格式

        Trace Viewer 可以識別四種不同格式的 trace 文件,JSON 類型格式包括 JSON 數(shù)組和 JSON 對象,另外兩種是 Linux ftrace 數(shù)據(jù)類型。比較通用的是 JSON 格式,也是 chrome tracing 使用的格式,Linux ftrace 類型本文不做贅述。

        JSON 數(shù)組(chrome devtools performance 生成格式):

        [{"args":{"name":"swapper"},"cat":"__metadata","name":"thread_name","ph":"M","pid":337,"tid":0,"ts":0},{"args":{"name":"CrBrowserMain"},"cat":"__metadata","name":"thread_name","ph":"M","pid":337,"tid":775,"ts":0}]


        JSON 對象(chrome tracing 生成格式):

        {"traceEvents":[ {"args":{"name":"swapper"},"cat":"__metadata","name":"thread_name","ph":"M","pid":337,"tid":0,"ts":0}, {"args":{"name":"Compositor"},"cat":"__metadata","name":"thread_name","ph":"M","pid":7546,"tid":42243,"ts":0}],"displayTimeUnit": "ns","systemTraceEvents": "SystemTraceData","otherData": {"version": "My Application v1.0"  },  "stackFrames": {...}  "samples": [...],}

        兩種格式結(jié)構(gòu)略有不同,但每條 TRACE_EVENT 對應的 args 字段基本一致,本文只需關注:

        • name: TRACE_EVENT 名稱

        • cat: TRACE_EVENT 類別

        • ts: TRACE_EVENT 事件的追蹤時時間戳,以微秒為單位

        通過以上得出結(jié)論:通過 flow 確認每一幀渲染必定經(jīng)過哪些關鍵 TRACE_EVENT ,然后分析對應的 trace 文件,即可計算得到 FPS 數(shù)據(jù)。

        2.4 統(tǒng)計 FPS

        2.4.1 FPS 統(tǒng)計關鍵 Trace Event

        下圖為幀繪制內(nèi)容數(shù)據(jù)的 flow 流向示意圖,與 Chrome tracing 的 flow 軌跡對應:

        幀繪制內(nèi)容數(shù)據(jù)的 flow 流向示意圖

        如圖所示,繪制內(nèi)容的數(shù)據(jù)流向要經(jīng)過幾個不同的進程和線程,不同的線程的任務由 Chromnium 中不同模塊(對應 category)負責,blink 主要負責主線程、cc 主要負責合成器線程、viz 主要負責 gpu 相關。

        在通過 Chrome tracing 跟蹤 flow 和跟蹤 chromnium 相關源碼過程中,主要發(fā)現(xiàn)以下關鍵點:

        1. 主線程很容易遭到阻塞(例如:js 執(zhí)行耗時較長),而此時合成器線程基本上是空閑的,合成器能夠自己運行某些動畫(合成滾動和加速 CSS 動畫),它可以在不等待 JS 的情況下運行這些動畫,所以不能選擇主線程 TRACE_EVENT

        2. 雖然按照 flow 流向,最終走向的 TRACEEVENT 在 gpu 進程,但通過實際測試和 chromnium 源碼的進一步分析,發(fā)現(xiàn) chromnium 在跨平臺處理時針對 linux 在 gpu 進程做了特殊處理,導致 linux 平臺下 data flow 的 TRACEEVENT 不一定在每一幀都確定走到 gpu

        3. Commit 是一種從主線程推送數(shù)據(jù)到合成器線程的方式,并且保證了該過程中的數(shù)據(jù)完整性。Commit 不是通過發(fā)送 ipc,而是通過阻塞主線程并復制數(shù)據(jù)的方式來完成提交。收到主線程請求后的某個時刻,調(diào)度器將調(diào)用 ScheduledActionBeginMainFrame 對請求進行響應,合成器線程會發(fā)送一個 BeginFrameArgs 到主線程啟動 BeginMainFrame。完成此操作后,cc 再進行后續(xù)柵格化等一系列流程。Commit 流程如下圖所示:

        Commit流程

        最終確定每一幀必定走到的 TRACEEVENT 有合成器線程 ScheduledActionBeginMainFrame 階段,因此選取 cat="cc"、name="Scheduler::NotifyBeginMainFrameStarted"的 event 作為 FPS 統(tǒng)計的關鍵 TRACEEVENT。

        2.4.2 統(tǒng)計流程

        確定 FPS 統(tǒng)計關鍵 Trace Event 后,核心問題就得到了解決,計算 FPS 大體流程如下:

        3. 總結(jié)

        針對 1.3 中提到的目前現(xiàn)有 web 前端 FPS 統(tǒng)計方式的痛點,alloyperf fps 模塊都已經(jīng)實現(xiàn)了相應的解決。

        1. 對于測試頁面,只需要提供頁面 url 和簡單配置,不會侵入業(yè)務代碼

        2. 通過 webdriver 模擬頁面交互操作,具有一定的通用性

        3. 通過 Chromnium 底層 TRACE_EVENT 分析統(tǒng)計 FPS,結(jié)果數(shù)據(jù)相對準確

        4. 可以在 CI 流水線引入進行 FPS 統(tǒng)計,生成性能報告

        目前 alloyperf fps 模塊已經(jīng)在騰訊文檔 CI 流水線運行,日常輸出 FPS 性能報告。
        alloyperf 其他模塊(首屏統(tǒng)計、內(nèi)存監(jiān)測等)正在陸續(xù)開發(fā)中,后續(xù) FPS 模塊也將持續(xù)優(yōu)化支持更多平臺和場景的測試,流水線接入更多的應用品類。


        關于AlloyTeam


        AlloyTeam 是國內(nèi)影響力最大的前端團隊之一,核心成員來自前 WebQQ 前端團隊。
        AlloyTeam負責過WebQQ、QQ群、興趣部落、騰訊文檔等大型Web項目,積累了許多豐富寶貴的Web開發(fā)經(jīng)驗。

        這里技術氛圍好,領導nice、錢景好,無論你是身經(jīng)百戰(zhàn)的資深工程師,還是即將從學校步入社會的新人,只要你熱愛挑戰(zhàn),希望前端技術和我們飛速提高,這里將是最適合你的地方。

        加入我們,請將簡歷發(fā)送至 [email protected],或直接在公眾號留言~

        期待您的回復??

        最后

        面試交流群持續(xù)開放,分享了近 許多 個面經(jīng)。
        加我微信: DayDay2021,備注面試,拉你進群。

        我是 小弋,我們下篇見~

        如何編寫Cleaner React代碼

        2021-06-08

        現(xiàn)代React狀態(tài)模式指南

        2021-06-03

        如何使用React創(chuàng)建視頻和動畫

        2021-06-01

        瀏覽 31
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 日本91久久 | 韩国三级自拍 | 操黑丝美女的逼 | 大乔未久av | 午夜精品一区二区三区文 |