EagleLand

2016.06.14

パフォーマンスに関する各種ブラウザAPI

◯◯ Timing APIはW3CのWeb Performance Working GroupのIlya Grigorikを中心に策定が進められている、ブラウザのパフォーマンスを計測するブラウザAPIである。

ブラウザのwindowオブジェクトにperformanceというプロパティがあるので、DevToolsを開いてConsoleパネルでwindow.performanceと入力すると、何やら見覚えのあるプロパティに数値が入っているのがわかる。

なかでもFrame TimingとServer Timingは若く、ブラウザに実験的実装はおろかスペックに関する資料も少ない。ここで記載している内容はこれから変わる可能性は高いのでご注意を。

User Timing API

User Timing APIは、performance.mark()performance.measure()で任意のタイミングのパフォーマンスを計測するAPI。performance.mark()で任意のタイミングにマークをし、performance.measure()でマーク間の差分を取得するような使い方。

以下はXMLHttpRequestによる非同期リクエストのパフォーマンスを計測する例。performance.mark()performance.measure()したデータは、performance.getEntriesByType('mark')のように計測タイプを指定して取得可能になっている。

let xhr = new XMLHttpRequest();
xhr.open('GET', '/', true);
xhr.onload = e => {
  performance.mark('mark-xhr-end');
  performance.measure('xhr-start-end', 'mark-xhr-start', 'mark-xhr-end');
  console.log(performance.getEntriesByType('mark'));
  console.log(performance.getEntriesByType('measure'));
};
performance.mark('mark-xhr-start');
xhr.send();

以前まではDevToolsのTimelineパネルにこのマークした情報が表示されていたが、53周辺では消えている気がする。Canaryだからかもしれないけど。

Navigation Timing APIは、ブラウザのページロード完了までのDOMContentLoadedloadのようなライフサイクルイベントを取得するAPI。

Processing Model

performance.timingから以下の情報を取得できる。

console.log(
  'Name: '       + performance.timing.name      + '\n' +
  'Entry Type: ' + performance.timing.entryType + '\n' +
  'Start Time: ' + performance.timing.startTime + '\n' +
  'Duration: '   + performance.timing.duration  + '\n' +
  'Unload: '     + (performance.timing.unloadEventEnd - performance.timing.unloadEventStart)   + 'n' +
  'Redirect: '   + (performance.timing.redirectEnd - performance.timing.redirectStart)         + 'n' +
  'App Cache: '  + (performance.timing.domainLookupStart - performance.timing.fetchStart)      + 'n' +
  'DNS: '        + (performance.timing.domainLookupEnd - performance.timing.domainLookupStart) + 'n' +
  'TCP: '        + (performance.timing.connectEnd - performance.timing.connectStart)           + 'n' +
  'Request: '    + (performance.timing.responseStart - performance.timing.requestStart)        + 'n' +
  'Response: '   + (performance.timing.responseEnd - performance.timing.responseStart)         + 'n' +
  'Processing: ' + (performance.timing.loadEventStart - performance.timing.responseEnd)        + 'n' +
  'Onload: '     + (performance.timing.loadEventEnd - performance.timing.loadEventStart)       + 'n'
);

ここからPerformanceEntryなどのインターフェースに対応するNavigation Timing Level 2という仕様の策定が進んでいる。これは後述のPerformanceObserverに必要なインターフェースとなっている。

DOMContentLoadedloaddocument.readyStateとの対応関係は次のようになりそう。

Browser lifecycledocument.readyStateNavigation Timing API
timing.domLoading
loading
timing.domInteractive
interactive
DOMContentLoadedtiming.domContentLoadedEventStart
timing.domContentLoadedEventEnd
timing.domComplete
complete
loadtiming.loadEvent

Resource Timing API

Resource Timing APIはページ内でロードされたリソースに関する情報を取得するAPI。雑に言えばNavigation Timing APIのサブリソース版。

performance.getEntriesByType()resourceを指定して取得する。

for (let resource of performance.getEntriesByType('resource')) {
  console.log(
    'Name: '       + resource.name      + '\n' +
    'Entry Type: ' + resource.entryType + '\n' +
    'Start Time: ' + resource.startTime + '\n' +
    'Duration: '   + resource.duration  + '\n' +
    'Redirect: '   + (resource.redirectEnd - resource.redirectStart)         + 'n' +
    'App Cache: '  + (resource.domainLookupStart - resource.fetchStart)      + 'n' +
    'DNS: '        + (resource.domainLookupEnd - resource.domainLookupStart) + 'n' +
    'TCP: '        + (resource.connectEnd - resource.connectStart)           + 'n' +
    'Request: '    + (resource.responseStart - resource.requestStart)        + 'n' +
    'Response: '   + (resource.responseEnd - resource.responseStart)         + 'n' +
    'Processing: ' + (resource.loadEventStart - resource.responseEnd)        + 'n'
  );
}

