Last active
August 29, 2015 14:17
-
-
Save Lucretiel/daf212a80d2bc896474d to your computer and use it in GitHub Desktop.
Monitor the availability of a port at a remote host.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# Copyright 2015 Nathan West | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
from socket import create_connection | |
from contextlib import contextmanager | |
from time import perf_counter, sleep | |
from datetime import datetime | |
from autocommand import autocommand | |
class timed: | |
''' | |
The context must take at least t seconds; if it takes less, the the program | |
sleeps until t seconds are up before continuing at the end of the context. | |
This context manager is reusable but not re-entrant. | |
''' | |
def __init__(self, t): | |
self.time = t | |
def __enter__(self): | |
self.end = perf_counter() + self.time | |
def __exit__(self, *exc): | |
sleep(max(0, self.end - perf_counter())) | |
def state_changes(host, port, interval, timeout): | |
''' | |
Every interval seconds, attempt to connect to (host, port) with the timeout. | |
If the state changes- that is, the connection succeeds where previously it | |
failed, or vice versa- yield True or False (the connection state) and the | |
error, if any. | |
''' | |
address = (host, port) | |
state = None | |
while True: | |
with timed(interval): | |
try: | |
create_connection(address, timeout) | |
except OSError as e: | |
error = e | |
new_state = False | |
else: | |
error = None | |
new_state = True | |
if state != new_state: | |
yield new_state, error | |
state = new_state | |
@autocommand(__name__) | |
def main( | |
host, *, | |
port=22, | |
interval=5.0, | |
timeout=2.0, | |
#Default formats are designed to be easily parsable by a script | |
upformat: | |
"The message format for when the connection goes up. Should be a " | |
"python format string, which receives a single {time} argument. " | |
"Defaults to %(default)r." | |
='UP {time} <>', | |
downformat: | |
"The message format for when the connection goes down. Should be a " | |
"python format string, which receives a {time} argument and an " | |
"{error} argument. Defaults to %(default)r." | |
='DOWN {time} <{error}>', | |
timeformat: | |
"The format for getting the timestamp. Should by a datetime." | |
"strftime format string. Defaults to %(default)r, which is ISO " | |
"8601." | |
='%Y-%m-%dT%H:%M:%SZ'): # ISO 8601 | |
''' | |
This program lets you monitor the connection status of a port at a remote | |
host. Every INTERVAL seconds, it will attempt to connect to the host, at | |
the given PORT (defaulting to 22). No data is actually sent to the remote | |
host. If the connection state changes- that is, if the previous connection | |
attempt succeeded and this one succeeded, or vice versa- a message is | |
printed to stdout and flushed right away. | |
''' | |
for state, error in state_changes(host, port, interval, timeout): | |
now = datetime.now().strftime(timeformat) | |
if state: | |
print(upformat.format(time=now), flush=True) | |
else: | |
print(downformat.format(time=now, error=error.strerror), flush=True) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment