| /* |
| * 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/smp.h> |
| #include <vppinfra/time.h> |
| #include <vppinfra/timer.h> |
| #include <vppinfra/error.h> |
| |
| #ifndef HZ |
| #define HZ 1000 |
| #endif |
| |
| 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 && isfinite (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 && isfinite (dt)) |
| { |
| _vec_len (timers) -= 1; |
| (*t->func) (t->arg, -dt); |
| } |
| else |
| { |
| /* Set timer for to go off in future. */ |
| struct itimerval itv; |
| clib_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; |
| |
| clib_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; |
| |
| clib_memset (&sa, 0, sizeof (sa)); |
| sa.sa_handler = timer_interrupt; |
| |
| 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) |
| os_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 |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |