パフォーマンス API の概要
公開: 2022-08-10パフォーマンス API は、実際のユーザー デバイスとネットワーク接続でのライブ Web アプリケーションの応答性を測定します。 以下を使用して、クライアント側とサーバー側のコードのボトルネックを特定するのに役立ちます。
- ユーザーのタイミング:クライアント側の JavaScript 関数のパフォーマンスのカスタム測定
- ペイントのタイミング:ブラウザのレンダリング メトリック
- リソースのタイミング:アセットの読み込みパフォーマンスと Ajax 呼び出し
- ナビゲーションのタイミング:リダイレクト、DNS ルックアップ、DOM の準備状況などを含むページ読み込みの指標
API は、一般的なパフォーマンス評価に関連するいくつかの問題に対処します。
- 開発者は、高速ネットワークに接続されたハイエンド PC でアプリケーションをテストすることがよくあります。 DevTools は低速のデバイスをエミュレートできますが、大部分のクライアントが空港の WiFi に接続された 2 年前のモバイルを実行している場合、現実の問題を常に浮き彫りにするわけではありません。
- Google アナリティクスなどのサードパーティのオプションがブロックされることが多く、偏った結果や仮定につながります。 また、一部の国ではプライバシーへの影響が発生する場合があります。
- Performance API は、
Date()
などのメソッドよりも優れたさまざまなメトリックを正確に測定できます。
次のセクションでは、パフォーマンス API の使用方法について説明します。 JavaScript とページ読み込みメトリクスに関するある程度の知識が推奨されます。
パフォーマンス API の可用性
IE10 や IE11 を含む最新のブラウザーのほとんどは、Performance API をサポートしています (IE9 でもサポートは限定的です)。 以下を使用して API の存在を検出できます。
if ('performance' in window) { // use Performance API }
API を完全にポリフィルすることはできないため、ブラウザーの欠落に注意してください。 ユーザーの 90% が Internet Explorer 8 で快適にブラウジングしている場合、より高性能なアプリケーションを使用しているクライアントの 10% しか測定していません。
この API は Web ワーカーで使用できます。これにより、ブラウザーの操作を停止することなくバックグラウンド スレッドで複雑な計算を実行する方法が提供されます。
ほとんどの API メソッドは、標準の perf_hooks モジュールを使用してサーバー側 Node.js で使用できます。
// Node.js performance import { performance } from 'node:perf_hooks'; // or in Common JS: const { performance } = require('node:perf_hooks'); console.log( performance.now() );
Deno は、標準のパフォーマンス API を提供します。
// Deno performance console.log( performance.now() );
高解像度の時間測定を有効にするには、 --allow-hrtime
権限でスクリプトを実行する必要があります。
deno run --allow-hrtime index.js
通常、サーバー側のパフォーマンスは、負荷、CPU、RAM、ハード ディスク、およびクラウド サービスの制限に依存するため、評価と管理が容易です。 PM2、クラスタリング、Kubernetes などのハードウェアのアップグレードやプロセス管理オプションは、コードのリファクタリングよりも効果的です。
このため、以下のセクションではクライアント側のパフォーマンスに焦点を当てます。
カスタム パフォーマンス測定
Performance API を使用して、アプリケーション関数の実行速度を計ることができます。 Date()
を使用してタイミング関数を使用または遭遇した可能性があります。
const timeStart = new Date(); runMyCode(); const timeTaken = new Date() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);
Performance API には、主に次の 2 つの利点があります。
- 精度の向上:
Date()
は最も近いミリ秒単位で測定されますが、パフォーマンス API はミリ秒の端数を測定できます (ブラウザーによって異なります)。 - 信頼性の向上:ユーザーまたは OS がシステム時刻を変更できるため、
Date()
ベースのメトリックが常に正確であるとは限りません。 これは、時計が進むと、関数が特に遅く見える可能性があることを意味します!
Date()
に相当するものはperformance.now()
で、ドキュメントの作成を担当するプロセスが開始された (ページが読み込まれた) ときにゼロに設定される高解像度のタイムスタンプを返します。
const timeStart = performance.now(); runMyCode(); const timeTaken = performance.now() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);
非標準のperformance.timeOrigin
プロパティは、1970 年 1 月 1 日からのタイムスタンプを返すこともできますが、これは IE と Deno では利用できません。
performance.now()
は、数回以上の測定を行うと実用的ではなくなります。 Performance API は、ラベル名をperformance.mark()
に渡すことで、後で分析するためにイベントを記録できるバッファーを提供します。
performance.mark('start:app'); performance.mark('start:init'); init(); // run initialization functions performance.mark('end:init'); performance.mark('start:funcX'); funcX(); // run another function performance.mark('end:funcX'); performance.mark('end:app');
パフォーマンス バッファ内のすべてのマーク オブジェクトの配列は、次を使用して抽出できます。
const mark = performance.getEntriesByType('mark');
結果の例:
[ { detail: null duration: 0 entryType: "mark" name: "start:app" startTime: 1000 }, { detail: null duration: 0 entryType: "mark" name: "start:init" startTime: 1001 }, { detail: null duration: 0 entryType: "mark" name: "end:init" startTime: 1100 }, ... ]
performance.measure()
メソッドは、2 つのマーク間の時間を計算し、パフォーマンス バッファにも格納します。 新しいメジャー名、開始マーク名 (またはページ読み込みから測定する場合は null)、および終了マーク名 (または現在の時間まで測定する場合は null) を渡します。
performance.measure('init', 'start:init', 'end:init');
PerformanceMeasure オブジェクトは、計算された継続時間とともにバッファーに追加されます。 この値を取得するには、すべてのメジャーの配列をリクエストできます。
const measure = performance.getEntriesByType('measure');
またはその名前でメジャーをリクエストします。
performance.getEntriesByName('init');
結果の例:
[ { detail: null duration: 99 entryType: "measure" name: "init" startTime: 1001 } ]
パフォーマンス バッファの使用
マークとメジャーだけでなく、パフォーマンス バッファーを使用して、ナビゲーション タイミング、リソース タイミング、およびペイント タイミング (後で説明します) を自動的に記録します。 バッファ内のすべてのエントリの配列を取得できます。
performance.getEntries();
デフォルトでは、ほとんどのブラウザーは、最大 150 のリソース メトリックを格納するバッファーを提供します。 ほとんどの評価ではこれで十分ですが、必要に応じてバッファ制限を増減できます。
// record 500 metrics performance.setResourceTimingBufferSize(500);
マークは名前でクリアすることも、空の値を指定してすべてのマークをクリアすることもできます。
performance.clearMarks('start:init');
同様に、メジャーは名前または空の値でクリアしてすべてをクリアできます。
performance.clearMeasures();
パフォーマンス バッファの更新の監視
PerformanceObserverは、Performance バッファーへの変更を監視し、特定のイベントが発生したときに関数を実行できます。 MutationObserverを使用して DOM の更新に応答したり、 IntersectionObserverを使用して要素がビューポートにスクロールされたことを検出したりした場合、構文はおなじみでしょう。
次の 2 つのパラメーターを使用してオブザーバー関数を定義する必要があります。
- 検出されたオブザーバー エントリの配列、および
- オブザーバー オブジェクト。 必要に応じて、その
disconnect()
メソッドを呼び出してオブザーバーを停止できます。
function performanceCallback(list, observer) { list.getEntries().forEach(entry => { console.log(`name : ${ entry.name }`); console.log(`type : ${ entry.type }`); console.log(`start : ${ entry.startTime }`); console.log(`duration: ${ entry.duration }`); }); }
関数は新しい PerformanceObserver オブジェクトに渡されます。 そのobserve()
メソッドには、監視するパフォーマンス バッファの entryTypes の配列が渡されます。
let observer = new PerformanceObserver( performanceCallback ); observer.observe({ entryTypes: ['mark', 'measure'] });
この例では、新しいマークまたはメジャーを追加するとperformanceCallback()
関数が実行されます。 ここではメッセージのみをログに記録しますが、データのアップロードをトリガーしたり、さらに計算を行うために使用できます。
塗装性能の測定
Paint Timing API は、クライアント側の JavaScript でのみ使用でき、Core Web Vitals にとって重要な 2 つのメトリックを自動的に記録します。
- first-paint:ブラウザーがページの描画を開始しました。
- first-contentful-paint:ブラウザーは、見出しや画像など、DOM コンテンツの最初の重要な項目を描画しました。
これらは、パフォーマンス バッファから配列に抽出できます。
const paintTimes = performance.getEntriesByType('paint');
ページが完全にロードされる前にこれを実行することには注意してください。 値は準備できません。 window.load
イベントを待つか、 PerformanceObserver
を使用してpaint
の entryType を監視します。
結果の例:
[ { "name": "first-paint", "entryType": "paint", "startTime": 812, "duration": 0 }, { "name": "first-contentful-paint", "entryType": "paint", "startTime": 856, "duration": 0 } ]
最初の描画が遅いのは、多くの場合、レンダリングをブロックする CSS または JavaScript が原因です。 ブラウザーが大きな画像をダウンロードしたり、複雑な要素をレンダリングしたりする必要がある場合、first-contentful-paint とのギャップが大きくなる可能性があります。
リソースのパフォーマンス測定
画像、スタイルシート、JavaScript ファイルなどのリソースのネットワーク タイミングは、パフォーマンス バッファーに自動的に記録されます。 ネットワーク速度の問題を解決するためにできることはほとんどありませんが (ファイル サイズを縮小する以外に)、大きなアセット、遅い Ajax 応答、パフォーマンスの悪いサードパーティ スクリプトなどの問題を浮き彫りにするのに役立ちます。
PerformanceResourceTiming メトリックの配列は、次を使用してバッファから抽出できます。
const resources = performance.getEntriesByType('resource');
または、アセットの完全な URL を渡すことで、アセットのメトリクスを取得できます。
const resource = performance.getEntriesByName('https://test.com/script.js');
結果の例:
[ { connectEnd: 195, connectStart: 195, decodedBodySize: 0, domainLookupEnd: 195, domainLookupStart: 195, duration: 2, encodedBodySize: 0, entryType: "resource", fetchStart: 195, initiatorType: "script", name: "https://test.com/script.js", nextHopProtocol: "h3", redirectEnd: 0, redirectStart: 0, requestStart: 195, responseEnd: 197, responseStart: 197, secureConnectionStart: 195, serverTiming: [], startTime: 195, transferSize: 0, workerStart: 195 } ]
次のプロパティを調べることができます。
- name : リソース URL
- entryType : 「リソース」
- initiatorType : リソースが開始された方法 (「スクリプト」や「リンク」など)
- serverTiming : HTTP Server-Timing ヘッダーでサーバーから渡される
PerformanceServerTiming
オブジェクトの配列 (サーバー側アプリケーションは、さらに分析するためにメトリックをクライアントに送信できます) - startTime : 取得開始時のタイムスタンプ
- nextHopProtocol : 使用されるネットワーク プロトコル
- workerStart : プログレッシブ Web App サービス ワーカーを開始する前のタイムスタンプ (リクエストがサービス ワーカーによってインターセプトされない場合は 0)
- redirectStart : リダイレクト開始時のタイムスタンプ
- redirectEnd : 最後のリダイレクト応答の最後のバイトの後のタイムスタンプ
- fetchStart : リソース取得前のタイムスタンプ
- domainLookupStart : DNS ルックアップ前のタイムスタンプ
- domainLookupEnd : DNS ルックアップ後のタイムスタンプ
- connectStart : サーバー接続を確立する前のタイムスタンプ
- connectEnd : サーバー接続確立後のタイムスタンプ
- secureConnectionStart : SSL ハンドシェーク前のタイムスタンプ
- requestStart : ブラウザがリソースをリクエストする前のタイムスタンプ
- responseStart : ブラウザーがデータの最初のバイトを受信したときのタイムスタンプ
- responseEnd : 最後のバイトを受信した後、または接続を閉じた後のタイムスタンプ
- duration : startTime と responseEnd の差
- transferSize : ヘッダーと圧縮された本文を含むバイト単位のリソース サイズ
- encodedBodySize : 解凍前のリソース本体 (バイト単位)
- decodedBodySize : 解凍後のリソース本体 (バイト単位)
このサンプル スクリプトは、Fetch API によって開始されたすべての Ajax リクエストを取得し、合計転送サイズと期間を返します。
const fetchAll = performance.getEntriesByType('resource') .filter( r => r.initiatorType === 'fetch') .reduce( (sum, current) => { return { transferSize: sum.transferSize += current.transferSize, duration: sum.duration += current.duration } }, { transferSize: 0, duration: 0 } );
ナビゲーション性能測定
前のページのアンロードと現在のページのロードのネットワーク タイミングは、単一のPerformanceNavigationTiming
オブジェクトとしてパフォーマンス バッファーに自動的に記録されます。
次を使用して配列に抽出します。
const pageTime = performance.getEntriesByType('navigation');
…またはページ URL を.getEntriesByName()
に渡すことによって:
const pageTiming = performance.getEntriesByName(window.location);
メトリックはリソースのメトリックと同じですが、ページ固有の値も含まれています。
- entryType : 例: 「ナビゲーション」
- type : 「navigate」、「reload」、「back_forward」、または「prerender」のいずれか
- redirectCount : リダイレクトの数
- unloadEventStart : 前のドキュメントのアンロード イベントの前のタイムスタンプ
- unloadEventEnd : 前のドキュメントのアンロード イベント後のタイムスタンプ
- domInteractive : ブラウザが HTML を解析して DOM を構築したときのタイムスタンプ
- domContentLoadedEventStart : ドキュメントの DOMContentLoaded イベントが発生する前のタイムスタンプ
- domContentLoadedEventEnd : ドキュメントの DOMContentLoaded イベントが完了した後のタイムスタンプ
- domComplete : DOM 構築および DOMContentLoaded イベントが完了した後のタイムスタンプ
- loadEventStart : ページ読み込みイベントが発生する前のタイムスタンプ
- loadEventEnd : ページ読み込みイベント後のタイムスタンプとすべてのアセットが利用可能
典型的な問題は次のとおりです。
- unloadEventEndとdomInteractiveの間の長い遅延。 これは、サーバーの応答が遅いことを示している可能性があります。
- domContentLoadedEventStartとdomCompleteの間の長い遅延。 これは、ページの起動スクリプトが遅すぎることを示している可能性があります。
- domCompleteとloadEventEndの間の長い遅延。 これは、ページにアセットが多すぎるか、いくつかのアセットの読み込みに時間がかかりすぎることを示している可能性があります。
パフォーマンスの記録と分析
Performance API を使用すると、実際の使用状況データを収集し、それをサーバーにアップロードしてさらに分析することができます。 Google アナリティクスなどのサードパーティ サービスを使用してデータを保存することもできますが、サードパーティのスクリプトがブロックされたり、新しいパフォーマンスの問題が発生したりするリスクがあります。 独自のソリューションを要件に合わせてカスタマイズして、監視が他の機能に影響を与えないようにすることができます。
ユーザーが古いブラウザーを使用している、JavaScript をブロックしている、または企業のプロキシの背後にいるなどの理由で、統計を判断できない状況に注意してください。 どのデータが欠落しているかを理解することは、不完全な情報に基づいて仮定を立てるよりも有益です。
理想的には、複雑な計算を実行したり、大量のデータをアップロードしたりして、分析スクリプトがパフォーマンスに悪影響を与えないようにします。 Web ワーカーを利用し、同期 localStorage 呼び出しの使用を最小限に抑えることを検討してください。 生データを後でバッチ処理することはいつでも可能です。
最後に、統計に悪影響を与える、非常に高速または非常に遅いデバイスや接続などの異常値に注意してください。 たとえば、9 人のユーザーが 2 秒でページを読み込み、10 番目のユーザーが 60 秒のダウンロードを経験した場合、平均待ち時間は 8 秒近くになります。 より現実的な指標は、中央値 (2 秒) または 90 パーセンタイル (10 人中 9 人のユーザーが 2 秒以下の読み込み時間を経験) です。
概要
Web パフォーマンスは、開発者にとって依然として重要な要素です。 ユーザーは、ほとんどのデバイスでサイトとアプリケーションが応答することを期待しています。 遅いサイトは Google でダウングレードされるため、検索エンジンの最適化も影響を受ける可能性があります。
パフォーマンス監視ツールはたくさんありますが、ほとんどの場合、サーバー側の実行速度を評価するか、限られた数の対応クライアントを使用してブラウザーのレンダリングを判断します。 Performance API は、他の方法では計算できない実際のユーザー指標を照合する方法を提供します。