summaryrefslogtreecommitdiffstats
path: root/src/vppinfra/timer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vppinfra/timer.c')
-rw-r--r--src/vppinfra/timer.c322
1 files changed, 322 insertions, 0 deletions
diff --git a/src/vppinfra/timer.c b/src/vppinfra/timer.c
new file mode 100644
index 00000000000..0221cb749a1
--- /dev/null
+++ b/src/vppinfra/timer.c
@@ -0,0 +1,322 @@
+/*
+ * 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;
+
+ 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)
+ 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:
+ */