]>
Commit | Line | Data |
---|---|---|
534215a1 OZ |
1 | /* |
2 | * BIRD -- Timers | |
3 | * | |
4 | * (c) 2013--2017 Ondrej Zajicek <santiago@crfreenet.org> | |
5 | * (c) 2013--2017 CZ.NIC z.s.p.o. | |
6 | * | |
7 | * Can be freely distributed and used under the terms of the GNU GPL. | |
8 | */ | |
9 | ||
6b5cd7c0 OZ |
10 | /** |
11 | * DOC: Timers | |
12 | * | |
13 | * Timers are resources which represent a wish of a module to call a function at | |
14 | * the specified time. The timer code does not guarantee exact timing, only that | |
15 | * a timer function will not be called before the requested time. | |
16 | * | |
17 | * In BIRD, time is represented by values of the &btime type which is signed | |
18 | * 64-bit integer interpreted as a relative number of microseconds since some | |
19 | * fixed time point in past. The current time can be obtained by current_time() | |
20 | * function with reasonable accuracy and is monotonic. There is also a current | |
21 | * 'wall-clock' real time obtainable by current_real_time() reported by OS. | |
22 | * | |
23 | * Each timer is described by a &timer structure containing a pointer to the | |
24 | * handler function (@hook), data private to this function (@data), time the | |
25 | * function should be called at (@expires, 0 for inactive timers), for the other | |
26 | * fields see |timer.h|. | |
27 | */ | |
534215a1 | 28 | |
f047271c | 29 | #include <stdio.h> |
534215a1 | 30 | #include <stdlib.h> |
abd4367f | 31 | #include <time.h> |
534215a1 OZ |
32 | |
33 | #include "nest/bird.h" | |
34 | ||
35 | #include "lib/heap.h" | |
36 | #include "lib/resource.h" | |
37 | #include "lib/timer.h" | |
38 | ||
39 | ||
40 | struct timeloop main_timeloop; | |
41 | ||
42 | ||
43 | #ifdef USE_PTHREADS | |
44 | ||
45 | #include <pthread.h> | |
46 | ||
47 | /* Data accessed and modified from proto/bfd/io.c */ | |
48 | pthread_key_t current_time_key; | |
49 | ||
50 | static inline struct timeloop * | |
51 | timeloop_current(void) | |
52 | { | |
53 | return pthread_getspecific(current_time_key); | |
54 | } | |
55 | ||
56 | static inline void | |
57 | timeloop_init_current(void) | |
58 | { | |
59 | pthread_key_create(¤t_time_key, NULL); | |
60 | pthread_setspecific(current_time_key, &main_timeloop); | |
61 | } | |
62 | ||
63 | void wakeup_kick_current(void); | |
64 | ||
65 | #else | |
66 | ||
67 | /* Just use main timelooop */ | |
68 | static inline struct timeloop * timeloop_current(void) { return &main_timeloop; } | |
69 | static inline void timeloop_init_current(void) { } | |
70 | ||
71 | #endif | |
72 | ||
73 | btime | |
74 | current_time(void) | |
75 | { | |
76 | return timeloop_current()->last_time; | |
77 | } | |
78 | ||
02552526 OZ |
79 | btime |
80 | current_real_time(void) | |
81 | { | |
82 | struct timeloop *loop = timeloop_current(); | |
83 | ||
84 | if (!loop->real_time) | |
85 | times_update_real_time(loop); | |
86 | ||
87 | return loop->real_time; | |
88 | } | |
89 | ||
534215a1 OZ |
90 | |
91 | #define TIMER_LESS(a,b) ((a)->expires < (b)->expires) | |
92 | #define TIMER_SWAP(heap,a,b,t) (t = heap[a], heap[a] = heap[b], heap[b] = t, \ | |
93 | heap[a]->index = (a), heap[b]->index = (b)) | |
94 | ||
95 | ||
96 | static void | |
a6f79ca5 | 97 | tm_free(resource *r) |
534215a1 | 98 | { |
a6f79ca5 | 99 | timer *t = (void *) r; |
534215a1 | 100 | |
a6f79ca5 | 101 | tm_stop(t); |
534215a1 OZ |
102 | } |
103 | ||
104 | static void | |
a6f79ca5 | 105 | tm_dump(resource *r) |
534215a1 | 106 | { |
a6f79ca5 | 107 | timer *t = (void *) r; |
534215a1 OZ |
108 | |
109 | debug("(code %p, data %p, ", t->hook, t->data); | |
110 | if (t->randomize) | |
111 | debug("rand %d, ", t->randomize); | |
112 | if (t->recurrent) | |
113 | debug("recur %d, ", t->recurrent); | |
114 | if (t->expires) | |
115 | debug("expires in %d ms)\n", (t->expires - current_time()) TO_MS); | |
116 | else | |
117 | debug("inactive)\n"); | |
118 | } | |
119 | ||
120 | ||
a6f79ca5 | 121 | static struct resclass tm_class = { |
534215a1 | 122 | "Timer", |
a6f79ca5 OZ |
123 | sizeof(timer), |
124 | tm_free, | |
125 | tm_dump, | |
534215a1 OZ |
126 | NULL, |
127 | NULL | |
128 | }; | |
129 | ||
a6f79ca5 OZ |
130 | timer * |
131 | tm_new(pool *p) | |
534215a1 | 132 | { |
a6f79ca5 | 133 | timer *t = ralloc(p, &tm_class); |
534215a1 OZ |
134 | t->index = -1; |
135 | return t; | |
136 | } | |
137 | ||
138 | void | |
a6f79ca5 | 139 | tm_set(timer *t, btime when) |
534215a1 OZ |
140 | { |
141 | struct timeloop *loop = timeloop_current(); | |
142 | uint tc = timers_count(loop); | |
143 | ||
144 | if (!t->expires) | |
145 | { | |
146 | t->index = ++tc; | |
147 | t->expires = when; | |
148 | BUFFER_PUSH(loop->timers) = t; | |
a6f79ca5 | 149 | HEAP_INSERT(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP); |
534215a1 OZ |
150 | } |
151 | else if (t->expires < when) | |
152 | { | |
153 | t->expires = when; | |
a6f79ca5 | 154 | HEAP_INCREASE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index); |
534215a1 OZ |
155 | } |
156 | else if (t->expires > when) | |
157 | { | |
158 | t->expires = when; | |
a6f79ca5 | 159 | HEAP_DECREASE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index); |
534215a1 OZ |
160 | } |
161 | ||
162 | #ifdef CONFIG_BFD | |
163 | /* Hack to notify BFD loops */ | |
164 | if ((loop != &main_timeloop) && (t->index == 1)) | |
165 | wakeup_kick_current(); | |
166 | #endif | |
167 | } | |
168 | ||
169 | void | |
a6f79ca5 | 170 | tm_start(timer *t, btime after) |
534215a1 | 171 | { |
a6f79ca5 | 172 | tm_set(t, current_time() + MAX(after, 0)); |
534215a1 OZ |
173 | } |
174 | ||
175 | void | |
a6f79ca5 | 176 | tm_stop(timer *t) |
534215a1 OZ |
177 | { |
178 | if (!t->expires) | |
179 | return; | |
180 | ||
181 | struct timeloop *loop = timeloop_current(); | |
182 | uint tc = timers_count(loop); | |
183 | ||
a6f79ca5 | 184 | HEAP_DELETE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index); |
534215a1 OZ |
185 | BUFFER_POP(loop->timers); |
186 | ||
187 | t->index = -1; | |
188 | t->expires = 0; | |
189 | } | |
190 | ||
191 | void | |
192 | timers_init(struct timeloop *loop, pool *p) | |
193 | { | |
194 | times_init(loop); | |
195 | ||
196 | BUFFER_INIT(loop->timers, p, 4); | |
197 | BUFFER_PUSH(loop->timers) = NULL; | |
198 | } | |
199 | ||
28a7d394 OZ |
200 | void io_log_event(void *hook, void *data); |
201 | ||
534215a1 OZ |
202 | void |
203 | timers_fire(struct timeloop *loop) | |
204 | { | |
205 | btime base_time; | |
a6f79ca5 | 206 | timer *t; |
534215a1 OZ |
207 | |
208 | times_update(loop); | |
209 | base_time = loop->last_time; | |
210 | ||
211 | while (t = timers_first(loop)) | |
212 | { | |
213 | if (t->expires > base_time) | |
214 | return; | |
215 | ||
216 | if (t->recurrent) | |
217 | { | |
218 | btime when = t->expires + t->recurrent; | |
219 | ||
220 | if (when <= loop->last_time) | |
221 | when = loop->last_time + t->recurrent; | |
222 | ||
223 | if (t->randomize) | |
224 | when += random() % (t->randomize + 1); | |
225 | ||
a6f79ca5 | 226 | tm_set(t, when); |
534215a1 OZ |
227 | } |
228 | else | |
a6f79ca5 | 229 | tm_stop(t); |
534215a1 | 230 | |
28a7d394 OZ |
231 | /* This is ugly hack, we want to log just timers executed from the main I/O loop */ |
232 | if (loop == &main_timeloop) | |
233 | io_log_event(t->hook, t->data); | |
234 | ||
534215a1 OZ |
235 | t->hook(t); |
236 | } | |
237 | } | |
238 | ||
239 | void | |
240 | timer_init(void) | |
241 | { | |
242 | timers_init(&main_timeloop, &root_pool); | |
243 | timeloop_init_current(); | |
244 | } | |
f047271c OZ |
245 | |
246 | ||
247 | /** | |
248 | * tm_parse_time - parse a date and time | |
249 | * @x: time string | |
250 | * | |
251 | * tm_parse_time() takes a textual representation of a date and time | |
252 | * (yyyy-mm-dd[ hh:mm:ss[.sss]]) and converts it to the corresponding value of | |
253 | * type &btime. | |
254 | */ | |
255 | btime | |
256 | tm_parse_time(char *x) | |
257 | { | |
258 | struct tm tm; | |
259 | int usec, n1, n2, n3, r; | |
260 | ||
261 | r = sscanf(x, "%d-%d-%d%n %d:%d:%d%n.%d%n", | |
262 | &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &n1, | |
263 | &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &n2, | |
264 | &usec, &n3); | |
265 | ||
266 | if ((r == 3) && !x[n1]) | |
267 | tm.tm_hour = tm.tm_min = tm.tm_sec = usec = 0; | |
268 | else if ((r == 6) && !x[n2]) | |
269 | usec = 0; | |
270 | else if ((r == 7) && !x[n3]) | |
271 | { | |
272 | /* Convert subsecond digits to proper precision */ | |
273 | int digits = n3 - n2 - 1; | |
274 | if ((usec < 0) || (usec > 999999) || (digits < 1) || (digits > 6)) | |
275 | return 0; | |
276 | ||
277 | while (digits++ < 6) | |
278 | usec *= 10; | |
279 | } | |
280 | else | |
281 | return 0; | |
282 | ||
283 | tm.tm_mon--; | |
284 | tm.tm_year -= 1900; | |
285 | s64 ts = mktime(&tm); | |
286 | if ((ts == (s64) (time_t) -1) || (ts < 0) || (ts > ((s64) 1 << 40))) | |
287 | return 0; | |
288 | ||
289 | return ts S + usec; | |
290 | } | |
291 | ||
292 | /** | |
293 | * tm_format_time - convert date and time to textual representation | |
294 | * @x: destination buffer of size %TM_DATETIME_BUFFER_SIZE | |
295 | * @fmt: specification of resulting textual representation of the time | |
296 | * @t: time | |
297 | * | |
298 | * This function formats the given relative time value @t to a textual | |
299 | * date/time representation (dd-mm-yyyy hh:mm:ss) in real time. | |
300 | */ | |
301 | void | |
302 | tm_format_time(char *x, struct timeformat *fmt, btime t) | |
303 | { | |
304 | btime dt = current_time() - t; | |
305 | btime rt = current_real_time() - dt; | |
306 | int v1 = !fmt->limit || (dt < fmt->limit); | |
307 | ||
863ecfc7 OZ |
308 | if (!tm_format_real_time(x, TM_DATETIME_BUFFER_SIZE, v1 ? fmt->fmt1 : fmt->fmt2, rt)) |
309 | strcpy(x, "<error>"); | |
f047271c OZ |
310 | } |
311 | ||
312 | /* Replace %f in format string with usec scaled to requested precision */ | |
313 | static int | |
314 | strfusec(char *buf, int size, const char *fmt, uint usec) | |
315 | { | |
316 | char *str = buf; | |
317 | int parity = 0; | |
318 | ||
319 | while (*fmt) | |
320 | { | |
321 | if (!size) | |
322 | return 0; | |
323 | ||
324 | if ((fmt[0] == '%') && (!parity) && | |
325 | ((fmt[1] == 'f') || (fmt[1] >= '1') && (fmt[1] <= '6') && (fmt[2] == 'f'))) | |
326 | { | |
327 | int digits = (fmt[1] == 'f') ? 6 : (fmt[1] - '0'); | |
328 | uint d = digits, u = usec; | |
329 | ||
330 | /* Convert microseconds to requested precision */ | |
331 | while (d++ < 6) | |
332 | u /= 10; | |
333 | ||
334 | int num = bsnprintf(str, size, "%0*u", digits, u); | |
335 | if (num < 0) | |
336 | return 0; | |
337 | ||
338 | fmt += (fmt[1] == 'f') ? 2 : 3; | |
339 | ADVANCE(str, size, num); | |
340 | } | |
341 | else | |
342 | { | |
343 | /* Handle '%%' expression */ | |
344 | parity = (*fmt == '%') ? !parity : 0; | |
345 | *str++ = *fmt++; | |
346 | size--; | |
347 | } | |
348 | } | |
349 | ||
350 | if (!size) | |
351 | return 0; | |
352 | ||
353 | *str = 0; | |
354 | return str - buf; | |
355 | } | |
356 | ||
863ecfc7 OZ |
357 | int |
358 | tm_format_real_time(char *x, size_t max, const char *fmt, btime t) | |
f047271c OZ |
359 | { |
360 | s64 t1 = t TO_S; | |
361 | s64 t2 = t - t1 S; | |
362 | ||
363 | time_t ts = t1; | |
364 | struct tm tm; | |
365 | if (!localtime_r(&ts, &tm)) | |
863ecfc7 | 366 | return 0; |
f047271c OZ |
367 | |
368 | byte tbuf[TM_DATETIME_BUFFER_SIZE]; | |
863ecfc7 OZ |
369 | if (!strfusec(tbuf, max, fmt, t2)) |
370 | return 0; | |
f047271c | 371 | |
863ecfc7 OZ |
372 | if (!strftime(x, max, tbuf, &tm)) |
373 | return 0; | |
f047271c | 374 | |
863ecfc7 | 375 | return 1; |
f047271c | 376 | } |