kamon on play2.6 で収集したデータをkibana(6.0)でみる

# Kamonとは

各種指標を計測できるツールと捉えています。

http://kamon.io/documentation/get-started/

中でも、

* JVM関連の指標(スレッド、Heap、NonHeap)
* システム(OSレベル)の指標
* Playframework(spray、素のakka-httpにも対応しているようですが、よくわかってないです)の処理時間計測
* akka周りの指標
* 採取したデータをバックエンド(fluentd、datadogなど)に送信

を計測する仕組みがモジュールの有効化だけで採取可能です。

今年の夏頃、abやjmeterを使って **どこまで耐えられるものなのかを主題にした** 負荷テストを行っていたとき、playframeworkの処理が滞ったのですが、どこにあるのだろうか？　
akka-stream / akka-http 周りで何か起きてるぽいんだけど、そのボトルネックを検知できるツールはないものか探していたおり、
サイバーエージェントさんのkamon.ioに関する記事を偶然見つけたのが、kamonに辿り着いた過程になります。

https://adtech.cyberagent.io/scalablog/2015/09/24/kamon_datadog_akka_performance/

akka関連の指標の見方については、まだ道半ばなのですが（汗）、設定やfluentdを介したelasticsearchの連携の部分はある程度自分の中では見えてきたかな？と思えるので載せて見ましたという流れです。

# play2.6 on Scala 2.12 の環境で動かす

kamonは内部でaspectjを使っているので操作を行っているようです。
以下の設定を適切にしないと、指標が十分に採取できないです。

残念ながら、公式のドキュメントやgithub上のドキュメントもやや古く、play2.6ではドキュメントどうりの方法では動かず、試行錯誤しました。
本来はsbt-aspectj-runnerのようなプラグインを使いこなせたほうが　**スマート** なのですが、うまく機能させるに至らなかったので環境変数で逃げてます。

## 設定

### build.sbt

kamon関連のライブラリは　**気持ち的には**　バージョンを合わせたかったのですが、微妙に対応していないバージョン(2017年11月時点)があったので、0.6.7と0.6.8が混在しています。 

```scala

libraryDependencies ++= Seq(
jdbc
,guice
,filters
,ws

,"com.typesafe.slick" %% "slick-codegen" % "3.2.0"
,"com.typesafe.play" %% "play-slick" % "3.0.0"
,"org.scalatestplus.play" %% "scalatestplus-play" % "3.1.1" % Test
,"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.8.9"
//ここから下がkamon関連のライブラリー
,"io.kamon" %% "kamon-core" % "0.6.7"
,"io.kamon" %% "kamon-play-2.6" % "0.6.8"
,"io.kamon" %% "kamon-akka-2.5" % "0.6.8"
,"io.kamon" %% "kamon-system-metrics" % "0.6.7"
,"io.kamon" %% "kamon-akka-http" % "0.6.8"
// kamonで計測したmetricsをfluentdに出力するためのライブラリ（今回のメインディッシュ）
,"io.kamon" %% "kamon-fluentd" % "0.6.7"
// kamonで計測したmetricsを標準出力に出すためのライブラリ（デバッグ用途）
,"io.kamon" %% "kamon-log-reporter" % "0.6.8"
// kamonで計測したmetricsをjmxで見えるかするためのライブラリ（今回は参考までに載せておきますレベル）
,"io.kamon" %% "kamon-jmx" % "0.6.7"
)

```

### application.conf

* 収集するmetricsの設定

```
kamon{
  metric {
    # 10秒ごとに計測
    tick-interval = 10 second
  }

  metric.filters {
    akka-actor {
      includes = [ "**" ]
    }

    akka-dispatcher {
      includes = [ "**" ]
    }

    akka-router {
      includes = [ "**" ]
    }
    trace.includes = [ "**" ] 
  }
  # system-metricsも採取
  system-metrics.sigar-enabled = false
  system-metrics.jmx-enabled = true

}

```

* play向けのモジュール有効化

```scala
play.modules.enabled += "kamon.play.di.GuiceModule"
```

* fluentdに転送する設定

```
kamon{
  fluentd {
    # fluentdへの接続情報
    hostname=localhost
    fluentd.port=24224
    # fluentdへの送信頻度
    # kamon.metric.tick-intervalと同じもしくは倍数でないとエラーになります
    flush-interval = 30 second
  
    subscriptions {
    
     akka-actor      = [ "**" ]
     akka-dispatcher = [ "**" ]
     akka-router     = [ "**" ]
     http-server     = [ "**" ]
     trace                = [ "**" ]
     trace-segment        = [ "**" ]
     akka-http-server     = [ "**" ]    
      
    }
    # 公式を見てると、 **percentiles** の部分をより細かく収集する設定になっていますが、
    # fluentdからelasticsearchへデータを登録する部分が詰まってしまったため、中央値のみ収集するようにしています。
    # サーバーのスペック強化などで回避できるのかもしれません
    histogram-stats {
      subscription = [ "min", "max", "average", "percentiles" ],
      percentiles = [50.0]
    }

  }
}
```

