Mercurial > templog
annotate py/utils.py @ 571:ccfaa4351fd8
scale integrate by delay time
namedtuple for StepIntegrator
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Wed, 11 Nov 2015 21:58:38 +0800 |
parents | afe2eb17723e |
children | 02aff9ff8d24 |
rev | line source |
---|---|
439 | 1 import os |
2 import sys | |
564 | 3 import ctypes |
439 | 4 import time |
5 import select | |
6 import logging | |
551
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
529
diff
changeset
|
7 import binascii |
556 | 8 import json |
564 | 9 import datetime |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
10 import collections |
439 | 11 |
448
fe729664a5e6
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
444
diff
changeset
|
12 D = logging.debug |
439 | 13 L = logging.info |
14 W = logging.warning | |
15 E = logging.error | |
16 | |
17 DEFAULT_TRIES = 3 | |
18 READLINE_SELECT_TIMEOUT = 1 | |
19 | |
443 | 20 def EX(msg, *args, **kwargs): |
21 kwargs['exc_info'] = True | |
22 logging.error(msg, *args, **kwargs) | |
23 | |
439 | 24 clock_gettime = None |
25 no_clock_gettime = True | |
26 def monotonic_time(): | |
27 global clock_gettime | |
28 global no_clock_gettime | |
29 if no_clock_gettime: | |
30 return time.time() | |
31 | |
32 class timespec(ctypes.Structure): | |
33 _fields_ = [ | |
34 ('tv_sec', ctypes.c_long), | |
35 ('tv_nsec', ctypes.c_long) | |
36 ] | |
37 if not clock_gettime: | |
38 try: | |
39 librt = ctypes.CDLL('librt.so.0', use_errno=True) | |
40 clock_gettime = librt.clock_gettime | |
41 clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)] | |
42 except: | |
43 W("No clock_gettime(), using fake fallback.") | |
44 no_clock_gettime = True | |
45 return time.time() | |
46 | |
47 t = timespec() | |
48 CLOCK_MONOTONIC = 1 # see <linux/time.h> | |
49 | |
50 if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(t)) != 0: | |
51 errno_ = ctypes.get_errno() | |
52 raise OSError(errno_, os.strerror(errno_)) | |
53 return t.tv_sec + t.tv_nsec * 1e-9 | |
54 | |
55 # decorator, tries a number of times, returns None on failure, sleeps between | |
56 # Must be used as "@retry()" if arguments are defaulted | |
57 def retry(retries=DEFAULT_TRIES, try_time = 1): | |
58 def inner(func): | |
59 def new_f(*args, **kwargs): | |
60 for i in range(retries): | |
61 d = func(*args, **kwargs) | |
62 if d is not None: | |
63 return d | |
64 time.sleep(try_time) | |
65 return None | |
66 | |
529 | 67 new_f.__name__ = func.__name__ |
439 | 68 return new_f |
69 return inner | |
70 | |
71 def readline(sock): | |
72 timeout = READLINE_SELECT_TIMEOUT | |
73 buf = '' | |
74 while True: | |
75 (rlist, wlist, xlist) = select.select([sock], [], [], timeout) | |
76 if sock not in rlist: | |
77 # hit timeout | |
78 return None | |
79 | |
80 c = sock.recv(1) | |
81 if c == '': | |
82 # lightblue timeout | |
83 return None | |
84 if c == '\r': | |
85 continue | |
86 | |
87 buf += c | |
88 if c == '\n': | |
89 return buf | |
90 | |
91 # from http://blog.stalkr.net/2011/04/pctf-2011-32-thats-no-bluetooth.html | |
92 def crc16(buff, crc = 0, poly = 0x8408): | |
93 l = len(buff) | |
94 i = 0 | |
95 while i < l: | |
96 ch = ord(buff[i]) | |
97 uc = 0 | |
98 while uc < 8: | |
99 if (crc & 1) ^ (ch & 1): | |
100 crc = (crc >> 1) ^ poly | |
101 else: | |
102 crc >>= 1 | |
103 ch >>= 1 | |
104 uc += 1 | |
105 i += 1 | |
106 return crc | |
107 | |
108 def cheap_daemon(): | |
109 L("Daemonising.") | |
110 sys.stdout.flush() | |
111 sys.stderr.flush() | |
112 out = file('/dev/null', 'a+') | |
113 os.dup2(out.fileno(), sys.stdout.fileno()) | |
114 os.dup2(out.fileno(), sys.stderr.fileno()) | |
115 | |
116 try: | |
117 pid = os.fork() | |
118 if pid > 0: | |
119 sys.exit(0) | |
529 | 120 except OSError as e: |
439 | 121 E("Bad fork()") |
122 sys.exit(1) | |
123 | |
124 os.setsid() | |
125 | |
126 try: | |
127 pid = os.fork() | |
128 if pid > 0: | |
129 sys.exit(0) | |
529 | 130 except OSError as e: |
439 | 131 E("Bad fork()") |
132 sys.exit(1) | |
133 | |
444 | 134 def uptime(): |
135 try: | |
136 return float(open('/proc/uptime', 'r').read().split(' ', 1)[0]) | |
529 | 137 except Exception as e: |
444 | 138 return -1 |
439 | 139 |
556 | 140 |
141 def json_load_round_float(s, **args): | |
142 return json.loads(s,parse_float = lambda f: round(float(f), 2), **args) | |
564 | 143 |
144 class NotTooOften(object): | |
145 """ prevents things happening more than once per limit. | |
146 Isn't monotonic, good enough for logging. eg | |
147 self.logfailure = NotTooOften(180) # 3 minutes | |
148 ... | |
149 if self.logfailure(): | |
150 L("blah") | |
151 """ | |
152 def __init__(self, limit): | |
153 """ limit is a delay in seconds or TimeDelta """ | |
154 if type(limit) is datetime.timedelta: | |
155 self.limit = limit | |
156 else: | |
157 self.limit = datetime.timedelta(seconds=limit) | |
158 | |
159 # must be positive | |
160 assert self.limit > datetime.timedelta(0) | |
161 self.last = datetime.datetime(10, 1, 1) | |
162 | |
163 def __call__(self): | |
164 if datetime.datetime.now() - self.last > self.limit: | |
165 self.last = datetime.datetime.now() | |
166 return True | |
167 | |
168 def log(self, msg): | |
169 """ calls L(msg) if it isn't too often, otherwise D(msg) | |
170 """ | |
171 if self(): | |
172 L(msg + " (log interval %s)" % str(self.limit)) | |
173 else: | |
174 D(msg) | |
569 | 175 |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
176 Period = collections.namedtuple('Period', 'start end') |
569 | 177 class StepIntegrator(object): |
178 """ | |
570 | 179 Takes on/off events and a monotonically increasing timefn. Returns the integral |
180 of (now-limittime, now) over those events. | |
181 | |
569 | 182 >>> s = StepIntegrator(lambda: t, 40) |
183 >>> t = 1 | |
184 >>> s.turn(1) | |
185 >>> t = 10 | |
186 >>> s.turn(0) | |
187 >>> t = 20 | |
188 >>> s.turn(1) | |
189 >>> t = 30 | |
190 >>> print(s.integrate()) | |
191 19 | |
192 >>> s.turn(0) | |
193 >>> print(s.integrate()) | |
194 19 | |
195 >>> t = 35 | |
196 >>> print(s.integrate()) | |
197 19 | |
198 >>> t = 42 | |
199 >>> print(s.integrate()) | |
200 18 | |
201 >>> t = 52 | |
202 >>> print(s.integrate()) | |
203 10 | |
204 >>> t = 69 | |
205 >>> print(s.integrate()) | |
206 1 | |
207 >>> t = 70 | |
208 >>> print(s.integrate()) | |
209 0 | |
210 >>> t = 170 | |
211 >>> print(s.integrate()) | |
212 0 | |
213 """ | |
214 def __init__(self, timefn, limittime): | |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
215 # _on_periods is a list of [period]. End is None if still on |
569 | 216 self._on_periods = [] |
217 self._timefn = timefn | |
218 self._limittime = limittime | |
219 | |
570 | 220 def set_limit(self, limittime): |
221 if self._limittime == limittime: | |
222 return | |
223 self._limittime = limittime | |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
224 self._trim() |
570 | 225 |
569 | 226 def turn(self, value): |
227 if not self._on_periods: | |
228 if value: | |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
229 self._on_periods.append(Period(self._timefn(), None)) |
569 | 230 return |
231 | |
232 # state hasn't changed | |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
233 on_now = (self._on_periods[-1].end is None) |
569 | 234 if value == on_now: |
235 return | |
236 | |
237 if value: | |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
238 self._on_periods.append(Period(self._timefn(), None)) |
569 | 239 else: |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
240 self._on_periods[-1] = self._on_periods[-1]._replace(end = self._timefn()) |
569 | 241 |
242 def _trim(self): | |
243 begin = self._timefn() - self._limittime | |
244 # shortcut, first start is after begin | |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
245 if not self._on_periods or self._on_periods[0].start >= begin: |
569 | 246 return |
247 | |
248 new_periods = [] | |
249 for s, e in self._on_periods: | |
250 if s == e: | |
251 continue | |
252 elif s >= begin: | |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
253 new_periods.append(Period(s,e)) |
569 | 254 elif e is not None and e < begin: |
255 continue | |
256 else: | |
571
ccfaa4351fd8
scale integrate by delay time
Matt Johnston <matt@ucc.asn.au>
parents:
570
diff
changeset
|
257 new_periods.append(Period(begin, e)) |
569 | 258 self._on_periods = new_periods |
259 | |
260 def integrate(self): | |
261 self._trim() | |
262 tot = 0 | |
263 for s, e in self._on_periods: | |
264 if e is None: | |
265 e = self._timefn() | |
266 tot += (e-s) | |
267 return tot | |
268 | |
269 | |
270 | |
271 | |
272 | |
273 | |
274 |