from file_helpers import get_disc_stats
from datetime import datetime as dt
from buffers import LogarithmicBuffer
+from itertools import tee
class DiscFillCheckerThread(Thread):
""" a thread that checks disc fill in regular intervals """
- def __init__(self, interval=10, warn_func=None, err_func=None):
+ def __init__(self, interval=10, decision_function = None):
super(DiscFillCheckerThread, self).__init__(name='discFillChck',
daemon=True)
# set variables
self.interval = interval
- if warn_func is None:
- self.warn_func = disc_fill_warn_func
+ if decision_function is None:
+ self.decision_function = default_disc_full_decision_function
else:
# need to check warn_func somehow (using introspection?)
- self.warn_func = warn_func
- if err_func is None:
- self.err_func = disc_fill_err_func
- else:
- # need to check err_func somehow (using introspection?)
- self.err_func = err_func
+ self.decision_function = decision_function
# remember relevant disc stats at start
bufs = dict((stat.name, LogarithmicBuffer(5)) for stat in stats)
# new file system -- need to create a new buffer
buf = LogarithmicBuffer(5)
- buf.add((now, stat.available))
+ buf.add((now, float(stat.available)))
+ fill_states = buf.get_all()
+
+ # estimate time until full (i.e. when available == 0)
+ times_until_empty = \
+ [calc_times_until_zero(old_state, new_state)
+ for old_state, new_state in pairwise(fill_states)]
+
+ # call user-defined function to decide
+ min_time = None
+ if any(times_until_empty > 0):
+ min_time = min(time for time in times_until_empty
+ if time>0)
+ avg_time = calc_times_until_zero(fill_states[0],
+ fill_states[-1])
+ self.decision_function(stat,
+ times_until_empty[-1], # newest
+ min(times_until_empty), # smallest
+ avg_time) # average
+
+
+def default_disc_full_decision_function(curr_state,
+ estim_empty_newest,
+ estim_empty_smallest,
+ estim_empty_mean):
+ """ decide whether to warn or raise error because disc is getting full
+
+ called by DiscFillCheckerThread regularly; params estim_empty_* are result
+ of :py:func:`calc_times_until_zero`, so None means that disc state did not
+ change.
+
+ :param estim_empty_newest: the newest time estimate
+ :param estim_empty_smallest: the smallest of all non-negative estimates
+ :param estim_empty_mean: estim between earliest and newest estimate
+ """
+ pass
+
+def calc_times_until_zero(old_state, new_state):
+ """ calc time until value hits zero given (time1, value1), (time2, value2)
+
+ returns time from now until empty in seconds
+
+ delta_date = new_date - old_date
+ delta_free = new_free - old_free
+ free(t) = new_free + (t-new_t) * delta_free / delta_date
+ = 0 <==>
+ diff_t := new_t - t = new_free * delta_date / delta_free
+
+ compare to now:
+ diff_to_now = t + (-new_t + new_t) - now = (t - new_t) + (new_t - now)
+ = -diff_t - (now-new_t)
+
+ To avoid zero-division, returns None if delta_free == 0
+ """
+ old_date, old_free = old_state
+ new_date, new_free = new_state
+ if new_free == old_free:
+ return None # avoid zero-division
+ time_diff = new_free * (new_date - old_date).total_seconds() \
+ / (old_free - new_free)
+
+ # compare to now
+ return (now - new_date).total_seconds() - time_diff
+
+
+def pairwise(iterable):
+ """ s -> (s0,s1), (s1,s2), (s2, s3), ...
- raise NotImplementedError('estimate time until disc full')
- raise NotImplementedError('warn or err if too soon')
+ taken from itertool recipes in python api doc of itertools
+ """
+ a, b = tee(iterable)
+ next(b, None)
+ return zip(a, b)
@contextlib.contextmanager