ラズパイのプロセス監視を slee-Pi3 のウォッチドッグタイマで【異常動作検知ー実装編】

今回は slee-Pi3 のウォッチドッグタイマを利用したプロセス監視を行います。
前回【[異常動作検知による監視](https://qiita.com/kiyonaga/items/56f405a8e226e2b4b0f7)】は動作確認でしたが、今回はより実用性を求めて service ファイルで**Nice 等は設定しません**。
※作業環境と事前準備は [こちら](https://qiita.com/kiyonaga/private/d86428c8f2ccbd18cd98#%E7%92%B0%E5%A2%83) をご覧ください。

# [関連ブログ]
slee-Pi3 のウォッチドッグタイマを利用したプロセス監視
【[OOM 状態からの即時復帰](https://qiita.com/kiyonaga/private/d86428c8f2ccbd18cd98)】
【[正常動作検知による監視](https://qiita.com/kiyonaga/private/388aa32891d7c0f33266)】
【[異常動作検知による監視](https://qiita.com/kiyonaga/items/56f405a8e226e2b4b0f7)】
【異常動作検知－実装編】←　本ブログ

# [目的と方法]
目的：停止したくないプログラムが停止した場合に、クリーンな状態で再開させたい。
方法：プログラム内にハートビートを組込んでおき、ウォッチドッグタイマが異常検知した際に再起動させる。

# [手順1]service ファイル作成
なるべくシンプルなものを作成します。
```/etc/systemd/system/test-heartbeat.service
[Unit]
Description=sleepi3 test heartbeat
DefaultDependencies=no　
After=sysinit.target

[Service]
Type=simple
ExecStart=/opt/mtx/test-heartbeat.py　←　実行ファイルのフルパス

[Install]
WantedBy=sysinit.target
```

# [手順2]実行プログラム＋ハートビートを作成
素数計算中にハートビートを発生させます。
RaspberryPiでよく使うpythonを用います。
```python
#!/bin/python3

import os
import time
import math
from datetime import datetime
from datetime import timedelta

LED_PATH = '/sys/class/leds/sleepi:led0/brightness'
PRIME_PATH = '/var/log/prime-num.log'

class LedBlink():
    def __init__(self,num,dt):
        self.num = num
        self.dt = dt

    def blink(self):
        now = datetime.now()
        if now - self.dt > timedelta(seconds=1):
            with open(LED_PATH,mode="w") as f:
                f.write(self.num)
            self.dt = now
            if self.num == "0":
                self.num = "1"
            else:
                self.num = "0"

def check_files():
    #calculation continue
    if os.path.isfile(PRIME_PATH):
        with open(PRIME_PATH) as f:
            saved_num = f.readline()
    #path lost
    else:
        with open(PRIME_PATH,mode="w") as f:
            f.write("3")
        print("new prime-log created")
    return int(saved_num)

def save_num(PATH,i):
    with open(PATH,mode="w") as f:
         f.write(str(i))

def prime_calc(num,Led):
    i = num
    while True:
        Led.blink()
        j = 3
        flag = 0
        r = math.sqrt(i)
        while j<=r:
            if i%j==0:
                flag = 1
                Led.blink()
                break
            j+=2
        if flag==0:
            save_num(PRIME_PATH,i)
        i+=2

def main():
    Led = LedBlink(num="0",dt=datetime.now())
    num = check_files()
    prime_calc(num,Led)

if __name__ == "__main__":
    main()
else:
    print("FILE NAME ERROR")
```

# [手順3]既存の sleepi3-heartbeat.service を停止して自作ハートビートを起動する
sleepi3-heartbeat.service は sleePi3-utils に含まれている service です。

```bash
sudo systemctl disable sleepi3-heartbeat.service
sudo systemctl enable test-heartbeat.service
sudo systemctl restart test-heartbeat.service 
```
test-heartbeat.service の部分は自作ハートビートのファイル名を入れてください。

# [手順4]OOMKiller 用プログラム作成と実行
[こちら](https://blog.longest-road.com/oom-killer-1/) のブログを参考に作成しました。

※ コード中に (\&amp;t) のような HTML エンティティが含まれています。コードを使用する際は該当部分を取り除いてご利用ください。

# [手順5]復帰確認
動作確認編と違い、Nice や OOMScoreAdjust を設定していないので**しばらく生き残りつつ、最終的には OOM によりハートビート停止 → ハードウェアリセット**が確認できるはずです。

下記コマンド実行で再起動要因がわかります。
```bash
sleepi3ctl -g wakeup-flag
watchdog
```
# [まとめ]
今回は slee-Pi3 のウォッチドッグタイマによるプロセス監視機能を利用しました。
監視されているプロセスが OOMKiller に kill された際に、ハードウェアリセットをかける仕組みです。