前端監控系列3 | 如何衡量一個站點的性能好壞

作者:彭莉,火山引擎 APM 研發工程師。2020年加入位元組,負責前端監控 SDK 的開發維護、平台數據消費的探索和落地。

背景

你知道有多少用戶沒等到頁面首屏出現就離開了嗎?性能不佳會對業務目標產生負面影響。比如, BBC 發現他們的網站加載時間每增加一秒,他們就會失去 10% 的用戶。高性能站點比低性能站點更能吸引和留住用戶,而留住用戶對於提高用戶轉化率至關重要。

這篇文章就是以此為背景,介紹位元組內部是如何衡量站點性能的,如何依靠性能監控定位線上站點性能問題的。

如何衡量站點性能

站點性能好壞的表現形式是多樣的,不是單純通過頁面加載速度、頁面渲染速度就能衡量,而是要關注從頁面開始加載到被關閉的整個過程中,用戶對性能的感知。一個頁面,即使很快渲染,如果對用戶的交互遲遲沒有響應,那麼在用戶心中這個站點的性能依然很差。

站點性能一般可以分為兩類,一類是首屏性能,另一類是運行時性能。前者衡量的是頁面從加載開始到可以穩定交互的性能情況,後者衡量的是頁面穩定後到頁面關閉的性能情況。

首屏性能

早在 2012 年, Web 性能工作組[1] 就針對頁面加載場景制定了加載過程模型,用來衡量頁面加載各個階段的耗時情況,從而衡量頁面加載的性能。具體的加載過程模型如圖所示:

圖片

這個模型定義了頁面加載開始到頁面加載完成整個過程的各個時間點,除了正常的初始化並且拉取到主文檔的時間點以外,還包括了解析渲染的詳細時間點。比如:

  • domLoading 代表 開始解析的時間點
  • domInteractive 代表 DOM 解析完成、開始加載內嵌資源的時間點
  • domComplete 代表 文檔解析完成的時間點
  • loadEventStart 代表 load 事件被觸發的時間點

雖然開發者可以根據這些時間點來衡量頁面加載時的性能情況,但是線上用戶其實感知不到這些時間點的區別。對於用戶而言,只能感知到頁面何時開始渲染、何時渲染出主要內容、何時可以交互、以及交互時是否有延遲。

那麼針對用戶感知到的這四個階段,有沒有可用于衡量的指標呢?

何時開始渲染:FP && FCP

  • FP:First Paint,首次渲染的時間點。FP 時間點之前,用戶看到的都是沒有任何內容的白色屏幕。
  • FCP:First Contentful Paint,首次有實際內容渲染的時間點。

這兩個指標都來源於 Paint Timing[2] 標準, 這個標準主要是記錄在頁面加載期間的一些關鍵時間點。通過這兩個指標,就可以衡量頁面何時開始渲染內容了。

何時渲染出主要內容:FMP && LCP && SI

  • FMP:First Meaningful Paint,完成首次有意義內容繪製的時間點。
  • LCP:Largest Contentful Paint,最大的內容在可視區域內變得可見的時間點。
  • SI:Speed Index,衡量頁面可視區域的加載速度,反映頁面的加載體驗差異。

有了這三個指標,就可以衡量頁面何時渲染出主要內容了。不過業界有測試得出, LCP  非常近似於 FMP 的時間點,同時 FMP 性能消耗較大,且會因為一些細小的變化導致數值巨大波動,所以推薦使用 LCP。 而 SI 因為計算複雜,指標難以解釋,所以一般只在實驗室環境下使用。

何時可以交互:TTI && TBT

  • TTI: Time to Interactive,頁面從開始加載到主要子資源完成渲染,並能夠快速、可靠地響應用戶輸入的時間點。
  • TBT: Total Blocking Time,頁面從 FCP 到 TTI 之間的阻塞時間,一般用來量化主線程在空閑之前的繁忙程度。

TTI 雖然可以衡量頁面可以交互的時間點,但是卻無法感知這個期間瀏覽器的繁忙狀態。而結合 TBT ,就能幫助理解在加載期間,頁面無法響應用戶輸入的時間有多久。

交互時是否有延遲:FID && MPFID

  • FID:First Input Delay,用戶第一次與頁面交互(例如當他們單擊鏈接、點按按鈕等操作)直到瀏覽器對交互作出響應,並且實際能夠開始處理事件程序所經過的時間。
  • MPFID: Max Potential First Input Delay,記錄在頁面加載過程中用戶和頁面進行首次交互操作可能花費的最長時間。

MPFID 是一個虛擬的可能的延遲時間,而FID是用戶真實的首次交互的延遲時間。所以一般推薦使用FID,它是用戶對頁面交互性和響應性的第一印象。良好的第一印象有助於用戶建立對整個應用的良好印象。同時在頁面加載階段,資源的處理任務最重,最容易產生輸入延遲。

