登録したサーバにping打って、一定回数、ダメなときにGmailでメール送信するスクリプト

複数の宛先にping送信して、9割NGだったときにエラーと判定してメール送信（Gmail)します。
指定回数連続してpingがOKだったら復旧判定します。


最初のソースは、マルチスレッドしてますが、よく理解していないので、なぜ動いているのかいまいちわかっていません。また、バグが確実にあるので誤ったソースです。

後半にバグがないはずのプログラムを記載します。マルチスレッドではなく、後半ではマルチプロセッシングします。2016/12/29修正：バグがあったので、修正しました。
後半のプログラムはソースの中のコメントで詳細を説明していますので御参照ください。

それでは誤ったソースの解説です。
とりあえず、importです。


```.py
import pandas as pd
import smtplib
import os, platform
import threading
```


次に、Gmail送信の関数です。Gmail送信するときには、POPアクセスをアカウントで有効にして、安全性の低いアプリのアクセスも許可する必要があります。検索すると、GUI画面も添付した親切な解説が見つかるので、そちらに細かいところはおまかせします。

```.py
def sendGmail(text):
    user = '****@gmail.com'  # username
    password = '******'  # password
    to = ['****@gmail.com'] # your favorite mail address
    sub = 'mail title'
    msg = '''From: %s\nTo: %s\nSubject: %s\n\n%s
    ''' % (user, ','.join(to), sub, text)

    server = smtplib.SMTP_SSL("smtp.gmail.com", 465)
    server.ehlo()
    server.login(user, password)
    server.sendmail(user, to, msg)
    server.close()
```


次に、ping送信の関数です。logはDataFrameです。別にデータフレームでなくてもいいのですが、使い慣れているので使っています。pingの結果を保存するために、たくさんの引数があって、ここが汚いと思っている部分です。


```.py
def ping(address, current_position, log):
    ping_str = '-n 1' if platform.system().lower() == 'windows' else '-c 1'
    response = os.system('ping ' + ping_str + ' ' + address)

    if response == 1:
        log[address][current_position] = 1
```


次にmain関数です。
順次、logというデータフレームに、pingの結果を書き込んでいって、都度、最新のログから700個さかのぼったとき、600個以上pingエラーがあった場合に、メールが飛ぶようにしています。

```.py
if __name__ == '__main__':

    domain_list = ['***address***'] # your favorite address

    log = pd.DataFrame(0, index=range(60 * 60), columns=domain_list)
    #とりあえず、大きめのDataFrameを作成。
    current_position = 0
    sum_of_seconds = 0

    sendGmail('tool has started.')

    while True:
        text = []
        for address in domain_list:
            p = threading.Thread(target=ping, args=(address, current_position, log))
            p.start()

            sum_error = 0

            for i in range(700):#ここが非効率と感じる
                 sum_error += log.ix[current_position - i if current_position >= i else 60 * 15 - (i - current_position), address]

            if sum_error > 600:
                text.append(address)

        if (sum_of_seconds % 600 == 0) and (text != []):
            sendGmail(text)

        current_position += 1
        if current_position >= 60 * 15:
            current_position = 0

        sum_of_seconds += 1
        time.sleep(1)

```

上のソースを書いた次の日、マルチプロセッシングのほうがよいだろうと思い、書き直しました。dict型がはじめて便利だと思いました。

まず、Gmail送信部分は同じです。
次に、Ping送信ですが、以下のように改めました。

```.py
import multiprocessing as mp

def ping(address, result):
 ping_str = '-n 1' if platform.system().lower() == 'windows' else '-c 1'
 response = subprocess.call('ping '+ ping_str + ' '+address)

 if response = 1:
  result[address] = 1
 else:
  result[address] = 0
```
resultは、dict型です。あとで、マルチプロセッシングします。
pingは、os.systemするより、subprocess.callが良いそうなので、理解せずに、書き換えています。

次に、main関数ですが、以下のとおりです。

```.py
if __name__ == '__main__':
    manager = mp.Manager()
 
    current_position = 0
    bottom = 60*60
    evaluate_range = 5 # x5
    recover_range = 2 #10
    evaluate_ratio = 0.9
    before_result = pd.Series()
    act_num = 0
 
    domain_list = ['***', '****']
 
    log = pd.DataFrame(0, index=range(bottom), columns=domain_list)
    #ping実行したあとの、結果を保存するDataFrameを定義しています。
 
    while True:
        proc = [] #マルチプロセッシングのため
        result = manager.dict() #プロセス間で共有するdictを定義
        for address in domain_list:
            p1 = mp.Process(target=ping, args=(address, result,))
            proc.append(p1)
 
        for p1 in proc:
            p1.start()
 
        for p1 in proc:
            p1.join()
 
        #dict型resultから、DataFrameのlogにpingの結果をコピー。
        for address in domain_list:
            log.ix[current_position, address] = result[address]

       #logはぐるぐる回して使い続けるので、最新のデータが入っているcurrent positionの
       #値に応じて、dataframe.sum()で集計する範囲を変更しています。
        value = log.ix[current_position-evaluate_range:current_position, :].sum() if current_position >= evaluate_range else \
            log.ix[0:current_position, :].sum() + log.ix[bottom-(evaluate_range-current_position):bottom,:].sum()

        #ping OK時の判定を早めに反映させるために、上のvalueと同様に、少しチェックレンジの短いrecover_valueを計算
       recover_value = log.ix[current_position-recover_range:current_position, :].sum() if current_position >= recover_range else \
            log.ix[0:current_position, :].sum() + log.ix[bottom-(recover_range-current_position):bottom,:].sum()
        result = value[(value > evaluate_range*evaluate_ratio) & (recover_value != 0)]

        #通知メールが飛びすぎないように、resultが変化したときにだけsendGmailを呼ぶようにしています。
        #ping NGの回数の詳細は見ない設計なので、indexだけを抽出して比較しています。
        #.difference()は、双方向でdiffチェックしてくれないので、orで片方向チェックをつないでいます。
        if not pd.Series().index.equals(result.index.difference(before_result.index)) or not pd.Series().index.equals(before_result.index.difference(result.index)):
            if result.size != 0:
                sendGmail(result.index.str.cat(sep='\n'))
                before_result = result
            else:
                sendGmail('All server are normally operating.')
                before_result = result
 
        current_position += 1
        if current_position > bottom:
            current_position = 0
        time.sleep(1)
```


logの集計は使い慣れたpandasがよいので、pandas使っています。
マルチプロセッシングのためには、なんらかの共有可能なデータが必要だったので、result = manager.dict()して、ping関数に渡しています。