ページ表示速度をPhantomJSで計測してZabbixに記録する

Webページの表示速度って大事ですよね。Googleの検索順位にも影響が出るっていうし、なによりユーザが見たいと思った時に、遅くてなかなか見られないというのは単純に機会損失になります。じゃあ表示速度を早くしようとなるわけですが、そこらへんはわりとベストプラクティスみたいなのが語られまくってますね。

ちょっと古いですが僕が昔か書いた[Webフロントエンド表示速度、最速化手法まとめ](http://qiita.com/zaru/items/51ee8a5be22b75a42927)みたいなのとか。

で、早くする技工的なものは良いんですが、一番大事なのは計測すること。これですね。普段、どれくらいの表示速度で、揺らぎがあるのかないのか、どこかの時点で遅くなっていないかなどを把握する必要があります。

# サイト表示速度の監視

サイトの表示速度を計測してくれるナイスなサービスがいくつかあります。

- [pingdom](https://www.pingdom.com/)
- [newrelic](https://newrelic.com)
- [ruxit](https://ruxit.com/web-monitoring/)
- [HAPPY APPS](http://www.happyapps.io/)
- [ALERT BOT](http://www.alertbot.com/)
- [Status Cake](https://www.statuscake.com/)
- [GTmetrix](https://gtmetrix.com/)

有名所だとpingdomとnewrelicでしょうか。かなり良さげなんです。これ使えば、まぁ解決するんですがお金がかかるんです。それがけっこうな金額で。弊社ベーシックでは、結構な数のWebサービスを運営しているため、お金もめちゃくちゃかかる…できればコストを最小限に抑えて自前で構築したい！　コスト削減重要。

また、計測サーバの場所も重要です。アメリカにあるサーバから、日本国内にあるサーバのパフォーマンスを計測すると、どうしてもレイテンシがきつくて実際の値とはかけ離れてしまいます。pingdom以外はアジアリージョンは選べなかったような気がします（newrelicはJS埋め込んでの計測なので関係ないと思うけど）。

## GoogleAnalyticsのサイト速度

GoogleAnalyticsにもサイトの表示速度を計測してくれる機能があります。これ使えよって思うんですが、表示速度が遅くなった時にアラートを出したり、特定のリソースファイルのパフォーマンスが悪いなどの細かい制御ができないため、独自でやってみることにしました。便利で良いんですけどね。

というわけで、PhantomJS + Zabbixを使って自前で構築してみたよーというのが、この記事の趣旨です。

## ZabbixでWeb監視

まず、監視と言ったらZabbixですね。というわけで、Zabbixにも「Web監視」という機能があります。これは、指定URLにたいしてGET/POSTしつつレスポンスタイムを計測してくれるという、パフォーマンス計測＋サイトステータスチェックとしても使える良い感じのやつです。

![zabbix.png](https://qiita-image-store.s3.amazonaws.com/0/2152/f554868b-7018-7557-0f19-fbb83081d5cd.png "zabbix.png")

複数のURLを指定でき、かつPOSTデータやCookieなどある程度細かくリクエストの制御ができます。

ただ、レスポンスタイムはあくまでHTMLのドキュメントが返ってくる時間なので、実際にユーザがブラウザで閲覧するときのレスポンスとは違います。できれば、画像やCSSなどを含んだレスポンスタイムが欲しいです。

## PhantomJSでパフォーマンス計測

そこでPhantomJSを使います。JavaScriptのテストだったり、スクレイピングだったりで大活躍なツールですね。これを使って表示パフォーマンスを計測します。

![phantomjs_image.png](https://qiita-image-store.s3.amazonaws.com/0/2152/f4131913-0ae8-b140-f156-152d87e401cf.png "phantomjs_image.png")

こんな感じのイメージです。PhantomJSが動く計測サーバを作ります。そして、PhantomJSで監視対象サイトをクロールして、取得したパフォーマンスデータをZabbixサーバに送ってあげる感じです。

### PhantomJSのインストール

PhantomJS v2.0のLinux用バイナリが提供されていないので、 ~~v1.9.x系を利用します。ビルド時間かかるし。~~ と思ったんだけど、v1.9.x系だとちょっと不具合があったのでv2.0を利用することにしました。

#### v1.9.xのバイナリインストール方法

一応、残しておく。

```
$ cd /tmp
$ wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2
$ tar jxf phantomjs-1.9.8-linux-x86_64.tar.bz2
$ sudo mv phantomjs-1.9.8-linux-x86_64/bin/phantomjs /usr/local/bin/
```

```
$ phantomjs -v
1.9.8
```

#### v2.0のインストール方法

Linux用のバイナリイメージは公式ではまだ配布されておらず、ソースからのビルドが推奨されています。ビルドするのに30分位かかります、まじで。ラーメンでも食べに行きましょう。

```
$ sudo yum -y install gcc gcc-c++ make flex bison gperf ruby openssl-devel freetype-devel fontconfig-devel libicu-devel sqlite-devel libpng-devel libjpeg-devel

$ wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.0.0-source.zip
$ unzip phantomjs-2.0.0-source.zip
$ cd phantomjs-2.0.0-source
$ ./build.sh
```

パスを適当に通しておきます。

```
$ sudo ln -s /home/ec2-user/phantomjs-2.0.0/bin/phantomjs /usr/local/bin/phantomjs
```

### node.jsのインストール

nodebrewを使ってnode.jsをインストールします。

```
$ curl -L git.io/nodebrew | perl - setup
```

```~/.bashrc
export PATH=$HOME/.nodebrew/current/bin:$PATH
```

最新バージョンのnode.jsをインストールします。それなりに時間がかかります。

```
$ nodebrew install latest
```

もし、gcc++が入っていない場合はyumなどでインストールします。

```
$ sudo yum install -y gcc-c++
```

```
$ nodebrew list
v5.1.0

current: none
```

```
$ nodebrew use v5.1.0
use v5.1.0

$ node -v
v5.1.0
```

もうv5.1.0なんですね。

### HARファイルを作成する

PhantomJSには便利なサンプルスクリプトが用意されていて、HARファイル（サイトのリクエスト情報をJSON形式で保存する）を作成するスクリプトがあります。このnetsniff.jsを使えば指定したページを閲覧するときのリクエスト情報が手に入ります。

- [netsniff.js](https://github.com/ariya/phantomjs/blob/master/examples/netsniff.js)

使い方は簡単。

```
$ phantomjs netsniff.js http://www.example.com/
```

これだけです。

出力されたJSONの中の`log -> pages[0] -> pageTimings -> onLoad`の値がページリクエスト完了までのミリ秒になります。

```
{
    "log": {
        "version": "1.2",
        "creator": {
            "name": "PhantomJS",
            "version": "2.0.0"
        },
        "pages": [
            {
                "startedDateTime": "2015-11-30T08:33:36.602Z",
                "id": "http://www.example.com/",
                "title": "サンプルページ",
                "pageTimings": {
                    "onLoad": 1564 // ←これ
                }
            }
        ],
        "entries": [
```

こいつを収集してZabbixでグラフ化すればOKというわけです。

ちなみに、公開されているnetsniff.jsは画像系のファイルはスキップするようになっているので、削除して利用しています。

#### （おまけ）HARファイルの中身を解析する

HARファイルの中身をオンラインで解析して表示したい場合は、以下のサイトを利用すると良いです。Chromeのデベロッパーツールのネットワーク表示のように出せます。

- [HAR Preview](http://www.softwareishard.com/har/viewer/)

![har_preview.png](https://qiita-image-store.s3.amazonaws.com/0/2152/c4e8730b-4771-786a-8a70-050efa54bde6.png "har_preview.png")

### HARを返すAPIサーバを立ち上げる

いきなりここからファ？！となる面倒臭さなんですが、Zabbixでグラフ化するには定期的にPhantomJS + netsniff.jsを叩かなくてはならないです。ZabbixサーバにPhantomJSインストールしても良いんですが、Zabbix以外にも使うかもしれない…というわけで、別途APIサーバをたてます。

ボロいスクリプトですがnode.jsで動くAPIを作りました。よければ使ってください。

- [zaru/nakaguri](https://github.com/zaru/nakaguri)

nakaguri→中ぐりです。ボール盤などでぐりぐり穴を開けるように、サイトの表示リクエストをグリグリ開ける感じのイメージです。ネーミングセンスないですね。ごめんなさい。

使い方は、普通にcloneもしくはダウンロードして、`npm install`でライブラリいれて、サーバ起動するだけです。適宜、foreverやpm2などでデーモン化してnginxかませばAPIサーバの出来上がりです。

```
$ git clone git@github.com:zaru/nakaguri.git
$ cd nakaguri
$ npm install
$ node app.js
```

```
http://example.com:3000/?url=計測URL
```

このように叩くと、計測URLのHARデータを返却します。

## Zabbixに記録する

nakaguriで計測APIサーバができたので、次はZabbixの設定です。計測したいサービスは複数あり、かつ計測したいURLも複数あるのが普通というか人情なので、ここはローレベルディスカバリという機能を使って自動的にアイテムとグラフの追加をするようにします。

### ディスカバリに追加

「設定」→「テンプレート」で適当なテンプレートを選択して、「ディスカバリルール」を選び新規追加します。

![zabbix-1.png](https://qiita-image-store.s3.amazonaws.com/0/2152/291e0436-db21-1056-e11b-64d0d10d63fe.png "zabbix-1.png")

|項目|値|
|:--|:--|
|名前|適当に|
|タイプ|外部チェック|
|キー|discover_site_speed[{$SITE_SPEED_URL}]|

キーの`discover_site_speed`はスクリプト名になるので、適宜変更してください。

```ruby:/usr/lib/zabbix/externalscripts/discover_site_speed
#!/usr/local/rbenv/shims/ruby

require 'json'


exit if ARGV[0].nil?

sites = {'data' => []}
ARGV[0].split(',').each do |url|
  sites['data'] << {'{#SITE_SPEED_URL}' => url}
end

puts sites.to_json
```

出力結果の例はこんな感じです。

```json
{
  "data":[
    {
      "{#SITE_SPEED_URL}":"http://www.example.com/"
    },
    {
      "{#SITE_SPEED_URL}":"http://www.example.com/page_a"
    }
  ]
}
```

### アイテムのプロトタイプ追加

アイテムのプロトタイプを追加します。

![zabbix-2.png](https://qiita-image-store.s3.amazonaws.com/0/2152/03c19a58-0eac-bfdb-d297-584831a3e804.png "zabbix-2.png")


|項目|値|
|:--|:--|
|名前|適当に|
|タイプ|外部チェック|
|キー|site-speed[{#SITE_SPEED_URL}]|
|単位|msec|
|更新間隔|60|

スマートフォン用のサイト計測をしたい場合はキーを以下のように変更します。

|項目|値|
|:--|:--|
|キー|site-speed[{#SITE_SPEED_URL}, SP]|

#### 計測スクリプトの作成

キー名の`site-speed`というのが計測スクリプトになります。先ほどたてた計測APIサーバのドメインに適宜変更をしてください。

```ruby:/usr/lib/zabbix/externalscripts/site-speed
#!/usr/local/rbenv/shims/ruby

require 'open-uri'
require 'json'

url = ARGV[0]
ua = (ARGV[1] == 'sp') ? '&ua=sp' : ''
json = open('http://example.com:3000/?url=' + url + ua).read
result = JSON.parse(json)
puts result['log']['pages'][0]['pageTimings']['onLoad']
```

### グラフのプロトタイプ追加

グラフのプロトタイプを追加します。

![zabbix-3.png](https://qiita-image-store.s3.amazonaws.com/0/2152/2e3d4c8e-3723-0828-d65e-534d6d4e0267.png "zabbix-3.png")

PC用とSP用を両方アイテムプロトタイプ作っている場合は、2つ追加してあげます。グラフの表示は好みなんでしょうが、ベーシックに折れ線グラフにします。以上で、ローレベルディスカバリの設定は完了です。

### ホストのマクロ設定

次に、適用したいホスト設定を選択し、マクロに監視したいURLを定義します。カンマ区切りで複数のURLを定義できます。

![zabbix-4.png](https://qiita-image-store.s3.amazonaws.com/0/2152/12b6905c-ea9c-e796-a85c-d019ce6bd4b8.png "zabbix-4.png")

|マクロ|値|
|:--|:--|
|{$SITE_SPEED_URL}|http://example.com/,http://example.com/hoge|

以上で、Zabbixの設定は完了です。しばらくたつとグラフが描画されていると思います。

![zabbix-5.png](https://qiita-image-store.s3.amazonaws.com/0/2152/969e429f-c98a-c1e9-e95d-60d29a16e651.png "zabbix-5.png")

良い感じですね！

### ZabbixテンプレートXML

Zabbixの設定が面倒な人は、テンプレートXMLを作ったのでこれをインポートすればそれっぽく計測できると思います。

- [テンプレートXML](https://gist.github.com/zaru/eb1dd9621a5528fce538/archive/216f29948496687945351acc62832d7c918e655e.zip)

PhantomJSでのページ取得はそれなりにリソースを食うので、大量にページを計測する場合にはスペックをそれなりに上げるか、スケーラブルなAPIサーバ構成にする必要があります。

## まとめ

今回の例だと単純にページの表示速度だけを計測していますが、読み込んでいる画像やJSの数、極端に遅いリソースを見つけ出してSlackにアラートを出すなど色々できます。ファイルサイズなども表示速度には大きく影響をあたえるので、それを炙りだすのもいいかもしれません。

これでいつどこでページの表示速度に変化があったのかが気づけて、改善のしがいが出てきますね！

明日は[Udaちゃん](http://qiita.com/na1412)です。SEOについて書くらしいので期待大です。