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