自訂指標

具備以使用者為中心的指標,可讓您在所有網站上全面進行評估,大大好處。這些指標可讓您:

  • 瞭解使用者整體的網路體驗。
  • 比較您的網站與競爭對手的網站。
  • 不必編寫自訂程式碼,即可在分析工具中追蹤實用且可做為行動依據的資料。

通用指標可提供良好的基準,但在許多情況下,您不僅必須多評估這些指標,,才能掌握特定網站的完整體驗。

您可以使用自訂指標,針對可能只適合網站瀏覽的網站體驗進行評估,例如:

  • 從單一頁面應用程式 (SPA) 切換到另一個「頁面」所需的時間。
  • 網頁顯示從資料庫擷取的資料 (已登入使用者) 所需的時間。
  • 伺服器端算繪 (SSR) 應用程式等待時間長度
  • 回訪者載入的資源快取命中率。
  • 遊戲中點擊或鍵盤事件的事件延遲時間。

評估自訂指標的 API

過去,網頁程式開發人員沒有太多低階 API 可用來評估成效,因此必須採取駭客手段,才能評估網站效能是否良好。

舉例來說,您可以執行 requestAnimationFrame 迴圈,並計算每個影格之間的差異,藉此判斷主執行緒是否因長時間執行的 JavaScript 工作而遭到封鎖。如果差異遠大於顯示器的影格速率,您可以將差異回報為長時間的工作。不過,我們不建議這麼做,因為這類入侵行為實際上會影響效能 (例如,耗盡電池)。

成效評估的第一個規則是確認成效評估技巧不會導致成效問題。因此,建議您針對網站上評估的所有自訂指標,盡可能使用下列其中一個 API。

Performance Observer API

瀏覽器支援

  • 52
  • 79
  • 57
  • 11

來源

Performance Observer API 是一種機制,可收集和顯示本頁所討論其他 Performance API 的資料。瞭解取得優質資料非常重要。

您可以使用 PerformanceObserver 以被動方式訂閱與成效相關的事件。這樣一來,API 回呼在閒置期間就會觸發,也就是說,通常不會幹擾網頁效能。

如要建立 PerformanceObserver,請傳遞一個回呼,以在每次分派新效能項目時執行。接著,您將使用 observe() 方法告知觀察器要監聽的項目類型:

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // Log the entry and all associated details.
      console.log(entry.toJSON());
    }
  });

  po.observe({type: 'some-entry-type'});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

以下各節列出了所有可供觀察的項目類型,但在新版瀏覽器中,您可以透過靜態 PerformanceObserver.supportedEntryTypes 屬性檢查可用的項目類型。

查看已發生的項目

根據預設,PerformanceObserver 物件只能在發生項目時觀測項目。如果您想延遲載入效能分析程式碼,避免封鎖優先順序較高的資源,可能就會發生上述問題。

如要取得歷史項目 (在產生之後),請在呼叫 observe() 時將 buffered 標記設為 true。首次呼叫 PerformanceObserver 回呼時,瀏覽器會納入效能項目緩衝區中的歷史項目。

po.observe({
  type: 'some-entry-type',
  buffered: true,
});

應避免的舊版 Performance API

在 Performance Observer API 之前,開發人員可以使用以下三種 performance 物件中定義的方法存取效能項目:

雖然這些 API 仍然受到支援,但我們不建議使用這些 API,因為您無法監聽發出新項目的時間。此外,許多新的 API (例如長工作) 無法透過 performance 物件公開,只能透過 PerformanceObserver 公開。

除非您特別需要與 Internet Explorer 相容,否則最好避免在程式碼中採用這些方法,並在日後使用 PerformanceObserver

使用者載入時間 API

User Timing API 是針對時間指標的一般用途評估 API,可讓您任意在時間點標記點,之後測量這些標記之間的持續時間。

// Record the time immediately before running a task.
performance.mark('myTask:start');
await doMyTask();

// Record the time immediately after running a task.
performance.mark('myTask:end');

// Measure the delta between the start and end of the task
performance.measure('myTask', 'myTask:start', 'myTask:end');

