#!/usr/bin/python3 # # Since this is meant to be run as a daemon, do not use #!/usr/bin/env python3, # because the process name would then be python3, not based on __FILE__. # # [filename] -d 3 192.168.1.1 192.168.1.12 & # disown [jobid] import os import sys import subprocess import time from argparse import ArgumentParser import threading import queue import signal def log_exit(_signal, frame): _print_queue.put([f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}' f' PID={os.getpid()} signal={signal.Signals(_signal).name} Exiting.']) _print_queue.join() sys.exit(0) def pinger(host, delay): """Independent worker thread: repeatedly ping, check, print and sleep.""" last_results = None while True: now = time.localtime() success = ping(host) if success != last_results: status = "UP" if success else "DOWN" _print_queue.put([f'{time.strftime("%Y-%m-%d %H:%M:%S", now)} {host} {status}']) last_results = success time.sleep(delay) def ping(host): """Returns True if host (str) responds to a ping request.""" return subprocess.call([*_ping_cmd, host], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0 def print_manager(): """The only thread allowed to write output.""" while True: job = _print_queue.get() for line in job: log(line) _print_queue.task_done() def log(*args, **kwargs): """Opens the logfile, writes the log, and closes the logfile.""" # I don't leave the log file open for writing because another process needs access too. with open(_logname, 'a') as fp: return print(*args, **kwargs, file=fp) if __name__ == '__main__': parser = ArgumentParser(description="Repeatedly ping sites. Ex: %(prog)s -d 3 192.168.1.1 192.168.1.12") parser.add_argument('-d', '--delay', type=float, default=3.0, help='Delay between pings') parser.add_argument('addresses', nargs='+', help='Addresses to ping') parser_args = parser.parse_args() _logname = os.path.join(os.path.expanduser('~'), 'log', os.path.basename(sys.argv[0]).replace('py', 'txt')) # Choose the ping parameters appropriate for the platform if sys.platform == 'cygwin' or sys.platform == 'win32': _ping_cmd = ('ping', '-n', '1', '-w', '2000') else: _ping_cmd = ('ping', '-c', '1', '-W', '2', '-q') _print_queue = queue.Queue() print_thread = threading.Thread(target=print_manager, daemon=True) print_thread.start() _print_queue.put([f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} PID={os.getpid()}' f' repeat={parser_args.delay}s addresses={",".join(parser_args.addresses)} Starting']) signal.signal(signal.SIGINT, log_exit) signal.signal(signal.SIGTERM, log_exit) # Create worker threads for all but one host address. (Saving one address for this thread.) for address in parser_args.addresses[:-1]: t = threading.Thread(target=pinger, args=(address, parser_args.delay), daemon=True) t.start() del t # Main thread becomes a worker pinger, too. pinger(parser_args.addresses[-1], parser_args.delay)