monitor-agentの監視スクリプトをつくった

fluentdのmonitor-agentで値とる監視スクリプトをつくったんですが、個人的にだいぶ勉強になったので備忘録を。


#### ・取りたいデータのこと
こんなかんじのcurlでとれるデータの集合（1台ごとに複数あってS3に送ってるのとlogサーバを介してelasticsearchに送ってる複数種類のログが混在）のうち、limitに最も抵触しそうなやつを取得して警告とか超えてたら障害とかアラート出したいという動機からはじまりました。

```sh
$ curl -s http://localhost:24220/api/plugins.json | jq .
    {
      "plugin_id": "object:3fdb1b767***",
      "plugin_category": "output",
      "type": null,
      "config": {
        "include_tag_key": "true",
        "tag_key": "@log_name",
        "hosts": "10.124.0.***:9200,*****",
        "logstash_format": "true",
        "logstash_prefix": "prod",
        "buffer_path": "/opt/fluent_buf/es/hoge.log*****",
        "flush_interval": "1s",
        "buffer_queue_limit": "512",
        "buffer_chunk_limit": "10m",
        "buffer_type": "file"
      },
      "output_plugin": true,
      "buffer_queue_length": 1,
      "buffer_total_queued_size": 309,
      "retry_count": 0
    },
```

#### ・監視すべき値の情報など

|メトリクス|概要|チェックすべき項目|
|:----------|:--|:--|
|buffer_queue_length|バッファーに蓄積されているキューの数|buffer_queue_limitで設定した値を超えるとログデータがロストするので注意|
|buffer_total_queued_size|	バッファーに蓄積されている合計サイズ|buffer_queue_limit×buffer_chunk_limitの値を超えるとログデータがロストするので注意|
|retry_count|	再送を試みた回数|	retry_limitで設定した回数を超えるとバッファーがリセットされるので注意|

`buffer_total_queued_size`は`total_queued_chunk_size`のことで、キューに入っている全 chunk のサイズ、なようです。あて先が落ちてるときbuffer_queue_limit×buffer_chunk_limitの値までは大丈夫だけどそれ以上はロストというような。
監視するとbuffer_total_queued_sizeよりも先にbuffer_queue_limitが閾値に達するのでbuffer_total_queued_size以外の2つみとけば対応はできそうな模様です。

