長いアニメーション フレーム API

Barry Pollard
Barry Pollard
Noam Rosenthal
Noam Rosenthal

Long Animation Frames API���LoAF と発音される Lo-Af)は、Long Tasks API のアップデートであり、遅いユーザー インターフェース(UI)の更新について理解を深めます。これは、応答性を測定する Interaction to Next Paint(INP) Core Web Vitals 指標に影響する可能性が高い低速なアニメーション フレームを特定する場合や、スムーズに影響する他の UI ジャンクを特定する場合に役立ちます。

API のステータス

対応ブラウザ

  • 123
  • 123
  • x
  • x

ソース

Chrome 116 から Chrome 122 へのオリジン トライアルの後、LoAF API は Chrome 123 からリリースされました。

バックグラウンド: Long Tasks API

対応ブラウザ

  • 58
  • 79
  • x
  • x

ソース

Long Animation Frames API は、Long Tasks API の代替となるものです。Long Tasks API は、しばらく前から Chrome で使用できました(Chrome 58 以降)。その名前が示すように、Long Task API では、メインスレッドを 50 ミリ秒以上占有する長いタスクをモニタリングできます。時間のかかるタスクは、PerformanceLongTaskTiming インターフェースと PeformanceObserver を使用してモニタリングできます。

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: 'longtask', buffered: true });

長時間のタスクは、応答性の問題を��き起こす可能性があります。ユーザーがページを操作する(たとえば、ボタンをクリックする、メニューを開くなど)と、メインスレッドがすでに長いタスクを処理している場合、そのタスクの完了を待つユーザーのインタラクションが遅延します

応答性を向上させるには、長いタスクを分割することが推奨されます。時間のかかる各タスクを一連の複数の小さなタスクに分割すれば、より重要なタスクを間に実行して、インタラクションへの応答で大幅な遅延を回避できます。

そのため、応答性を改善しようとする場合、まずはパフォーマンス トレースを実行して時間のかかるタスクを確認することになることがよくあります。これには、Lighthouse(長いメインスレッド タスクを避けるの監査がある)のようなラボベースの監査ツールを利用するか、Chrome DevTools で長いタスクを確認することができます。

ラボベースのテストは、応答性の問題を特定するための出発点として適切でないことが多い。これらのツールにはインタラクションが含まれていない場合があるからです。インタラクションが含まれている場合でも、想定されるインタラクションのごく一部にすぎません。現場でのやり取りが遅い原因を測定するのが理想的です。

Long Tasks API の欠点

現場での時間のかかるタスクを Performance Observer で測定しても、役に立つ場面はそれほど多くありません。実際には、長いタスクが行われたという事実と、それにかかった時間以外の多くの情報は得られません。

リアルタイム ユーザー モニタリング(RUM)ツールは多くの場合、長いタスクの数や所要時間を傾向として把握したり、それらが発生したページを特定したりするためにこれを使用しますが、長時間のタスクの原因に関する根本的な詳細情報がなければ、これは限られた用途に過ぎません。Long Tasks API には、基本的なアトリビューション モデルのみがあります。このモデルでは、長いタスクが発生したコンテナ(トップレベルのドキュメントまたは <iframe>)のみがわかりますが、それを呼び出したスクリプトや関数はわかりません。一般的なエントリをご覧ください。

{
  "name": "unknown",
  "entryType": "longtask",
  "startTime": 31.799999997019768,
  "duration": 136,
  "attribution": [
    {
      "name": "unknown",
      "entryType": "taskattribution",
      "startTime": 0,
      "duration": 0,
      "containerType": "window",
      "containerSrc": "",
      "containerId": "",
      "containerName": ""
    }
  ]
}

また、Long Tasks API は、一部の重要なタスクを除外する可能性があるため、不完全なビューです。レンダリングなどの一部の更新は、前の実行と一緒に含めることが理想的である個別のタスクで発生するため、その更新でそのインタラクションの「総作業」を正確に測定できます。タスクへの依存の限界について詳しくは、説明の「長時間のタスクがうまくいかない場合」セクションをご覧ください。

