如何使用 Navigation Timing 和 Resource Timing 評估欄位中的載入效能

瞭解如何使用 Navigation 和 Resource Timing API,評估實際環境中的載入效能。

發布日期:2021 年 10 月 8 日

如果您曾在瀏覽器的開發人員工具 (或 Chrome 中的 Lighthouse) 網路面板中使用連線節流功能評估載入效能,就會知道這些工具在效能調整方面有多方便。您可以使用穩定一致的基準連線速度,快速評估效能最佳化措施的影響。唯一的問題是,這是合成測試,會產生實驗室資料,而非現場資料

合成測試本身並非不良,但無法代表實際使用者載入網站的速度。這需要欄位資料,您可以透過 Navigation Timing 和 Resource Timing API 收集。

可協助您評估實際載入效能的 API

「導覽時間」和「資源時間」是兩個類似的 API,有許多重疊之處,但測量的內容卻截然不同:

  • 導覽時間會測量 HTML 文件 (即導覽要求) 的要求速度。
  • 資源時間會測量文件相關資源 (例如 CSS、JavaScript、圖片和其他資源類型) 的要求速度。

這些 API 會在成效項目緩衝區中公開資料,您可以使用 JavaScript 在瀏覽器中存取這些資料。查詢效能緩衝區的方法有很多種,但常見的方法是使用 performance.getEntriesByType

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

performance.getEntriesByType 接受字串,說明您要從成效項目緩衝區擷取的項目類型。'navigation''resource' 分別擷取 Navigation Timing 和 Resource Timing API 的時間。

這些 API 提供的資訊量可能相當龐大,但這是您在實際環境中評估載入效能的關鍵,因為您可以從造訪網站的使用者收集這些時間資訊。

網路要求的生命週期和時間軸

收集及分析導覽和資源時間,有點像是考古學,因為您是在事後重建網路要求的短暫生命週期。有時,概念的視覺化呈現有助於理解,而瀏覽器的開發人員工具可協助您瞭解網路要求。

Chrome 開發人員工具中顯示的網路時間。圖中以不同顏色的長條表示要求排隊、連線交涉、要求本身和回應的時間。
Chrome 開發人員工具網路面板中的網路要求視覺化

網路要求有不同的生命週期階段,例如 DNS 查詢、建立連線、TLS 協商和其他延遲來源。這些時間會以 DOMHighResTimestamp 表示。視瀏覽器而定,時間精細度可能達到微秒,或向上取整至毫秒。您需要詳細檢查這些階段,以及這些階段與 Navigation Timing 和 Resource Timing 的關聯。

DNS 查詢

使用者前往網址時,系統會查詢網域名稱系統 (DNS),將網域轉譯為 IP 位址。這個程序可能需要相當長的時間,甚至需要現場測量。「導覽時間」和「資源時間」會公開兩項與 DNS 相關的時間:

  • domainLookupStart 是指 DNS 查詢開始的時間。
  • domainLookupEnd 是指 DNS 查詢結束的時間。

如要計算 DNS 查詢總時間,請從結束指標減去開始指標:

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

連線協商

影響載入效能的另一個因素是連線交涉,也就是連線至網路伺服器時發生的延遲。如果涉及 HTTPS,這個程序也會包含 TLS 協商時間。連線階段包含三個時間點:

  • connectStart 是指瀏覽器開始開啟與網路伺服器的連線。
  • secureConnectionStart 標記表示用戶端開始進行 TLS 協商。
  • connectEnd 是指已建立與網路伺服器的連線。

測量連線總時間的方式與測量 DNS 查詢總時間類似:從結束時間減去開始時間。不過,如果未使用 HTTPS 或連線為持續性連線,則可能會有額外的 secureConnectionStart 屬性。0如要測量 TLS 協議時間,請注意以下事項:

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

DNS 查詢和連線交涉結束後,系統就會開始擷取文件及其依附資源,並記錄相關時間。

要求與回應

載入效能會受到兩類因素影響:

  • 外部因素:例如延遲時間和頻寬。除了選擇主機代管公司和可能的 CDN 之外,這些因素 (大部分) 不在我們的掌控範圍內,因為使用者可以從任何地方存取網路。
  • 內在因素:包括伺服器和用戶端架構,以及資源大小和我們針對這些項目進行最佳化的能力,這些都在我們的掌控範圍內。

