カスタム指標

特定のウェブサイトで普遍的に測定できるユーザー中心の指標には、多くの価値があります。これらの指標を使用すると、次のことができます。

  • 実際のユーザーがウェブ全体をどのように体験しているかを把握できます。
  • 自分のサイトを競合他社のサイトと比較する。
  • カスタム コードを記述しなくても、分析ツールで有用で実用的なデータをトラッキングできます。

ユニバーサル指標は優れたベースラインとなりますが、多くの場合、特定のサイトの全体的なエクスペリエンスを把握するには、これらの指標以外にも多くの指標を測定する必要があります。

カスタム指標を使用すると、そのサイトのみに当てはまる、サイトの利便性に関する次のような要素を測定できます。

  • シングルページ アプリ(SPA)が 1 つの「ページ」から別の「ページ」に遷移するまでの時間。
  • データベースから取得したデータをログイン ユーザーのページに表示するのにかかる時間です。
  • サーバーサイド レンダリング(SSR)アプリのハイドレートにかかる時間。
  • リピーターによって読み込まれたリソースのキャッシュ ヒット率。
  • ゲーム内のクリック イベントまたはキーボード イベントのイベント レイテンシ。

カスタム指標を測定するための API

これまで、ウェブ デベロッパーはパフォーマンスを測定する低レベルの API をあまり持っていませんでした。そのため、サイトのパフォーマンスが良好かどうかを測定するためにハックに頼らざるを得ませんでした。

たとえば、requestAnimationFrame ループを実行して各フレーム間の差分を計算することで、長時間実行される JavaScript タスクが原因でメインスレッドがブロックされているかどうかを判断できます。差分がディスプレイのフレームレートよりも大幅に長い場合は、長いタスクとして報告できます。ただし、このようなハックは、バッテリーの消耗など、実際にパフォーマンスに影響するため、おすすめしません。

効果的なパフォーマンス測定の第一のルールは、パフォーマンス測定手法自体がパフォーマンスの問題を引き起こしていないことを確認することです。そのため、サイトで測定するカスタム指標については、可能な限り次のいずれかの API を使用することをおすすめします。

Performance Observer API

対応ブラウザ

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 57.
  • Safari: 11.

ソース

Performance Observer API は、このページで説明する他のすべての Performance API からデータを収集して表示するメカニズムです。これを理解することは、優れたデータを得るために重要です。

PerformanceObserver を使用すると、パフォーマンス関連のイベントを受動的にサブスクライブできます。これにより、API コールバックをアイドル状態中に実行できるため、通常はページのパフォーマンスに影響しません。

PerformanceObserver を作成するには、新しいパフォーマンス エントリがディスパッチされるたびに実行されるコールバックを渡します。次に、observe() メソッドを使用して、リッスンするエントリの種類をオブザーバーに伝えます。

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'});

以降のセクションでは、監視に使用できるさまざまなエントリタイプをすべて示します。新しいブラウザでは、静的 PerformanceObserver.supportedEntryTypes プロパティを使用して、使用可能なエントリタイプを検査できます。

すでに発生したエントリをモニタリングする

デフォルトでは、PerformanceObserver オブジェクトはエントリが発生したときにのみエントリを監視できます。パフォーマンス分析コードを遅延読み込みして優先度の高いリソースをブロックしないようにする場合、この問題が発生する可能性があります。

過去のエントリを取得するには(発生後に取得するには)、observe() を呼び出すときに buffered フラグを true に設定します。ブラウザは、PerformanceObserver コールバックが初めて呼び出されたときに、パフォーマンス エントリ バッファから過去のエントリを、そのタイプの最大バッファサイズまで含めます。

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

使用しないレガシー パフォーマンス API

Performance Observer API の導入以前は、performance オブジェクトで定義された次の 3 つのメソッドを使用してパフォーマンス エントリにアクセスできました。

これらの API は引き続きサポートされていますが、新しいエントリがエミットされたときにリッスンできないため、使用はおすすめしません。また、多くの新しい API(largest-contentful-paint など)は performance オブジェクトを介してではなく、PerformanceObserver を介してのみ公開されます。

Internet Explorer との互換性が特に必要な場合を除き、コードでこれらのメソッドを使用しないでください。今後は PerformanceObserver を使用してください。

User Timing API

対応ブラウザ

  • Chrome: 28.
  • Edge: 12.
  • Firefox: 38.
  • Safari: 11.

ソース

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 DevTools では、[パフォーマンス] パネルにユーザー タイミングの測定結果が可視化されます。また、多くの分析プロバイダでは、測定結果が自動的にトラッキングされ、所要時間データが分析バックエンドに送信されます。

User Timing の測定結果をレポートするには、PerformanceObserver を使用して、measure タイプのエントリを監視するように登録します。

// 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});

Long Tasks API

対応ブラウザ

  • Chrome: 58.
  • Edge: 79.
  • Firefox: サポートされていません。
  • Safari: サポートされていません。

ソース

Long Tasks API は、ブラウザのメインスレッドが、フレームレートや入力レイテンシに影響するほど長時間ブロックされているタイミングを知るのに役立ちます。API は、50 ミリ秒を超えて実行されたタスクを報告します。

負荷の高いコードを実行する必要がある場合や、大規模なスクリプトを読み込んで実行する必要がある場合は、そのコードがメインスレッドをブロックするかどうかを追跡すると便利です。実際、多くの上位レベルの指標(Time to Interactive(TTI)Total Blocking Time(TBT)など)は、Long Tasks API 自体の上に構築されています。

長時間のタスクが発生したタイミングを特定するには、PerformanceObserver を使用して、longtask タイプのエントリを監視するように登録します。

// 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});

Long Animation Frames API

対応ブラウザ

  • Chrome: 123。
  • Edge: 123。
  • Firefox: サポートされていません。
  • Safari: サポートされていません。

ソース

Long Animation Frames API は、Long Tasks API の新しいバージョンです。50 ミリ秒を超える長いタスクではなく、長いフレームを対象としています。これにより、アトリビューションの改善や、問題となる可能性のある遅延の範囲の拡大など、Long Tasks API の欠点の一部に対処できます。

長いフレームがいつ発生したかを特定するには、PerformanceObserver を使用して、long-animation-frame タイプのエントリを監視するように登録します。

// 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 `long-animation-frame` entries to be dispatched.
po.observe({type: 'long-animation-frame', buffered: true});

Element Timing API

対応ブラウザ

  • Chrome: 77。
  • Edge: 79.
  • Firefox: サポートされていません。
  • Safari: サポートされていません。

ソース

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>
  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});
</script>

Event Timing API

対応ブラウザ

  • Chrome: 76。
  • Edge: 79.
  • Firefox: 89。
  • Safari: サポートされていません。

ソース

Interaction to Next Paint(INP)指標は、ページの全期間に発生するすべてのクリック、タップ、キーボード操作をモニタリングすることで、ページの全体的な応答性を評価します。ページの INP は、ほとんどの場合、ユーザーがインタラクションを開始してから、ブラウザがユーザーの入力の視覚的な結果を示す次のフレームをペイントするまでの時間で、最も長くかかったインタラクションです。

INP 指標は Event Timing API によって実現されています。この API は、イベントのライフサイクル中に発生する次のようなさまざまなタイムスタンプを公開します。

  • startTime: ブラウザがイベントを受信した時刻。
  • processingStart: ブラウザがイベントのイベント ハンドラの処理を開始できる時刻。
  • processingEnd: ブラウザが、このイベントのイベント ハンドラから開始されたすべての同期コードの実行を完了した時刻。
  • duration: ブラウザがイベントを受信してから、イベント ハンドラから開始されたすべての同期コードの実行が完了し、次のフレームをペイントできるようになるまでの時間(セキュリティ上の理由から 8 ミリ秒に丸められます)。