最後の問題���、長いタスクを測定しても、50 ミリ秒の制限を超える個々のタスクのみがレポートされることです。アニメーション フレームは、この 50 ミリ秒の制限よりも小さい複数のタスクで構成される場合がありますが、それでも全体としてブラウザのレンダリング能力はブロックされます。

Long Animation Frames API

対応ブラウザ

  • 123
  • 123
  • x
  • x

ソース

Long Animation Frames API(LoAF)は、Long Tasks API の欠点のいくつかに対処することを目指した新しい API です。応答性の問題への対処と INP の改善に役立つ実用的な分析情報をデベロッパーが取得できるようにすることを目的としています。

応答性が高いとは、ページで行われた操作に対する応答が速いことを意味します。そのためには、ユーザーが必要とする更新を適切なタイミングで描画できるようにし、更新の実行をブロックしないようにする必要があります。INP では 200 ミリ秒以下で応答することをおすすめしますが、その他の更新(アニメーションなど)では、これでも応答が長すぎる可能性があります。

Long Animation Frames API は、ブロッキング処理の測定に代わる手法です。Long Animation Frames API では、個々のタスクではなく、長いアニメーション フレームが測定されます。長いアニメーション フレームとは、レンダリングの更新が 50 ミリ秒(Long Tasks API のしきい値と同じ)を超えて遅延する場合です。

長いアニメーション フレームは、PerformanceObserver を使用して長いタスクと同様の方法で監視できますが、代わりに long-animation-frame タイプに注目してください。

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: 'long-animation-frame', buffered: true });

以前の長いアニメーション フレームは、次のようにパフォーマンス タイムラインからクエリすることもできます。

const loafs = performance.getEntriesByType('long-animation-frame');

ただし、パフォーマンス エントリに maxBufferSize があり、その後新しいエントリは破棄される���め、PerformanceObserver アプローチをおすすめします。long-animation-frame のバッファサイズは、long-tasks と同じ 200 に設定されています。

タスクではなくフレームを見るメリット

タスクの視点ではなくフレームの視点からこれを考える主な利点は、長いアニメーションが、累積して長いアニメーション フレームになる任意の数のタスクで構成されていることです。これにより、前述の最後の点が解消されます。アニメーション フレームの前にレンダリングをブロックする多くの小さなタスクの合計が、Long Tasks API では表示されない可能性があります。

長いタスクに対するこの代替ビューのさらなる利点は、フレーム全体のタイミングの内訳を提供できることです。Long Tasks API のように単に startTimeduration を含めるのではなく、LoAF にはフレーム時間のさまざまな部分について、次のような詳細な内訳が含まれています。

  • startTime: ナビゲーションの開始時間に対する相対的な長いアニメーション フレームの開始時間。
  • duration: 長いアニメーション フレームの再生時間(プレゼンテーション時間は含みません)。
  • renderStart: レンダリング サイクルの開始時間。requestAnimationFrame コールバック、スタイルとレイアウトの計算、サイズ変更オブザーバー、交差オブザーバーのコールバックが含まれます。
  • styleAndLayoutStart: スタイルとレイアウトの計算にかかった期間の開始。
  • firstUIEventTimestamp: このフレーム中に処理される最初の UI イベント(マウス、キーボードなど)の時間。
  • blockingDuration: アニメーション フレームがブロックされた時間(ミリ秒単位)。

これらのタイムスタンプにより、長いアニメーション フレームをタイミングに分割できます。

時間配分 計算
開始時刻 startTime
終了時刻 startTime + duration
処理期間 renderStart ? renderStart - startTime : duration
レンダリング所要時間 renderStart ? (startTime + duration) - renderStart: 0
レンダリング: プレレイアウト時間 styleAndLayoutStart ? styleAndLayoutStart - renderStart : 0
レンダリング: スタイルとレイアウトの時間 styleAndLayoutStart ? (startTime + duration) - styleAndLayoutStart : 0

個々のタイミングについて詳しくは、説明をご覧ください。長いアニメーション フレームに寄与しているアクティビティについて、詳しく説明されています。

アトリビューションの改善

long-animation-frame エントリタイプを使用すると、長いアニメーション フレームの原因となった各スクリプトのアトリビューション データが改善されます。

