フロントエンドパフォーマンス監視:カスタム指標とデータ収集の実装手法

パフォーマンス測定の重要性と API 概要

Web アプリケーションの最適化において、定量的なデータに基づいた判断は不可欠です。ブラウザが提供する Performance API を活用することで、ページ読み込みからリソース取得までの詳細なタイミングを取得し、独自の性能指標を定義してサーバーへ送信することが可能になります。

主要なインターフェースには、Performance Timeline、Navigation Timing、User Timing、Resource Timing があり、これらは window.performance オブジェクトを通じてアクセスできます。現代のブラウザでは高いサポート率を誇っており、実運用での利用に適しています。

高精度なタイムスタンプの取得

処理時間の計測には、Date.now() ではなく performance.now() の使用を推奨します。両者の主な違いは精度と基準点にあります。

  • 精度: performance.now() はマイクロ秒単位での計測が可能であり、サブミリ秒の差異も捉えることができます。
  • 基準点: Date.now() が Unix エポックからの経過時間を返すのに対し、performance.now() はページ読み込み開始時(timeOrigin)からの経過時間を返します。
  • 安定性: システム時計の変更影響を受けず、単調増加する時計(Monotonic Clock)として動作するため、処理間の差分計算に最適です。

業務ロジック内の特定処理耗时を計測する際は、この API を利用して開始・終了時刻を記録し、その差分を指標として採用します。

ナビゲーションとリソースタイミングの取得

ページ遷移に関する情報は、performance.getEntriesByType('navigation') から取得できます。これにより、リダイレクト回数や遷移タイプ(リンク遷移、フォーム送信など)を把握可能です。リダイレクトが多すぎる場合などは、サーバー側の設定見直しを検討する材料となります。

また、個々の HTTP リクエストに関する詳細は、performance.getEntriesByType('resource') で取得できます。これには CSS、JavaScript、画像などの読み込み時間が含まれます。ただし、ブラウザキャッシュヒット時は計測値が極端に短くなる点に注意が必要です。

従来の performance.timing も利用可能ですが、Navigation Timing Level 2 では非推奨となっているため、新しいプロジェクトでは getEntriesByType を採用すべきです。

カスタムビジネス指標の定義

標準の指標に加え、ユーザー体験に直結する以下のビジネス指標を定義することが重要です。

  • HTML ドキュメントの解析完了時刻
  • ビューポート内画像の描画完了時刻
  • 主要 API レスポンスの受信完了時刻

これらの計測には、コード内にマークを埋め込む必要があります。業務ロジックへの侵入を最小限にするため、API クライアント層で Promise を返す仕組みにし、そこへ計測ロジックをフックする設計が望ましいです。

実装コード例

以下に、カスタム指標の収集と、PerformanceObserver を利用したリソース監視の実装例を示します。

class PerformanceMonitor {
    constructor() {
        this.metrics = {};
        this.startTime = performance.timeOrigin;
    }

    // ビジネスキー指標の記録
    recordMetric(key, timestamp) {
        this.metrics[key] = timestamp - this.startTime;
    }

    // 例:HTML 読み込み完了
    markHtmlLoaded() {
        const navEntry = performance.getEntriesByType('navigation')[0];
        if (navEntry) {
            this.recordMetric('htmlComplete', navEntry.domContentLoadedEventEnd);
        }
    }

    // 例:主要 API 完了
    markApiComplete(apiName, endTime) {
        this.recordMetric(`api_${apiName}`, endTime);
    }
}

// PerformanceObserver による非同期監視
const observerCallback = (list, observer) => {
    list.getEntries().forEach((entry) => {
        // リソースタイプに応じて処理を分岐
        if (entry.entryType === 'resource') {
            console.log(`Resource Loaded: ${entry.name}, Duration: ${entry.duration}ms`);
            // 这里に送信ロジックを追加
        }
        if (entry.entryType === 'paint') {
            console.log(`Paint Event: ${entry.name}, Time: ${entry.startTime}ms`);
        }
    });
};

const perfObserver = new PerformanceObserver(observerCallback);
perfObserver.observe({ entryTypes: ['resource', 'paint', 'largest-contentful-paint'] });

ビューポート内画像の loading 時間計測

首屏(ファーストスクリーン)の画像読み込み完了時間を特定するには、DOM 内の画像要素と、パフォーマンスエントリーを照合させる必要があります。viewport 内に表示されている画像の中で、最も遅く完了したものを指標とします。

const calculateViewportImageTiming = () => {
    const allImages = document.querySelectorAll('img');
    const resourceEntries = performance.getEntriesByType('resource');
    const visibleImageEntries = [];

    allImages.forEach((img) => {
        const rect = img.getBoundingClientRect();
        const isVisible = 
            rect.top >= 0 && 
            rect.left >= 0 && 
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && 
            rect.right <= (window.innerWidth || document.documentElement.clientWidth);

        if (isVisible) {
            const entry = resourceEntries.find((e) => e.name === img.src);
            if (entry) {
                visibleImageEntries.push(entry);
            }
        }
    });

    if (visibleImageEntries.length === 0) return null;

    // 最も完了が遅いエントリーを取得
    return visibleImageEntries.reduce((prev, current) => {
        return (prev.responseEnd > current.responseEnd) ? prev : current;
    }).responseEnd;
};

なお、performance.getEntries() にフィルタオプションを渡す仕様も存在しますが、ブラウザのサポート状況を確認しつつ利用する必要があります。

収集すべき主要パフォーマンス指標

効果的な最適化を行うためには、以下の指標を継続的にモニタリングし、データプラットフォームへ送信する仕組みを構築します。

  • 白屏時間(White Screen Time)
  • DOM コンテンツ読み込み完了時間
  • ファーストスクリーン画像描画完了時間
  • 主要ビジネス API レスポンス完了時間
  • CSS/JavaScript リソース個別の読み込み耗时
  • FP(First Paint:初回描画時間)
  • FCP(First Contentful Paint:初回コンテンツ描画時間)
  • onload イベント発火時間

タグ: PerformanceAPI WebPerformance javascript PerformanceObserver FrontendMonitoring

7月7日 16:45 投稿