LoginSignup
1
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-26

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

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

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

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

import pandas as pd
import smtplib
import os, platform
import threading

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

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の結果を保存するために、たくさんの引数があって、ここが汚いと思っている部分です。

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エラーがあった場合に、メールが飛ぶようにしています。

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送信ですが、以下のように改めました。

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関数ですが、以下のとおりです。

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関数に渡しています。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2