天天看點

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

作者:位元組跳動技術團隊

1. 背景

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

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

2. 如何衡量站點性能

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

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

3. 首屏性能

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

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

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

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

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

那麼針對使用者感覺到的這四個階段,有沒有可用于衡量的名額呢?

3.1 何時開始渲染:FP && FCP

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

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

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

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

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

3.3 何時可以互動:TTI && TBT

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

TTI 雖然可以衡量頁面可以互動的時間點,但是卻無法感覺這個期間浏覽器的繁忙狀态。而結合 TBT ,就能幫助了解在加載期間,頁面無法響應使用者輸入的時間有多久。

3.4 互動時是否有延遲:FID && MPFID

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

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

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

4. 運作時性能

運作時性能一般可以通過Long tasks 來感覺。Long tasks主要是衡量主線程的繁忙情況。

4.1 Long tasks

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

5. 性能名額的計算原理

頁面性能相關的名額都有了,那麼如何采集這些資料呢?

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

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

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

// navigation timing 2
performance.getEntriesByType('navigation')           
前端監控系列3|如何衡量一個站點的性能好壞

基于這些資料,不僅可以計算出 DNS / TCP / Request 等耗時,還可以計算出 DOMReady / DOMParse / Load 等耗時。

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

5.2 采集 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"]});           

5.3 采集 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 的名額定義。

5.4 采集 FMP

與 FP / FCP / LCP 相比, FMP 的采集相對比較複雜,它需要通過算法計算得出,而業界并沒有統一的算法。不過比較認可的一個計算 FMP 的方式是「認定頁面在加載和渲染過程中最大布局變動之後的那個繪制時間即為目前頁面的 FMP 」。

由于在頁面渲染過程中,「 DOM 結構變化的時間點」和與之對應的「渲染的時間點」近似相同,是以位元組内部計算 FMP 的方式是:計算出 DOM 結構變化最劇烈的時間點,即為 FMP。

具體步驟為:

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

5.5 采集 TTI && TBT

與 FMP 相似,浏覽器也沒有提供直接擷取 TTI 的 API ,不過針對如何計算 TTI 有詳細的描述,實作對應描述就可以得出 TTI 的時間點。

具體的描述是:先找到 FCP 的時間點,再往前找到一個安靜視窗。安靜視窗需要滿足三個條件: 1) 沒有 Long task。2)視窗中正在處理的 GET 請求不超過兩個。3) 視窗時間窗讀應該至少 5s。

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

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

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

阻塞時間是 Long task 中超過 50ms 後的任務耗時。
前端監控系列3|如何衡量一個站點的性能好壞

5.6 采集 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 報告的内容和名額本身的定義有部分差異,是以計算時要處理這些差異;二是 部分場景下浏覽器不會報告對應内容,這些場景下需要模拟測量。

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

雖然有衆多性能名額,但是每個性能名額評估的都是單一方面,如何整體來看站點的性能是好是壞呢? 對于每個單一名額,是否有标準去定義名額的值在具體哪個範圍内能算優秀?線上的站點性能應該重點考量哪些性能名額?各個性能名額的權重占多少合适呢?

6.1 性能名額基準線

Google 提供了各個性能名額的基準線,有一定的參考意義。

為什麼隻說是有一定參考意義?首先基準線本身是在變化的,随着名額計算的逐漸更新以及軟體硬體的更新,基準線也會有一定的調整。其次使用者的使用場景對性能名額也會有很大的影響,比如 iOS 使用者上報的性能名額一般都優于 Android 使用者上報的性能名額。

下方是目前位元組内部使用的部分性能名額基準線,基本對齊 Google 建議的基準線,通過這些資料可以分析站點的性能達标率情況。

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

6.2 衡量站點滿意度

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

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

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

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

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

7. 如何優化站點性能

通常,我們可以從性能名額下手去做針對性的優化。雖然名額各不相同,但是優化的思路是相通的。在了解清楚名額的依賴項以後,通過優化名額的相關依賴項,就能快速優化性能名額,進而提升站點性能。

說起來比較抽象,舉個例子:比如當我們想要優化 TTI ,根據剛剛提到的 TTI 的計算方式,可以得出 TTI 的依賴項主要包含 FCP 、請求和 Long tasks,那麼盡快的渲染、盡早的請求、請求盡快結束、避免長任務就是優化的關鍵。關于名額的具體優化措施的内容比較多,将在後續的文章中展開介紹。

了解全面的優化措施固然重要,但把每個措施都做一遍并不一定能夠高效地解決站點面臨的關鍵性能問題。如何立竿見影、具有針對性的去優化?通過還原使用者加載時的情況來幫助定位性能問題是一個思路。

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

一般情況下,前端監控除了監控性能名額以外,還會監控請求和資源的加載以及 Long tasks 等資料,這些資料可以幫助還原使用者的加載現場,幫助找到蛛絲馬迹。

比如下面這個例子, 多項性能名額都很差。通過監控平台還原出的資源加載瀑布圖, 可以看出絕大部分時間都耗在了拉取資源上。那麼就可以初步得出性能優化方案,将優化措施側重在資源優化上,比如縮小 JS 檔案體積、延遲加載未使用的 JS 代碼等等。

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

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

9. 相關資料

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

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

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

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

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

繼續閱讀