Long Tasks API と同様に、これは属性エントリの配列で提供されます。各エントリには次の詳細が含まれます。

  • nameEntryType はどちらも script を返します。
  • スクリプトの呼び出し方法を示すわかりやすい invoker'IMG#id.onload''Window.requestAnimationFrame''Response.json.then' など)。
  • スクリプトのエントリ ポイントの invokerType:
    • user-callback: ウェブ プラットフォーム API(setTimeoutrequestAnimationFrame など)から登録された既知のコールバック。
    • event-listener: プラットフォーム イベント(clickloadkeyup など)のリスナー。
    • resolve-promise: プラットフォーム Promise のハンドラ(例: fetch())。Promise の場合、同じ Promise のすべてのハンドラが 1 つの「スクリプト」として混在することに注意してください)。.
    • reject-promise: resolve-promise に従いますが、拒否用です。
    • classic-script: スクリプト評価(例: <script>import()
    • module-script: classic-script と同じですが、モジュール スクリプト用です。
  • そのスクリプトの個別のタイミング データ:
    • startTime: エントリ関数が呼び出された時刻。
    • duration: startTime から後続のマイクロタスク キューが処理を完了するまでの時間。
    • executionStart: コンパイル後の時刻。
    • forcedStyleAndLayoutDuration: この関数内の強制レイアウトとスタイルの処理にかかった合計時間(スラッシングを参照)。
    • pauseDuration: 同期オペレーション(アラート、同期 XHR)の「一時停止」にかかった合計時間。
  • スクリプト ソースの詳細:
    • sourceURL: 使用可能なスクリプト リソース名(見つからない場合は空)。
    • sourceFunctionName: スクリプト関数名(使用可能な場合)。見つからない場合は、空になります。
    • sourceCharPosition: 使用可能なスクリプト文字の位置(検出されない場合は -1)。
  • windowAttribution: 長いアニメーション フレームが発生したコンテナ(最上位ドキュメントまたは <iframe>)。
  • window: 同一オリジン ウィンドウへの参照。

ソース エントリが指定されている場合、デベロッパーは長いアニメーション フレーム内の各スクリプトがどのように呼び出されたかを、呼び出し元のスクリプト内の文字位置まで正確に把握できます。これにより、長いアニメーション フレームにつながった JavaScript リソース内の正確な位置がわかります。

long-animation-frame パフォーマンス エントリの例

単一のスクリプトを含む完全な long-animation-frame パフォーマンス エントリの例を次に示します。

{
  "blockingDuration": 0,
  "duration": 60,
  "entryType": "long-animation-frame",
  "firstUIEventTimestamp": 11801.099999999627,
  "name": "long-animation-frame",
  "renderStart": 11858.800000000745,
  "scripts": [
    {
      "duration": 45,
      "entryType": "script",
      "executionStart": 11803.199999999255,
      "forcedStyleAndLayoutDuration": 0,
      "invoker": "DOMWindow.onclick",
      "invokerType": "event-listener",
      "name": "script",
      "pauseDuration": 0,
      "sourceURL": "https://web.dev/js/index-ffde4443.js",
      "sourceFunctionName": "myClickHandler",
      "sourceCharPosition": 17796,
      "startTime": 11803.199999999255,
      "window": [Window object],
      "windowAttribution": "self"
    }
  ],
  "startTime": 11802.400000000373,
  "styleAndLayoutStart": 11858.800000000745
}

ご覧のように、これにより、前例のない量のデータが得られ、ウェブサイトはレンダリング更新の遅延の原因を把握することができます。

フィールドで Long Animation Frames API を使用する

Chrome DevTools や Lighthouse などのツールは、問題の発見と再現には役立ちますが、フィールド データでしか得られないユーザー エクスペリエンスの重要な側面を見落とす可能性があります。

Long Animation Frames API は、Long Tasks API ではできなかった、ユーザー操作に関する重要なコ����キ����� データ���収集するために、フィールドで使用するように設計されています。これにより、他の方法では発見できなかった可能性のあるインタラクティビティに関する問題を特定し、再現できます。

Long Animation Frames API サポートの機能検出

次のコードを使用すると、API がサポートされているかどうかをテストできます。

if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
  // Monitor LoAFs
}

