自転車ロードレース放送予告bot[twitter:@cycletvschedule]を最近作った[twitter:@riocampos]です。
当初はtweetするRubyスクリプトを5分毎に起動するようcronというかlaunchdに5分刻みの時刻を指定する設定を行っていました。しかし(Mac miniの動作環境のせいか)5分ごとに起動してくれない。
ということで、clockworkdでdaemon化した別スクリプトを走らせ、そこからtweetするスクリプトを5分ごとに起動するようにしました。そのスクリプトを例にして説明します。
参考:
- https://github.com/tomykaira/clockwork
- clockwork について - 君の瞳はまるでルビー - Ruby 関連まとめサイト
- Clockwork Cheat Sheet - Qiita
clockworkとは
cronみたいなものです。
cronの無くなったherokuでも便利に使えるようです。
本家でも。
スクリプト
当然ですが、gem install clockworkしておいてください。
clockworkのスクリプト crontweet.rb
#!/usr/bin/env ruby # coding: utf-8 require 'clockwork' load File.expand_path('jspocycletimetable.rb') def make_each_5min_array(last_digit) last_digit = last_digit.round last_digit = last_digit - 5 if last_digit >= 5 array = [] (0..11).each do |m| min = "%02d" % (m * 5 + last_digit) array[m] = "**:#{min}" end array end Clockwork::every( 1.hour, 'cyclerrtvtweet', { :thread => true, :at => make_each_5min_array(0)}) do Jsports.jspocycletimetable end GC.start
ツイートするスクリプト jspocycletimetable.rb(大半省略m(_ _)m)
require 'twitter' module Jsports def tweet # ツイート文作成 end def jspocycletimetable load "./set.rb" tweet_jspocycle = set tweet_jspocycle.update(tweet) # その他もろもろ end end
解説
Clockwork::handlerをつかうやり方が一般的かも知れませんが、各時刻条件で作業が違う場合には以下のやり方が楽だと思います。
clockworkの基本フォーマットは
Clockwork::every(時刻条件, 名前, パラメータ){ブロック}
となっています。
時刻条件
起動間隔を指定します。メソッドの表記が単数・複数形の両方ありますが、どちらも同じです。
20.seconds #20秒間隔 5.mins #5分間隔 6.hours #6時間間隔 2.days #2日間隔 1.week #1週間間隔
名前
ログに出力する名前です。
本来は、Clockwork::handlerで呼ぶときの名前です。
パラメータ
ハッシュで指定します。3種類です。指定不要の場合は省略可能です。
{:at => "23:45" # 23時45分に起動 :if => if: lambda { |t| t.day == 1 }) # 日付が1日の場合に起動 :thread => true } # マルチスレッドで起動
:atは配列で指定することも出来ます。また時刻部分はワイルドカード可能です。例えば
Clockwork::every(1.hour, "everyminute", :at => ["*:05", "*:20", "*:35", "*:45"]) # 毎時5分、20分、35分、45分に…何もしない(作業が指定されていないから)。
ブロック
起動させる作業を指定します。
別メソッドを呼ぶなり、ブロックの中でごにょごにょするなり、なんなり指定してくださいw
もう一度当初のスクリプトを見直す
時刻指定
:atで5分ごとに指定したいと思ったので、メソッドを作成しました。一の位が0から4までの自然数を指定します。
分の部分は二桁になるよう整形しています。時刻の部分はワイルドカード*にしています。
def make_each_5min_array(last_digit) last_digit = last_digit.round last_digit = last_digit - 5 if last_digit >= 5 array = [] (0..11).each do |m| min = "%02d" % (m * 5 + last_digit) array[m] = "*:#{min}" end array end
make_each_5min_array()を呼ぶと5分ごとの時刻を指定した配列を返してきます。
作業
名前を'cyclerrtvtweet'としています。
末尾0分/5分にブロックを呼び出します。
1時間のうちの起動時刻をmake_each_5min_array(0)で指定しているので、時刻条件は1時間毎、つまり 1.hour を指定します。
メソッドの作業に時間を要する可能性があるのでマルチスレッド指定をしています。
ブロックでは、モジュールJsportsのメソッドJsports#jspocycletimetableを呼び出すよう指定しています。
Clockwork::every( 1.hour, 'cyclerrtvtweet', { :thread => true, :at => make_each_5min_array(0)}) do Jsports.jspocycletimetable end
Jsports#jspocycletimetableでは必要に応じてツイートをします。
daemonとして起動
clockworkdヘルプ
clockworkdはclockworkとは別のgemなので、別途gem install clockworkdしてください。
必要なのはdaemons gemでしたm(_ _)m
$ clockworkd --help Usage: clockworkd -c FILE [options] start|stop|restart|run --pid-dir=DIR Alternate directory in which to store the process ids. Default is /.../clockwork/tmp. -i, --identifier=STR An identifier for the process. Default is clock file name. -l, --log Redirect both STDOUT and STDERR to a logfile named clockworkd[.<identifier>].output in the pid-file directory. --log-dir=DIR A specific directory to put the log files into (default location is pid directory). -m, --monitor Start monitor process. -c, --clock=FILE Clock .rb file. Default is /.../clockwork/clock.rb. -h, --help Show this message
logを出力させる設定で起動
$ clockworkd -c crontweet.rb --log start
logは tmp/clockworkd.(スクリプト名crontweet).output に出力されます。
tail -Fを使うと自動更新してくれます。
clockworkd.crontweet: process with pid 74526 started. I, [2013-06-25T13:45:02.774780 #74526] INFO -- : Starting clock for 12 events: [ cyclerrtvtweet cyclerrtvtweet cyclerrtvtweet cyclerrtvtweet cyclerrtvtweet cyclerrtvtweet cyclerrtvtweet cyclerrtvtweet cyclerrtvtweet cyclerrtvtweet cyclerrtvtweet cyclerrtvtweet ] I, [2013-06-25T13:45:02.775175 #74526] INFO -- : Triggering 'cyclerrtvtweet' :
logディレクトリ指定(Macの場合)
なお、Macのユーザプロセスのlogディレクトリは~/Library/Logsなので
$ clockworkd -c crontweet.rb --log start --log-dir=~/Library/Logs
としておくとコンソールでlogが見れて便利です。
daemonで稼働しているclockwork(ruby)プロセスのPIDはtmp/clockworkd.(スクリプト名crontweet).pidに出力されます。もちろん、そのプロセスが終了すればそのファイルも削除されます。
なにか設定を変えたらrestartで再読込
$ clockworkd -c crontweet.rb --log restart clockworkd.crontweet: pid file: /.../tmp/clockworkd.crontweet.pid clockworkd.crontweet: output log file: /.../tmp/clockworkd.crontweet.output clockworkd.crontweet: trying to stop process with pid 71129... clockworkd.crontweet: process with pid 71129 successfully stopped.
上記のようにlogの位置を変更している場合にはlogディレクトリを指定しましょう。log出力を指定しながらディレクトリを指定し忘れるとデフォルトディレクトリにlog出力されます。
$ clockworkd -c crontweet.rb --log --log-dir=~/Library/Logs restart
launchdで自動起動(Macの場合)
Mac起動時にclockworkdを起動させ、且つ異常終了した場合に再起動するように設定しておきます。
plistに実行させるコマンドは
/Users/hoge/.rbenv/shims/clockworkd -c /.../crontweet.rb --log --log-dir=/Users/hoge/Library/Logs --pid-dir=/.../tmp restart
です(詳細なパスは省略していますので、ご自身の環境に合わせてください)。restartなのは、clockworkdスクリプトやそのスクリプトで指定しているrubyスクリプトを修正した後にこのplistを読み込ませたいからです(startだと重複起動してしまう)。
これをplistに変換すると以下のようになります。
名前をcrontweet_bootup.plistとしています。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <key>Label</key> <string>crontweet_bootup</string> <key>ProgramArguments</key> <array> <string>/Users/hoge/.rbenv/shims/clockworkd</string> <string>-c</string> <string>/.../crontweet.rb</string> <string>--log</string> <string>--log-dir=/Users/hoge/Library/Logs</string> <string>--pid-dir=/.../tmp</string> <string>restart</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist>
ファイル出力先指定を忘れると
--pid-dirを指定していないと、logファイルやpidファイルが/tmpへ出力されます(Linuxでcronする場合でも同様でしょう)。
--log-dirだけ指定して--pid-dirを指定し忘れた例:
$ ls -l /tmp | grep crontweet -rw-r--r-- 1 hoge wheel 25 6 4 02:18 clockworkd.crontweet.pid
なにか設定を変えたらrestartで再読込
lunchy gemを使って再起動させます。
もちろんlaunchctl unloadしてからlaunchctl loadしても同じですが、フルパス指定しなければいけないのは面倒なので。
$ lunchy restart crontweet_bootup stopped crontweet_bootup started crontweet_bootup
大満足
このclockworkdのおかげで、botのツイート時刻ズレが全く無くなりました。大感謝。
残念ながら数十秒単位のずれはあります。他のスケジューラgemを検討中。