次の例は、これらの値を使用してカスタム測定を作成する方法を示しています。

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 presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
    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 processing time (ms): ${processingTime}`);
    console.log(`Presentation delay (ms): ${presentationDelay}`);
    console.log(`Total event duration (ms): ${duration}`);
    console.log(`Event type: ${eventType}`);
    console.log(target);
  });
});

// A durationThreshold of 16ms is necessary to include more
// interactions, since the default is 104ms. The minimum
// durationThreshold is 16ms.
po.observe({type: 'event', buffered: true, durationThreshold: 16});

Resource Timing API

対応ブラウザ

  • Chrome: 29.
  • Edge: 12。
  • Firefox: 35.
  • Safari: 11.

ソース

Resource Timing API を使用すると、特定のページのリソースがどのように読み込まれたかをデベロッパーが詳しく把握できます。API の名前とは異なり、提供される情報はタイミング データに限定されません(ただし、タイミング データは豊富にあります)。アクセスできるその他のデータには、次のようなものがあります。

  • initiatorType: リソースの取得方法(<script> タグや <link> タグからの取得、fetch() 呼び出しからの取得など)。
  • nextHopProtocol: リソースの取得に使用されるプロトコル(h2quic など)。
  • encodedBodySize / decodedBodySize: エンコードまたはデコードされた形式のリソースのサイズ(それぞれ)
  • transferSize: ネットワーク経由で実際に転送されたリソースのサイズ。リソースがキャッシュによって提供される場合、この値は encodedBodySize よりもはるかに小さくなることがあります。また、キャッシュの再検証が不要な場合はゼロになることもあります。

リソース タイミング エントリの transferSize プロパティを使用して、キャッシュ ヒット率指標またはキャッシュに保存されたリソースの合計サイズ指標を測定できます。これは、リソース キャッシュ戦略がリピーターのパフォーマンスにどのように影響しているかを把握するのに役立ちます。

次の例では、ページによってリクエストされたすべてのリソースをログに記録し、各リソースがキャッシュによって提供されたかどうかを示します。

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // If transferSize is 0, the resource was fulfilled using the cache.
    console.log(entry.name, entry.transferSize === 0);
  }
});

// Start listening for `resource` entries to be dispatched.
po.observe({type: 'resource', buffered: true});

対応ブラウザ

  • Chrome: 57
  • Edge: 12.
  • Firefox: 58.
  • Safari: 15。

ソース

Navigation Timing API は Resource Timing API に似ていますが、ナビゲーション リクエストのみをレポートします。navigation エントリ タイプも resource エントリ タイプに似ていますが、ナビゲーション リクエストに固有の追加情報が含まれています(DOMContentLoaded イベントと load イベントがトリガーされたときなど)。

多くのデベロッパーがサーバー レスポンス時間(Time to First Byte(TTFB))を把握するために追跡する指標は、Navigation Timing API を使用して利用できます。具体的には、エントリの responseStart タイムスタンプです。

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // If transferSize is 0, the resource was fulfilled using the cache.
    console.log('Time to first byte', entry.responseStart);
  }
});

// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});

Service Worker を使用するデベロッパーが重要視するもう一つの指標は、ナビゲーション リクエストの Service Worker の起動時間です。これは、ブラウザが Service Worker スレッドを開始してから fetch イベントのインターセプトを開始するまでの時間です。

特定のナビゲーション リクエストの Service Worker の起動時間は、entry.responseStartentry.workerStart の差から判断できます。

// 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});

Server Timing API

対応ブラウザ

  • Chrome: 65。
  • Edge: 79.
  • Firefox: 61.
  • Safari: 16.4。

ソース

Server Timing API を使用すると、レスポンス ヘッダーを介して、リクエスト固有のタイミング データをサーバーからブラウザに渡すことができます。たとえば、特定のリクエストに対してデータベースでデータを検索するのに要した時間を指定できます。これは、サーバーの遅延が原因で発生したパフォーマンスの問題のデバッグに役立ちます。

サードパーティの分析プロバイダを使用するデベロッパーにとって、サーバー パフォーマンス データを、これらの分析ツールが測定している他のビジネス指標と関連付ける唯一の方法は、Server Timing API です。

レスポンスでサーバーの時間データを指定するには、Server-Timing レスポンス ヘッダーを使用します。次に例を示します。

HTTP/1.1 200 OK

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

その後、ページから、Resource Timing API と Navigation Timing API の resource エントリまたは navigation エントリの両方でこのデータを読み取ることができます。

// 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});