Long Animation Frames API の最も明白なユースケースは、Interaction to Next Paint(INP) の問題の診断と修正を支援することです。これが Chrome チームがこの API を開発した主な理由の一つでした。優れた INP とは、インタラクションからフレームが描画されるまで、すべてのインタラクションに 200 ミリ秒以内で応答することです。Long Animation Frames API は、所要時間が 50 ミリ秒以上のフレームをすべて測定するため、問題のある INP には、インタラクションの診断に役立つ LoAF データが含まれているはずです。

次の図に示すように、「INP LoAF」は INP インタラクションを含む LoAF です。

ページ上の長いアニメーション フレームの例(INP LoAF がハイライト表示されている)。
1 つのページに多くの LoAF が存在することがあります。そのうちの 1 つは INP インタラクションに関連しています。

場合によっては、INP イベントが 2 つの LoAF にまたがる可能性があります。通常は、前のフレームのレンダリング部分がフレームで開始された後にインタラクションが発生し、次のフレームで処理したイベント ハンドラがある場合です。

ページ上の長いアニメーション フレームの例(INP LoAF がハイライト表示されている)。
1 つのページに多くの LoAF が存在することがあります。そのうちの 1 つは INP インタラクションに関連しています。

まれに、3 つ以上の LoAF にまたがることもあります。

INP インタラクションに関連する LoAF(s) データを記録すると、診断に役立つ INP インタラクションに関する詳細情報を取得できます。これは、入力遅延を把握するうえで特に役立ちます。そのフレームで他のどのスクリプトが実行されているかを確認できるためです。

また、デベロッパーが確認した値をイベント ハンドラが再現しない場合、原因不明の処理時間表示の遅延を把握しておくと役に立ちます。テストには含まれない他のスクリプトがユーザーに対して実行されている可能性があるためです。

INP エントリを関連する 1 つ以上の LoAF エントリにリンクするための直接的な API はありませんが、それぞれの開始時間と終了時間を比較することで、コードでリンクできます(WhyNp のサンプル スクリプトをご覧ください)。

v4 の web-vitals ライブラリには、INP アトリビューション インターフェースの longAnimationFramesEntries プロパティで交差するすべての LoAF が含まれています。

LoAF エントリをリンクすると、INP アトリビューションに情報を含めることができます。scripts オブジェクトには、それらのフレームで他の何が実行されているかを示すことができる最も有用な情報が含まれています。そのため、そのデータをアナリティクス サービスに返すことで、インタラクションが遅い理由をより深く理解できます。

INP インタラクションの LoAF を報告することは、ページで最も差し迫ったインタラクティビティの問題を特定するための優れた方法です。ユーザーがページに対して行った操作は異なり、十分な量の INP アトリビューション データがある場合、多くの潜在的な問題が INP アトリビューション データに含まれます。これにより、スクリプトの量を並べ替えて、どのスクリプトが INP の遅延に関係しているかを確認できます。

長いアニメーション データを分析エンドポイントに報告する

INP LoAF に着目するだけの欠点の一つは、将来の INP の問題を引き起こす可能性がある、改善の余地がある他の領域を見落とす可能性があることです。これにより、INP の問題を修正すると大幅改善を期待して、尾を追いかけるような感覚が起こってきますが、次に最も遅いインタラクションを見つけるだけでは、それよりわずかな差しかないため、INP はあまり改善されません。

したがって、INP LoAF だけに注目するのではなく、ページのライフタイム全体のすべての LoAF を考慮することをおすすめします。

LoAF が多数存在するページ。その一部は、INP インタラクション以外でも���ンタラクション中に発生します。
すべての LoAF を調べると、将来の INP の問題を特定できます。

ただし、各 LoAF エントリには相当なデータが含まれているため、そのすべてにビーコンを返すことは望ましくありません。代わりに、分析を一部の LoAF または一部のデータに制限する必要があります。

推奨されるパターンは次のとおりです。

どのパターンが最適かは、最適化の過程のどの段階にあるか、またアニメーション フレームが長くなる頻度によって異なります。これまで応答性を最適化したことがないサイトの場合、インタラクションがある LoAF のみに制限したり、しきい値を高く設定したり、最も悪いもののみを確認したりする LoAF が多数あるかもしれません。一般的な応答性の問題を解決する際は、やり取りだけにとどまらず、しきい値を下げたり、特定のパターンを探したりすることで、この問題を拡大できます。

