aboutsummaryrefslogtreecommitdiffstats
path: root/src/vppinfra/test_tw_timer.c
blob: bde958f3ca2507c3833cfd982c9e4ab4af55ce1a (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
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
#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);
      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);
	  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);
      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);
	  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);
      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);
      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);
	  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);
      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);
	  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);
      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);
      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);
  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);
      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);
      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;

  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:
 */