特定のウェブサイトで普遍的に測定できるユーザー中心の指標を持つことは、大きな価値があります。これらの指標を使用すると、次のことが可能になります。
- 実際のユーザーがウェブ全体をどのように利用しているかを把握する。
- 自分のサイトを競合他社のサイトと比較する。
- カスタム コードを記述しなくても、分析ツールで有用で実用的なデータをトラッキングできます。
ユニバーサル指標は優れたベースラインとなりますが、多くの場合、特定のサイトの全体的なエクスペリエンスを把握するには、これらの指標以外にも多くの指標を測定する必要があります。
カスタム指標を使用すると、サイトにのみ適用されるサイト エクスペリエンスの要素を測定できます。たとえば、次のような指標を作成できます。
- シングルページ アプリ(SPA)が 1 つの「ページ」から別の「ページ」に遷移するまでの時間。
- ログインしたユーザーに対して、データベースから取得したデータをページに表示するまでの時間。
- サーバーサイド レンダリング(SSR)アプリのハイドレーションにかかる時間。
- リピーターによって読み込まれたリソースのキャッシュ ヒット率。
- ゲーム内のクリック イベントまたはキーボード イベントのイベント レイテンシ。
カスタム指標を測定する API
これまで、ウェブ デベロッパーはパフォーマンスを測定する低レベルの API をあまり持っていませんでした。そのため、サイトのパフォーマンスが良好かどうかを測定するためにハックに頼らざるを得ませんでした。
たとえば、requestAnimationFrame
ループを実行して各フレーム間の差分を計算することで、長時間実行される JavaScript タスクが原因でメインスレッドがブロックされているかどうかを判断できます。差分がディスプレイのフレームレートよりも大幅に長い場合は、長いタスクとして報告できます。ただし、このようなハックは、バッテリーの消耗など、実際にパフォーマンスに影響するため、おすすめしません。
効果的なパフォーマンス測定の第一のルールは、パフォーマンス測定手法自体がパフォーマンスの問題を引き起こしていないことを確認することです。そのため、サイトで測定するカスタム指標については、可能な限り次のいずれかの API を使用することをおすすめします。
Performance Observer API
Performance Observer API は、このページで説明する他のすべてのパフォーマンス 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
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
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
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
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
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
Resource Timing API を使用すると、特定のページのリソースの読み込み方法について、デベロッパーは詳細な分析情報を得ることができます。API の名前とは異なり、提供される情報はタイミング データに限定されません(ただし、タイミング データは豊富に提供されます)。アクセスできるその他のデータには、次のようなものがあります。
initiatorType
: リソースの取得方法(<script>
タグまたは<link>
タグからの取得、fetch()
呼び出しからの取得など)。nextHopProtocol
: リソースの取得に使用されるプロトコル(h2
やquic
など)。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});
Navigation Timing API
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 を使用する開発者が注目する可能性があるもう 1 つの指標は、ナビゲーション リクエストの Service Worker の起動時間です。これは、ブラウザが Service Worker スレッドを開始してから fetch イベントのインターセプトを開始するまでの時間です。
特定のナビゲーション リクエストの Service Worker の起動時間は、entry.responseStart
と entry.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
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});