このほかにplay.http.secret.keyはプロダクションモードで動かすためにはマストですので、適切な値を設定してください

## 起動

### 開発モードでの起動

環境変数 **SBT_OPTS** でaspectjweaverのjarへのパスを通して **run** すれば動きます
aspectjweaver-1.8.10.jarをsbtプロジェクトのフォルダ **lib** （＝アンマネージドライブラリの置き場所）に配置


```bash
export SBT_OPTS=-javaagent:./lib/aspectjweaver-1.8.10.jar
sbt run
```

### プロダクションモードでの起動

環境変数 **JAVA_OPTS** でaspectjweaverのjarへのパスを通して **run** すれば動きます

```bash
sbt stage
export JAVA_OPTS=-javaagent:./target/universal/stage/lib/aspectjweaver-1.8.10.jar
sbt start
```

### どうなったらうまくいっているのか？

以下のようなメッセージが **でなくなったら** 成功です

```bash
  ___                           _      ___   _    _                                 ___  ___ _            _
 / _ \                         | |    |_  | | |  | |                                |  \/  |(_)          (_)
/ /_\ \ ___  _ __    ___   ___ | |_     | | | |  | |  ___   __ _ __   __ ___  _ __  | .  . | _  ___  ___  _  _ __    __ _
|  _  |/ __|| '_ \  / _ \ / __|| __|    | | | |/\| | / _ \ / _` |\ \ / // _ \| '__| | |\/| || |/ __|/ __|| || '_ \  / _` |
| | | |\__ \| |_) ||  __/| (__ | |_ /\__/ / \  /\  /|  __/| (_| | \ V /|  __/| |    | |  | || |\__ \\__ \| || | | || (_| |
\_| |_/|___/| .__/  \___| \___| \__|\____/   \/  \/  \___| \__,_|  \_/  \___||_|    \_|  |_/|_||___/|___/|_||_| |_| \__, |
            | |                                                                                                      __/ |
            |_|                                                                                                     |___/

```


# elasticsearchに流してKibanaでみる

elasticsearch / kibanaも最近メジャーアップデートがありましたので、試しもかねて6.0系で試してます。
kamon関連、system情報(JVMの情報など)、その他で展開する先（index）を分離しています

```
<source>
  @type forward
  port 24224
  @label @fw  
</source>

<label @fw>
  # kamon関連のmetrics
  <match kamon.fluentd.my-app.akka-actor.kamon/** >
    @type copy
        
    <store>
      @type elasticsearch
      host es
      port 9200

      # インデックス名はlogstash形式(下だとapplog-{yyyymmdd})
      # 日ごとにインデックスが作成されます
      logstash_format true
      logstash_prefix kamon
      logstash_dateformat %Y%m%d
      # 保存先のタイプ名を指定
      type_name kamon_log
      # タグの内容は「@log_name」という名前のフィールドに格納されてElasticSearchへ展開される
      include_tag_key true
      tag_key @log_name
      # 10秒ごとにElasticSearchへ転送
      flush_interval 10s
      #bufferの形式、パス
      buffer_type file
      buffer_path /fluentd/buffer/kamon-es.*.buffer
    </store>

  </match>

  <match kamon.fluentd.my-app.system-metric.** >
    @type copy
    
    <store>
      @type elasticsearch
      host es
      port 9200

      # インデックス名はlogstash形式(下だとapplog-{yyyymmdd})
      logstash_format true
      logstash_prefix system-metric
      logstash_dateformat %Y%m%d
      # 保存先のタイプ名を指定
      type_name system-metric
      # タグの内容は「@log_name」という名前のフィールドに格納されてElasticSearchへ展開される
      include_tag_key true
      tag_key @log_name
      # 10秒ごとにElasticSearchへ転送
      flush_interval 10s
      #bufferの形式、パス
      buffer_type file
      buffer_path /fluentd/buffer/system-metric-es.*.buffer
    </store>

  </match>

  <match >
    @type copy
    
    <store>
    @type elasticsearch
    host es
    port 9200

    # インデックス名はlogstash形式(下だとapplog-{yyyymmdd})
    logstash_format true
    logstash_prefix applog
    logstash_dateformat %Y%m%d
    # 保存先のタイプ名を指定
    type_name app_log
    # タグの内容は「@log_name」という名前のフィールドに格納されてElasticSearchへ展開される
    include_tag_key true
    tag_key @log_name
    # 10秒ごとにElasticSearchへ転送
    flush_interval 10s
    #bufferの形式、パス
    buffer_type file
    buffer_path /fluentd/buffer/app-es.*.buffer
    </store>
  </match>

</label>
```


# 攻撃してみました

playframeworkのCPU使用率をかけつつ、abコマンドで負荷をかけたときに、akka / akka-streamでとれるメトリックスをkibanaでグラフ化したりしながら・・・という話を書きたかったのですが、
絶賛勉強中＋文章が長くなりそうなので今回は飛ばさせていただきます。<(_ _)>

# ハマったところ

## elasticsearchでフィールド数の上限を超える

kamonはfluentdにデータをロードするにあたり多くのフィールドを出力します。
この関係上、デフォルトのフィールド数の上限に速攻で達してしまいます。
以下はエラーログの一例です。

```json
{"index"=>{
  "_index"=>"applog-20171123", "_type"=>"app_log", "_id"=>"NnDe6F8B5y9U0V8wC4qG", "status"=>400
  , "error"=>{"type"=>"illegal_argument_exception"
  , "reason"=>"Limit of total fields [1000] in index [applog-20171223] has been exceeded"}}}]}
```

回避には以下のようなelasticsearchの設定変更をすることで回避できます。


```
PUT /_settings
{
  "index.mapping.total_fields.limit": 10000
}
```


## 5～10分程度でkamonからのデータがElasticSearchにデータが来なくなる？？

正直なところ、この問題の原因究明にかなりの時間を要しました。
docker for Macやdocker on Linuxな環境では起きていないことや、
最近自分のPCが壊れたため、リプレイスしたのですが、それ以降問題は起きてません。

私のPCの老朽化が原因だった・・・というオチで収束したので、皆さんには関係のない悲しいオチで収束しました

# Tips

## ElasticSearchで使えるクエリー
どういった項目があるのか。SQL的にはGroup Byのようなものです。
以下はすべてkibanaのdevtoolで実行してます。
また、インデックス「applog-\* 」（インデックス名はlogstash形式にしてますので、\*の部分には年月日が入ります）にデータを格納してます。

* metrics一覧を取得

```
GET /applog-*/_search
{
  "from": 0,
  "size": 0,
  "sort": [],
  "aggregations": {
    "agg01": {
      "terms": {
        "field": "canonical_metric.name.keyword"
      ,"size": 10000
      }
    }
  }
}
```

* カテゴリ一覧を取得

```
GET /applog-*/_search
{
  "size": 0,
  "aggregations": {
    "agg01": {
      "terms": {
        "field": "category.name.keyword"
      }
    }
  }
}
```

* カテゴリ、メトリックス一覧を取得

```
GET /applog-*/_search
{
  "from": 0,
  "size": 0,
  "sort": [],
  "aggregations": {
    "agg01": {
      "terms": {
        "field": "category.name.keyword"
      ,"size": 10000
      },
      "aggs":
      {
      "agg02": {
        "terms": {
          "field": "canonical_metric.name.keyword"
        ,"size": 10000
        }
      }
      }
    }
  }
}
```

## 採取するmetricsを限定する

以下にあるように、entitynameをベースにフィルタリングができます。

```
* match any number of characters up to the next ‘/’ character found in the entity name.
? match exactly one character, other than ‘/’.
** match any number of characters, regardless of any ‘/’ character found after this wildcard.
exact entity name match if not wildcards are provided.
```
http://kamon.io/documentation/kamon-core/0.6.6/metrics/core-concepts/


### 例１／kamonそのものに関するmetricsをオフにする

```
kamon{
  metric {
    tick-interval = 10 second
  }

  metric.filters {
    akka-actor {
      includes = [ "**" ]
      excludes = [ "kamon/user/**"  ]
    }
    akka-dispatcher {
      includes = [ "**" ]
    }

    akka-router {
      includes = [ "**" ]
    }
    trace.includes = [ "**" ] 

    system-metric {
      includes = [ "**" ]
    }

  }

}

```

### 例２／採取する情報をJVMのメモリに関するものに限定

```
kamon{
  metric {
    tick-interval = 10 second
  }

  metric.filters {
    system-metric {
      includes = [ "jmx-memory" ]
    }
  }
  system-metrics.sigar-enabled = false
  system-metrics.jmx-enabled = true

}

```

# リンク

* 今回のプロジェクトについて  
https://github.com/yurayuramen/try-kamon  
プロジェクト一式をgithubにプッシュしました。
readme.mdに簡単なセットアップ方法を記載しましたので、煮るなり焼くなり好きに使ってください。 

* kamon.io
そもそもなんて読むんですかね。ググっても出てこないので、だれか教えてください。
http://kamon.io/documentation/get-started/

* kamon.io のgithub公式
https://github.com/kamon-io/Kamon

* elasticsearch / kibana の dockerについて
https://www.elastic.co/guide/en/elasticsearch/reference/6.0/docker.html
https://www.elastic.co/guide/en/kibana/6.0/docker.html

* kamonの存在を知るきっかけとなったサイバーエージェントさんのサイト
https://adtech.cyberagent.io/scalablog/2015/09/24/kamon_datadog_akka_performance/

* fluentdのdockerイメージ
https://hub.docker.com/r/fluent/fluentd/

* fluentdからelasticsearchにロードするgem
https://github.com/uken/fluent-plugin-elasticsearch
最新は2.x系だとdocker buildでエラーが出た問題を解消できなかったので、1.x系で逃げてます。

* playframework
https://www.playframework.com/documentation/2.6.x/Home

* sbt
http://www.scala-sbt.org/1.x/docs/index.html
playframeworkの2.6.6からsbt1.0をサポートしたとのことなので、今回は1.0系にしてます。