nohup コマンドから slack に通知を飛ばして、ついでに管理とか時間計測とかする

# はじめに
会社で研究とか PoC とかで DeepLearning を回したり、でかいデータで処理が重い処理を動かしたりすることが割とあり、nohup コマンドで回していました。処理に数時間かかるものや、2~3日かかるのとかがよくあったので、終了したら通知が欲しくなりました。

また、クラウドサーバーで大体やるので、無駄にインスタンスを増やしてもお金がかかるし、今誰がどのサーバー使っているか、切り忘れていないか(スケジュールで停止させてるインスタンスもありますが、2~3日かかるやつに自動停止とかできないので)、などを最低限の労力で管理したくなったので、コマンド作るかと思って作りました。

ついでに、時間も計測してくれたら便利だなということで、時間を測るコマンドも適当に追加しました。


# Webhookをslackに導入
ここの記事にこれ以上ないくらいまとまっているので、導入は以下の方法を見てください。
好きなチャンネルにインストールします。
https://zenn.dev/hotaka_noda/articles/4a6f0ccee73a18

ここから、Webhook URLというところをコピーします。
![スクリーンショット 2022-12-20 13.28.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005331/b0dde60b-5478-8c37-15a3-ca9365f1a6d6.png)


# slackに通知
とりあえず、これでslackに通知を飛ばすコマンドが出来上がりました。
通知をつけて、finish というメッセージが飛んできます。自分は、rtafds(仮) というユーザー名なのでそうなっていますが、そこの部分と、URLのxxxx/yyyy/zzzzの部分は自分のものに変えてください。
```
curl -X POST --data-urlencode "payload={\"text\": \"<@rtafds>\nfinished\" }" https://hooks.slack.com/services/xxxxxxxxx/yyyyyyyyyy/zzzzzzzzzzzzzz
```

# コマンド化
## 結論
後は、このコマンドを nohup コマンドの終了時に投げられるようにすればいいわけですが、なんか導入と使用が楽ちんな方法はないかなと思い、結局 bashrc に関数を直記入することにしました。 まあslackの通知のコマンドにそこまでセキュリティとか神経質にならなくてもいいとは思いますが、気にする人は、URLとかコマンドをちゃんと管理してください。
色々と経緯がめんどくさい人はこれを ~/.bashrc あたりにぶっ込んでください。

**USERNAME と、MACHINE とURLの部分は適宜変更してください。**
変に抽象化するとわかりにくいので、自分のユーザー名(仮)で残してます。

```
function noti () {
    cmd='USERNAME="rtafds";'
    cmd+='MACHINE="a100-server";'
    cmd+='curl -X POST --data-urlencode "payload={\"text\": \"<@$USERNAME>\nmachine:$MACHINE\nstart\" }" https://hooks.slack.com/services/xxxxxxxxx/yyyyyyyyyy/zzzzzzzzzzzzzz >/dev/null 2>&1;'
    cmd+="SECONDS=0;"
    cmd+=$1
    cmd+=';curl -X POST --data-urlencode "payload={\"text\": \"<@$USERNAME>\nmachine:$MACHINE\nfinished\ntime(s):$SECONDS\" }" https://hooks.slack.com/services/xxxxxxxxx/yyyyyyyyyy/zzzzzzzzzzzzzz >/dev/null 2>&1'
    nohup bash -c "${cmd}" &
}
```
後は、bashrcを読み込み直して、以下のような感じで実行します。
```
noti "python test.py"
```
これを実行すると、こんな感じで通知がきます。

![スクリーンショット 2022-12-20 14.48.28のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005331/efabc101-61ae-d779-5363-98436c5fbb69.png)



エラーで終了しても、通常終了しても、実行したコマンドが終了すれば通知が来ます。

## 解説
nohupコマンドを順番に実行するには、以下のような形にします。
参考 : https://public-constructor.com/nohup-multiple-commands/
```
nohup sh -c 'echo hello; date; echo world;' &
```
nohup の時は、&&で繋いだりしてもうまく順番に実行してくれません。処理1が終わったら、処理2...とやりたい場合、上記の方法でやります。