インタラクションを含む長いアニメーション フレームを監視する

INP の長いアニメーション フレーム以外の分析情報を得るには、インタラクション(firstUIEventTimestamp 値が存在することによって検出可能)を含むすべての LoAF を確認します。

これは、INP LoAF と INP LoAF の相関関係を相関分析しようとするよりも簡単な方法で INP LoAF をモニタリングする方法であり、より複雑になる可能性があります。ほとんどの場合、これには特定の訪問の INP LoAF が含まれますが、まれに、それ以外のユーザーの INP インタラクションであるため、修正が重要な長いインタラクションが引き続き表示されます。

次のコードは、フレーム内でインタラクションが発生した��150 ミリ秒を超えるすべての LoAF エントリをログに記録します。ここでは、INP の「良好」なしきい値である 200 ミリ秒よりわずかに小さい 150 を使用します。必要に応じて、より高い値または低い値を選択できます。

const REPORTING_THRESHOLD_MS = 150;

const observer = new PerformanceObserver(list => {
    for (const entry of list.getEntries()) {
      if (entry.duration > REPORTING_THRESHOLD_MS &&
        entry.firstUIEventTimestamp > 0
      ) {
        // Example here logs to console, but could also report back to analytics
        console.log(entry);
      }
    }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

特定のしきい値よりも長いアニメーション フレームを監視する

もう一つの戦略は、すべての LoAF をモニタリングし、特定のしきい値を超える LoAF を��ーコンして、後で分析できるように分析エンドポイントに戻すことです。

const REPORTING_THRESHOLD_MS = 150;

const observer = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.duration > REPORTING_THRESHOLD_MS) {
      // Example here logs to console, but could also report back to analytics
      console.log(entry);
    }
  }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

長いアニメーション フレーム エントリは非常に大きくなる可能性があるため、デベロッパーはエントリからどのデータ���分析���������するか��決定��る必要があります。たとえば、エントリの要約時刻、スクリプト名、または必要と思われる他のコンテキスト データの最小セットなどです。

最も長いアニメーション フレームを監視する

サイトで、ビーコン送信が必要なデータの量を減らすために、設定されたしきい値を設定するのではなく、最長のアニメーション フレームのデータを収集することをおすすめします。そのため、ページにどれだけ長いアニメーション フレームがあるとしても、最低でも必要な数のアニメーション フレームのデータのみをビーコンで返します。

MAX_LOAFS_TO_CONSIDER = 10;
let longestBlockingLoAFs = [];

const observer = new PerformanceObserver(list => {
  longestBlockingLoAFs = longestBlockingLoAFs.concat(list.getEntries()).sort(
    (a, b) => b.blockingDuration - a.blockingDuration
  ).slice(0, MAX_LOAFS_TO_CONSIDER);
});
observer.observe({ type: 'long-animation-frame', buffered: true });

これらの戦略は組み合わせることもできます。150 ミリ秒を超えるインタラクションが含まれる 10 件の最悪の LoAF のみに注目します。

適切なタイミング(visibilitychange イベント時が理想的)に、アナリティクスにビーコンを返します。ローカルテストでは、定期的に console.table を使用できます。

console.table(longestBlockingLoAFs);

長いアニメーション フレームの一般的なパターンを識別する

別の戦略として、長いアニメーション フレーム エントリで最も頻繁に表示される一般的なスクリプトに注目することもできます。繰り返し違反を行っているユーザーを特定するために、文字と文字の位置レベルでデータを報告することもできます。

これは、パフォーマンスの問題を引き起こしているテーマやプラグインを複数のサイトで特定できるカスタマイズ可能なプラットフォームで特に有効な場合があります。

長いアニメーション フレームでの一般的なスクリプト(サードパーティのオリジン)の実行時間を合計して報告することで、1 つのサイトまたはサイトのコレクションで長いアニメーション フレームの一般的な原因を特定できます。たとえば、URL を調べる手順は次のとおりです。