雖然 Date.now()performance.now() 等 API 可提供類似功能,但 User Timing API 卻能與效能工具完美整合。舉例來說,Chrome 開發人員工具會以視覺化方式呈現效能面板中的使用者載入時間測量結果,許多分析供應商也會自動追蹤您所做的任何測量,並將時間長度資料傳送至其分析後端。

如要回報使用者載入時間測量結果,您可以使用 PerformanceObserver 並註冊,觀察 measure 類型的項目:

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // Log the entry and all associated details.
      console.log(entry.toJSON());
    }
  });

  // Start listening for `measure` entries to be dispatched.
  po.observe({type: 'measure', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Long Tasks API

瀏覽器支援

  • 58
  • 79
  • x
  • x

來源

如要得知瀏覽器的主執行緒何時遭到封鎖,足以影響影格速率或輸入延遲,Long Tasks API 就非常實用。API 會回報執行時間超過 50 毫秒的所有工作。

每當您需要執行會耗用大量資源的程式碼,或載入及執行大型指令碼時,建議追蹤該程式碼是否封鎖主執行緒。事實上,許多高階指標都是以 Long Tasks API 為基礎建構而成,例如 Time to Interactive (TTI)Total Blocking Time (TBT)

如要判斷長時間工作發生的時間,您可以使用 PerformanceObserver 並註冊,觀察 longtask 類型的項目:

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // Log the entry and all associated details.
      console.log(entry.toJSON());
    }
  });

  // Start listening for `longtask` entries to be dispatched.
  po.observe({type: 'longtask', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

元素時間 API

瀏覽器支援

  • 77
  • 79
  • x
  • x

來源

「Largest Contentful Paint (LCP)」指標可用來瞭解系統何時繪製最大的圖片或文字區塊,但在某些情況下,還是需要測量不同元素的轉譯時間。

在這種情況下,請使用 Element Timing API。LCP API 實際上是以 Element Timing API 為基礎建構而成,並會自動新增最大內容元素的自動回報功能。不過,您也可以為其他元素明確加入 elementtiming 屬性,並註冊 PerformanceObserver,以觀察 element 項目類型。

<img elementtiming="hero-image" />
<p elementtiming="important-paragraph">This is text I care about.</p>
<!-- ... -->

<script>
  // Catch errors since some browsers throw when using the new `type` option.
  // https://bugs.webkit.org/show_bug.cgi?id=209216
  try {
    // Create the performance observer.
    const po = new PerformanceObserver((entryList) => {
      for (const entry of entryList.getEntries()) {
        // Log the entry and all associated details.
        console.log(entry.toJSON());
      }
    });

    // Start listening for `element` entries to be dispatched.
    po.observe({type: 'element', buffered: true});
  } catch (e) {
    // Do nothing if the browser doesn't support this API.
  }
</script>

事件時間 API

「與下一個顯示的內容互動 (INP)」指標會觀察網頁在整個流程中的點擊、輕觸和鍵盤互動,藉此評估網頁的整體回應速度。網頁 INP 通常是耗時最長的互動時間,從使用者啟動互動開始,到瀏覽器繪製下一個影格並顯示使用者輸入的視覺結果為止。

INP 指標是由 Event Timing API 提供。這個 API 會顯示事件生命週期內發生的幾種時間戳記,包括:

  • startTime:瀏覽器收到事件的時間。
  • processingStart:瀏覽器能開始處理事件事件處理常式的時間。
  • processingEnd:瀏覽器為這個事件執行由事件處理常式啟動的所有同步程式碼的時間。
  • duration:從瀏覽器收到事件開始,到執行事件處理常式啟動的所有同步程式碼後,直到繪製下一個影格為止所需的時間長度 (以 8 毫秒為單位)。

以下範例說明如何使用這些值建立自訂測量結果:

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  const po = new PerformanceObserver((entryList) => {
    // Get the last interaction observed:
    const entries = Array.from(entryList.getEntries()).forEach((entry) => {
      // Get various bits of interaction data:
      const inputDelay = entry.processingStart - entry.startTime;
      const processingTime = entry.processingEnd - entry.processingStart;
      const duration = entry.duration;
      const eventType = entry.name;
      const target = entry.target || "(not set)"

      console.log("----- INTERACTION -----");
      console.log(`Input delay (ms): ${inputDelay}`);
      console.log(`Event handler time (ms): ${processingTime}`);
      console.log(`Total event duration (ms): ${duration}`);
      console.log(`Event type: ${eventType}`);
      console.log(target);
    });
  });

  // A durationThreshold of 16ms is necessary to surface more
  // interactions, since the default is 104ms. The minimum
  // durationThreshold is 16ms.
  po.observe({type: 'event', buffered: true, durationThreshold: 16});
} catch (error) {
  // Do nothing if the browser doesn't support this API.
}

Resource Timing API

Resource Timing API 可讓開發人員深入瞭解特定網頁的資源載入方式。儘管 API 名稱為這個 API,它提供的資訊並不限於時間資料 (但這有很多種)。您可以存取的其他資料包括:

  • initiatorType:資源的擷取方式,例如透過 <script>/<link> 標記或 fetch() 呼叫擷取資源。
  • nextHopProtocol:用來擷取資源的通訊協定,例如 h2quic
  • encodedBodySize/decodedBodySize]:以編碼或解碼格式表示的資源大小 (分別指定)
  • transferSize:實際透過網路轉移的資源大小。當快取執行資源時,這個值可以遠低於 encodedBodySize,在某些情況下,這個值甚至可能為零 (如果不需要重新驗證快取)。

您可以使用資源時間項目的 transferSize 屬性來評估「快取命中率」指標或「快取資源總大小」指標,瞭解資源快取策略對重複訪客的效能有何影響。

以下範例會記錄網頁要求的所有資源,並指出快取是否已完成各項資源。

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // If transferSize is 0, the resource was fulfilled via the cache.
      console.log(entry.name, entry.transferSize === 0);
    }
  });

  // Start listening for `resource` entries to be dispatched.
  po.observe({type: 'resource', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

瀏覽器支援

  • 57
  • 12
  • 58
  • 15

來源

Navigation Timing API 與 Resource Timing API 很類似,但只會回報導覽要求navigation 項目類型也與 resource 項目類型相似,但包含一些僅適用於導覽要求的其他資訊 (例如 DOMContentLoadedload 事件觸發時)。

許多開發人員使用 Navigation Timing API (具體來說是項目的 responseStart 時間戳記),對於瞭解伺服器回應時間 (TTFB) 而追蹤的指標。

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // If transferSize is 0, the resource was fulfilled via the cache.
      console.log('Time to first byte', entry.responseStart);
    }
  });

  // Start listening for `navigation` entries to be dispatched.
  po.observe({type: 'navigation', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

使用 Service Worker 的另一項指標開發人員,可能是 Service Worker 啟動導航要求的時間。這是瀏覽器在開始攔截擷取事件前,啟動 Service Worker 執行緒所需的時間。

特定導覽要求的 Service Worker 啟動時間可根據 entry.responseStartentry.workerStart 之間的差異決定。

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      console.log('Service Worker startup time:',
          entry.responseStart - entry.workerStart);
    }
  });

  // Start listening for `navigation` entries to be dispatched.
  po.observe({type: 'navigation', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

伺服器時間 API

Server Timing API 可讓您透過回應標頭,將要求的特定時間資料從伺服器傳送至瀏覽器。例如,您可以指出針對特定要求查詢資料庫中的資料花費的時間,這在偵錯因伺服器速度緩慢而造成的效能問題時很有用。

對於使用第三方分析服務供應商的開發人員來說,Server Timing API 是將伺服器效能資料與這些分析工具所評估的其他業務指標建立關聯的唯一方法。

如要在回應中指定伺服器時間資料,可以使用 Server-Timing 回應標頭。以「Black cat ate the mouse」

HTTP/1.1 200 OK

Server-Timing: miss, db;dur=53, app;dur=47.2

接著,您可以從網頁中讀取 Resource Timing 和 Navigation Timing API 的 resourcenavigation 項目資料。

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // Logs all server timing data for this response
      console.log('Server Timing', entry.serverTiming);
    }
  });

  // Start listening for `navigation` entries to be dispatched.
  po.observe({type: 'navigation', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}