這兩種因素都會影響載入效能。與這些因素相關的時間點至關重要,因為這些時間點說明資源下載所需的時間。導覽時機和資源時機都會使用下列指標說明載入效能:

  • fetchStart 標記瀏覽器開始擷取資源 (資源時間) 或導覽要求的文件 (導覽時間) 時。這項要求會先於實際要求傳送,瀏覽器會在傳送這項要求時檢查快取 (例如 HTTP 和 Cache 執行個體)。
  • workerStart 標記服務工作人員 fetch 事件處理常式開始處理要求的時間。如果沒有 Service Worker 控制目前網頁,這個值會是 0
  • requestStart 是指瀏覽器發出要求的時間。
  • responseStart 是指收到回應的第一個位元組。
  • responseEnd 是指收到回應的最後一個位元組。

您可以透過這些時間點評估載入效能的各個層面,例如 Service Worker 中的快取查閱下載時間:

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

您也可以評估要求和回應延遲的其他層面:

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

其他可進行的測量

Navigation Timing 和 Resource Timing 的用途不只在於先前的範例。以下列舉其他可能值得探討的相關時間點:

  • 網頁重新導向:重新導向是造成延遲的常見原因,尤其是重新導向鏈結。延遲時間的增加方式有很多種,例如 HTTP 至 HTTPS 的躍點,以及 302/未快取的 301 重新導向。redirectStartredirectEndredirectCount 時間有助於評估重新導向延遲時間。
  • 卸載文件:unload 事件處理常式中執行程式碼的網頁,瀏覽器必須先執行該程式碼,才能導覽至下一個網頁。unloadEventStartunloadEventEnd 測量文件卸載。
  • 文件處理:除非網站傳送的 HTML 酬載非常大,否則文件處理時間可能不會造成影響。如果符合上述情況,您可能會對 domInteractivedomContentLoadedEventStartdomContentLoadedEventEnddomComplete 時間軸感興趣。

如何在程式碼中取得時間戳記

到目前為止,所有範例都使用 performance.getEntriesByType,但您也可以使用 performance.getEntriesByNameperformance.getEntries 等其他方式查詢效能項目緩衝區。如果只需要進行輕量分析,這些方法就相當合適。但在其他情況下,這些 API 可能會疊代大量項目,甚至重複輪詢效能緩衝區來尋找新項目,進而導致主執行緒工作過多。

如要從效能項目緩衝區收集項目,建議使用 PerformanceObserverPerformanceObserver 會監聽效能項目,並在項目新增至緩衝區時提供:

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

與直接存取效能項目緩衝區相比,這種收集時間的方法可能感覺有些不自然,但與其讓主執行緒忙於處理不具關鍵用途且使用者看不到的工作,不如採用這種方法。

如何打電話回家

收集所有需要的計時資料後,即可將這些資料傳送至端點,以供進一步分析。您可以透過 navigator.sendBeacon 或設定 fetchkeepalive 選項,這兩種方法都會以非封鎖方式,將要求傳送至指定端點,且如有需要,要求會排入佇列,在目前的網頁工作階段結束後繼續執行:

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // Caution: If you have lots of performance entries, don't
  // do this. This is an example for illustrative purposes.
  const data = JSON.stringify(performance.getEntries());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

在本範例中,JSON 字串會以 POST 酬載的形式傳送,您可以視需要解碼、處理及儲存在應用程式後端。

結論

收集指標後,您可自行決定如何分析該欄位資料。分析現場資料時,請遵循幾項一般規則,確保得出有意義的結論:

  • 避免使用平均值,因為平均值無法代表任何一位使用者的體驗,而且可能會受到離群值影響。
  • 請參考百分位數。在以時間為準的成效指標資料集中,數值越低越好。也就是說,如果優先處理低百分位數,您只會注意到最快的體驗。
  • 優先處理長尾價值。優先處理第 75 百分位數以上的體驗,表示您將重心放在最慢的體驗上,這才是正確的做法。

本指南並非要詳盡介紹 Navigation 或 Resource Timing,而是提供入門資訊。以下提供其他實用資源:

有了這些 API 和提供的資料,您就能更瞭解實際使用者體驗到的載入效能,更有把握地診斷及解決現場的載入效能問題。