概要
時間のかかるプログラム(ディープなモデルの学習など) のプロセスの終了を Slack で通知してくれるようにしたので、そのメモ。
特徴としては、
- プログラム実行環境にて、追跡するプログラムのプロセスIDを引数とするシェルスクリプトを実行
- Slack にて、追跡開始の確認、およびプログラムの実行コマンドの表示
- Slack にて、プログラムの終了通知、および追跡を終了するプロセスIDの表示
を行います。
使用ツール & 環境
-
使用ツール
- シェルスクリプト
- Slack
- Incoming Webhook (Slack App)
-
環境
- Ubuntu 18.04
シェルスクリプトをほぼ初めて触ったので、冗長なコーディングがあればご指摘お願いします。
導入手順
1. Slack のチャンネルに Incoming Webhook を連携させる
Incoming Webhook へ飛んで、自分の属するワークスペースのチャンネルに連携させてください。
各自お好みで設定していただければと思います。ここで Webhook URL
が、この後の作業で必要となります。
2. プロセスの追跡を行うシェルスクリプトの作成
僕は、以下のディレクトリ構成を作成しました。わかりやすければどこでも良いと思います。
~/
└ Documents/
└ slack/
└ (ここに作成するよ)
まず、 ~/Documents/slack/
に以下のコマンドでシェルスクリプトを作成します。
$ touch watch # 名前もなんでも良いと思います。
次に、エディタで watch
を開きます。そこへ僕は以下のシェルスクリプトを記述しました。
$ vi watch
#!/bin/bash
set -eu
URL="" ### ここにWebhook URLを記述! ###
USERNAME="あおい姐さん"
PID=$1
if [ ! $PID ]; then
echo "ERROR: PID required." >&2
exit 1
fi
# 実行開始時のプロセス
START_MSG="`ps ho args $PID`"
ret=$?
if [ $ret -ne 0 ]; then
echo "ERROR: Process not found." >&2
exit 1
else
ary=(`echo $START_MSG`)
for i in `seq 1 ${#ary[@]}`
do
set +u
if [ "$i" -eq 1 ]
then
PRETXT="${ary[$i-1]##*/} を実行したよ"
else
TEXT="$TEXT ${ary[$i-1]}"
fi
done
fi
set -u
TITLE="実行コマンド"
TEXT="\`\$ ${ary[0]##*/}$TEXT\`"
BEGIN_DATA="payload={\"username\": \"$USERNAME\", \"text\": \"追跡開始! (PID: \`$PID\` )\", \"attachments\": [{\"fallback\": \"実行コマンド確認\",\"color\": \"#003399\",\"pretext\": \"$PRETXT\" ,\"title\": \"$TITLE\",\"text\": \"$TEXT\"}]}"
curl -s -X POST --data-urlencode "$BEGIN_DATA" ${URL} >/dev/null
# プロセスの終了時
END_MSG="${ary[0]##*/} が終了したよ\n追跡終了! (PID: \`$PID\` )"
: "start watch ${PID}"
{
while true
do
if ! ps -p ${PID} >/dev/null ;then
curl -s -X POST --data-urlencode "payload={\"username\": \"$USERNAME\", \"text\": \"${END_MSG}\"}" ${URL} >/dev/null
exit
fi
sleep 1m
done
} &
こちらのコードを参考にさせていただきました。本当にありがとうございます。
参考 : https://gist.github.com/ohsuga/41a459792748e8c2afdb81d0b261c3de
3.テスト
ここまでできたら、一度テストしてみましょう。
まず、適当にPythonで30秒カウントするソースコードを作成します。同時にプロセスIDも出力してくれるようにos.getpid()
をprintするようにします。
$ vi test.py
import os
import sys
import time
def print_args(pid, args):
print(os.getpid(), args)
if __name__ == "__main__":
args = sys.argv
print_args(os.getpid(), args)
for i in range(0, 30, 1):
time.sleep(1)
print(i, "sec")
そしてテストのコマンドが以下になります。コマンドライン引数のテストも兼ねて色々付け足しておきましょう。
ここで、プロセスIDはプロセスごとで異なるので、臨機応変に対応お願いします。以下の場合ですと17027
です。
$ python test.py 1 2 3 hello --args-conf
17027 ['test.py', '1', '2', '3', 'hello', '--args-conf']
0 sec
1 sec
2 sec
3 sec
...
30秒以内にすかさず以下のコマンドを実行しましょう。
$ bash watch 17027
そうすると Slack の Incoming Webhook を連携したチャンネルに以下のような投稿がされると思います。
そして30秒が経過すると。。。
26 sec
27 sec
28 sec
29 sec
$
タイムラグがあると思いますが、以下のような投稿がされると思います。
4. PATHを通す
せっかくコマンドを作成したので、PC上のどこからでもアクセスできるようにPATHを通してしまいましょう。
まずは以下のコマンドで~/.bash_profile
にシェルスクリプトのPATHを記述します。
$ echo 'export "PATH=$HOME/Documents/slack:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile
エイリアスも設定して、bash
を打たずに実行できるようにしましょう。僕は aoi
というコマンドで実行できるようにしました。
$ echo "alias aoi='bash watch'" >> ~/.bash_aliases
$ source ~/.bash_aliases
これでコマンドラインにaoi
と打ってみて、以下のようなエラーが出ていればPATHが通っています。
$ aoi
/home/user/Documents/slack/watch: line 11: $1: unbound variable
実行例
PyTorchのチュートリアルであるMNISTの分類タスクをやってみましょう。こちらはtorch
とtorchvision
を必要とするのでご注意ください。
コードを持ってきたら、まずは以下のコマンドでmainのコードを走らせましょう。
$ python main.py --no-cuda
Train Epoch: 1 [0/60000 (0%)] Loss: 2.305401
Train Epoch: 1 [640/60000 (1%)] Loss: 1.359780
Train Epoch: 1 [1280/60000 (2%)] Loss: 0.830692
Train Epoch: 1 [1920/60000 (3%)] Loss: 0.620273
Train Epoch: 1 [2560/60000 (4%)] Loss: 0.354148
Train Epoch: 1 [3200/60000 (5%)] Loss: 0.461558
Train Epoch: 1 [3840/60000 (6%)] Loss: 0.278612
...
そしてaoi
を実行するためにプロセスIDを確認しましょう。以下のコマンドで確認できると思います。
$ ps x | grep "python main.py"
11313 pts/0 Rl+ 0:10 /home/user/.pyenv/versions/3.6.7/bin/python main.py --no-cuda
11387 pts/5 S+ 0:00 grep --color=auto python main.py
おそらくプロセスIDは11313
ですね。それでは、aoi
を実行しましょう!
$ aoi 11313
すると。。。
こんな感じで学習の開始と終了を通知してくれるようになりました。
おわりに
まだまだ発展途上の段階の実装だと思うので、導入の際は注意してください m(_ _)m
今後の課題としては
- プロセスIDの確認の手間を省きたい
- Slack にて、学習のログの表示やエラーによる停止の通知など拡張したい
したいなと思ってます。
もしよろしければ GitHubリポジトリ へのContribution絶賛お待ちしております!