Skip to content

Instantly share code, notes, and snippets.

@Lucretiel
Last active August 29, 2015 14:17
Show Gist options
  • Save Lucretiel/daf212a80d2bc896474d to your computer and use it in GitHub Desktop.
Save Lucretiel/daf212a80d2bc896474d to your computer and use it in GitHub Desktop.
Monitor the availability of a port at a remote host.
#!/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