後は、長ったらしいコマンドを文字列で作るだけですが、shellで長い文字列に変数を代入したり色々するのが面倒なので、文字列を足して長ったらしい文字列を作ります。ベースはこんな感じです。

```
function noti_base () {
    cmd=$1
    cmd+='; curl -X POST --data-urlencode "payload={\"text\": \"<@rtafds>\nfinished\" }" https://hooks.slack.com/services/xxxxxxxxx/yyyyyyyyyy/zzzzzzzzzzzzzz >/dev/null 2>&1'
    nohup bash -c "${cmd}" &
}
```

自分が使っていた debian のマシンでは、"${cmd}" のように、ダブルクオテーションをつけないと展開してくれませんでした。Mac(zsh)だとなしでも展開してくれます。この辺、どういう仕様なんでしょうね。

webhook の部分は nohup.out に記載して欲しくないので、標準出力とエラー出力は捨てます。

後は、時間を計測してほしいな〜と思い、時間も吐かせることにしました。sh ではうまくできなかったので、bash の SECONDS というシェル変数を利用して計測することにしました。SECONDS は bash が起動してからの経過時間を、秒単位で出力してくれます。最初に初期化すれば、簡易的に経過時間を測定できます。(ガチの計測はちゃんとプログラム中に仕込んでログを吐かせてください)

ここで SECONDS を利用する関係上、bash -c で動かしています。 


```
function noti_seconds () {
    cmd="SECONDS=0;"
    cmd+=$1
    cmd+='; curl -X POST --data-urlencode "payload={\"text\": \"<@rtafds>\nfinished\ntime(s):$SECONDS\" }" https://hooks.slack.com/services/xxxxxxxxx/yyyyyyyyyy/zzzzzzzzzzzzzz >/dev/null 2>&1'
    nohup bash -c "${cmd}" &
}

```

これで、コマンドを打って、終わったらslackに通知が来るコマンドができました。


後は、コマンド実行前の通知と、マシン名を追加したら、とりあえず nohup コマンドを回しているマシンと人が管理できるなあと思い、コマンドを実行前にも通知を来させるようにしました。それで出来上がったのが、最初のコマンドです。

```
function noti () {
    cmd='USERNAME="rtafds";'
    cmd+='MACHINE="a100-server";'
    cmd+='curl -X POST --data-urlencode "payload={\"text\": \"<@$USERNAME>\nmachine:$MACHINE\nstart\" }" https://hooks.slack.com/services/xxxxxxxxx/yyyyyyyyyy/zzzzzzzzzzzzzz >/dev/null 2>&1;'
    cmd+="SECONDS=0;"
    cmd+=$1
    cmd+=';curl -X POST --data-urlencode "payload={\"text\": \"<@$USERNAME>\nmachine:$MACHINE\nfinished\ntime(s):$SECONDS\" }" https://hooks.slack.com/services/xxxxxxxxx/yyyyyyyyyy/zzzzzzzzzzzzzz >/dev/null 2>&1'
    nohup bash -c "${cmd}" &
}
```

# 所感
使ってみると、すごく便利だなと感じています。インスタンスを無駄に立てると、停止していても無駄にお金取られるので絞っていたのですが、このインスタンス今処理回してるの？切り忘れ?誰が使ってる？みたいなコミュニケーションコストを少し解消できたと思います。
ガッツリ開発に使っているときは大体誰が使っているのかわかるので、あまり問題ではなく、nohup で放置している時に、今使ってる？問題が起きるので、そこの issue を一定解消できました。
ついでに時間計測と終了通知も来て便利だなという感じです。導入も、bashrc にぶっこむだけなので楽ちんです。

通知内容は人によってこさせたいのがあるかもしれないので、好きに変えてください。


# 改良
本当は処理が終わったら、シャットダウンさせたりするオプションも追加したかったのですが、バックグラウンドジョブからシャットダウンさせることはできませんでした。
gcloudとかでやろうかと思ったんですが、色々面倒だなと思って断念しました。
もし誰かいい感じのやり方知ってたら教えてください。



# 参考文献
https://zenn.dev/hotaka_noda/articles/4a6f0ccee73a18
https://public-constructor.com/nohup-multiple-commands/