vlib: add virtual time support

Type: feature

Change-Id: Iabd76558e9c72ed8286cfeeb1fbaa4fde4832a90
Signed-off-by: Benoît Ganne <bganne@cisco.com>
diff --git a/src/vlib/CMakeLists.txt b/src/vlib/CMakeLists.txt
index df51b2e..a74c8ce 100644
--- a/src/vlib/CMakeLists.txt
+++ b/src/vlib/CMakeLists.txt
@@ -90,6 +90,7 @@
   punt_node.c
   threads.c
   threads_cli.c
+  time.c
   trace.c
   unix/cli.c
   unix/input.c
@@ -130,6 +131,7 @@
   physmem.h
   punt.h
   threads.h
+  time.h
   trace_funcs.h
   trace.h
   unix/mc_socket.h
diff --git a/src/vlib/threads.c b/src/vlib/threads.c
index ad677dc..f45e935 100644
--- a/src/vlib/threads.c
+++ b/src/vlib/threads.c
@@ -1637,7 +1637,6 @@
 show_clock_command_fn (vlib_main_t * vm,
 		       unformat_input_t * input, vlib_cli_command_t * cmd)
 {
-  int i;
   int verbose = 0;
   clib_timebase_t _tb, *tb = &_tb;
 
@@ -1650,24 +1649,19 @@
 		   verbose, format_clib_timebase_time,
 		   clib_timebase_now (tb));
 
-  if (vlib_get_n_threads () == 1)
-    return 0;
-
   vlib_cli_output (vm, "Time last barrier release %.9f",
 		   vm->time_last_barrier_release);
 
-  for (i = 1; i < vlib_get_n_threads (); i++)
+  foreach_vlib_main ()
     {
-      vlib_main_t *ovm = vlib_get_main_by_index (i);
-      if (ovm == 0)
-	continue;
+      vlib_cli_output (vm, "%d: %U", this_vlib_main->thread_index,
+		       format_clib_time, &this_vlib_main->clib_time, verbose);
 
-      vlib_cli_output (vm, "%d: %U", i, format_clib_time, &ovm->clib_time,
-		       verbose);
-
-      vlib_cli_output (
-	vm, "Thread %d offset %.9f error %.9f", i, ovm->time_offset,
-	vm->time_last_barrier_release - ovm->time_last_barrier_release);
+      vlib_cli_output (vm, "Thread %d offset %.9f error %.9f",
+		       this_vlib_main->thread_index,
+		       this_vlib_main->time_offset,
+		       vm->time_last_barrier_release -
+			 this_vlib_main->time_last_barrier_release);
     }
   return 0;
 }
diff --git a/src/vlib/time.c b/src/vlib/time.c
new file mode 100644
index 0000000..cfe45a0
--- /dev/null
+++ b/src/vlib/time.c
@@ -0,0 +1,84 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2021 Cisco Systems, Inc.
+ */
+
+/* Virtual time allows to adjust VPP clock by arbitrary amount of time.
+ * It is done such that the order of timer expirations is maintained,
+ * and if a timer expiration callback reschedule another timer, this
+ * timer will also properly expire in the right order. IOW, the order
+ * of events is preserved.
+ *
+ * When moving time forward, each VPP thread (main and workers) runs an
+ * instance of the input node 'virtual-time-input' below. This node is
+ * responsible of advancing its own VPP thread clock to the next timer
+ * expiration.  IOW each thread will move its clock independently one
+ * timer at a time. This also means that while moving time forward, each
+ * thread might not have the exact same view of what 'now' means. Once
+ * the main thread has finished moving its time forward, the worker thread
+ * barrier will ensure the timer between main and workers is synchronized.
+ *
+ * Using an input node in poll-mode has several advantages, including
+ * preventing 'unix-epoll-input' to sleep (as it will not sleep if at
+ * least one polling node is active). */
+
+#include <vlib/vlib.h>
+#include <vlib/time.h>
+
+static f64 vlib_time_virtual_stop;
+
+static uword
+vlib_time_virtual_input (vlib_main_t *vm, vlib_node_runtime_t *node,
+			 vlib_frame_t *frame)
+{
+  const f64 next = vlib_time_get_next_timer (vm);
+  /* each thread will advance its own time. In case a thread is much faster
+   * than another, we must make sure it does not run away... */
+  if (vlib_time_now (vm) + next > vlib_time_virtual_stop)
+    vlib_node_set_state (vm, node->node_index, VLIB_NODE_STATE_DISABLED);
+  else
+    vlib_time_adjust (vm, next);
+  return 0;
+}
+
+VLIB_REGISTER_NODE (vlib_time_virtual_input_node) = {
+  .function = vlib_time_virtual_input,
+  .type = VLIB_NODE_TYPE_INPUT,
+  .name = "virtual-time-input",
+  .state = VLIB_NODE_STATE_DISABLED,
+};
+
+static clib_error_t *
+vlib_time_virtual_adjust_command_fn (vlib_main_t *vm, unformat_input_t *input,
+				     vlib_cli_command_t *cmd)
+{
+  f64 val;
+
+  if (!unformat (input, "%f", &val))
+    return clib_error_create ("unknown input `%U'", format_unformat_error,
+			      input);
+
+  vlib_time_virtual_stop = vlib_time_now (vm) + val;
+
+  foreach_vlib_main ()
+    vlib_node_set_state (this_vlib_main, vlib_time_virtual_input_node.index,
+			 VLIB_NODE_STATE_POLLING);
+
+  vlib_worker_thread_barrier_release (vm);
+  while ((val = vlib_process_wait_for_event_or_clock (vm, val)) >= 0.001)
+    ;
+  /* this barrier sync will resynchronize all the clocks, so even if the main
+   * thread was faster than some workers, this will make sure the workers will
+   * disable their virtual-time-input node on their next iteration (as stop
+   * time is reached). If a worker is too slow, there is a slight chance
+   * several of its timers expire at the same time at this point. Time will
+   * tell... */
+  vlib_worker_thread_barrier_sync (vm);
+  return 0;
+}
+
+VLIB_CLI_COMMAND (vlib_time_virtual_command) = {
+  .path = "set clock adjust",
+  .short_help = "set clock adjust <nn>",
+  .function = vlib_time_virtual_adjust_command_fn,
+};
diff --git a/src/vlib/time.h b/src/vlib/time.h
new file mode 100644
index 0000000..61873bb
--- /dev/null
+++ b/src/vlib/time.h
@@ -0,0 +1,26 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2021 Cisco Systems, Inc.
+ */
+
+#ifndef included_vlib_time_h
+#define included_vlib_time_h
+
+#include <vlib/vlib.h>
+#include <vppinfra/tw_timer_1t_3w_1024sl_ov.h>
+
+static inline f64
+vlib_time_get_next_timer (vlib_main_t *vm)
+{
+  vlib_node_main_t *nm = &vm->node_main;
+  TWT (tw_timer_wheel) *wheel = nm->timing_wheel;
+  return TW (tw_timer_first_expires_in_ticks) (wheel) * wheel->timer_interval;
+}
+
+static inline void
+vlib_time_adjust (vlib_main_t *vm, f64 offset)
+{
+  vm->time_offset += offset;
+}
+
+#endif /* included_vlib_time_h */