ブラウザで API リクエストにかかった時間を取得する

クライアントで API の呼び出しにかかった時間を Resource Timing API を用いて計測する方法を解説します。

## Resource Timing API とは

Resource Timing API とは Web ページで読み込まれるリソースの読み込みに要した時間を詳細に取得するための API で、 `XMLHttpRequest` に限らず `<img>` などの取得に要する時間を知ることもできます。

Resource Timing API では、その結果を `PerformanceResourceTiming` というオブジェクトに格納します。`PerformanceResourceTiming` は [Resource Timing Level 1](https://www.w3.org/TR/resource-timing-1/) で定められており、 2017 年 3 月に Candidate Recommendation となったので、もう使ってしまって問題ないでしょう。

## `PerformanceResourceTiming` を取得する

ある URL に対応する `PerformanceResourceTiming` を [`Performance.getEntriesByName`](https://www.w3.org/TR/performance-timeline/#dom-performance-getentriesbyname) を使って取得するコードを以下に示します:

```js
/**
 * @param {String} url
 * @return {?PerformanceResourceTiming}
 */
function getResourceTiming(url) {
  // Performance API が使えない場合は null を返す
  if (!window.performance) return null
  // getEntriesByName が無かったら null を返す。 Safari 9, 10
  if (!performance.getEntriesByName) return null
  // 同一 URL に繰り返しリクエストしている場合 resources は複数になる。
  const resources = performance.getEntriesByName(url, "resource")
  // 最後のやつを return する
  return resources[resources.length - 1]
}
```

たとえばある `XMLHttpRequest` に対応する `PerformanceResourceTiming` を求めるには:

```js
getResourceTiming(xhr.responseURL)
```

## 各種時間を取得する

取得した `PerformanceResourceTiming` オブジェクトからリクエストにかかった各種時間を求めます。なおここから先のサンプルコードは次のコード中の `/* ここ */` の中にいるものとして記述します。

```js
const resource = getResourceTiming(url)
if (resource) { /* ここ */ }
```

### DNS lookup にかかった時間

```js
resource.domainLookupEnd - resource.domainLookupStart
```

### TCP コネクションにかかった時間

```js
resource.connectEnd - resource.connectStart
```

### SSL ハンドシェイクにかかった時間

```js
resource.connectEnd - resource.secureConnectionStart
```

### リダイレクトにかかった時間

```js
resource.redirectEnd - resource.redirectStart
```

### [TTFB (Time To First Byte)](https://en.wikipedia.org/wiki/Time_To_First_Byte)

```js
resource.responseStart - resource.startTime
```

### 全体の時間
`XMLHttpRequest` を send してからリダイレクトなども含めて全てのデータを受信し終えるまでにかかった時間は

```js
resource.duration
```

です。これは `resource.responseEnd - resource.startTime` と同じです。

## クロスオリジン
セキュリティのため Resource Timing API も同一生成元ポリシーに従います。オリジンをまたいで Resource Timing API を使いたい場合 [`Timing-Allow-Origin`](https://www.w3.org/TR/resource-timing/#timing-allow-origin) ヘッダをつける必要があります。

もし同一生成元ポリシーに引っかかっている場合、生成される `PerformanceResourceTiming` の以下のプロパティが全て `0` を返すようになります:

- `redirectStart`
- `redirectEnd`
- `domainLookupStart`
- `domainLookupEnd`
- `connectStart`
- `connectEnd`
- `secureConnectionStart`
- `requestStart`
- `responseStart`

そのため、次のようなコードで同一生成元ポリシーに違反したものかどうかを知ることができます:

```js
// 同一生成元ポリシーに違反している場合 false になる
!!response.requestStart
```

なお同一生成元ポリシーに違反している場合でも `duration` `startTime` `responseEnd` は使うことができます。

## 参考

- [Resource Timing Level 1](https://www.w3.org/TR/resource-timing-1/)
- [Performance Timeline](https://www.w3.org/TR/performance-timeline/)
- [Resource Timing Level 1](https://www.w3.org/TR/resource-timing/#timing-allow-origin)
- [パフォーマンスまわりのAPIについて - Qiita](https://qiita.com/makotot/items/70bd392a62afd43d3189#resource-timing-api)
- [Resource Timing practical tips | High Performance Web Sites](http://www.stevesouders.com/blog/2014/08/21/resource-timing-practical-tips/)