Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした