Kibana の elasticsearch のデータで監視

2016/1/12 追記
elasticsearch 2.x では Facet 機能が無くなっているので Aggregation を使って同様の結果を得る方法を追記しました

----

Kibana でグラフなどを眺めるだけでなく、elasticsearch (以降ES) の計算機能を利用して監視に使う方法です。

ブラウザの Developer Tools などで Kibana が ES に問い合わせているクエリを確認しながら試します。ES は機能が豊富すぎてなかなか大変です。ES を使うなら「<a href="http://www.amazon.co.jp/dp/4048662023">高速スケーラブル検索エンジン ElasticSearch Server</a>」を読むと良いと思いますが、本だけ読んでもクエリの種類や機能が多すぎて挫折します。試しながら必要な時に必要な箇所を読みましょう。全く読まないとそれはそれで落とし穴に嵌りそうなのが ES です。暗黙での動作に要注意です。

それでは本題です。

Kibana の histgram 機能でサービスのレスポンスタイムを表示したりすると思いますが、この平均値がしきい値を超えたらアラートを上げる仕組みを作りましょう。

## statistical facet (elasticsearch < 2.0)

Kibana のクエリを見ると <a href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-statistical-facet.html">statistical facet</a> という機能が使われていることがわかります。
この statistical では次の値を得ることができます（数学・統計前線わからない…）。

* std_deviation （[標準偏差](http://ja.wikipedia.org/wiki/%E6%A8%99%E6%BA%96%E5%81%8F%E5%B7%AE)）
* count （マッチした数）
* total （値の合計）
* min （値の最小値）
* max （値の最大値）
* mean （値の平均値）
* sum_of_squares （[変動・平方和](http://www.weblio.jp/content/sum+of+squares)）
* variance （[分散](http://ja.wikipedia.org/wiki/%E5%88%86%E6%95%A3)）

パフォーマンスモニタリングとしてはパーセンタイルが欲しいのですけどね。

次のクエリを http://kibana-es-server:9200/foo-2014.04.14/_search に対して POST します

```json:query
{
  "size": 0, <=マッチしたレコードは不要（計算結果だけで良い）
  "facets": {
    "0": { <=任意のファセット名
      "facet_filter": {
        "fquery": {
          "query": {
            "filtered": {
              "filter": {
                "range": { <=範囲指定フィルタ
                  "@timestamp": { <=時刻でフィルタ
                    "to":   1397445600000, <=UnixTimestampミリ秒
                    "from": 1397445300000
                  }
                }
              }
            }
          }
        }
      },
      "statistical": { <=statisticalファセットを使う
        "field": "responseTime" <=計算対象のフィールドを指定
      }
    }
  }
}
```

@timestamp の `from` や `to` には `now` という指定も可能です。

facet は一つのリクエストで複数指定可能です。Kibana の histgram で複合グラフに使われています。よって、たとえば10台のホストがあったとしてそれぞれの値を個別に取り出すことが可能です。次のようなクエリになります。

```json:query
{
  "size": 0, <=マッチしたレコードは不要（計算結果だけで良い）
  "facets": {
    "host1": { <=任意のファセット名レスポンスで返ってきます
      "facet_filter": {
        "fquery": {
          "query": {
            "filtered": {
              "query": {
                "term": { "host": "host1" } <=ホストフィールドで対象を絞る
              },
              "filter": {
                "range": {
                  "@timestamp": {
                    "to":   1397445600000,
                    "from": 1397445300000
                  }
                }
              }
            }
          }
        }
      },
      "statistical": { <=statisticalファセットを使う
        "field": "responseTime" <=計算対象のフィールドを指定
      }
    },
    "host2": { <=任意のファセット名レスポンスで返ってきます
      "facet_filter": {
        "fquery": {
          "query": {
            "filtered": {
              "query": {
                "term": { "host": "host2" } <=ホストフィールドで対象を絞る
              },
              "filter": {
                "range": {
                  "@timestamp": {
                    "to":   1397445600000,
                    "from": 1397445300000
                  }
                }
              }
            }
          }
        }
      },
      "statistical": {
        "field": "responseTime"
      }
    },
    "host3": { <=任意のファセット名レスポンスで返ってきます
      "facet_filter": {
        "fquery": {
          "query": {
            "filtered": {
              "query": {
                "term": { "host": "host3" } <=ホストフィールドで対象を絞る
              },
              "filter": {
                "range": {
                  "@timestamp": {
                    "to":   1397445600000,
                    "from": 1397445300000
                  }
                }
              }
            }
          }
        }
      },
      "statistical": {
        "field": "responseTime"
      }
    }
  }
}
```

次のような結果が返ってきます。

```json:response
{
  "facets": {
    "host3": { <=クエリで指定したファセット名
      "std_deviation": 127.19652427446599,
      "_type": "statistical",
      "count": 9718,
      "total": 785833,
      "min": 0,
      "max": 1794,
      "mean": 80.8636550730603,
      "sum_of_squares": 220772421,
      "variance": 16178.955787504816
    },
    "host2": { <=クエリで指定したファセット名
      "std_deviation": 174.08834053452765,
      "_type": "statistical",
      "count": 8775,
      "total": 927709,
      "min": 1,
      "max": 5276,
      "mean": 105.72182336182337,
      "sum_of_squares": 364020821,
      "variance": 30306.750310065665
    },
    "host1": { <=クエリで指定したファセット名
      "std_deviation": 169.53906702811668,
      "_type": "statistical",
      "count": 10940,
      "total": 1206185,
      "min": 0,
      "max": 2330,
      "mean": 110.25457038391225,
      "sum_of_squares": 447441247,
      "variance": 28743.49524876424
    }
  },
  "hits": {
    "hits": [],
    "max_score": 0,
    "total": 5947405
  },
  "_shards": {
    "failed": 0,
    "successful": 5,
    "total": 5
  },
  "timed_out": false,
  "took": 170
}
```

JSONの階層が深くてクエリ書くのが結構大変ですが、これで監視できますね。

## Stats Aggregation 

elasticsearch 2.0 からは facet 機能がなくなっていますので aggregation を使います。

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-stats-aggregation.html

elasticsearch が 2.x なら kibana は 4.3 が使えますから kibana の plugin として sense が使えます。クエリのテストをするにはこれを使いましょう。

https://www.elastic.co/guide/en/sense/current/index.html

では facet でやったホストごとの stats を得るクエリを書いてみましょう。

```json:request
{
  "size": 0,  <=マッチしたレコード内容は不要なので0
  "query": {
    "filtered": {
      "filter": {
        "range": {  <=対象の時間帯をrange指定
          "@timestamp": {   <=過去1時間とかなら相対指定が便利
            "from": "now-1h",
            "to": "now"
          }
        }
      }
    }
  },
  "aggs": {
    "hostname": {  <=任意のaggs名
      "terms": {
        "field": "host"  <=hostフィールドの値で分割
      },
      "aggs": {  <=サブアグリゲーション
        "stats": {  <=任意のaggs名
          "stats": {
            "field": "responseTime"  <=レスポンスタイムのstatsを計算
          }
        }
      }
    }
  }
}
```

facet の時よりもシンプルになりました。

```json:response
{
  "took": 1031,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 271194,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "hostname": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "server1",
          "doc_count": 70838,
          "stats": {
            "count": 70838,
            "min": 0,
            "max": 3301,
            "avg": 114.49469211440187,
            "sum": 8110575
          }
        },
        {
          "key": "server2",
          ... (省略)
        },
        {
          "key": "server3",
          ... (省略)
        },
        {
          "key": "server4",
          "doc_count": 65532,
          "stats": {
            "count": 65532,
            "min": 0,
            "max": 2870,
            "avg": 97.3189434169566,
            "sum": 6377505
          }
        }
      ]
    }
  }
}
```

## 保存

Kibana で長期間のグラフを表示するのは無理があるのでこうやって取り出した値を <a href="https://github.com/kazeburo/HRForecast">HRForecast</a> とか <a href="http://kazeburo.github.io/GrowthForecast/">GrowsForecast</a> に POST するのもアリかもしれません。

~~監視だけだとパーセンタイル的なことできそうですね。「1秒を超えるリクエストが全体の10%を超えた場合」とすれば「90パーセンタイル値が1秒を超えた場合」となりますね。~~


percentile も aggs で取得できるようになってます elasticsearch 1.3 あたりから。
http://qiita.com/yteraoka/items/f69544a8572a02fca1f3