const observer = new PerformanceObserver(list => {
  const allScripts = list.getEntries().flatMap(entry => entry.scripts);
  const scriptSource = [...new Set(allScripts.map(script => script.sourceURL))];
  const scriptsBySource= scriptSource.map(sourceURL => ([sourceURL,
      allScripts.filter(script => script.sourceURL === sourceURL)
  ]));
  const processedScripts = scriptsBySource.map(([sourceURL, scripts]) => ({
    sourceURL,
    count: scripts.length,
    totalDuration: scripts.reduce((subtotal, script) => subtotal + script.duration, 0)
  }));
  processedScripts.sort((a, b) => b.totalDuration - a.totalDuration);
  // Example here logs to console, but could also report back to analytics
  console.table(processedScripts);
});

observer.observe({type: 'long-animation-frame', buffered: true});

この出力の例を次に示します。

(index) sourceURL count totalDuration
0 'https://example.consent.com/consent.js' 1 840
1 'https://example.com/js/analytics.js' 7 628
2 'https://example.chatapp.com/web-chat.js' 1 5

ツールで Long Animation Frames API を使用する

この API では、ローカル デバッグ用の追加のデベロッパー ツールも使用できます。Lighthouse や Chrome DevTools などの一部のツールは、低レベルのトレース詳細を使用してこれらのデータの多くを収集できましたが、この高レベルの API を使用することで、他のツールがこのデータにアクセスできる可能性があります。

DevTools で長いアニメーション フレームのデータを表示する

DevTools で performance.measure() API を使用して長いアニメーション フレームを表示できます。このフレームは、DevTools カスタム速度トラックのパフォーマンス トレースに表示され、パフォーマンス改善のためにどこに重点を置くべきかがわかります。

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    performance.measure('LoAF', {
      start: entry.startTime,
      end: entry.startTime + entry.duration,
    });
  }
});

observer.observe({ type: 'long-animation-frame', buffered: true });

長期的には、DevTools 自体に組み込まれる予定ですが、それまでは、前述のコード スニペットを使用することで確認できるようになります。

他のデベロッパー ツールで長いアニメーション フレームのデータを使用する

パフォーマンスの問題を診断するために、Web Vitals 拡張機能がサマリー デバッグ情報のロギングに値を表示しました。

また、各 INP コールバックと各インタラクションの長いアニメーション フレームデータも表示されるようになりました。

Web Vitals 拡張機能のコンソール ロギング。
Web Vitals 拡張機能コンソールのロギングに LoAF データが表示されます。

自動テストツールで長いアニメーション フレームのデータを使用する

同様に、CI/CD パイプラインの自動テストツールで、さまざまなテストスイートを実行しながら長いアニメーション フレームを測定することで、潜在的なパフォーマンスの問題の詳細を明らかにできます。

よくある質問

この API に関するよくある質問には、次のようなものがあります。

Long Tasks API を拡張または反復処理してはどうでしょうか。

これは、潜在的な応答性の問題に関する、類似しているものの、最終的には異なる測定結果を報告する別の見方です。既存のユースケースが中断されないようにするには、既存の Long Tasks API を使用しているサイトが機能し続けることが重要です。

Long Tasks API は LoAF のいくつかの機能(より優れたアトリビューション モデルなど)の恩恵を受けることができますが、タスクではなくフレームに焦点を当てることには多くのメリットがあり、既存の Long Tasks API とは根本的に異なる API になると考えています。

これは Long Tasks API に代わるものですか?

Google は、Long Animation Frames API の方が、長いタスクを測定するための優れた、より完全な API であると考えていますが、現時点では、Long Tasks API を廃止する予定はありません。

フィードバックのお願い

フィードバックは GitHub の問題リストでお送りください。また、Chrome の API 実装に関するバグについては Chrome の Issue Tracker にお寄せください。

おわりに

Long Animation Frames API は、従来の Long Tasks API よりも多くのメリットがある、魅力的な新しい API です。

INP が測定した応答性の問題に対処するための重要なツールであることが証明されています。INP は最適化が難しい指標であり、この API は、デベロッパーが問題を簡単に特定して対処できるようにする方法の 1 つです。

ただし、Long Animation Frames API のスコープは INP にとどまらず、ウェブサイトの全体的なスムーズなユーザー エクスペリエンスに影響を与える可能性のある、更新が遅い他の原因を特定するのに役立ちます。

謝辞

Henry Be による Unsplash のサムネイル画像。