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