clojure製Webアプリケーションのメトリクス取得してみた

## ゴール

clojureで書いたWebアプリケーションの状態を取得して、簡単な可視化をしてみました。これを読むと、簡単にclojure製Webアプリの可視化を試してみる方法が分かります！

### モチベーション

仕事で携わっているclojureアプリケーションに、私の未熟のせいで何度もエラーを吐かせてしまっていて、それが悔しくて「ミスったとしても気づける環境を用意しておきたい！！！！！」と思ったのが最初のモチベーションでした。ただそれから行動に移せていなかったので、この機にモニタリングツールを触ってみることにしました。

### はなすこと

:bulb: Graphiteについての簡単な紹介
:bulb: 簡単にGraphiteサーバーを立てる方法
:bulb: metricsについての簡単な紹介
:bulb: duct製アプリケーションにアプリケーションメトリクスを吐かせる方法
:bulb: そのメトリクスをGraphiteに食わせる方法
:bulb: メトリクスをグラフに表示する方法

### 話さないこと

:monkey_face: clojureについて
:monkey_face: ductについて
:monkey_face: Graphiteの難しい話

## Graphiteは、モニタリングツールの一種

![graphiteのlogo](https://qiita-image-store.s3.amazonaws.com/0/57085/86264203-45bf-4d59-ead2-3e75efe6bf27.png)

Graphiteは、モニタリングツールの一種で、低スペックなホストでも多くのリアルタイム情報を収集できるのがウリ、らしいです。

Graphiteは1つのプログラムで動くのではなく、ELKスタックみたいに複数のサブシステムを同時に動かして稼働するシステムの総称で、graphite-web / carbon / whisper の3つのサブシステムから構成されます。それぞれ、

- graphite-web: 収集したメトリクスをグラフとして描画するGUI
- carbon: 様々なインターフェースからのメトリクス情報をうけつけるツール
- whisper: タイムシフト情報を格納するデータストア

の役割を持っています。
また、他にもcollectDというサーバの情報を取得してくれるツールや、statsDと呼ばれる自前のアプリケーション用に様々なインターフェースから情報を取得できるツールなど、様々なモジュールが用意されています。

## Graphiteサーバーを立てよう！

さて、そんなGraphiteですが、使うためには上記の各サブシステムやツールを動かすためには当然いろいろなセットアップが必要です。公式もそれを見越してDigitalOceanのコンテナイメージやVagrantのVMを提供してくれています。
しかしDigitalOceanはクラウドサービスだしVagrantはインストールにすごい容量と長いセットアップ時間が必要です。

そこで、Dockerを使います。[DockerHub](https://hub.docker.com)に、[GraphiteとstatsDが入ったDockerイメージ](https://hub.docker.com/r/hopsoft/graphite-statsd/)がアップロードされているので、これをpullしてきてローカルに立てます。(一応、Dockerのインストール方法は[こちら](https://docs.docker.com/engine/installation/))

```
docker run -d\
 --name graphite\
 --restart=always\
 -p 80:80\
 -p 2003-2004:2003-2004\
 -p 2023-2024:2023-2024\
 -p 8125:8125/udp\
 -p 8126:8126\
 hopsoft/graphite-statsd
```
(出典: [hopsoft/graphite-statsdイメージのREADME](https://hub.docker.com/r/hopsoft/graphite-statsd/))

これだけで、ローカルホストの特定のポートをリッスンするGraphiteサーバーが起動します。セットアップ全くなしで必要なツールが起動するなんて、すごいですね！

## metricsは、アプリケーションの状態を送信できるライブラリ

こうして立てたGraphiteサーバーに、アプリケーションの状態を送信しましょう。
[metrics](http://metrics.dropwizard.io/3.1.0/)はdropwizardのライブラリで、動いているアプリケーションの内部で起こっていることやアプリケーションの状態を外部から取得することができます。これをアプリケーションに埋め込んで、適切な設定をすることで、動いているアプリケーションの内部で起こっていることを外部から監視することができます。

## アプリケーションにmetricsを埋め込もう

ではこのmetricsライブラリを、clojure製Webアプリに埋め込みましょう。今回はductで書いたアプリケーションに設定を埋め込みます。
[metrics-clojure](https://github.com/sjl/metrics-clojure)というmetricsのclojureラッパーがあるので、それを使いましょう。

```clj:project.clj
[metrics-clojure "2.7.0"]
```

ring用の設定やmiddlewareが入っているライブラリ、Graphiteへのデータ送信をしてくれるライブラリも入れておきます。

```clj:project.clj
[metrics-clojure-ring "2.7.0"]
[metrics-clojure-graphite "2.7.0"]
```

これで依存ライブラリはOKです。

metrics-clojureのringモジュールは、middlewareの形で

* [よくある設定](http://metrics-clojure.readthedocs.io/en/latest/ring.html#instrumenting-a-ring-app)
* [json形式のメトリクス情報が取得できる`/metrics/`エンドポイント](http://metrics-clojure.readthedocs.io/en/latest/ring.html#exposing-metrics-as-json)

を追加してくれます。ductにそれを追加しましょう。

```clj:system.edn
 :config
 {:app
  {:middleware
   {:functions
    {:hide-errors ;; ...
     ;; 各種middleware設定
     :metrics       #var metrics.ring.expose/expose-metrics-as-json
     :metrics-ins   #var metrics.ring.instrument/instrument}
    :applied
    [:not-found :webjars :ring-defaults :hide-errors :metrics :metrics-ins]
```

次に、このmiddlewareが設定してくれた情報をGraphiteに送信する処理を開始しなければいけません。これはシステムのライフサイクルに組み込めるので、componentを作って組み込みましょう。

```clj:component/graphite.clj
(ns my-app.component.graphite
  (:require [clojure.tools.logging :as log]
            [com.stuartsierra.component :as component]
            [clojure.tools.logging :as log]
            [metrics.reporters.graphite :as graphite])
  (:import [java.util.concurrent TimeUnit]
           [com.codahale.metrics MetricFilter]))

(defrecord Graphite []
  component/Lifecycle
  (start [component]
    (if (:graphite component)
      component
      (let [reporter (graphite/reporter {:host "localhost"
                                         :prefix "gt1gv1"
                                         :rate-unit TimeUnit/SECONDS
                                         :duration-unit TimeUnit/MILLISECONDS
                                         :filter MetricFilter/ALL})]
        (graphite/start reporter 10)
        (log/info "Started Graphite Reporting")
        (assoc component :graphite reporter))))
  (stop [component]
    (if (:graphite component)
      (do
        (graphite/stop (:graphite component))
        (log/info "Stopped Graphite Reporting")
        (dissoc component :graphite))
      component)))

(defn graphite [options]
  (map->Graphite options))
```

Graphiteへのデータ送信用インスタンスを生成してstart/stopを叩くだけです。シンプルですね。

これで、

```clj

user> (dev)
:loaded
dev> (go)
:started
```
するだけでGraphiteサーバーへの情報送信が始まります。

## アプリケーションの内部状態が可視化される

この状態でアプリケーションをしばらく叩くと、Graphiteで以下のようなグラフで情報を可視化することができます。

<img width="648" alt="スクリーンショット 2016-12-11 16.22.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/57085/63743b8c-f44d-af94-933a-9d29d504f8ab.png">

これは、青い線が2xx系レスポンス、紫の線が5xx系レスポンスを示しています。紫のグラフが急に上がると、なにか想定しないInternal Server Errorがユーザーに返却されていることが分かります。(空白の時間は、いろいろ試していてサーバーを止めた時です)

これをよりキレイなGUIで見られる[Grafana](http://grafana.org/)と連携させたり、収集した情報を元にアラートをあげるようにすると、アプリケーションの設定は汚さずにアプリケーション内部で起こっていることを可視化することができそうです！

## やってみて

モニタリングツール、今まで触ったことがなさすぎて仕組みやすべてのサブモジュールを理解しようとしたらめちゃ辛かったです。
でも一方で、わからないなりに言われたとおりにやれば一旦動くものが見られるようにはなっていて、これから深く知っていくキッカケが手に入ったように思います！