Skip to content

Instantly share code, notes, and snippets.

@AdamG
Created October 24, 2011 20:00
Show Gist options
  • Save AdamG/1309979 to your computer and use it in GitHub Desktop.
Save AdamG/1309979 to your computer and use it in GitHub Desktop.
python tagtimed
#!/usr/bin/env python
"""
TagTime daemon, in python.
Only trying to get to 'worksforme' here - the perl daemon just isn't
launching anything 3/4 of the time, and I have no idea how to debug
it.
"""
import argparse
import datetime
import logging
import math
import os
import random
import sys
import time
# logging stuff
logger = logging.getLogger("tagtimed")
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
log = logger.debug
# This directory, needed to give path to ping.pl
dirname = os.path.dirname(__file__)
now = datetime.datetime.now
def main():
parser = argparse.ArgumentParser(
description="start tagtimed.py")
parser.add_argument(
"interval", type=str,
help=
"Average interval between pings. If a number, the interval "
"is that number of seconds. Use 'm' or 'h' as suffixes to indicate "
"minutes or hours.")
args = parser.parse_args()
if args.interval == "test":
TagTimer(None).ping()
return
interval = _convert_to_seconds(args.interval)
TagTimer(interval).run()
class TagTimer(object):
def __init__(self, interval):
self.interval = interval
self.next_ping = None
def run(self):
"Initialize .next_ping, then sleep, run inner, and repeat."
# For the first ping, pretend we're in the middle of an
# existing period. The perl version walks forward from an
# epoch, but I'm OK with faking it. Note that the interval
# passed to .get_next_ping() is negative, so .next_ping will
# initially be a time in the past, and then .schedule_ping()
# prepares the future time.
#
# Note that this can mean a ping can happen immediately, but
# I'm not fussed about it.
self.next_ping = self.get_next_ping(now(), -self.interval/2.)
self.schedule_ping()
while True:
time.sleep(1)
# I don't like doing everything 3 levels of indentation
# in, OK?
self.run_inner()
def run_inner(self):
"Inner loop body of TagTimer.run()"
if now() >= self.next_ping:
self.ping()
self.schedule_ping()
def ping(self):
"Run a ping."
log("{0}:ping".format(now()))
# TODO: what a long, ugly line. And make it configurable.
cmd = "xterm -T 'TagTime' -fg white -bg red -cr MidnightBlue -bc -rw -e {dirname}/ping.pl {epochtime}".format(
dirname=dirname, epochtime=_epochtime(now()))
os.system(cmd)
def schedule_ping(self):
"Update .next_ping based on the previous value"
last_ping = self.next_ping
self.next_ping = self.get_next_ping(last_ping)
log("next ping at {0}".format(self.next_ping.strftime("%H:%M:%S")))
def get_next_ping(self, after, interval=None):
"""Get a next ping after datetime 'after'.
Pass interval to override the default interval.
"""
return self.get_next_ping_poisson(after, interval)
def get_next_ping_poisson(self, after, interval=None):
"sub nextping { my($prev)=@_; return max($prev+1,round1($prev+exprand())); }"
if interval is None: interval = self.interval
value = -interval * math.log(random.random())
return after + datetime.timedelta(seconds=max(1, value))
def get_next_ping_uniform(self, after, interval=None):
if interval is None: interval = self.interval
return after + datetime.timedelta(seconds=random.uniform(0, interval*2))
def _epochtime(dt):
"get epoch time integer of datetime 'dt'"
return int(time.mktime(dt.timetuple()))
def _convert_to_seconds(data):
"Convert string 'data' to an interval in seconds, interpreting m/h suffix"
# "3600"
if data.isdigit(): return float(data)
# "60m"
if data.lower()[-1] == "m": return float(data[:-1]) * 60
# "1h"
if data.lower()[-1] == "h": return float(data[:-1]) * 3600
if __name__ == "__main__": main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment