uhyohyo.net

JavaScript初級者から中級者になろう

十五章第四回 Resource Timing

前回の続きです。前回はPerformanceEntryオブジェクトの扱い方を紹介しました。今回はその具体例です。

Resource Timingという仕様では、Webページからimg要素やlink要素によって読み込まれたリソースについて、それにかかった時間などを知ることができます。これは、一五章第二回で紹介したnavigation Timingと似ていますね。あちらはページ本体に読み込みにかかった時間であり、本体が読み込み完了してHTMLのパース・DOMの構築まで終わったら読み込み完了としていたので、外部リソースに読み込みにかかった時間までは測れませんでした。

PerformanceRequestTiming

Resource Timingでは、ページから読み込まれた外部リソース一つ一つについてPerformanceRequestTimingオブジェクトが作成され、Performanceオブジェクトに登録されます。このPerformanceRequestTimingというのは、前回紹介したPerformanceEntryを継承したオブジェクトです。このページもCSSなどを読み込んでいますので、試しに次のコードを実行してみると、PerformanceRequestTimingオブジェクトがいくつも入った配列が得られるのがわかります。注:現在(2014年7月)対応しているのはGoogle Chrome, Opera, IE11でした。

console.log(performance.getEntries());

それではこのPerformanceRequestTimingからはどんな情報が得られるのでしょうか。プロパティを次に列挙してみました。

initiatorType
そのリソースが何によって読み込まれたか。HTML要素ならばその要素名、CSSならば"css"、XMLHttpRequestならば"xmlhttprequest"になる。
redirectStart
リダイレクトがあった場合、一番最初のリソースにアクセスした瞬間の時間です。リダイレクトが無かった場合は0です。
redirectEnd
リダイレクトがあった場合、最後のリダイレクトページから受信を完了した瞬間の時間です。リダイレクトが無かった場合は0です。
fetchStart
リソースへのアクセスを開始した瞬間です。これは厳密には、URLの文字列解析を開始する瞬間と(多くの場合)一致します。
domainLookupStart
URLの解析が終わり、DNSサーバーに問い合わせを開始した瞬間です。
domainLookupEnd
DNSサーバーとの通信が終了した瞬間です。
connectStart
リソースのあるサーバーへの通信を開始した瞬間です。
connectEnd
サーバーへの通信を確立した瞬間です。
secureConnectionStart
HTTPS通信の場合、ハンドシェイクを開始した瞬間です(ブラウザによってはサポートしないこともあるようです)。HTTPS通信でない場合は0です。
requestStart
サーバーへリクエストを送信し始める瞬間です。
responseStart
サーバーからレスポンスの最初の1バイトを受信した瞬間です。
responseEnd
サーバーからレスポンスを最後まで受信した瞬間です。

こうしてみると十五章第二回を思い出しますね。さらに、PerformanceEntryにもともとあるプロパティがどのような値をとるかを次で紹介します。

name
nameプロパティには、リソースのURLが入っています。
entryType
entryTypeプロパティは"resource"で固定です。これは、このPerformanceEntryがPerformanceRequestTimingであることを示す値になっています。
startTime
redirectStartが有効な場合はその値、そうでなければfetchStartの値です。
duration
responseEndとstartTimeの差です。

つまり、そのリソースを取得するのにどれだけ時間がかかったかを調べるには結局durationの値を見れば十分なわけですね。より細かく遅い原因を調べたいときなど、他のプロパティの値を調べることがあるかもしれません。

ちなみに、時間というのは、performance.timing.navigationStart十五章第二回で紹介)の時刻を0とするミリ秒単位の時間で表されます。なので、これらの値を利用して絶対時間を求めることが可能ですが、基本的には2つの値の差をとって使います。

また、initiatorTypeプロパティについて補足しておくと、例えばimg要素で画像を読み込む場合はこの値は"img"になるし、link要素でCSSファイルを読み込む場合は"link"です。また"iframe"などもありえます。CSSのbackground-imageなどで画像ファイルを読み込む場合は、CSSファイル経由で読み込まれているので"css"ですね。

ただ、やはりJavaScript他のオリジンの情報には厳しくて、他のオリジンのリソースの場合にはfetchStartとresponseEndの値以外は軒並み0になってしまいます。この2つが有効なのでdurationなどは取得することができますが、他の値は役に立ちません。ただし、リソースをHTTPで読み込む際にTiming-Allow-Originヘッダがある場合は、他のオリジンであっても時間の情報を取得することができます。これは、十三章第一回で紹介したAccess-Control-Allow-Originヘッダと似ています。

Performanceオブジェクトの拡張

実は、PerformanceオブジェクトはResource Timing関連のメソッド等を持っています。

一つはclearResourceTimings(引数なし)です。これは、Performanceに登録されているPerformanceRequestTimingオブジェクトを消去してしまいます。

もう一つはsetResourceTimingBufferSizeです。これは引数に数値を指定することで、「最大何件までPerformanceRequestTimingオブジェクトを保存するか」を設定できます。

実は、デフォルトでは150件まで保存されることになっています。これは、やはり情報を保存するというのはメモリを食うので、何らかの形で制限する必要があるからでしょう。

それだと困るという場合には、こちら側から数を増やしてやることもできます。

また、実はPerformanceオブジェクトはEventTargetであるので、イベントが発生することがあります。Performanceオブジェクトで発生するイベントの一つがresourcetimingbufferfullイベントです。 これは、PerformanceRequestTimingが保存できる最大件数に達した場合に発生します。このイベントが発生したらそれに併せてsetResourceTimingBufferSizeメソッドで数を増やすということも可能ではありますね。

performance.addEventListener("resourcetimingbufferfull",function(e){
  //最大件数に達した場合の処理
});

addEventListenerを使ってイベントを登録する方法のほかに、次のようにイベントハンドラを直接プロパティにセットする方法もあります。

performance.onresourcetimingbufferfull=function(e){
};

以上でResource Timingに関して紹介すべきことは終了です。最後にサンプルを用意しました。このページから読み込まれたリソースを列挙するサンプルです。

window.addEventListener("load",function(e){
  //ページ内に<div id="resultarea"></div>をあらかじめ用意しておく
  setTimeout(function(){
    var div=document.getElementById("resultarea");

    var entries=performance.getEntriesByType("resource");
    entries.forEach(function(obj){
      //objはPerformanceRequestTimingオブジェクト
      var p=document.createElement("p");
      p.textContent=obj.name+"を"+obj.duration.toFixed(2)+"ミリ秒かけて読み込みました";
      div.appendChild(p);
    });
  },1000);
},false);

最初のloadイベントというのは、ページ本体の読み込みが完了したときに発生するイベントでしたね。ここではとりあえず、読み込み時間を考慮して1000ミリ秒待った後で実行しています。これを実際にこのページで動かしてみた結果が以下です。

こんな感じでリソースに関する情報を取得することができます。

次回も関連した話です。