カスタム指標

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

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

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

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

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

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

これまでウェブ デベロッパーは、パフォーマンスを測定するための低レベル API をあまり用意していなかったため、サイトのパフォーマンスを測定するためにハッキングに頼る必要がありました。

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

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

Performance Observer API

対応ブラウザ

  • 52
  • 79
  • 57
  • 11

ソース

Performance Observer API は、このページで説明する他のすべてのパフォーマンス 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,
});

従来のパフォーマンス API を使用して

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

これらの 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 DevTools では [パフォーマンス] パネルにカスタム速度の測定値が可視化されます。また、多くの分析プロバイダも自動的に測定を行い、時間データを分析バックエンドに送信します。

カスタム速度の測定値を報告するには、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.
}

長時間タスク API

対応ブラウザ

  • 58
  • 79
  • x
  • x

ソース

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

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

時間のかかるタスクが発生するタイミングを判断するには、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.
}

Element Timing API

対応ブラウザ

  • 77
  • 79
  • x
  • x

ソース

Largest Contentful Paint(LCP)指標は、最も大きな画像またはテキスト ブロックが画面に描画されたタイミングを把握するのに便利ですが、別の要素のレンダリング時間を測定したい場合もあるでしょう。

このような場合は、Element Timing API を使用します。LCP API は Element Timing API の上に構築されており、最も大きいコンテンツの要素を自動的にレポートします。ただし、他の要素に明示的に elementtiming 属性を追加し、element エントリタイプを監視する PerformanceObserver を登録することで、他の要素もレポートできます。

<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>

Event Timing API

Interaction to Next Paint(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 エントリタイプに似ていますが、ナビゲーション リクエストのみに固有の追加情報DOMContentLoaded イベントと load イベントが発生したタイミングなど)が含まれています。

多くのデベロッパーがサーバーの応答時間(Time to First Byte(TTFB))を理解するために追跡している指標の一つは、Navigation Timing API、具体的にはエントリの responseStart タイムスタンプです。

// 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.
}

Server Timing API

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 エントリの両方について、このデータを読み取ることができます。

// 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.
}