Google AnalyticsとNavigation Timing API

:::note warn
この記事はGoogle Analytics 4ではなく、Universal Analyticsを対象としています。
:::

Google Analyticsではクライアント側のNavigation Timing APIの値を1%程度にサンプリングした上で集計していて、8つの速度指標としてAnalyticsのレポートやBigQuery側で利用することができます。

この記事ではそれら指標がどのような形で設定されているのか、仕様を整理していきます。

## Measurement Protocolのパラメータを確認

実際にどういったパラメータ名で送信しているのかログを確認してみます。

速度指標はtimingヒットとしてデフォルトでは1%程度の確率で送信されることになります。サンプリングされたままではパラメータの確認が困難ですので、フィールド設定で`{'siteSpeedSampleRate': 100}`とし、100%の確率で送信されるように変更してみます。


```javascript
ga('create', 'UA-XXXX-Y', {'siteSpeedSampleRate': 100});
```

`t`パラメータを`timing`、そして`plt`や`pdt`などの普段見慣れないパラメータが含まれていることが確認できました。

![スクリーンショット 2021-02-13 20.35.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/b4f8dd1e-c78c-6d93-cd40-df602cba7b61.png)

なお、パラメータリファレンスからもパラメータ名を確認することができます。

[Measurement Protocol のパラメータ リファレンス  |  アナリティクス Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters?hl=ja#timing)

## 値の算出方法を確認

次にそれぞれの値がどのように算出されているか確認します。

速度指標はクライアント側の値を利用していますので、https://www.google-analytics.com/analytics.js のコードにてNavigation Timing API関連の記述を参照してみます。

難読化されてはいますが以下のようなコード行が確認できました。

```javascript
var c = b.navigationStart;
if (0 == c) return !1;
a[Eb] = b.loadEventStart - c;
a[Gb] = b.domainLookupEnd - b.domainLookupStart;
a[Jb] = b.connectEnd - b.connectStart;
a[Ib] = b.responseStart - b.requestStart;
a[Fb] = b.responseEnd - b.responseStart;
a[Hb] = b.fetchStart - c;
a[Kb] = b.domInteractive - c;
a[Lb] = b.domContentLoadedEventStart - c;
```

```javascript
Eb = S("l1", "plt"),
Fb = S("l2", "pdt"),
Gb = S("l3", "dns"),
Hb = S("l4", "rrt"),
Ib = S("l5", "srt"),
Jb = S("l6", "tcp"),
Kb = S("l7", "dit"),
Lb = S("l8", "clt"),
```

この内容から以下のような対応関係になると推測できます。

| パラメータ | 意味 | 計算式                                       |
|------------|---|----------------------------------------------|
| plt        | page load time| loadEventStart - navigationStart             |
| pdt        | page download time| domainLookupEnd - domainLookupStart          |
| dns        |dns lookup time| connectEnd - connectStart                    |
| rrt        |redirection time| responseStart - requestStart                 |
| srt        |server response time| responseEnd - responseStart                  |
| tcp        |tcp connection time| fetchStart - navigationStart                 |
| dit        |dom interactive time| domInteractive - navigationStart             |
| clt        |content load time| domContentLoadedEventStart - navigationStart |

なお、windowオブジェクトのperformance.timingに入っている各項目は時系列に沿って以下のようにセットされています。

![navigationtimingapi.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/e833be77-be41-22f8-e21f-1ae08b09317d.jpeg)

[Measuring performance - Learn web development | MDN](https://developer.mozilla.org/en-US/docs/Learn/Performance/Measuring_performance)

## 各メトリクスを整理

以上の内容からパラメータ名と役割、そして指標の計算方法が把握ができましたので、8つの指標を改めて整理してみます。

### Page Load Time (ms)

> Total time (in milliseconds), from pageview initiation (e.g., a click on a page link) to page load completion in the browser, the pages in the sample set take to load.

- 日本語表記: `ページ読み込み時間 (ミリ秒)`
- Reporting API: `ga:pageLoadTime`
- Measurement Protocol Parameter: `plt`
- BigQueryフィールド: `hits.latencyTracking.pageLoadTime`
- analytics.js: `performance.timing.loadEventStart - performance.timing.navigationStart`

### Page Download Time (ms)

> The total time (in milliseconds) to download this page among all samples.

- 日本語表記: `ページ ダウンロード時間 (ミリ秒)`
- Reporting API: `ga:pageDownloadTime`
- Measurement Protocol Parameter: `pdt`
- BigQueryフィールド: `hits.latencyTracking.pageDownloadTime`
- analytics.js: `performance.timing.domainLookupEnd - performance.timing.domainLookupStart`

### Domain Lookup Time (ms)

> The total time (in milliseconds) all samples spent in DNS lookup for this page.

- 日本語表記: `ドメイン ルックアップ時間 (ミリ秒)`
- Reporting API: `ga:domainLookupTime`
- Measurement Protocol Parameter: `dns`
- BigQueryフィールド: `hits.latencyTracking.domainLookupTime`
- analytics.js: `performance.timing.connectEnd - performance.timing.connectStart`

### Redirection Time (ms)

> The total time (in milliseconds) all samples spent in redirects before fetching this page. If there are no redirects, this is 0.

- 日本語表記: `リダイレクト時間 (ミリ秒)`
- Reporting API: `ga:redirectionTime`
- Measurement Protocol Parameter: `rrt`
- BigQueryフィールド: `hits.latencyTracking.redirectionTime`
- analytics.js: `performance.timing.responseStart - performance.timing.requestStart`

### Server Response Time (ms)

> The total time (in milliseconds) the site's server takes to respond to users' requests among all samples; this includes the network time from users' locations to the server.

- 日本語表記: `サーバー応答時間 (ミリ秒)`
- Reporting API: `ga:serverResponseTime`
- Measurement Protocol Parameter:`srt`
- BigQueryフィールド: `hits.latencyTracking.serverResponseTime`
- analytics.js: `performance.timing.responseEnd - performance.timing.responseStart`

### Server Connection Time (ms)

> Total time (in milliseconds) all samples spent in establishing a TCP connection to this page.

- 日本語表記: `サーバー接続時間  (ミリ秒)`
- Reporting API: `ga:serverConnectionTime`
- Measurement Protocol Parameter: `tcp`
- BigQueryフィールド: `hits.latencyTracking.serverConnectionTime`
- analytics.js: `performance.timing.fetchStart - performance.timing.navigationStart`

### Document Interactive Time (ms)

> The time (in milliseconds), including the network time from users' locations to the site's server, the browser takes to parse the document (DOMInteractive). At this time, users can interact with the Document Object Model even though it is not fully loaded.

- 日本語表記: `サーバー接続時間  (ミリ秒)`
- Reporting API: `ga:domInteractiveTime`
- Measurement Protocol Parameter: `dit`
- BigQueryフィールド: `hits.latencyTracking.domInteractiveTime`
- analytics.js: `performance.timing.domInteractive - performance.timing.navigationStart`

### Document Content Loaded Time (ms)

> The time (in milliseconds), including the network time from users' locations to the site's server, the browser takes to parse the document and execute deferred and parser-inserted scripts (DOMContentLoaded). When parsing of the document is finished, the Document Object Model (DOM) is ready, but the referenced style sheets, images, and subframes may not be finished loading. This is often the starting point of Javascript framework execution, e.g., JQuery's onready() callback.

- 日本語表記: `ドキュメント コンテンツ読み込み時間 (ミリ秒)`
- Reporting API: `ga:domContentLoadedTime`
- Measurement Protocol Parameter: `clt`
- BigQueryフィールド: `hits.latencyTracking.domContentLoadedTime`
- analytics.js: `performance.timing.domContentLoadedEventStart - performance.timing.navigationStart`

## 可視化の方法

### Google Analytics
各パフォーマンス指標には平均値のディメンションが用意されているので、この値を使えばカスタムレポートでもCoreReportingAPI経由でも平均値で可視化することができます。
例えば カスタムレポートで任意のページのサーバーレスポンスを可視化する場合には以下のような形になります。
![アナリティクス-360.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/5646bae7-e8aa-ceec-436b-e6456ad34085.png)

![スクリーンショット 2021-02-13 23.05.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/9b03862e-c438-89a1-d173-983c1d37d80f.png)

### BigQuery/データポータル
平均値だけでなく50パーセンタイル(中央値)や90パーセンタイル、あるいは分散といった統計量で可視化したい場合には BigQuery(データ抽出・データ加工)やデータポータル(データ加工・可視化)を利用することもできます。

```sql
SELECT
  date,
  latencyTracking.serverResponseTime
FROM
  `project_name.dataset_name.ga_sessions_*`,
  UNNEST(hits) AS hits
WHERE
  REGEXP_CONTAINS(hits.page.pagePath, "example.com/dir/")
  AND latencyTracking.serverResponseTime IS NOT NULL
  AND _table_suffix BETWEEN "YYYYMMDD"
  AND "YYYYMMDD"
```

BigQueryのクエリ結果の画面から`データを探索`を押せば、そのままデータポータル側で可視化することができます。

![スクリーンショット 2021-02-13 22.58.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/99eaf857-344d-0896-6fe4-1f5761e8c553.png)

以下の例では日毎のサーバ応答時間の中央値を可視化しています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/4a122c55-ef63-6bd8-ebfa-cb7be184bbb4.png)

アドホックな分析ではBigQuery + データポータルの組み合わせがストレスなくて快適です。

## 速度指標をどう活用するか

RUM(Real User Measurement)としてのパフォーマンス指標は、例えば以下のような形で私自身も実際の業務に役立てています。

### 障害モニタリング

障害発生時にクライアント起点での検知が可能になりますので、クライアント側の影響範囲が実際にどの程度のものだったか把握することができます。

### 問い合わせの対応
お客様から「ページの表示が遅い」といった問い合わせをいただいた場合に、`カスタムディメンションにセットしているuid`と`速度指標`を突合することで「原因がサーバサイド/インフラ(`サーバー応答時間`)にあったのか」など、問題の切り分けや状況把握できるようになります。

※デフォルトでは1%程度のサンプリング値となっていますので、そのままでは突合が困難です。割合を100%に切り替えたいところですがサイトの規模が大きい場合にはどうしてもサンプリングが発生しますので、自前で window.peformance.timingの値をカスタム指標にセットする必要があります。

> timing タイプのヒットには、gtag.js、ga.js、analytics.js によって自動的に送信されるサイトの速度のヒットを含めて、追加の制限が課されます。1 日に処理される timing タイプの最大ヒット数は、プロパティあたり 10,000 件またはプロパティでその日に処理されるページビュー合計数の 1% のいずれか大きい方です。これより少ないヒット数には追加の制限が適用されます。<br>
[Google アナリティクスのデータ収集上限  |  ウェブ向けアナリティクス（analytics.js）](https://developers.google.com/analytics/devguides/collection/analyticsjs/limits-quotas?hl=ja#timing_hits)

Google Analyticsでの速度指標の算出方法は analytics.jsの内容から判明していますので、同じ内容で実装すればサンプリング無しでの計測が可能になります。

例えばServer Response TimeをTag Managerを経由して計測するとしたら、以下のようなカスタムJavaScriptを設定してGoogle Analytics側にカスタム指標として渡すことになります。

```javascript
function(){
    var ret = "(not_set)"
    var perfData = window.performance.timing;
    if(perfData.responseEnd && perfData.requestStart){
         ret = perfData.responseEnd - perfData.requestStart;
    }
    return ret;
}
```

※ここではカスタムJavaScriptの中で計算していますが、計算せずに値をそのままGoogleAnalytics側に送り、「計算指標」として処理することも可能です。

[計算指標 [ベータ版] について - アナリティクス ヘルプ](https://support.google.com/analytics/answer/6121409?hl=ja)

## おわりに
サンプリングされているとはいえ重要な指標がデフォルト設定で収集されているため、気軽に実測値(RUM)の各統計量を可視化することができます。Core Web Vitalまでの値にはなりませんが、これだけでも十分UX改善に役立ちそうですね。

## 参考URL
- [Dimensions & Metrics Explorer — Google Analytics Demos & Tools](https://ga-dev-tools.appspot.com/dimensions-metrics-explorer/)
- [Google アナリティクスのデータ収集上限  |  ウェブ向けアナリティクス（analytics.js）](https://developers.google.com/analytics/devguides/collection/analyticsjs/limits-quotas?hl=ja#timing_hits)
- [サイトの速度について - アナリティクス ヘルプ](https://support.google.com/analytics/answer/1205784?hl=ja)
- [Google AnalyticsとTiming APIでブログのサードパーティスクリプトのパフォーマンス計測](https://1000ch.net/posts/2016/ga-measure-performance.html)
- [サイトの速度 - MOL](https://t32k.me/mol/log/no-more-stopwatch/)