<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 7111字,需瀏覽 15分鐘

           ·

          2021-07-01 01:56

          1. 背景

          1.1 FPS 統(tǒng)計(jì)意義

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


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

          1.2.1 Chrome devtools

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


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

          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 的特性來計(jì)算統(tǒng)計(jì) FPS ,原理如下:

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

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

          統(tǒng)計(jì) 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)計(jì)出監(jiān)控階段的回調(diào)調(diào)用次數(shù),即可計(jì)算出對應(yīng) FPS,對 FPS 也比較方便進(jìn)行收集和上報(bào),是目前使用最多的 FPS 統(tǒng)計(jì)方式。

          缺點(diǎn):

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

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

          1.3 痛點(diǎn)

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

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

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

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

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

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

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

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

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

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

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

          2.2 Selenium WebDriver 介紹

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

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

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

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

          2.3 Chrome tracing 介紹

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

          2.3.1 Tracing ecosystem

          Tracing ecosystem 即 tracing 的生態(tài)系統(tǒng),tracing 即跟蹤應(yīng)用運(yùn)行過程并生成記錄的行為。Tracing ecosystem 的運(yùn)行基于"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 同樣具有這樣的強(qiáng)大能力,chrome tracing 相對展示的信息更加詳細(xì)。

          chrome devtools performance 圖示


          chrome tracing 圖示

          2.3.2 Trace viewer

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

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

          Trace viewer 結(jié)果展示


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

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

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


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

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

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

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

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

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

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

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

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

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

          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 對應(yīng)的 args 字段基本一致,本文只需關(guān)注:

          • name: TRACE_EVENT 名稱

          • cat: TRACE_EVENT 類別

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

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

          2.4 統(tǒng)計(jì) FPS

          2.4.1 FPS 統(tǒng)計(jì)關(guān)鍵 Trace Event

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

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

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

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

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

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

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

          Commit流程

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

          2.4.2 統(tǒng)計(jì)流程

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

          3. 總結(jié)

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

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

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

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

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

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


          內(nèi)推社群


          我組建了一個氛圍特別好的騰訊內(nèi)推社群,如果你對加入騰訊感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時候隨時幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。


          瀏覽 55
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  爱液视频精选 | 伊人伊人在线 | 韩国三级黄片视频 | 4438激情网 | 最近2019中文字幕第一页 |