544bf52764e44ae17a2ba3a2b6956cced0c8ec14
David Blume first commit.

David Blume authored 4 years ago

1) #!/usr/bin/python3
2) #
3) # Since this is meant to be run as a daemon, do not use #!/usr/bin/env python3,
4) # because the process name would then be python3, not based on __FILE__.
5) #
6) # [filename] -d 3 192.168.1.1 192.168.1.12 &
7) # disown [jobid]
8) import os
9) import sys
10) import subprocess
11) import time
12) from argparse import ArgumentParser
13) import threading
14) import queue
David Blume Add notifications for SIGTE...

David Blume authored 4 years ago

15) import signal
David Blume Add type hints.

David Blume authored 3 years ago

16) from typing import Dict, Optional
David Blume first commit.

David Blume authored 4 years ago

17) 
David Blume Serialize the reading and w...

David Blume authored 4 years ago

18) pinger_lock = threading.Lock()
19) 
David Blume first commit.

David Blume authored 4 years ago

20) 
David Blume Add type hints.

David Blume authored 3 years ago

21) def log_exit(sig: int, frame) -> None:
David Blume Rename a parameter instead...

David Blume authored 4 years ago

22)     _print_queue.put([f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} '
23)                       f'PID={os.getpid()} signal={signal.Signals(sig).name} Exiting.'])
David Blume Add notifications for SIGTE...

David Blume authored 4 years ago

24)     _print_queue.join()
25)     sys.exit(0)
26) 
27) 
David Blume Add type hints.

David Blume authored 3 years ago

28) def pinger() -> None:
David Blume first commit.

David Blume authored 4 years ago

29)     """Each queue item is a new address to ping. Ping and compare and print.
30)     Issue: Each of these threads reads from and writes to the global _results dict.
31)     """
32)     while True:
33)         host = _ping_queue.get()
34)         now = time.localtime()
35)         success = ping(host)
David Blume Serialize the reading and w...

David Blume authored 4 years ago

36)         with pinger_lock:
37)             # Access to _results needs to be serialized
38)             need_update = success != _results[host]
39)             if need_update:
40)                 _results[host] = success
41)         if need_update:
David Blume first commit.

David Blume authored 4 years ago

42)             status = "UP" if success else "DOWN"
43)             _print_queue.put([f'{time.strftime("%Y-%m-%d %H:%M:%S", now)} {host} {status}'])
44)         _ping_queue.task_done()
45) 
46) 
David Blume Add type hints.

David Blume authored 3 years ago

47) def ping(host: str) -> int:
David Blume first commit.

David Blume authored 4 years ago

48)     """Returns True if host (str) responds to a ping request."""
49)     return subprocess.call([*_ping_cmd, host], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0
50) 
51) 
David Blume Add type hints.

David Blume authored 3 years ago

52) def print_manager() -> None:
David Blume first commit.

David Blume authored 4 years ago

53)     """The only thread allowed to write output."""
54)     while True:
55)         job = _print_queue.get()
56)         for line in job:
57)             log(line)
58)         _print_queue.task_done()
59) 
60) 
David Blume Add type hints.

David Blume authored 3 years ago

61) def log(*args, **kwargs) -> None:
David Blume first commit.

David Blume authored 4 years ago

62)     """Opens the logfile, writes the log, and closes the logfile."""
63)     # I don't leave the log file open for writing because another process needs access too.
64)     with open(_logname, 'a') as fp:
David Blume Add type hints.

David Blume authored 3 years ago

65)         print(*args, **kwargs, file=fp)
David Blume first commit.

David Blume authored 4 years ago

66) 
67) 
68) if __name__ == '__main__':
69)     parser = ArgumentParser(description="Repeatedly ping sites. Ex: %(prog)s -d 3 192.168.1.1 192.168.1.12")
70)     parser.add_argument('-d', '--delay', type=float, default=1.0, help='Delay between pings')
71)     parser.add_argument('addresses', nargs='+', help='Addresses to ping')
72)     parser_args = parser.parse_args()
73) 
David Blume Add type hints.

David Blume authored 3 years ago

74)     _results: Dict[str, Optional[int]] = dict()
David Blume first commit.

David Blume authored 4 years ago

75)     for addr in parser_args.addresses:
76)         _results[addr] = None
77)     delay = parser_args.delay
78) 
79)     _ping_queue = queue.Queue(2 * len(parser_args.addresses))  # No more than 2 pending pings per address
80)     _print_queue = queue.Queue()
81) 
82)     _logname = os.path.join(os.path.expanduser('~'), 'log', os.path.basename(sys.argv[0]).replace('py', 'txt'))
83)     _pid = os.getpid()
84) 
85)     # Choose the ping parameters appropriate for the platform
86)     if sys.platform == 'cygwin' or sys.platform == 'win32':
87)         _ping_cmd = ('ping', '-n', '1', '-w', '2000')
88)     else:
89)         _ping_cmd = ('ping', '-c', '1', '-W', '2', '-q')
90) 
91)     for i in range(len(parser_args.addresses)):
92)         t = threading.Thread(target=pinger, daemon=True)
93)         t.start()
94)         del t
95) 
96)     t = threading.Thread(target=print_manager, daemon=True)
97)     t.start()
98)     del t
99) 
100)     _print_queue.put([f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}'
David Blume Add notifications for SIGTE...

David Blume authored 4 years ago

101)         f' PID={os.getpid()} repeat={delay}s addresses={",".join(parser_args.addresses)} Starting'])
102) 
103)     signal.signal(signal.SIGINT, log_exit)
104)     signal.signal(signal.SIGTERM, log_exit)
105)