diff options
Diffstat (limited to 'vppinfra/vppinfra/timer.c')
-rw-r--r-- | vppinfra/vppinfra/timer.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/vppinfra/vppinfra/timer.c b/vppinfra/vppinfra/timer.c new file mode 100644 index 00000000000..2b2c958729d --- /dev/null +++ b/vppinfra/vppinfra/timer.c @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2015 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + Copyright (c) 2001, 2002, 2003 Eliot Dresselhaus + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <sys/param.h> + +#include <vppinfra/vec.h> +#include <vppinfra/time.h> +#include <vppinfra/timer.h> +#include <vppinfra/error.h> + +typedef struct { + f64 time; + timer_func_t * func; + any arg; +} timer_callback_t; + +/* Vector of currently unexpired timers. */ +static timer_callback_t * timers; + +/* Convert time from 64bit floating format to struct timeval. */ +always_inline void f64_to_tv (f64 t, struct timeval * tv) +{ + tv->tv_sec = t; + tv->tv_usec = 1e6*(t - tv->tv_sec); + while (tv->tv_usec >= 1000000) + { + tv->tv_usec -= 1000000; + tv->tv_sec += 1; + } +} + +/* Sort timers so that timer soonest to expire is at end. */ +static int timer_compare (const void * _a, const void * _b) +{ + const timer_callback_t * a = _a; + const timer_callback_t * b = _b; + f64 dt = b->time - a->time; + return dt < 0 ? -1 : (dt > 0 ? +1 : 0); +} + +static inline void sort_timers (timer_callback_t * timers) +{ qsort (timers, vec_len (timers), sizeof (timers[0]), timer_compare); } + +#define TIMER_SIGNAL SIGALRM + +/* Don't bother set timer if time different is less than this value. */ +/* We would like to initialize this to 0.75 / (f64) HZ, + * but HZ may not be a compile-time constant on some systems, + * so instead we do the initialization before first use. + */ +static f64 time_resolution; + +/* Interrupt handler. Call functions for all expired timers. + Set time for next timer interrupt. */ +static void timer_interrupt (int signum) +{ + f64 now = unix_time_now (); + f64 dt; + timer_callback_t * t; + + while (1) + { + if (vec_len (timers) <= 0) + return; + + /* Consider last (earliest) timer in reverse sorted + vector of pending timers. */ + t = vec_end (timers) - 1; + + ASSERT (now >= 0 && finite (now)); + + /* Time difference between when timer goes off and now. */ + dt = t->time - now; + + /* If timer is within threshold of going off + call user's callback. */ + if (dt <= time_resolution && finite (dt)) + { + _vec_len (timers) -= 1; + (* t->func) (t->arg, -dt); + } + else + { + /* Set timer for to go off in future. */ + struct itimerval itv; + memset (&itv, 0, sizeof (itv)); + f64_to_tv (dt, &itv.it_value); + if (setitimer (ITIMER_REAL, &itv, 0) < 0) + clib_unix_error ("sititmer"); + return; + } + } +} + +void timer_block (sigset_t * save) +{ + sigset_t block_timer; + + memset (&block_timer, 0, sizeof (block_timer)); + sigaddset (&block_timer, TIMER_SIGNAL); + sigprocmask (SIG_BLOCK, &block_timer, save); +} + +void timer_unblock (sigset_t * save) +{ sigprocmask (SIG_SETMASK, save, 0); } + +/* Arrange for function to be called some time, + roughly equal to dt seconds, in the future. */ +void timer_call (timer_func_t * func, any arg, f64 dt) +{ + timer_callback_t * t; + sigset_t save; + + /* Install signal handler on first call. */ + static word signal_installed = 0; + + if (! signal_installed) + { + struct sigaction sa; + + /* Initialize time_resolution before first call to timer_interrupt */ + time_resolution = 0.75 / (f64) HZ; + + sa.sa_handler = timer_interrupt; + sa.sa_flags = 0; + + if (sigaction (TIMER_SIGNAL, &sa, 0) < 0) + clib_panic ("sigaction"); + + signal_installed = 1; + } + + timer_block (&save); + + /* Add new timer. */ + vec_add2 (timers, t, 1); + + t->time = unix_time_now () + dt; + t->func = func; + t->arg = arg; + + { + word reset_timer = vec_len (timers) == 1; + + if (_vec_len (timers) > 1) + { + reset_timer += t->time < (t-1)->time; + sort_timers (timers); + } + + if (reset_timer) + timer_interrupt (TIMER_SIGNAL); + } + + timer_unblock (&save); +} + +#ifdef TEST + +#include <vppinfra/random.h> + +/* Compute average delay of function calls to foo. + If this is a small number over a lot of iterations we know + the code is working. */ + +static f64 ave_delay = 0; +static word ave_delay_count = 0; + +always_inline update (f64 delay) +{ + ave_delay += delay; + ave_delay_count += 1; +} + +typedef struct { + f64 time_requested, time_called; +} foo_t; + +static f64 foo_base_time = 0; +static foo_t * foos = 0; + +void foo (any arg, f64 delay) +{ + foos[arg].time_called = unix_time_now () - foo_base_time; + update (delay); +} + +typedef struct { + word count; + word limit; +} bar_t; + +void bar (any arg, f64 delay) +{ + bar_t * b = (bar_t *) arg; + + fformat (stdout, "bar %d delay %g\n", b->count++, delay); + + update (delay); + if (b->count < b->limit) + timer_call (bar, arg, random_f64 ()); +} + +int main (int argc, char * argv[]) +{ + word i, n = atoi (argv[1]); + word run_foo = argc > 2; + bar_t b = {limit: 10}; + + if (run_foo) + { + f64 time_limit; + + time_limit = atof (argv[2]); + + vec_resize (foos, n); + for (i = 0; i < n; i++) + { + foos[i].time_requested = time_limit * random_f64 (); + foos[i].time_called = 1e100; + } + + foo_base_time = unix_time_now (); + for (i = 0; i < n; i++) + timer_call (foo, i, foos[i].time_requested); + } + else + timer_call (bar, (any) &b, random_f64 ()); + + while (vec_len (timers) > 0) + sched_yield (); + + if (vec_len (foos) > 0) + { + f64 min = 1e100, max = -min; + f64 ave = 0, rms = 0; + + for (i = 0; i < n; i++) + { + f64 dt = foos[i].time_requested - foos[i].time_called; + if (dt < min) + min = dt; + if (dt > max) + max = dt; + ave += dt; + rms += dt*dt; + } + ave /= n; + rms = sqrt (rms / n - ave*ave); + fformat (stdout, "error min %g max %g ave %g +- %g\n", min, max, ave, rms); + } + + fformat (stdout, "%d function calls, ave. timer delay %g secs\n", + ave_delay_count, ave_delay / ave_delay_count); + + return 0; +} +#endif |