Frame Timing API

Frame Timing APIはページ描画のフレーム情報を取得するAPI。Navigation Timing APIやResource Timing APIはネットワーク処理に関するパフォーマンスの情報だが、これはレンダリングパフォーマンスの情報ということになる。

1フレームはイベントループにおけるvsyncからvsyncまでを指す。

Frame Model

performance.getEntriesByType()rendererないしcompositeを指定して取得する。

for (let renderer of performance.getEntriesByType('renderer')) {
  console.log(
    'Name: '       + renderer.name      + '\n' +
    'Entry Type: ' + renderer.entryType + '\n' +
    'Start Time: ' + renderer.startTime + '\n' +
    'Duration: '   + renderer.duration  + '\n'
  );
}

for (let composite of performance.getEntriesByType('composite')) {
  console.log(
    'Name: '       + composite.name      + '\n' +
    'Entry Type: ' + composite.entryType + '\n' +
    'Start Time: ' + composite.startTime + '\n' +
    'Duration: '   + composite.duration  + '\n'
  );
}

rendererはメインスレッドからCompositorへ行われるコミット、compositeはCompositorからのドローコールに関する情報である。

これらは必ずしも対にはならず、Compositorからのドローコールはメインスレッドからのコミットに依存しない。例えばスクロールなんかの時はビットマップの再構築をせずディスプレイへのピクセルを送り直すということだと思われる。たぶん。

Server Timing API

Server Timing APIは、レスポンスヘッダのServer-Timingフィールドにサーバーの各処理にかかった時間を含めて、クライアントでそれを参照できるようにしようというAPI。IlyaのGist(igrigorik/server-timing.md)レベルなので、まだまだ仕様の着地には遠そうだが、便利そう。

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: max-age=360, public
Transfer-Encoding: chunked
Server-Timing: db=150; cache=22

レスポンスヘッダのServer-Timingフィールドがあれば、この場合はDBアクセスに150msかかり、キャッシュの参照に22msかかったということが表せる。

performance.getEntriesByType()serverを指定して取得する。

for (let server of performance.getEntriesByType('server')) {
  console.log(
    'Name: '        + renderer.name        + '\n' +
    'Entry Type: '  + renderer.entryType   + '\n' +
    'Start Time: '  + renderer.startTime   + '\n' +
    'Duration: '    + renderer.duration    + '\n' +
    'Metric: '      + renderer.metric      + '\n' +
    'Description: ' + renderer.description + '\n'
  );
}

High Resolution Time API

High Resolution Time APIはマイクロ秒単位でパフォーマンスを計測するためのAPI。Date.now()のミリ秒では不十分ということで、細かな計測にはperformance.now()を使うと良い。

performance.now()が返すDOMHighResTimeStampは、ここまで紹介してきた各APIの時間に関するプロパティでも使われている。内部的にはただのdoubleである。

Performance Observer

ブラウザパフォーマンスを計測するAPIは先に挙げた、User Timing API・Navigation Timing API・Resource Timing API・Frame Timing API・Server Timing APIの5つがある。Performance Observerはこれらの変更を監視するAPIである。

PerformanceObserverのインスタンスを作り、監視開始にはobserve()、終了にはdisconnect()を実行する。

const observer = new PerformanceObserver(list => {
  for (let entry of list.getEntries()) {
    console.log(
      'Name: '       + entry.name      + '\n' +
      'Entry Type: ' + entry.entryType + '\n' +
      'Start Time: ' + entry.startTime + '\n' +
      'Duration: '   + entry.duration  + '\n'
    );
  }
});

observer.observe({
  entryTypes : ['resource']
});

observer.disconnect();

observe()の引数にはどの種類のパフォーマンスのタイムスタンプを監視するかを指定してあげる。ここではResource Timing APIで取得する値を監視するべくresourceを指定しているが、ここまでperformance.getEntriesByType()の引数に指定してきた文字列を取りうる。

メトリクス毎にPerformanceObserverを分けるなどの使い方も可能だろう。

タイトルと URL をコピーしました