至此,通過上面每個階段的指標,基本可以實現全面衡量首屏性能。那麼運行時的性能又可以怎樣衡量呢?

運行時性能

運行時性能一般可以通過Long tasks 和 Input Delay來感知。Long tasks主要是衡量主線程的繁忙情況,而 Input Delay 主要是衡量用戶交互的延遲情況。

Long tasks

如果一個任務在主線程上運行超過 50 毫秒,那麼它就是 Long task。如果可以收集到運行時的所有Long tasks,就能知道運行時的性能情況。在具體實踐中,可以關注耗時較長的Long task,將它和用戶行為關聯在一起,可以有效幫助定位線上卡頓的原因。

Input Delay

它源於 Event Timing[3] 標準,這個標準主要是幫助深入了解由用戶交互觸發的某些事件的延遲,通過計算用戶輸入和處理輸入後的頁面繪製時間的差值來感知延遲時間。這些延遲通常是由於開發人員代碼編寫不當,引起 JS 執行時間過長而產生的。

性能指標的計算原理

頁面性能相關的指標都有了,那麼如何採集這些數據呢?

採集頁面加載過程的各階段耗時

頁面加載過程中的時間點主要依賴 Navigation Timing[4] 標準,這個標準後來升級到了2.0版本, 對應 Navigation Timing 2[5] 標準,兩者雖然不盡相同,但是可計算出的指標基本一致。在瀏覽器中可以通過下面的方式獲取:

// navigation timing
const timing = window.performance.timing

// navigation timing 2
performance.getEntriesByType('navigation')
複製代碼

圖片

基於這些數據,不僅可以計算出 DNS / TCP / Request 等耗時,還可以計算出 DOMReady / DOMParse / Load 等耗時。

圖片

採集 FP && FCP

FP 和 FCP 可以通過瀏覽器提供的 API 直接獲取,所以採集原理並不複雜。如果頁面已經完成了首次繪製和首次內容繪製,可以使用下面的方式直接獲取。

window.performance.getEntriesByType('paint')
// or
window.performance.getEntriesByName('first-paint')
window.performance.getEntriesByName('first-contentful-paint')
複製代碼

但是如果頁面還沒有開始首次繪製,就需要通過監聽獲取。

const observer = new PerformanceObserver(function(list) {
  const perfEntries = list.getEntries();
  for (const perfEntry of perfEntries) {
      // Process entries
      // report back for analytics and monitoring
      // ...
  }
});

// register observer for paint timing notifications
observer.observe({entryTypes: ["paint"]});
複製代碼

採集 LCP

LCP 主要依賴 PerformanceObserver,具體監聽方式如下:

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log('LCP candidate:', entry.startTime, entry);
  }
}).observe({type: 'largest-contentful-paint', buffered: true});
複製代碼

瀏覽器會多次報告 LCP ,而一般真正的 LCP 是用戶交互前最近一次報告的 LCP ,因為交互往往會改變用戶可見的內容,所以用戶交互後新報告的 LCP 不再符合 LCP 的指標定義。

採集 FMP

與 FP / FCP / LCP 相比, FMP 的採集相對比較複雜,它需要通過算法計算得出,而業界並沒有統一的算法。不過比較認可的一個計算 FMP 的方式是「認定頁面在加載和渲染過程中最大布局變動之後的那個繪製時間即為當前頁面的 FMP 」。由於在頁面渲染過程中,「 DOM 結構變化的時間點」和與之對應的「渲染的時間點」近似相同,所以位元組內部計算 FMP 的方式是:計算出 DOM 結構變化最劇烈的時間點,即為 FMP。具體步驟為:

  1. 通過 MutationObserver 監聽每一次頁面整體的 DOM 變化,觸發 MutationObserver 的回調
  2. 在回調計算出當前 DOM 樹的分數
  3. 在結算時,通過對比得出分數變化最劇烈的時刻,即為 FMP

採集 TTI && TBT

與 FMP 相似,瀏覽器也沒有提供直接獲取 TTI 的 API ,不過針對如何計算 TTI 有詳細的描述,實現對應描述就可以得出 TTI 的時間點。具體的描述是:先找到 FCP 的時間點,再往前找到一個安靜窗口。安靜窗口需要滿足三個條件:  1) 沒有  Long task。2)窗口中正在處理的 GET 請求不超過兩個。3) 窗口時間窗讀應該至少 5s。

窗口前的最後一個長任務的結束時間就是 TTI 。

圖片

而通過計算 FCP 和 TTI 之間的長任務的阻塞時間的總和,就能得出 TBT 。

阻塞時間是 Long task 中超過 50ms 後的任務耗時。

圖片

採集 FID && MPFID

FID 同樣依賴 PerformanceObserver,具體監聽方式如下:

new PerformanceObserver(function(list, obs) {
  const firstInput = list.getEntries()[0];

  // Measure the delay to begin processing the first input event.
  const firstInputDelay = firstInput.processingStart - firstInput.startTime;
  // Measure the duration of processing the first input event.
  // Only use when the important event handling work is done synchronously in the handlers.
  const firstInputDuration = firstInput.duration;
  // Obtain some information about the target of this event, such as the id.
  const targetId = firstInput.target ? firstInput.target.id : 'unknown-target';
  // Process the first input delay and perhaps its duration...

  // Disconnect this observer since callback is only triggered once.
  obs.disconnect();
}).observe({type: 'first-input', buffered: true});
複製代碼

MPFID 是 FCP 之後最長的長任務耗時,可以通過監聽 FCP 之後的 Long tasks,對比拿到最長的長任務就是 MPFID 。

雖然瀏覽器提供了足夠的 API 來幫助採集各個性能指標,但是在 JS 中計算具體指標要更為複雜。原因有兩點:一是 API 報告的內容和指標本身的定義有部分差異,所以計算時要處理這些差異;二是 部分場景下瀏覽器不會報告對應內容,這些場景下需要模擬測量。

怎樣評估站點整體的性能好壞

雖然有眾多性能指標,但是每個性能指標評估的都是單一方面,如何整體來看站點的性能是好是壞呢? 對於每個單一指標,是否有標準去定義指標的值在具體哪個範圍內能算優秀?線上的站點性能應該重點考量哪些性能指標?各個性能指標的權重佔多少合適呢?

性能指標基準線

Google 提供了各個性能指標的基準線,有一定的參考意義。為什麼只說是有一定參考意義?首先基準線本身是在變化的,隨着指標計算的逐漸更新以及軟件硬件的更新,基準線也會有一定的調整。其次用戶的使用場景對性能指標也會有很大的影響,比如 iOS 用戶上報的性能指標一般都優於 Android 用戶上報的性能指標。

下方是目前位元組內部使用的部分性能指標基準線,基本對齊 Google 建議的基準線,通過這些數據可以分析站點的性能達標率情況。

圖片

衡量站點滿意度

站點滿意度的衡量除了要考慮常規的性能指標外,還要考慮體驗類的指標,比如專門衡量視覺穩定性的指標 CLS。

線上的站點滿意度衡量,一般會在參考lighthouse的滿意度計算規則的基礎上,去除一些推薦在實驗室環境測量的指標的權重。

下方是目前位元組使用的線上站點性能滿意度的權重計算公式,去除了SI 和 TBT這兩個不推薦在線上環境測量的指標。

image.png

那麼有了一個站點滿意度以後,我們終於能知道一個站點的性能好壞了。那麼假設性能不好,我們應該怎樣優化?

如何優化站點性能

通常,我們可以從性能指標下手去做針對性的優化。雖然指標各不相同,但是優化的思路是相通的。在了解清楚指標的依賴項以後,通過優化指標的相關依賴項,就能快速優化性能指標,從而提升站點性能。 說起來比較抽象,舉個例子:比如當我們想要優化 TTI ,根據剛剛提到的 TTI 的計算方式,可以得出 TTI 的依賴項主要包含 FCP 、請求和 Long tasks,那麼儘快的渲染、儘早的請求、請求儘快結束、避免長任務就是優化的關鍵。關於指標的具體優化措施的內容比較多,將在後續的文章中展開介紹。了解全面的優化措施固然重要,但把每個措施都做一遍並不一定能夠高效地解決站點面臨的關鍵性能問題。如何****立竿見影、具有針對性的去優化? 通過還原用戶加載時的情況來幫助定位性能問題是一個思路。

利用線上監控定位性能問題

一般情況下,前端監控除了監控性能指標以外,還會監控請求和資源的加載以及 Long tasks 等數據,這些數據可以幫助還原用戶的加載現場,幫助找到蛛絲馬跡。比如下面這個例子, 多項性能指標都很差。通過監控平台還原出的資源加載瀑布圖, 可以看出絕大部分時間都耗在了拉取資源上 那麼就可以初步得出性能優化方案,將優化措施側重在資源優化上,比如縮小JS文件體積、延遲加載未使用的JS代碼等等。

圖片

線上監控幫助定位性能問題的例子還有很多,這裡不一一介紹了。截圖中使用的是位元組內部的前端監控平台,目前這套解決方案已同步在火山引擎上,接入即可對 Web 端真實數據進行實時監控、報警歸因、聚類分析和細節定位,解決白屏、性能瓶頸、慢查詢等關鍵問題,歡迎體驗。

評論區留言申請免費使用⬇️

 

相關資料

[1] Web性能工作組://www.w3.org/webperf/

[2] Paint Timing://w3c.github.io/paint-timing/

[3] Event Timing: //w3c.github.io/event-timing/

[4] Navigation Timing: //www.w3.org/TR/navigation-timing/

[5] Navigation Timing 2: //www.w3.org/TR/navigation-timing-2/