IE11 を始めとする各種ブラウザーで、Web アプリケーションの本当のパフォーマンスを測定する

W3C Web パフォーマンス ワーキング グループ (英語) は、Google や Mozilla を始めとするコミュニティ リーダーと共に、Navigation TimingResource TimingUser TimingPerformance Timeline の各インターフェイスの標準化を完了しました。これらのインターフェイスは、Web アプリケーションにおけるナビゲーション、リソース取得、スクリプト実行の実際のパフォーマンス測定をサポートするものです。これらのインターフェイスでは、Web アプリケーションを使うユーザーの現実世界でのエクスペリエンスを測定、分析ができるため、人工的な環境でアプリケーション パフォーマンスを測定する合成テストを使う必要がなくなります。これらのインターフェイスを通して取得できる時間測定データを活用して、Web アプリケーションのパフォーマンス向上に現実的に有効な対策を見極めることができます。IE11 ではこれらすべてのインターフェイスをサポートします。インターフェイスの実際の動作は Performance Timing Test Drive (英語) でご確認いただけます。

Performance Timing Test Drive では、各種の時間測定 API をテストできます
Performance Timing Test Drive (英語) では、各種の時間測定 API をテストできます

Performance Timeline

W3C 勧告として公開された Performance Timeline (英語) 仕様は、IE11 と Chrome 30 がフルサポートしています。このインターフェイスを使うと、アプリケーション内で行われるナビゲーション、リソース取得、スクリプト実行に費やされた時間を最初から最後まで可視化することができます。すべてのパフォーマンス指標は、この仕様が定義する最低限の属性を実装する必要があります。また、どのような種類のパフォーマンス指標を取得する場合も、この仕様が定義するインターフェイスを使用できます。

すべてのパフォーマンス指標は以下の 4 つの属性をサポートする必要があります。

  • name: パフォーマンス指標の独自の識別子を格納します。リソースの場合は、リソースの解決済み URL になります。
  • entryType: パフォーマンス指標の種類を格納します。リソースの指標の場合は、「resource」として格納されます。
  • startTime: パフォーマンス指標が最初に記録された時点のタイムスタンプを格納します。
  • duration: 指標が記録するイベントの開始から終了までの経過時間を格納します。

すべての時間測定データは、High Resolution Time (英語) が定義する DOMHighResTimeStamps 型を使って高精度時間で記録されます。1970 年 1 月 1 日からの時間をミリ秒単位で測定する DOMTimeStamps とは違い、高精度時間の場合は、ドキュメントのナビゲーション開始からの時間を少なくともマイクロ秒単位で測定します。たとえば、Date.now() の高精度時間版と言える performance.now() を使って現在の時刻を確認する場合、現在時刻は以下のように解釈されます。

> performance.now();

4038.2319370044793

 

> Date.now()

1386797626201

この時間値には、時計のずれや補正の影響を受けないというメリットもあります。高精度時間の用途は、What Time Is It Test Drive (英語) でご確認いただけます。

以下のインターフェイスを使用すると、呼び出し時のパフォーマンス指標を一覧で取得することができます。startTime、duration、その他指標が提供するその他の属性を使うことで、ユーザーが体験するのと同じページ パフォーマンスを開始から終了までタイムライン形式で取得できます。

PerformanceEntryList getEntries();

PerformanceEntryList getEntriesByType(DOMString entryType);

PerformanceEntryList getEntriesByName(DOMString name, optional DOMString entryType);

getEntries メソッドはページ上のすべてのパフォーマンス指標の一覧を返します。その他のメソッドは、名前または種類に基づいて特定の項目を返します。私たちは、ほとんどの開発者は単に指標の一覧全体に対して JSON の stringify を使い、送信先は分析のための結果のクライアントではなくサーバーであると予測しています。

ここからは、ナビゲーション、リソース、マーク、計測値のパフォーマンス指標をそれぞれ詳しく見ていきましょう。

Navigation Timing

Navigation Timing インターフェイスを使うと、Web アプリケーションのナビゲーションの各フェーズに費やされた時間を正確に測定できます。Navigation Timing L1 (英語) 仕様は W3C 勧告として公開され、IE9、Chrome 28、Firefox 23 からフルサポートされています。Navigation Timing L2 (英語) 仕様は公開草案初版で、IE11 がサポートしています。

Navigation Timing を使うと、ページ読み込みの開始から終了までの時間を、サーバーからのページ取得にかかった時間を含めて正確に取得できます。それだけでなく、ネットワーク処理と DOM 処理の各フェーズ (アンロード、リダイレクト、アプリケーションのキャッシュ、DNS、TCP、要求、応答、DOM 処理、読み込みイベント) にかかった時間の内訳を取得できます。以下のスクリプトでは Navigation Timing L2 を使って詳細情報を取得しています。この指標のエントリの種類は「navigation」で、名前は「document」です。IE Test Drive サイトの Navigation Timing のデモ (英語) をご確認ください。

<!DOCTYPE html>

<html>

<head></head>

<body>

<script>

 function sendNavigationTiming() {

   var nt = performance.getEntriesByType('navigation')[0];

   var navigation = ' Start Time: ' + nt.startTime + 'ms';

   navigation += ' Duration: ' + nt.duration + 'ms';

   navigation += ' Unload: ' + (nt.unloadEventEnd - nt.unloadEventStart) + 'ms';

   navigation += ' Redirect: ' + (nt.redirectEnd - nt.redirectStart) + 'ms';

   navigation += ' App Cache: ' + (nt. domainLookupStart - nt.fetchStart) + 'ms';

   navigation += ' DNS: ' + (nt.domainLookupEnd - nt.domainLookupStart) + 'ms';

   navigation += ' TCP: ' + (nt.connectEnd - nt.connectStart) + 'ms';

   navigation += ' Request: ' + (nt.responseStart - nt.requestStart) + 'ms';

   navigation += ' Response: ' + (nt.responseEnd - nt.responseStart) + 'ms';

   navigation += ' Processing: ' + (nt.domComplete - nt.domLoading) + 'ms';

   navigation += ' Load Event: ' + (nt.loadEventEnd - nt.loadEventStart) + 'ms';

   sendAnalytics(navigation);

 }

</script>

</body>

</html>

ネットワーク関連の各フェーズにかかった時間を詳しく把握できれば、パフォーマンスの問題をより正確に診断し、より適切に修正できるようになります。修正対応には、リダイレクトに時間がかかっている場合にリダイレクトを止めることを検討する、DNS の時間が長い場合に DNS キャッシュ サービスを使用する、応答時間が長くなっている場合はユーザーにより近い CDN を使用する、応答時間が長くなっている場合はコンテンツを GZip 圧縮する、などが考えられます。ネットワーク パフォーマンス向上のヒントとテクニックについては、このビデオ (英語) をご覧ください。

Navigation Timing 仕様の 2 つのバージョン間の主な違いは、時間測定データへのアクセス方法と、時間の測定方法です。L1 インターフェイスはこれらの属性を、performance.timing オブジェクトで、1970 年 1 月 1 日からのミリ秒単位で定義しています。L2 インターフェイスの場合は同じ属性の Performance Timeline メソッドによる取得を可能にすることで、タイムライン ビューに属性をより簡単に配置できるようにしています。時間が高精度タイマーで記録される点も異なります。

Navigation Timing 以前は、ページ読み込みのパフォーマンスを測定する場合、ドキュメントの先頭に以下のような JavaScript を記述することが一般的でした。このテクニックのデモは IE Test Drive サイト (英語) でご確認いただけます。

<!DOCTYPE html>

<html>

<head>

<script>

    var start = Date.now();

 

    function sendPageLoad() {

        var now = Date.now();

        var latency = now - start;

        sendAnalytics('Page Load Time: ' + latency);

    }

 

</script>

</head>

<body onload='sendPageLoad()'>

</body>

</html>

ただしこの方法ではサーバーからのページ取得にかかった時間は測定できません。このため、ページ読み込みのパフォーマンス測定は不正確でした。ドキュメント先頭で JavaScript を実行することも、一般的にはパフォーマンス低下を招くパターンです。

Resource Timing

Resource Timing により、ページのリソース取得にかかった時間に関する情報を正確に測定することができます。Navigation Timing と同様に、Resource Timing も、リソース取得処理の各フェーズ (リダイレクト、DNS、TCP、要求、応答) に関する情報を提供します。Resource Timing 仕様は W3C 勧告候補として公開済みで、IE10 と Chrome 30 がサポートしています。

以下のサンプル コードでは、<img> 要素によって開始されたすべてのリソースを getEntriesByType を使って取得しています。リソースのエントリの種類は「resource」で、リソースの解決済み URL が名前になります。IE Test Drive サイトの Resource Timing のデモ (英語) をご確認ください。

<!DOCTYPE html>

<html>

  <head>

  </head>

  <body onload='sendResourceTiming()'>

    <img src='https://some-server/image1.png'>

    <img src='https://some-server/image2.png'>

 

    <script>

        function sendResourceTiming()

        {

            var resourceList = window.performance.getEntriesByType('resource');

            for (i = 0; i < resourceList.length; i++)

            {

                if (resourceList[i].initiatorType == 'img')

                {

                    sendAnalytics('Image Fetch Time: ' + resourceList[i].duration);

                }

            }

        }

    </script>

  </body>

</html>

セキュリティ上の理由から、クロスオリジン リソースの場合は開始時間と経過時間だけが表示され、詳細な時間測定属性はゼロに設定されます。これによって、詳細なネットワーク時間を調べることでリソースがユーザーのキャッシュにあるかどうかを確認し、組織内のユーザー メンバーシップ情報を割り出そうとする、統計的フィンガープリンティングの問題を防止できます。クロスオリジン サーバーがユーザーと時間測定データを共有する場合には、timing-allow-origin HTTP ヘッダーを送信できます。

User Timing

User Timing は、アプリケーション内で実行されるスクリプトに関する詳細な時間測定情報を提供します。これは、ネットワークに関する詳細な時間測定情報を提供する Navigation Timing および Resource Timing を補完するインターフェイスです。User Timing を使うとスクリプトの情報がネットワークの情報と同じタイムライン ビューに表示されるため、アプリケーションの開始から終了まで、パフォーマンスを全体的に把握することができます。User Timing (英語) 仕様は W3C 勧告として公開済みで、IE10 と Chrome 30 がサポートしています。

User Timing は、スクリプトの時間を測定するために、マーク (mark) と測定値 (measure) という 2 つの指標を定義します。マークは、スクリプト実行中の特定の時点を単独で表します (高精度タイムスタンプを使用)。測定値は、2 つのマーク間の差異を表します。

マークと測定値を作成するには、以下のメソッドを使用します。

void mark(DOMString markName);

void measure(DOMString measureName, optional DOMString startMark, optional DOMString endMark);

マークと測定値をスクリプトに追加したら、getEntrygetEntryByTypegetEntryByName のいずれかのメソッドを使って時間測定データを取得できます。マークのエントリの種類は「mark」で、測定値のエントリの種類は「measure」です。

以下のサンプル コードでは、mark メソッドと measure メソッドを使って、doTask1() メソッドと doTask2() メソッドの実行時間を測定しています。IE Test Drive サイトの User Timing のデモ (英語) をご確認ください。

<!DOCTYPE html>

<html>

  <head>

  </head>

  <body onload='doWork()'>

    <script>

        function doWork()

        {

            performance.mark('markStartTask1');

            doTask1();

            performance.mark('markEndTask1');

           

            performance.mark('markStartTask2');

            doTask2();

            performance.mark('markEndTask2');

 

            performance.measure('measureTask1', 'markStartTask1', 'markEndTask1');

            performance.measure('measureTask2', 'markStartTask2', 'markEndTask2');

 

            sendUserTiming(performance.getEntries());

        }

    </script>

  </body>

</html>

Microsoft は、今回紹介したインターフェイス設計に尽力した W3C Web パフォーマンス ワーキング グループ、そして、相互運用性の重要性を理解し、これらのインターフェイスに取り組むすべてのブラウザー ベンダーに感謝しています。開発者の皆様は、これらのインターフェイスを活用して、アプリケーションの本当のパフォーマンスを測定し、パフォーマンス向上に本当に役立つ対策を見極めることができます。

アプリケーションを開発中の方は、今回のパフォーマンス測定インターフェイスと IE11 によるテストをぜひお試しください。また皆様のフィードバックをいつでもお待ちしております。その際は、Connect をご利用いただけるとさいわいです。

よろしくお願いいたします。

Internet Explorer プログラム マネージャー、Jatinder Mann

Comments

  • Anonymous
    December 24, 2013
    The comment has been removed