[Monitoring Agent を使って Zabbix で Fluentd を監視する - Qiita](http://qiita.com/Trickey/items/b9b18e60056007539648)
[今さら聞けないfluentd～クラウド時代のログ管理入門（3）：fluentdの効果的な活用例と安定運用のポイント (3/3) - ＠IT](http://www.atmarkit.co.jp/ait/articles/1404/04/news004_3.html)
[td-agent(fluentd) の monitor_agent で取得出来る情報を Graphite + Grafana で見る試み - ようへいの日々精進XP](http://inokara.hateblo.jp/entry/2014/05/31/081249)
[flunetd、forward先がダメだった時にforward元である程度ログを担保したい - さよならインターネット](http://blog.kenjiskywalker.org/blog/2014/04/01/fluentd-buffer-forward/)
[fluentdを導入時にまず知っておいたほうがよさそうなこと（インストール、監視、HA構成、チューニングなど） - Qiita](http://qiita.com/uzresk/items/3bfeeac82dfcb2a4300e)
[Fluentd ソースコード完全解説 · GitHub](https://gist.github.com/sonots/c54882f73e3e747f4b20)

forestつかっててまとめてとるしかなさそげで、
値の最大値がとれてもしょうがなくてlimitに近いやつが要るかなあとなんとなく。

#### ・デバッガ、初めてのpry

おしえていただいて使ってみてだいぶベンリでした。

[今更聞けないpryの使い方と便利プラグイン集 - Qiita](http://qiita.com/k0kubun/items/b118e9ccaef8707c4d9f)
[Ruby デバッガー binding.pry - のんびりしているエンジニアの日記](http://nonbiri-tereka.hatenablog.com/entry/2014/06/25/113725)
[rubyでsortする対象にnilが含まれてるとエラー - (ﾟ∀ﾟ)o彡 sasata299's blog](http://blog.livedoor.jp/sasata299/archives/51176071.html)


#### ・詰まったところなど

なんか初心者ですみませんでしたというようなのが多くてアレですが。

->　bashでハッシュを扱うのは私にはムリそうなのでrubyをつかう
->　curlでとってきたjsonデータパースとか初だった（ネストされたデータの扱い方があんまわかってなかった）
->　最初は変数が少なくて上書きしちゃってて最大値とれてなかった
->　閾値の値が明示的に設定してないととれないのでrescueでデフォルト値を入れる
->　if～elsifだとみづらかったのでcase～whenにした
->　配列のつもりがそうでなかったので'[0]'とかすると1文字しかとれなかったり
->　next unless varで変数に値がなかったら次のループにいくのでインデント浅くできるのとifの否定はunlessでいいぽい
->　小数点の値比較は鬼門だった

buffer_total_queued_sizeを見ないことになったので使わなかったやつ
->　scanは文字列にしか使えないので型変換して計算するときにまた戻す（splitでも掛け算の手間は同様だった）
->　バイトの文字列を数値に直すのを分割して掛け算するしかなかった


#### ・小数点含んだ最大レート比較は難しい話

なんかホラーみたいなことに。理系でないので？？？状態に。

```rb
[18] pry(main)> format = "%.6e" 
=> "%.6e" 
[20] pry(main)> sprintf(format, max_use_rate) >= sprintf(format, 0.8)
=> false
[21] pry(main)> error_limit = 1.00000
=> 1.0
[22] pry(main)> warn_limit = 0.80000
=> 0.8
[23] pry(main)> sprintf(format, max_use_rate) >= sprintf(format, error_limit)
=> true
[24] pry(main)> sprintf(format, max_use_rate) >= sprintf(format, warn_limit)
=> false
[25] pry(main)> warn_limit
=> 0.8
[26] pry(main)> sprintf(format, warn_limit)
=> "8.000000e-01" 
[27] pry(main)> sprintf(format, error_limit)
=> "1.000000e+00" 
[38] pry(main)> max_use_rate
=> 4.559941589832306e-06
```

4.559941589832306e-06 は e-06 で 0.000004... みたいなことっぽいのでerror_limitより大きいがtrueなのがまずいのではないのかというような。ぐぐったり聞いたりしたところ小数点の比較は他の言語でもむずいので有名な模様でした。
formatがまずかったんですかね。

[Rubyで浮動小数点数の加算減算がずれないようにする | 秋山ブログ](http://d.akiroom.com/2014-01/ruby-float-add-sub-precision/)
[浮動小数点数の同値比較には計算機イプシロンを使うこと - Tociyuki::Diary](http://d.hatena.ne.jp/tociyuki/20090620/1245503707)
[浮動小数点数の等値比較 - Qiita](http://qiita.com/fujieda/items/90d5465c887f2607e21a)

#### ・イプシロンが中二っぽい語感なのでぐぐった
http://matome.naver.jp/odai/2137766691607047301

ふーんギリシャ語のEですか。ガンダムかなにかっぽいと思った通りモビルスーツもありました。
プルートゥーそういえばみたことあった。
数学的には微小数だそうで。

計算機イプシロンを使うというのは私には高度すぎて読んでるうちに~~( ˘ω˘ ) ｽﾔｧ~~脳内が霧の中みたいになったのでRationalでいいかなーと思いました。
わかりやすいってのは結構だいじというか過ぎたるはなんとやらで足るを知るのがいいような。
複雑なもんを残してしまうと運用に困りそうです(とりあえず私自身がきれいさっぱり忘れ去って困る可能性が最も高そう)。このへんのバランス感覚は難しいなと思います。

#### ・Rational(有理数)をつかってみた

[class Rational (Ruby 1.9.3)](http://docs.ruby-lang.org/ja/1.9.3/class/Rational.html)
[若手エンジニア／初心者のためのRuby 2.1入門（6）：RubyのNumericとTimeで数値と時間をさまざまな操作・演算・判定 (1/4) - ＠IT](http://www.atmarkit.co.jp/ait/articles/1407/29/news030.html)

こんなかんじになりました。

```rb
#!/bin/env ruby
require 'net/http'
require 'json'
require 'rational'
##require 'pry'

## get json data.
host, port, metric = ARGV
response = Net::HTTP.start(host, port) do |http|
  http.get("/api/plugins.json")
end
body = JSON.parse(response.body)
## retry_limitよりretry_countが大きいとバッファがリセットされるのであぶない
## retry_limitはデフォルト値なのでjsonからとれない
## buffer_queue_limit*buffer_chunk_limit<buffer_total_queued_size=loglost.
## buffer_queue_length>buffer_queue_limit=loglost.
#
## set default value(pick up from source code config_param).
defalut_vars = {
  'buffer_queue_limit' => 256,
  'retry_limit' => 17
}
maximum_value = 0
use_rate = 0
max_use_rate = 0
buffer_queue_limit_n = defalut_vars['buffer_queue_limit']
retry_limit_n = defalut_vars['retry_limit']

## loop(get max_use_rate of metric. and set variables of limits.)
body['plugins'].each do |plugin|
  if plugin['output_plugin'] 
    metr = plugin[metric]
    next unless metr
    case metric
    when 'retry_count' then
      retry_limit_n = plugin["config"]["retry_limit"][0] rescue defalut_vars['retry_limit']
      use_rate = Rational(metr,retry_limit_n)
    when 'buffer_queue_length' then
      buffer_queue_limit_n = plugin["config"]["buffer_queue_limit"][0] rescue defalut_vars['buffer_queue_limit']
      use_rate = Rational(metr,buffer_queue_limit_n)
    end
    ## set max_values. 
    if use_rate > max_use_rate
      max_use_rate = use_rate
      maximum_value = metr
      max_buffer_queue_limit = buffer_queue_limit_n
      max_retry_limit = retry_limit_n
    end
  end
end

## for test.
#p "metric: #{metric}" 
#p "maximum_value  #{maximum_value}" 
#p "max_use_rate: #{max_use_rate}" 
#p "buffer_queue_limit: #{buffer_queue_limit_n}"  
#p "retry_limit: #{retry_limit_n}"  
# binding.pry

## check value.
error_limit = Rational(8,10)
warn_limit = Rational(7,10)
if max_use_rate >= error_limit
  result_value = maximum_value
elsif max_use_rate >= warn_limit
  result_value = 1
elsif max_use_rate < warn_limit
  result_value = 0
end

## outputs the result value.
p result_value
```

このスクリプトの気になる点としては以下のようなのがあります。
　>警告以下の値は丸めちゃうのでグラフ化して傾向分析するのにはあんま向いてない
　>最も閾値に近い値だけとってるので他を見逃す可能性がないとはいえない
　>毎回どの値がとられるかは状況次第なので閾値こえてるのグラフにしてもめっちゃ波うちそう。
　　そもそも警告の段階で対処するのが理想ではあるのでやっぱりグラフ化に向いてない。
　>どのlimitを増やせばいいか等は実際にまたcurlでとって詳しくみなおす必要がありそう。

#### ・故意にアラート出すテスト

とりあえずlimitを全部1にしてchunkのバイト文字列変換を掛け算せず1で固定にして分子を分母より大きくしてみるなどしたところ想定どおり結果とれました。

```sh
$ ruby montest.rb localhost 24220 buffer_total_queued_size
"metric: buffer_total_queued_size"
"maximum_value: 11211"
"max_use_rate: 11211/10"
"max_buffer_queue_limit: 1"
"max_buffer_chunk_limit: 10.0"
"max_retry_limit: 1"
11211
```

#### ・監視連携

zabbixを使っているのですが、senderでもuser_scriptでもsystem.runでもどれでもスクリプト配布しないといけないのは変わらないです。
zabbixサーバにやさしいのがいいなと。とりあえずsystem.runでいいかなということに。

※system.runの待ち時間の上限は3秒だと[「Zabbix統合監視徹底活用」](http://www.amazon.co.jp/dp/4774162884/)に記載されていると聞きました。
　続報ではバックグラウンドに渡すと値はとれるかわからないが処理詰まりはなくなる模様。じゃなかったらキューイングせよと続く模様。

ちなみにsystem.runにはnowaitというモードもあるようですが、データが取れなくなる(問答無用で1が帰ってくる)らしいので使わないほうがよさそうです。
http://www.zabbix.jp/node/966

warningのトリガは以下のようなかんじで。errorが>1というところでしょうか。
	`{system.run["ruby /path_to_file/monitor-agent-out-chk.rb localhost 24220 retry_count &"].last(0)}=1`

とりあえずこんなところで失礼します。