aboutsummaryrefslogtreecommitdiffstats
path: root/src/vppinfra/test_tw_timer.c
blob: 90fbc9e18b7e48aeaaf933a2ac86160e1259ec11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/*
 * Copyright (c) 2020 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.
 */

#include <vat/vat.h>
#include <vlibapi/api.h>
#include <vlibmemory/api.h>
#include <vppinfra/error.h>

#include <vnet/ip/ip_format_fns.h>
#include <vnet/ip/ip.h>
#include <vnet/ethernet/ethernet_format_fns.h>
#include <l2tp/l2tp.h>

/* define message IDs */
#include <l2tp/l2tp.api_enum.h>
#include <l2tp/l2tp.api_types.h>
#include <vlibmemory/vlib.api_types.h>

typedef struct
{
  /* API message ID base */
  u16 msg_id_base;
  u32 ping_id;
  vat_main_t *vat_main;
} l2tp_test_main_t;

l2tp_test_main_t l2tp_test_main;

#define __plugin_msg_base l2tp_test_main.msg_id_base
#include <vlibapi/vat_helper_macros.h>

/* Macro to finish up custom dump fns */
#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
#define FINISH                                  \
    vec_add1 (s, 0);                            \
    vl_print (handle, (char *)s);               \
    vec_free (s);                               \
    return handle;

static void vl_api_l2tpv3_create_tunnel_reply_t_handler
  (vl_api_l2tpv3_create_tunnel_reply_t * mp)
{
  vat_main_t *vam = &vat_main;
  i32 retval = ntohl (mp->retval);
  if (vam->async_mode)
    {
      vam->async_errors += (retval < 0);
    }
  else
    {
      vam->retval = retval;
      vam->sw_if_index = ntohl (mp->sw_if_index);
      vam->result_ready = 1;
    }
}

static int
api_l2tpv3_create_tunnel (vat_main_t * vam)
{
  unformat_input_t *i = vam->input;
  ip6_address_t client_address, our_address;
  int client_address_set = 0;
  int our_address_set = 0;
  u32 local_session_id = 0;
  u32 remote_session_id = 0;
  u64 local_cookie = 0;
  u64 remote_cookie = 0;
  u8 l2_sublayer_present = 0;
  vl_api_l2tpv3_create_tunnel_t *mp;
  int ret;

  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (i, "client_address %U", unformat_ip6_address,
		    &client_address))
	client_address_set = 1;
      else if (unformat (i, "our_address %U", unformat_ip6_address,
			 &our_address))
	our_address_set = 1;
      else if (unformat (i, "local_session_id %d", &local_session_id))
	;
      else if (unformat (i, "remote_session_id %d", &remote_session_id))
	;
      else if (unformat (i, "local_cookie %lld", &local_cookie))
	;
      else if (unformat (i, "remote_cookie %lld", &remote_cookie))
	;
      else if (unformat (i, "l2-sublayer-present"))
	l2_sublayer_present = 1;
      else
	break;
    }

  if (client_address_set == 0)
    {
      errmsg ("client_address required");
      return -99;
    }

  if (our_address_set == 0)
    {
      errmsg ("our_address required");
      return -99;
    }

  M (L2TPV3_CREATE_TUNNEL, mp);

  clib_memcpy (mp->client_address.un.ip6, client_address.as_u8,
	       sizeof (ip6_address_t));

  clib_memcpy (mp->our_address.un.ip6, our_address.as_u8,
	       sizeof (ip6_address_t));

  mp->local_session_id = ntohl (local_session_id);
  mp->remote_session_id = ntohl (remote_session_id);
  mp->local_cookie = clib_host_to_net_u64 (local_cookie);
  mp->remote_cookie = clib_host_to_net_u64 (remote_cookie);
  mp->l2_sublayer_present = l2_sublayer_present;

  S (mp);
  W (ret);
  return ret;
}

static int
api_l2tpv3_set_tunnel_cookies (vat_main_t * vam)
{
  unformat_input_t *i = vam->input;
  u32 sw_if_index;
  u8 sw_if_index_set = 0;
  u64 new_local_cookie = 0;
  u64 new_remote_cookie = 0;
  vl_api_l2tpv3_set_tunnel_cookies_t *mp;
  int ret;

  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
	sw_if_index_set = 1;
      else if (unformat (i, "sw_if_index %d", &sw_if_index))
	sw_if_index_set = 1;
      else if (unformat (i, "new_local_cookie %lld", &new_local_cookie))
	;
      else if (unformat (i, "new_remote_cookie %lld", &new_remote_cookie))
	;
      else
	break;
    }

  if (sw_if_index_set == 0)
    {
      errmsg ("missing interface name or sw_if_index");
      return -99;
    }

  M (L2TPV3_SET_TUNNEL_COOKIES, mp);

  mp->sw_if_index = ntohl (sw_if_index);
  mp->new_local_cookie = clib_host_to_net_u64 (new_local_cookie);
  mp->new_remote_cookie = clib_host_to_net_u64 (new_remote_cookie);

  S (mp);
  W (ret);
  return ret;
}

static int
api_l2tpv3_interface_enable_disable (vat_main_t * vam)
{
  unformat_input_t *i = vam->input;
  vl_api_l2tpv3_interface_enable_disable_t *mp;
  u32 sw_if_index;
  u8 sw_if_index_set = 0;
  u8 enable_disable = 1;
  int ret;

  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
	sw_if_index_set = 1;
      else if (unformat (i, "sw_if_index %d", &sw_if_index))
	sw_if_index_set = 1;
      else if (unformat (i, "enable"))
	enable_disable = 1;
      else if (unformat (i, "disable"))
	enable_disable = 0;
      else
	break;
    }

  if (sw_if_index_set == 0)
    {
      errmsg ("missing interface name or sw_if_index");
      return -99;
    }

  M (L2TPV3_INTERFACE_ENABLE_DISABLE, mp);

  mp->sw_if_index = ntohl (sw_if_index);
  mp->enable_disable = enable_disable;

  S (mp);
  W (ret);
  return ret;
}

static int
api_l2tpv3_set_lookup_key (vat_main_t * vam)
{
  unformat_input_t *i = vam->input;
  vl_api_l2tpv3_set_lookup_key_t *mp;
  u8 key = ~0;
  int ret;

  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (i, "lookup_v6_src"))
	key = L2T_LOOKUP_SRC_ADDRESS;
      else if (unformat (i, "lookup_v6_dst"))
	key = L2T_LOOKUP_DST_ADDRESS;
      else if (unformat (i, "lookup_session_id"))
	key = L2T_LOOKUP_SESSION_ID;
      else
	break;
    }

  if (key == (u8) ~ 0)
    {
      errmsg ("l2tp session lookup key unset");
      return -99;
    }

  M (L2TPV3_SET_LOOKUP_KEY, mp);

  mp->key = key;

  S (mp);
  W (ret);
  return ret;
}

static void vl_api_sw_if_l2tpv3_tunnel_details_t_handler
  (vl_api_sw_if_l2tpv3_tunnel_details_t * mp)
{
  vat_main_t *vam = &vat_main;

  print (vam->ofp, "* %U (our) %U (client) (sw_if_index %d)",
	 format_ip6_address, mp->our_address,
	 format_ip6_address, mp->client_address,
	 clib_net_to_host_u32 (mp->sw_if_index));

  print (vam->ofp,
	 "   local cookies %016llx %016llx remote cookie %016llx",
	 clib_net_to_host_u64 (mp->local_cookie[0]),
	 clib_net_to_host_u64 (mp->local_cookie[1]),
	 clib_net_to_host_u64 (mp->remote_cookie));

  print (vam->ofp, "   local session-id %d remote session-id %d",
	 clib_net_to_host_u32 (mp->local_session_id),
	 clib_net_to_host_u32 (mp->remote_session_id));

  print (vam->ofp, "   l2 specific sublayer %s\n",
	 mp->l2_sublayer_present ? "preset" : "absent");

}

static int
api_sw_if_l2tpv3_tunnel_dump (vat_main_t * vam)
{
  vl_api_sw_if_l2tpv3_tunnel_dump_t *mp;
  vl_api_control_ping_t *mp_ping;
  int ret;

  /* Get list of l2tpv3-tunnel interfaces */
  M (SW_IF_L2TPV3_TUNNEL_DUMP, mp);
  S (mp);

  /* Use a control ping for synchronization */
  if (!l2tp_test_main.ping_id)
    l2tp_test_main.ping_id =
      vl_msg_api_get_msg_index ((u8 *) (.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#include <vppinfra/time.h>
#include <vppinfra/cache.h>
#include <vppinfra/error.h>
#include <vppinfra/tw_timer_2t_1w_2048sl.h>
#include <vppinfra/tw_timer_16t_2w_512sl.h>
#include <vppinfra/tw_timer_4t_3w_256sl.h>
#include <vppinfra/tw_timer_1t_3w_1024sl_ov.h>

typedef struct
{
  /** Handle returned from tw_start_timer */
  u32 stop_timer_handle;

  /** Test item should expire at this clock tick */
  u64 expected_to_expire;
} tw_timer_test_elt_t;

typedef struct
{
  /** Pool of test objects */
  tw_timer_test_elt_t *test_elts;

  /** The single-wheel */
  tw_timer_wheel_2t_1w_2048sl_t single_wheel;

  /** The double-wheel */
  tw_timer_wheel_16t_2w_512sl_t double_wheel;

  /* The triple wheel */
  tw_timer_wheel_4t_3w_256sl_t triple_wheel;

  /* The triple wheel with overflow vector */
  tw_timer_wheel_1t_3w_1024sl_ov_t triple_ov_wheel;

  /** random number seed */
  u64 seed;

  /** number of timers */
  u32 ntimers;

  /** number of "churn" iterations */
  u32 niter;

  /** number of clock ticks per churn iteration */
  u32 ticks_per_iter;

  /** cpu timer */
  clib_time_t clib_time;
} tw_timer_test_main_t;

tw_timer_test_main_t tw_timer_test_main;

static void
run_single_wheel (tw_timer_wheel_2t_1w_2048sl_t * tw, u32 n_ticks)
{
  u32 i;
  f64 now = tw->last_run_time + 1.01;

  for (i = 0; i < n_ticks; i++)
    {
      tw_timer_expire_timers_2t_1w_2048sl (tw, now);
      now += 1.01;
    }
}

static void
run_double_wheel (tw_timer_wheel_16t_2w_512sl_t * tw, u32 n_ticks)
{
  u32 i;
  f64 now = tw->last_run_time + 1.01;

  for (i = 0; i < n_ticks; i++)
    {
      tw_timer_expire_timers_16t_2w_512sl (tw, now);
      now += 1.01;
    }
}

static void
run_triple_wheel (tw_timer_wheel_4t_3w_256sl_t * tw, u32 n_ticks)
{
  u32 i;
  f64 now = tw->last_run_time + 1.01;

  for (i = 0; i < n_ticks; i++)
    {
      tw_timer_expire_timers_4t_3w_256sl (tw, now);
      now += 1.01;
    }
}

static void
run_triple_ov_wheel (tw_timer_wheel_1t_3w_1024sl_ov_t * tw, u32 n_ticks)
{
  u32 i;
  f64 now = tw->last_run_time + 1.01;

  for (i = 0; i < n_ticks; i++)
    {
      tw_timer_expire_timers_1t_3w_1024sl_ov (tw, now);
      now += 1.01;
    }
}

static void
expired_timer_single_callback (u32 * expired_timers)
{
  int i;
  u32 pool_index, timer_id;
  tw_timer_test_elt_t *e;
  tw_timer_test_main_t *tm = &tw_timer_test_main;

  for (i = 0; i < vec_len (expired_timers); i++)
    {
      pool_index = expired_timers[i] & 0x7FFFFFFF;
      timer_id = expired_timers[i] >> 31;

      ASSERT (timer_id == 1);

      e = pool_elt_at_index (tm->test_elts, pool_index);

      if (e->expected_to_expire != tm->single_wheel.current_tick)
	{
	  fformat (stdout, "[%d] expired at %lld not %lld\n",
		   e - tm->test_elts, tm->single_wheel.current_tick,
		   e->expected_to_expire);
	}
      pool_put (tm->test_elts, e);
    }
}

static void
expired_timer_double_callback (u32 * expired_timers)
{
  int i;
  u32 pool_index, timer_id;
  tw_timer_test_elt_t *e;
  tw_timer_test_main_t *tm = &tw_timer_test_main;

  for (i = 0; i < vec_len (expired_timers); i++)
    {
      pool_index = expired_timers[i] & 0x0FFFFFFF;
      timer_id = expired_timers[i] >> 28;

      ASSERT (timer_id == 14);

      e = pool_elt_at_index (tm->test_elts, pool_index);

      if (e->expected_to_expire != tm->double_wheel.current_tick)
	{
	  fformat (stdout, "[%d] expired at %lld not %lld\n",
		   e - tm->test_elts, tm->double_wheel.current_tick,
		   e->expected_to_expire);
	}
      pool_put (tm->test_elts, e);
    }
}

static void
expired_timer_triple_callback (u32 * expired_timers)
{
  int i;
  u32 pool_index, timer_id;
  tw_timer_test_elt_t *e;
  tw_timer_test_main_t *tm = &tw_timer_test_main;

  for (i = 0; i < vec_len (expired_timers); i++)
    {
      pool_index = expired_timers[i] & 0x3FFFFFFF;
      timer_id = expired_timers[i] >> 30;

      ASSERT (timer_id == 3);

      e = pool_elt_at_index (tm->test_elts, pool_index);

      if (e->expected_to_expire != tm->triple_wheel.current_tick)
	{
	  fformat (stdout, "[%d] expired at %lld not %lld\n",
		   e - tm->test_elts, tm->triple_wheel.current_tick,
		   e->expected_to_expire);
	}
      pool_put (tm->test_elts, e);
    }
}

static void
expired_timer_triple_ov_callback (u32 * expired_timers)
{
  int i;
  u32 pool_index;
  tw_timer_test_elt_t *e;
  tw_timer_test_main_t *tm = &tw_timer_test_main;

  for (i = 0; i < vec_len (expired_timers); i++)
    {
      pool_index = expired_timers[i];

      e = pool_elt_at_index (tm->test_elts, pool_index);

      if (e->expected_to_expire != tm->triple_ov_wheel.current_tick)
	{
	  fformat (stdout, "[%d] expired at %lld not %lld\n",
		   e - tm->test_elts, tm->triple_ov_wheel.current_tick,
		   e->expected_to_expire);
	}
      pool_put (tm->test_elts, e);
    }
}

static clib_error_t *
test2_single (tw_timer_test_main_t * tm)
{
  u32 i, j;
  tw_timer_test_elt_t *e;
  u32 initial_wheel_offset;
  u64 expiration_time;
  u32 max_expiration_time = 0;
  u32 *deleted_indices = 0;
  u32 adds = 0, deletes = 0;
  f64 before, after;

  clib_time_init (&tm->clib_time);

  tw_timer_wheel_init_2t_1w_2048sl (&tm->single_wheel,
				    expired_timer_single_callback,
				    1.0 /* timer interval */ , ~0);

  /* Prime offset */
  initial_wheel_offset = 757;

  run_single_wheel (&tm->single_wheel, initial_wheel_offset);

  fformat (stdout, "initial wheel time %d, fast index %d\n",
	   tm->single_wheel.current_tick,
	   tm->single_wheel.current_index[TW_TIMER_RING_FAST]);

  initial_wheel_offset = tm->single_wheel.current_tick;

  fformat (stdout,
	   "test %d timers, %d iter, %d ticks per iter, 0x%llx seed\n",
	   tm->ntimers, tm->niter, tm->ticks_per_iter, tm->seed);

  before = clib_time_now (&tm->clib_time);

  /* Prime the pump */
  for (i = 0; i < tm->ntimers; i++)
    {
      pool_get (tm->test_elts, e);
      clib_memset (e, 0, sizeof (*e));

      do
	{
	  expiration_time = random_u64 (&tm->seed) & (2047);
	}
      while (expiration_time == 0);

      if (expiration_time > max_expiration_time)
	max_expiration_time = expiration_time;

      e->expected_to_expire = expiration_time + initial_wheel_offset;
      e->stop_timer_handle =
	tw_timer_start_2t_1w_2048sl (&tm->single_wheel, e - tm->test_elts,
				     1 /* timer id */ ,
				     expiration_time);
    }

  adds += i;

  for (i = 0; i < tm->niter; i++)
    {
      run_single_wheel (&tm->single_wheel, tm->ticks_per_iter);

      j = 0;
      vec_reset_length (deleted_indices);
      /* *INDENT-OFF* */
      pool_foreach (e, tm->test_elts,
      ({
        tw_timer_stop_2t_1w_2048sl (&tm->single_wheel, e->stop_timer_handle);
        vec_add1 (deleted_indices, e - tm->test_elts);
        if (++j >= tm->ntimers / 4)
          goto del_and_re_add;
      }));
      /* *INDENT-ON* */

    del_and_re_add:
      for (j = 0; j < vec_len (deleted_indices); j++)
	{
	  pool_put_index (tm->test_elts, deleted_indices[j]);
	}

      deletes += j;

      for (j = 0; j < tm->ntimers / 4; j++)
	{
	  pool_get (tm->test_elts, e);
	  clib_memset (e, 0, sizeof (*e));

	  do
	    {
	      expiration_time = random_u64 (&tm->seed) & (2047);
	    }
	  while (expiration_time == 0);

	  if (expiration_time > max_expiration_time)
	    max_expiration_time = expiration_time;

	  e->expected_to_expire =
	    expiration_time + tm->single_wheel.current_tick;
	  e->stop_timer_handle = tw_timer_start_2t_1w_2048sl
	    (&tm->single_wheel, e - tm->test_elts, 1 /* timer id */ ,
	     expiration_time);
	}
      adds += j;
    }

  vec_free (deleted_indices);

  run_single_wheel (&tm->single_wheel, max_expiration_time + 1);

  after = clib_time_now (&tm->clib_time);

  fformat (stdout, "%d adds, %d deletes, %d ticks\n", adds, deletes,
	   tm->single_wheel.current_tick);
  fformat (stdout, "test ran %.2f seconds, %.2f ops/second\n",
	   (after - before),
	   ((f64) adds + (f64) deletes +
	    (f64) tm->single_wheel.current_tick) / (after - before));

  if (pool_elts (tm->test_elts))
    fformat (stdout, "Note: %d elements remain in pool\n",
	     pool_elts (tm->test_elts));

  /* *INDENT-OFF* */
  pool_foreach (e, tm->test_elts,
  ({
    fformat (stdout, "[%d] expected to expire %d\n",
             e - tm->test_elts,
             e->expected_to_expire);
  }));
  /* *INDENT-ON* */

  pool_free (tm->test_elts);
  tw_timer_wheel_free_2t_1w_2048sl (&tm->single_wheel);
  return 0;
}

static clib_error_t *
test2_double (tw_timer_test_main_t * tm)
{
  u32 i, j;
  tw_timer_test_elt_t *e;
  u32 initial_wheel_offset;
  u32 expiration_time;
  u32 max_expiration_time = 0;
  u32 *deleted_indices = 0;
  u32 adds = 0, deletes = 0;
  f64 before, after;

  clib_time_init (&tm->clib_time);

  tw_timer_wheel_init_16t_2w_512sl (&tm->double_wheel,
				    expired_timer_double_callback,
				    1.0 /* timer interval */ , ~0);

  /* Prime offset */
  initial_wheel_offset = 7577;

  run_double_wheel (&tm->double_wheel, initial_wheel_offset);

  fformat (stdout, "initial wheel time %d, fast index %d slow index %d\n",
	   tm->double_wheel.current_tick,
	   tm->double_wheel.current_index[TW_TIMER_RING_FAST],
	   tm->double_wheel.current_index[TW_TIMER_RING_SLOW]);

  initial_wheel_offset = tm->double_wheel.current_tick;

  fformat (stdout,
	   "test %d timers, %d iter, %d ticks per iter, 0x%llx seed\n",
	   tm->ntimers, tm->niter, tm->ticks_per_iter, tm->seed);

  before = clib_time_now (&tm->clib_time);

  /* Prime the pump */
  for (i = 0; i < tm->ntimers; i++)
    {
      pool_get (tm->test_elts, e);
      clib_memset (e, 0, sizeof (*e));

      do
	{
	  expiration_time = random_u64 (&tm->seed) & ((1 << 17) - 1);
	}
      while (expiration_time == 0);

      if (expiration_time > max_expiration_time)
	max_expiration_time = expiration_time;

      e->expected_to_expire = expiration_time + initial_wheel_offset;

      e->stop_timer_handle =
	tw_timer_start_16t_2w_512sl (&tm->double_wheel, e - tm->test_elts,
				     14 /* timer id */ ,
				     expiration_time);
    }

  adds += i;

  for (i = 0; i < tm->niter; i++)
    {
      run_double_wheel (&tm->double_wheel, tm->ticks_per_iter);

      j = 0;
      vec_reset_length (deleted_indices);
      /* *INDENT-OFF* */
      pool_foreach (e, tm->test_elts,
      ({
        tw_timer_stop_16t_2w_512sl (&tm->double_wheel, e->stop_timer_handle);
        vec_add1 (deleted_indices, e - tm->test_elts);
        if (++j >= tm->ntimers / 4)
          goto del_and_re_add;
      }));
      /* *INDENT-ON* */

    del_and_re_add:
      for (j = 0; j < vec_len (deleted_indices); j++)
	pool_put_index (tm->test_elts, deleted_indices[j]);

      deletes += j;

      for (j = 0; j < tm->ntimers / 4; j++)
	{
	  pool_get (tm->test_elts, e);
	  clib_memset (e, 0, sizeof (*e));

	  do
	    {
	      expiration_time = random_u64 (&tm->seed) & ((1 << 17) - 1);
	    }
	  while (expiration_time == 0);

	  if (expiration_time > max_expiration_time)
	    max_expiration_time = expiration_time;

	  e->expected_to_expire = expiration_time +
	    tm->double_wheel.current_tick;

	  e->stop_timer_handle = tw_timer_start_16t_2w_512sl
	    (&tm->double_wheel, e - tm->test_elts, 14 /* timer id */ ,
	     expiration_time);
	}
      adds += j;
    }

  vec_free (deleted_indices);

  run_double_wheel (&tm->double_wheel, max_expiration_time + 1);

  after = clib_time_now (&tm->clib_time);

  fformat (stdout, "%d adds, %d deletes, %d ticks\n", adds, deletes,
	   tm->double_wheel.current_tick);
  fformat (stdout, "test ran %.2f seconds, %.2f ops/second\n",
	   (after - before),
	   ((f64) adds + (f64) deletes +
	    (f64) tm->double_wheel.current_tick) / (after - before));

  if (pool_elts (tm->test_elts))
    fformat (stdout, "Note: %d elements remain in pool\n",
	     pool_elts (tm->test_elts));

  /* *INDENT-OFF* */
  pool_foreach (e, tm->test_elts,
  ({
    fformat (stdout, "[%d] expected to expire %d\n",
             e - tm->test_elts,
             e->expected_to_expire);
  }));
  /* *INDENT-ON* */

  pool_free (tm->test_elts);
  tw_timer_wheel_free_16t_2w_512sl (&tm->double_wheel);
  return 0;
}

static u32
get_expiration_time (tw_timer_test_main_t * tm)
{
  u32 expiration_time;
  do
    {
      expiration_time = random_u64 (&tm->seed) & ((1 << 17) - 1);
    }
  while (expiration_time == 0);
  return expiration_time;
}

static clib_error_t *
test2_double_updates (tw_timer_test_main_t * tm)
{
  u32 i, j;
  tw_timer_test_elt_t *e;
  u32 initial_wheel_offset;
  u32 expiration_time;
  u32 max_expiration_time = 0, updates = 0;
  f64 before, after;

  clib_time_init (&tm->clib_time);
  tw_timer_wheel_init_16t_2w_512sl (&tm->double_wheel,
				    expired_timer_double_callback,
				    1.0 /* timer interval */ , ~0);

  /* Prime offset */
  initial_wheel_offset = 7577;
  run_double_wheel (&tm->double_wheel, initial_wheel_offset);
  fformat (stdout, "initial wheel time %d, fast index %d slow index %d\n",
	   tm->double_wheel.current_tick,
	   tm->double_wheel.current_index[TW_TIMER_RING_FAST],
	   tm->double_wheel.current_index[TW_TIMER_RING_SLOW]);

  initial_wheel_offset = tm->double_wheel.current_tick;
  fformat (stdout,
	   "test %d timers, %d iter, %d ticks per iter, 0x%llx seed\n",
	   tm->ntimers, tm->niter, tm->ticks_per_iter, tm->seed);

  before = clib_time_now (&tm->clib_time);

  /* Prime the pump */
  for (i = 0; i < tm->ntimers; i++)
    {
      pool_get (tm->test_elts, e);
      clib_memset (e, 0, sizeof (*e));

      expiration_time = get_expiration_time (tm);
      max_expiration_time = clib_max (expiration_time, max_expiration_time);

      e->expected_to_expire = expiration_time + initial_wheel_offset;
      e->stop_timer_handle = tw_timer_start_16t_2w_512sl (&tm->double_wheel,
							  e - tm->test_elts,
							  14 /* timer id */ ,
							  expiration_time);
    }

  for (i = 0; i < tm->niter; i++)
    {
      run_double_wheel (&tm->double_wheel, tm->ticks_per_iter);

      j = 0;

      /* *INDENT-OFF* */
      pool_foreach (e, tm->test_elts,
      ({
        expiration_time = get_expiration_time (tm);
        max_expiration_time = clib_max (expiration_time, max_expiration_time);
	e->expected_to_expire = expiration_time
				+ tm->double_wheel.current_tick;
        tw_timer_update_16t_2w_512sl (&tm->double_wheel, e->stop_timer_handle,
                                      expiration_time);
        if (++j >= tm->ntimers / 4)
          goto done;
      }));
      /* *INDENT-ON* */

    done:
      updates += j;
    }

  run_double_wheel (&tm->double_wheel, max_expiration_time + 1);

  after = clib_time_now (&tm->clib_time);

  fformat (stdout, "%d updates, %d ticks\n", updates,
	   tm->double_wheel.current_tick);
  fformat (stdout, "test ran %.2f seconds, %.2f ops/second\n",
	   (after - before),
	   ((f64) updates + (f64) tm->double_wheel.current_tick) / (after -
								    before));

  if (pool_elts (tm->test_elts))
    fformat (stdout, "Note: %d elements remain in pool\n",
	     pool_elts (tm->test_elts));

  /* *INDENT-OFF* */
  pool_foreach (e, tm->test_elts,
  ({
    fformat (stdout, "[%d] expected to expire %d\n",
             e - tm->test_elts,
             e->expected_to_expire);
  }));
  /* *INDENT-ON* */

  pool_free (tm->test_elts);
  tw_timer_wheel_free_16t_2w_512sl (&tm->double_wheel);
  return 0;
}

static clib_error_t *
test2_triple (tw_timer_test_main_t * tm)
{
  u32 i, j;
  tw_timer_test_elt_t *e;
  u32 initial_wheel_offset = 0;
  u32 expiration_time;
  u32 max_expiration_time = 0;
  u32 *deleted_indices = 0;
  u32 adds = 0, deletes = 0;
  f64 before, after;

  clib_time_init (&tm->clib_time);

  tw_timer_wheel_init_4t_3w_256sl (&tm->triple_wheel,
				   expired_timer_triple_callback,
				   1.0 /* timer interval */ , ~0);


  /* Prime offset */
  initial_wheel_offset = 75700;
  run_triple_wheel (&tm->triple_wheel, initial_wheel_offset);

  fformat (stdout,
	   "initial wheel time %d, fi %d si %d gi %d\n",
	   tm->triple_wheel.current_tick,
	   tm->triple_wheel.current_index[TW_TIMER_RING_FAST],
	   tm->triple_wheel.current_index[TW_TIMER_RING_SLOW],
	   tm->triple_wheel.current_index[TW_TIMER_RING_GLACIER]);

  initial_wheel_offset = tm->triple_wheel.current_tick;

  fformat (stdout,
	   "test %d timers, %d iter, %d ticks per iter, 0x%llx seed\n",
	   tm->ntimers, tm->niter, tm->ticks_per_iter, tm->seed);

  before = clib_time_now (&tm->clib_time);

  /* Prime the pump */
  for (i = 0; i < tm->ntimers; i++)
    {
      pool_get (tm->test_elts, e);
      clib_memset (e, 0, sizeof (*e));

      do
	{
	  expiration_time = random_u64 (&tm->seed) & ((1 << 17) - 1);
	}
      while (expiration_time == 0);

      if (expiration_time > max_expiration_time)
	max_expiration_time = expiration_time;

      e->expected_to_expire = expiration_time + initial_wheel_offset;

      e->stop_timer_handle =
	tw_timer_start_4t_3w_256sl (&tm->triple_wheel, e - tm->test_elts,
				    3 /* timer id */ ,
				    expiration_time);
    }

  adds += i;

  for (i = 0; i < tm->niter; i++)
    {
      run_triple_wheel (&tm->triple_wheel, tm->ticks_per_iter);

      j = 0;
      vec_reset_length (deleted_indices);
      /* *INDENT-OFF* */
      pool_foreach (e, tm->test_elts,
      ({
        tw_timer_stop_4t_3w_256sl (&tm->triple_wheel, e->stop_timer_handle);
        vec_add1 (deleted_indices, e - tm->test_elts);
        if (++j >= tm->ntimers / 4)
          goto del_and_re_add;
      }));
      /* *INDENT-ON* */

    del_and_re_add:
      for (j = 0; j < vec_len (deleted_indices); j++)
	pool_put_index (tm->test_elts, deleted_indices[j]);

      deletes += j;

      for (j = 0; j < tm->ntimers / 4; j++)
	{
	  pool_get (tm->test_elts, e);
	  clib_memset (e, 0, sizeof (*e));

	  do
	    {
	      expiration_time = random_u64 (&tm->seed) & ((1 << 17) - 1);
	    }
	  while (expiration_time == 0);

	  if (expiration_time > max_expiration_time)
	    max_expiration_time = expiration_time;

	  e->expected_to_expire = expiration_time +
	    tm->triple_wheel.current_tick;

	  e->stop_timer_handle = tw_timer_start_4t_3w_256sl
	    (&tm->triple_wheel, e - tm->test_elts, 3 /* timer id */ ,
	     expiration_time);
	}
      adds += j;
    }

  vec_free (deleted_indices);

  run_triple_wheel (&tm->triple_wheel, max_expiration_time + 1);

  after = clib_time_now (&tm->clib_time);

  fformat (stdout, "%d adds, %d deletes, %d ticks\n", adds, deletes,
	   tm->triple_wheel.current_tick);
  fformat (stdout, "test ran %.2f seconds, %.2f ops/second\n",
	   (after - before),
	   ((f64) adds + (f64) deletes +
	    (f64) tm->triple_wheel.current_tick) / (after - before));

  if (pool_elts (tm->test_elts))
    fformat (stdout, "Note: %d elements remain in pool\n",
	     pool_elts (tm->test_elts));

  /* *INDENT-OFF* */
  pool_foreach (e, tm->test_elts,
  ({
    fformat (stdout, "[%d] expected to expire %d\n",
             e - tm->test_elts,
             e->expected_to_expire);
  }));
  /* *INDENT-ON* */

  pool_free (tm->test_elts);
  tw_timer_wheel_free_4t_3w_256sl (&tm->triple_wheel);
  return 0;
}

static clib_error_t *
test2_triple_ov (tw_timer_test_main_t * tm)
{
  u32 i, j;
  tw_timer_test_elt_t *e;
  u32 initial_wheel_offset = 0;
  u32 expiration_time;
  u32 max_expiration_time = 0;
  u32 *deleted_indices = 0;
  u32 adds = 0, deletes = 0;
  f64 before, after;

  clib_time_init (&tm->clib_time);

  tw_timer_wheel_init_1t_3w_1024sl_ov (&tm->triple_ov_wheel,
				       expired_timer_triple_ov_callback,
				       1.0 /* timer interval */ , ~0);


  /* Prime offset */
  initial_wheel_offset = 75700;
  run_triple_ov_wheel (&tm->triple_ov_wheel, initial_wheel_offset);

  fformat (stdout,
	   "initial wheel time %d, fi %d si %d gi %d\n",
	   tm->triple_ov_wheel.current_tick,
	   tm->triple_ov_wheel.current_index[TW_TIMER_RING_FAST],
	   tm->triple_ov_wheel.current_index[TW_TIMER_RING_SLOW],
	   tm->triple_ov_wheel.current_index[TW_TIMER_RING_GLACIER]);

  initial_wheel_offset = tm->triple_ov_wheel.current_tick;

  fformat (stdout,
	   "test %d timers, %d iter, %d ticks per iter, 0x%llx seed\n",
	   tm->ntimers, tm->niter, tm->ticks_per_iter, tm->seed);

  before = clib_time_now (&tm->clib_time);

  /* Prime the pump */
  for (i = 0; i < tm->ntimers; i++)
    {
      pool_get (tm->test_elts, e);
      clib_memset (e, 0, sizeof (*e));

      do
	{
	  expiration_time = random_u64 (&tm->seed) & ((1 << 17) - 1);
	}
      while (expiration_time == 0);

      if (expiration_time > max_expiration_time)
	max_expiration_time = expiration_time;

      e->expected_to_expire = expiration_time + initial_wheel_offset;

      e->stop_timer_handle =
	tw_timer_start_1t_3w_1024sl_ov (&tm->triple_ov_wheel,
					e - tm->test_elts, 0 /* timer id */ ,
					expiration_time);
    }

  adds += i;

  for (i = 0; i < tm->niter; i++)
    {
      run_triple_ov_wheel (&tm->triple_ov_wheel, tm->ticks_per_iter);

      j = 0;
      vec_reset_length (deleted_indices);
      /* *INDENT-OFF* */
      pool_foreach (e, tm->test_elts,
      ({
        tw_timer_stop_1t_3w_1024sl_ov (&tm->triple_ov_wheel,
                                       e->stop_timer_handle);
        vec_add1 (deleted_indices, e - tm->test_elts);
        if (++j >= tm->ntimers / 4)
          goto del_and_re_add;
      }));
      /* *INDENT-ON* */

    del_and_re_add:
      for (j = 0; j < vec_len (deleted_indices); j++)
	pool_put_index (tm->test_elts, deleted_indices[j]);

      deletes += j;

      for (j = 0; j < tm->ntimers / 4; j++)
	{
	  pool_get (tm->test_elts, e);
	  clib_memset (e, 0, sizeof (*e));

	  do
	    {
	      expiration_time = random_u64 (&tm->seed) & ((1 << 17) - 1);
	    }
	  while (expiration_time == 0);

	  if (expiration_time > max_expiration_time)
	    max_expiration_time = expiration_time;

	  e->expected_to_expire = expiration_time +
	    tm->triple_ov_wheel.current_tick;

	  e->stop_timer_handle = tw_timer_start_1t_3w_1024sl_ov
	    (&tm->triple_ov_wheel, e - tm->test_elts, 0 /* timer id */ ,
	     expiration_time);
	}
      adds += j;
    }

  vec_free (deleted_indices);

  run_triple_ov_wheel (&tm->triple_ov_wheel, max_expiration_time + 1);

  after = clib_time_now (&tm->clib_time);

  fformat (stdout, "%d adds, %d deletes, %d ticks\n", adds, deletes,
	   tm->triple_ov_wheel.current_tick);
  fformat (stdout, "test ran %.2f seconds, %.2f ops/second\n",
	   (after - before),
	   ((f64) adds + (f64) deletes +
	    (f64) tm->triple_ov_wheel.current_tick) / (after - before));

  if (pool_elts (tm->test_elts))
    fformat (stdout, "Note: %d elements remain in pool\n",
	     pool_elts (tm->test_elts));

  /* *INDENT-OFF* */
  pool_foreach (e, tm->test_elts,
  ({
    TWT (tw_timer) * t;

    fformat (stdout, "[%d] expected to expire %d\n",
             e - tm->test_elts,
             e->expected_to_expire);
    t = pool_elt_at_index (tm->triple_ov_wheel.timers, e->stop_timer_handle);
    fformat (stdout, "  expiration_time %lld\n", t->expiration_time);
  }));
  /* *INDENT-ON* */

  pool_free (tm->test_elts);
  tw_timer_wheel_free_1t_3w_1024sl_ov (&tm->triple_ov_wheel);
  return 0;
}

static clib_error_t *
test1_single (tw_timer_test_main_t * tm)
{
  u32 i;
  tw_timer_test_elt_t *e;
  u32 offset;

  tw_timer_wheel_init_2t_1w_2048sl (&tm->single_wheel,
				    expired_timer_single_callback,
				    1.0 /* timer interval */ , ~0);

  /*
   * Prime offset, to make sure that the wheel starts in a
   * non-trivial position
   */
  offset = 123;

  run_single_wheel (&tm->single_wheel, offset);

  fformat (stdout, "initial wheel time %d, fast index %d\n",
	   tm->single_wheel.current_tick,
	   tm->single_wheel.current_index[TW_TIMER_RING_FAST]);

  offset = tm->single_wheel.current_tick;

  for (i = 0; i < tm->ntimers; i++)
    {
      u32 expected_to_expire;
      u32 timer_arg;

      timer_arg = 1 + i;
      timer_arg &= 2047;
      if (timer_arg == 0)
	timer_arg = 1;

      expected_to_expire = timer_arg + offset;

      pool_get (tm->test_elts, e);
      clib_memset (e, 0, sizeof (*e));
      e->expected_to_expire = expected_to_expire;
      e->stop_timer_handle = tw_timer_start_2t_1w_2048sl
	(&tm->single_wheel, e - tm->test_elts, 1 /* timer id */ ,
	 timer_arg);
    }
  run_single_wheel (&tm->single_wheel, tm->ntimers + 3);

  if (pool_elts (tm->test_elts))
    fformat (stdout, "Note: %d elements remain in pool\n",
	     pool_elts (tm->test_elts));

  /* *INDENT-OFF* */
  pool_foreach (e, tm->test_elts,
  ({
    fformat(stdout, "[%d] expected to expire %d\n",
                     e - tm->test_elts,
                     e->expected_to_expire);
  }));
  /* *INDENT-ON* */

  fformat (stdout,
	   "final wheel time %d, fast index %d\n",
	   tm->single_wheel.current_tick,
	   tm->single_wheel.current_index[TW_TIMER_RING_FAST]);

  pool_free (tm->test_elts);
  tw_timer_wheel_free_2t_1w_2048sl (&tm->single_wheel);
  return 0;
}

static clib_error_t *
test1_double (tw_timer_test_main_t * tm)
{
  u32 i;
  tw_timer_test_elt_t *e;
  u32 offset;

  tw_timer_wheel_init_16t_2w_512sl (&tm->double_wheel,
				    expired_timer_double_callback,
				    1.0 /* timer interval */ , ~0);

  /*
   * Prime offset, to make sure that the wheel starts in a
   * non-trivial position
   */
  offset = 227989;

  run_double_wheel (&tm->double_wheel, offset);

  fformat (stdout, "initial wheel time %d, fast index %d\n",
	   tm->double_wheel.current_tick,
	   tm->double_wheel.current_index[TW_TIMER_RING_FAST]);

  for (i = 0; i < tm->ntimers; i++)
    {
      pool_get (tm->test_elts, e);
      clib_memset (e, 0, sizeof (*e));

      e->expected_to_expire = i + offset + 1;
      e->stop_timer_handle = tw_timer_start_16t_2w_512sl
	(&tm->double_wheel, e - tm->test_elts, 14 /* timer id */ ,
	 i + 1);
    }
  run_double_wheel (&tm->double_wheel, tm->ntimers + 3);

  if (pool_elts (tm->test_elts))
    fformat (stdout, "Note: %d elements remain in pool\n",
	     pool_elts (tm->test_elts));

  /* *INDENT-OFF* */
  pool_foreach (e, tm->test_elts,
  ({
    fformat(stdout, "[%d] expected to expire %d\n",
                     e - tm->test_elts,
                     e->expected_to_expire);
  }));
  /* *INDENT-ON* */

  fformat (stdout,
	   "final wheel time %d, fast index %d\n",
	   tm->double_wheel.current_tick,
	   tm->double_wheel.current_index[TW_TIMER_RING_FAST]);

  pool_free (tm->test_elts);
  tw_timer_wheel_free_16t_2w_512sl (&tm->double_wheel);
  return 0;
}

static clib_error_t *
test3_triple_double (tw_timer_test_main_t * tm)
{
  tw_timer_test_elt_t *e;
  u32 initial_wheel_offset = 0;
  u32 expiration_time;
  u32 max_expiration_time = 0;
  u32 adds = 0, deletes = 0;
  f64 before, after;

  clib_time_init (&tm->clib_time);

  tw_timer_wheel_init_4t_3w_256sl (&tm->triple_wheel,
				   expired_timer_triple_callback,
				   1.0 /* timer interval */ , ~0);

  initial_wheel_offset = 0;
  run_triple_wheel (&tm->triple_wheel, initial_wheel_offset);

  fformat (stdout,
	   "initial wheel time %d, fi %d si %d gi %d\n",
	   tm->triple_wheel.current_tick,
	   tm->triple_wheel.current_index[TW_TIMER_RING_FAST],
	   tm->triple_wheel.current_index[TW_TIMER_RING_SLOW],
	   tm->triple_wheel.current_index[TW_TIMER_RING_GLACIER]);

  initial_wheel_offset = tm->triple_wheel.current_tick;

  fformat (stdout, "Create a timer which expires at wheel-time (1, 0, 0)\n");

  before = clib_time_now (&tm->clib_time);

  /* Prime the pump */
  pool_get (tm->test_elts, e);
  clib_memset (e, 0, sizeof (*e));

  /* 1 glacier ring tick from now */
  expiration_time = TW_SLOTS_PER_RING * TW_SLOTS_PER_RING;
  e->expected_to_expire = expiration_time + initial_wheel_offset;
  max_expiration_time = expiration_time;

  e->stop_timer_handle =
    tw_timer_start_4t_3w_256sl (&tm->triple_wheel, e - tm->test_elts,
				3 /* timer id */ ,
				expiration_time);

  run_triple_wheel (&tm->triple_wheel, max_expiration_time + 1);

  after = clib_time_now (&tm->clib_time);

  fformat (stdout, "%d adds, %d deletes, %d ticks\n", adds, deletes,
	   tm->triple_wheel.current_tick);
  fformat (stdout, "test ran %.2f seconds, %.2f ops/second\n",
	   (after - before),
	   ((f64) adds + (f64) deletes +
	    (f64) tm->triple_wheel.current_tick) / (after - before));

  if (pool_elts (tm->test_elts))
    fformat (stdout, "Note: %d elements remain in pool\n",
	     pool_elts (tm->test_elts));

  /* *INDENT-OFF* */
  pool_foreach (e, tm->test_elts,
  ({
    fformat (stdout, "[%d] expected to expire %d\n",
             e - tm->test_elts,
             e->expected_to_expire);
  }));
  /* *INDENT-ON* */

  pool_free (tm->test_elts);
  tw_timer_wheel_free_4t_3w_256sl (&tm->triple_wheel);
  return 0;
}

static clib_error_t *
test4_double_double (tw_timer_test_main_t * tm)
{
  u32 i;
  tw_timer_test_elt_t *e;
  u32 initial_wheel_offset;
  u32 expiration_time;
  u32 max_expiration_time = 0;
  u32 *deleted_indices = 0;
  u32 adds = 0, deletes = 0;
  f64 before, after;

  clib_time_init (&tm->clib_time);

  tw_timer_wheel_init_16t_2w_512sl (&tm->double_wheel,
				    expired_timer_double_callback,
				    1.0 /* timer interval */ , ~0);
  /* Prime offset */
  initial_wheel_offset = 0;

  run_double_wheel (&tm->double_wheel, initial_wheel_offset);

  fformat (stdout, "initial wheel time %d, fast index %d slow index %d\n",
	   tm->double_wheel.current_tick,
	   tm->double_wheel.current_index[TW_TIMER_RING_FAST],
	   tm->double_wheel.current_index[TW_TIMER_RING_SLOW]);

  initial_wheel_offset = tm->double_wheel.current_tick;

  fformat (stdout, "test timer which expires at 512 ticks\n");

  before = clib_time_now (&tm->clib_time);

  /* Prime the pump */
  for (i = 0; i < tm->ntimers; i++)
    {
      pool_get (tm->test_elts, e);
      clib_memset (e, 0, sizeof (*e));

      expiration_time = 512;

      if (expiration_time > max_expiration_time)
	max_expiration_time = expiration_time;

      e->expected_to_expire = expiration_time + initial_wheel_offset;
      e->stop_timer_handle =
	tw_timer_start_16t_2w_512sl (&tm->double_wheel, e - tm->test_elts,
				     14 /* timer id */ ,
				     expiration_time);
    }

  adds = 1;

  vec_free (deleted_indices);

  run_double_wheel (&tm->double_wheel, max_expiration_time + 1);

  after = clib_time_now (&tm->clib_time);

  fformat (stdout, "%d adds, %d deletes, %d ticks\n", adds, deletes,
	   tm->double_wheel.current_tick);
  fformat (stdout, "test ran %.2f seconds, %.2f ops/second\n",
	   (after - before),
	   ((f64) adds + (f64) deletes +
	    (f64) tm->double_wheel.current_tick) / (after - before));

  if (pool_elts (tm->test_elts))
    fformat (stdout, "Note: %d elements remain in pool\n",
	     pool_elts (tm->test_elts));

  /* *INDENT-OFF* */
  pool_foreach (e, tm->test_elts,
  ({
    fformat (stdout, "[%d] expected to expire %d\n",
             e - tm->test_elts,
             e->expected_to_expire);
  }));
  /* *INDENT-ON* */

  pool_free (tm->test_elts);
  tw_timer_wheel_free_16t_2w_512sl (&tm->double_wheel);
  return 0;
}

static clib_error_t *
test5_double (tw_timer_test_main_t * tm)
{
  u32 i;
  tw_timer_test_elt_t *e;
  u32 initial_wheel_offset;
  u32 expiration_time;
  u32 max_expiration_time = 0;
  u32 adds = 0, deletes = 0;
  f64 before, after;

  clib_time_init (&tm->clib_time);

  tw_timer_wheel_init_16t_2w_512sl (&tm->double_wheel,
				    expired_timer_double_callback,
				    1.0 /* timer interval */ , ~0);

  /* Prime offset */
  initial_wheel_offset = 7567;

  run_double_wheel (&tm->double_wheel, initial_wheel_offset);

  fformat (stdout, "initial wheel time %d, fast index %d slow index %d\n",
	   tm->double_wheel.current_tick,
	   tm->double_wheel.current_index[TW_TIMER_RING_FAST],
	   tm->double_wheel.current_index[TW_TIMER_RING_SLOW]);

  initial_wheel_offset = tm->double_wheel.current_tick;

  fformat (stdout,
	   "test %d timers, %d iter, %d ticks per iter, 0x%llx seed\n",
	   tm->ntimers, tm->niter, tm->ticks_per_iter, tm->seed);

  before = clib_time_now (&tm->clib_time);

  /* Prime the pump */
  for (i = 0; i < tm->ntimers; i++)
    {
      pool_get (tm->test_elts, e);
      clib_memset (e, 0, sizeof (*e));

      expiration_time = i + 1;

      if (expiration_time > max_expiration_time)
	max_expiration_time = expiration_time;

      e->expected_to_expire = expiration_time + initial_wheel_offset;
      e->stop_timer_handle =
	tw_timer_start_16t_2w_512sl (&tm->double_wheel, e - tm->test_elts,
				     14 /* timer id */ ,
				     expiration_time);
    }

  adds += i;

  run_double_wheel (&tm->double_wheel, max_expiration_time + 1);

  after = clib_time_now (&tm->clib_time);

  fformat (stdout, "%d adds, %d deletes, %d ticks\n", adds, deletes,
	   tm->double_wheel.current_tick);
  fformat (stdout, "test ran %.2f seconds, %.2f ops/second\n",
	   (after - before),
	   ((f64) adds + (f64) deletes +
	    (f64) tm->double_wheel.current_tick) / (after - before));

  if (pool_elts (tm->test_elts))
    fformat (stdout, "Note: %d elements remain in pool\n",
	     pool_elts (tm->test_elts));

  /* *INDENT-OFF* */
  pool_foreach (e, tm->test_elts,
  ({
    fformat (stdout, "[%d] expected to expire %d\n",
             e - tm->test_elts,
             e->expected_to_expire);
  }));
  /* *INDENT-ON* */

  pool_free (tm->test_elts);
  tw_timer_wheel_free_16t_2w_512sl (&tm->double_wheel);
  return 0;
}

static clib_error_t *
timer_test_command_fn (tw_timer_test_main_t * tm, unformat_input_t * input)
{

  int is_test1 = 0, is_updates = 0;
  int num_wheels = 1;
  int is_test2 = 0;
  int is_test3 = 0;
  int is_test4 = 0;
  int is_test5 = 0;
  int overflow = 0;

  clib_memset (tm, 0, sizeof (*tm));
  /* Default values */
  tm->ntimers = 100000;
  tm->seed = 0xDEADDABEB00BFACE;
  tm->niter = 1000;
  tm->ticks_per_iter = 727;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "seed %lld", &tm->seed))
	;
      else if (unformat (input, "test1"))
	is_test1 = 1;
      else if (unformat (input, "test2"))
	is_test2 = 1;
      else if (unformat (input, "overflow"))
	overflow = 1;
      else if (unformat (input, "lebron"))
	is_test3 = 1;
      else if (unformat (input, "wilt"))
	is_test4 = 1;
      else if (unformat (input, "linear"))
	is_test5 = 1;
      else if (unformat (input, "updates"))
	is_updates = 1;
      else if (unformat (input, "wheels %d", &num_wheels))
	;
      else if (unformat (input, "ntimers %d", &tm->ntimers))
	;
      else if (unformat (input, "niter %d", &tm->niter))
	;
      else if (unformat (input, "ticks_per_iter %d", &tm->ticks_per_iter))
	;
      else
	break;
    }

  if (is_test1 + is_test2 + is_test3 + is_test4 + is_test5 == 0)
    return clib_error_return (0, "No test specified [test1..n]");

  if (num_wheels < 1 || num_wheels > 3)
    return clib_error_return (0, "unsupported... 1 or 2 wheels only");

  if (is_test1)
    {
      if (num_wheels == 1)
	return test1_single (tm);
      else
	return test1_double (tm);
    }
  if (is_test2)
    {
      if (num_wheels == 1)
	return test2_single (tm);
      else if (num_wheels == 2)
	if (is_updates)
	  return test2_double_updates (tm);
	else
	  return test2_double (tm);
      else if (num_wheels == 3)
	{
	  if (overflow == 0)
	    return test2_triple (tm);
	  else
	    return test2_triple_ov (tm);
	}
    }
  if (is_test3)
    return test3_triple_double (tm);

  if (is_test4)
    return test4_double_double (tm);

  if (is_test5)
    return test5_double (tm);

  /* NOTREACHED */
  return 0;
}

#ifdef CLIB_UNIX
int
main (int argc, char *argv[])
{
  unformat_input_t i;
  clib_error_t *error;
  tw_timer_test_main_t *tm = &tw_timer_test_main;

  clib_mem_init (0, 3ULL << 30);

  unformat_init_command_line (&i, argv);
  error = timer_test_command_fn (tm, &i);
  unformat_free (&i);

  if (error)
    {
      clib_error_report (error);
      return 1;
    }
  return 0;
}
#endif /* CLIB_UNIX */

/* For debugging... */
int
pifi (void *p, u32 index)
{
  return pool_is_free_index (p, index);
}

u32
vl (void *p)
{
  return vec_len (p);
}

uword
pe (void *v)
{
  return (pool_elts (v));
}

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */