SensuにCron的なスケジューリングを組み込んでみる

この投稿は 「[Sensu Advent Calendar 2014](http://qiita.com/advent-calendar/2014/sensu)」 用のクイックハックですが、かなり本気のネタです。誰か本家にパッチ送ってください。

なんらかのシステムであれば、毎日決まった時間に実行される処理がありますよね。
そのバッチ処理のエラーはどうしてます？いつもうまく動く前提？独自の処理を入れてアラート？出来上がったデータをSensuでチェック？

いろいろやり方や運用方法はあると思いますが、バッチの起動自体もSensuで出来たらどうでしょう？アラートがひとつにまとまってHUB的に良い感じになりそうじゃないですか？

あっ、ちなみ結果だけを投げるのであればクライアントで3030ポートが開いてますのでそこにCronの結果をUDPなりで投げれば、なにも面倒なことしなくても大丈夫です。デフォルトの機能なので安心して使えます。([参考:sensu-client は port 3030 で listen していますの巻](http://qiita.com/inokappa/items/49e342ce30077c6cc864#comment-aa2a19e14a4b5197b7f0))
でもマルチバイト通らないようなんですよね...

ということで、SensuにCronっぽい処理を組み込んでみたいと思います。

まずCronっぽいライブラリなんですけど、Rubyでは「[Clockwork](https://github.com/tomykaira/clockwork)」が有名どころですね。でも、組み込んでみたら処理がブロックされるようで他のプラグインが動きませんでした。Windowsで開発してたからでしょうか。
次に「[rufus/scheduler](https://github.com/jmettraux/rufus-scheduler)」というものを探しだしまして、これを利用したところブロックもせずに良好！

パッチは以下の感じで。ついでにUTF8の日本語も通るようにしておきました。

``` diff:client.rb.diff
> diff -u sensu-0.14.0/lib/sensu/client.rb.org sensu-0.14.0/lib/sensu/client.rb
--- sensu-0.14.0/lib/sensu\clien.rb.org 2014-10-16 20:40:50 +0900
+++ sensu-0.14.0/lib/sensu\clien.rb     2014-12-08 19:34:36 +0900
@@ -1,5 +1,6 @@
 require 'sensu/daemon'
 require 'sensu/socket'
+require 'rufus/scheduler'

 module Sensu
   class Client
@@ -102,7 +103,8 @@
           started = Time.now.to_f
           Spawn.process(command, :timeout => check[:timeout]) do |output, stats|
             check[:duration] = ('%.3f' % (Time.now.to_f - started)).to_f
-            check[:output] = output
+            check[:output] = output.encode('UTF-8', 'UTF-8')
+            # check[:outpu] = output
             check[:status] = status
             publish_result(check)
             @checks_in_progress.delete(check[:name])
@@ -204,15 +206,39 @@
       end
     end

+    def cron_checks(checks)
+        checks.each do |check|
+          check[:issued] = Time.now.to_i
+          scheduler = Rufus::Scheduler.new
+          cron_id = scheduler.cron check[:cron] do |cron|
+            unless @state == :paused
+              process_check(check)
+            end
+            @logger.info("cron checks resettings", {
+              :name => check[:name], :schedule => cron.next_time
+            })
+          end
+          cron = scheduler.job(cron_id)
+          @logger.info("cron checks settings", {
+            :name => check[:name], :schedule => cron.next_time
+          })
+        end
+    end
+
     def setup_standalone
       @logger.debug('scheduling standalone checks')
       standard_checks = @settings.checks.select do |check|
         check[:standalone]
       end
       extension_checks = @extensions.checks.select do |check|
-        check[:standalone] && check[:interval].is_a?(Integer)
+        check[:standalone] && check[:interval].is_a?(Integer) && !check[:cron]
       end
       schedule_checks(standard_checks + extension_checks)
+
+      crons_checks = @extensions.checks.select do |check|
+        check[:cron]
+      end
+      cron_checks(crons_checks)
     end

     def setup_sockets
```

ということで、設定ファイルの「interval」の代わりに「cron」を指定すると決まった時刻に処理するバッチ処理用のSensuプラグインが作れるようになりました。

あとはStandaloneのプラグインを作るなり、Extensionを作るなりすればいいはずです。

作ったExtensionはあるのですが、実際の業務べったりのコードになってますので気が向いたら整理してアップしますね。

では、よい監視ライフを！