Create a unit-test plugin
[vpp.git] / src / plugins / unittest / bihash_test.c
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <vlib/vlib.h>
16 #include <vppinfra/time.h>
17 #include <vppinfra/cache.h>
18 #include <vppinfra/error.h>
19 #include <sys/resource.h>
20 #include <stdio.h>
21 #include <pthread.h>
22
23 #include <vppinfra/bihash_8_8.h>
24 #include <vppinfra/bihash_template.h>
25
26 #include <vppinfra/bihash_template.c>
27
28 typedef struct
29 {
30   volatile u32 thread_barrier;
31   volatile u32 threads_running;
32   volatile u64 sequence_number;
33   u64 seed;
34   u32 nbuckets;
35   u32 nitems;
36   u32 ncycles;
37   u32 report_every_n;
38   u32 search_iter;
39   int careful_delete_tests;
40   int verbose;
41   int non_random_keys;
42   u32 nthreads;
43   uword *key_hash;
44   u64 *keys;
45   uword hash_memory_size;
46     BVT (clib_bihash) hash;
47   clib_time_t clib_time;
48   void *global_heap;
49
50   unformat_input_t *input;
51
52   /* convenience */
53   vlib_main_t *vlib_main;
54 } bihash_test_main_t;
55
56 static bihash_test_main_t bihash_test_main;
57
58 static clib_error_t *
59 test_bihash_vec64 (bihash_test_main_t * tm)
60 {
61   u32 user_buckets = 1228800;
62   u32 user_memory_size = 209715200;
63   BVT (clib_bihash_kv) kv;
64   int i, j;
65   f64 before;
66   f64 *cum_times = 0;
67   BVT (clib_bihash) * h;
68
69   h = &tm->hash;
70
71   BV (clib_bihash_init) (h, "test", user_buckets, user_memory_size);
72
73   before = clib_time_now (&tm->clib_time);
74
75   for (j = 0; j < 10; j++)
76     {
77       for (i = 1; i <= j * 1000 + 1; i++)
78         {
79           kv.key = i;
80           kv.value = 1;
81
82           BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */ );
83         }
84
85       vec_add1 (cum_times, clib_time_now (&tm->clib_time) - before);
86     }
87
88   for (j = 0; j < vec_len (cum_times); j++)
89     fformat (stdout, "Cum time for %d: %.4f (us)\n", (j + 1) * 1000,
90              cum_times[j] * 1e6);
91
92   return 0;
93 }
94
95 void *
96 test_bihash_thread_fn (void *arg)
97 {
98   BVT (clib_bihash) * h;
99   BVT (clib_bihash_kv) kv;
100   bihash_test_main_t *tm = &bihash_test_main;
101   int i, j;
102   u32 my_thread_index = (u32) (u64) arg;
103
104   while (tm->thread_barrier)
105     ;
106
107   h = &tm->hash;
108
109   for (i = 0; i < tm->ncycles; i++)
110     {
111       for (j = 0; j < tm->nitems; j++)
112         {
113           kv.key = ((u64) my_thread_index << 32) | (u64) j;
114           kv.value = ((u64) my_thread_index << 32) | (u64) j;
115           (void) __atomic_add_fetch (&tm->sequence_number, 1,
116                                      __ATOMIC_ACQUIRE);
117           BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */ );
118         }
119       for (j = 0; j < tm->nitems; j++)
120         {
121           kv.key = ((u64) my_thread_index << 32) | (u64) j;
122           kv.value = ((u64) my_thread_index << 32) | (u64) j;
123           (void) __atomic_add_fetch (&tm->sequence_number, 1,
124                                      __ATOMIC_ACQUIRE);
125           BV (clib_bihash_add_del) (h, &kv, 0 /* is_add */ );
126         }
127     }
128
129   (void) __atomic_sub_fetch (&tm->threads_running, 1, __ATOMIC_ACQUIRE);
130   pthread_exit (0);
131   return (0);                   /* not so much */
132 }
133
134 static clib_error_t *
135 test_bihash_threads (bihash_test_main_t * tm)
136 {
137   int i;
138   pthread_t handle;
139   BVT (clib_bihash) * h;
140   int rv;
141   f64 before, after, delta;
142
143   h = &tm->hash;
144
145   BV (clib_bihash_init) (h, "test", tm->nbuckets, tm->hash_memory_size);
146
147   tm->thread_barrier = 1;
148
149   /* Start the worker threads */
150   for (i = 0; i < tm->nthreads; i++)
151     {
152       rv = pthread_create (&handle, NULL, test_bihash_thread_fn,
153                            (void *) (u64) i);
154       if (rv)
155         {
156           clib_unix_warning ("pthread_create returned %d", rv);
157         }
158     }
159   tm->threads_running = i;
160   tm->sequence_number = 0;
161   CLIB_MEMORY_BARRIER ();
162
163   /* start the workers */
164   before = vlib_time_now (tm->vlib_main);
165   tm->thread_barrier = 0;
166
167   while (tm->threads_running > 0)
168     CLIB_PAUSE ();
169
170   after = vlib_time_now (tm->vlib_main);
171   delta = after - before;
172
173   clib_warning ("%lld items in %.2f seconds, %.2f items/sec",
174                 (u64) tm->nthreads * (u64) tm->nitems, delta,
175                 delta >
176                 0.0 ? ((f64) ((u64) tm->nthreads * (u64) tm->nitems)) /
177                 delta : 0.0);
178   return 0;
179 }
180
181 /*
182  * Callback to blow up spectacularly if anthing remains in the table
183  */
184 static void
185 count_items (BVT (clib_bihash_kv) * kvp, void *notused)
186 {
187   _clib_error (CLIB_ERROR_ABORT, 0, 0,
188                "bihash test FAILED, items left in table!");
189 }
190
191 static clib_error_t *
192 test_bihash (bihash_test_main_t * tm)
193 {
194   int i, j;
195   uword *p;
196   uword total_searches;
197   f64 before, delta;
198   BVT (clib_bihash) * h;
199   BVT (clib_bihash_kv) kv;
200   u32 acycle;
201
202   h = &tm->hash;
203
204   BV (clib_bihash_init) (h, "test", tm->nbuckets, tm->hash_memory_size);
205
206   for (acycle = 0; acycle < tm->ncycles; acycle++)
207     {
208       if ((acycle % tm->report_every_n) == 0)
209         {
210           fformat (stdout, "Cycle %lld out of %lld...\n",
211                    acycle, tm->ncycles);
212
213           fformat (stdout, "Pick %lld unique %s keys...\n",
214                    tm->nitems, tm->non_random_keys ? "non-random" : "random");
215         }
216
217       for (i = 0; i < tm->nitems; i++)
218         {
219           u64 rndkey;
220
221           if (tm->non_random_keys == 0)
222             {
223
224             again:
225               rndkey = random_u64 (&tm->seed);
226
227               p = hash_get (tm->key_hash, rndkey);
228               if (p)
229                 goto again;
230             }
231           else
232             rndkey = (u64) (i + 1) << 16;
233           rndkey += acycle;
234
235           hash_set (tm->key_hash, rndkey, i + 1);
236           vec_add1 (tm->keys, rndkey);
237         }
238
239
240       if ((acycle % tm->report_every_n) == 0)
241         fformat (stdout, "Add items...\n");
242
243       for (i = 0; i < tm->nitems; i++)
244         {
245           kv.key = tm->keys[i];
246           kv.value = i + 1;
247
248           BV (clib_bihash_add_del) (h, &kv, 1 /* is_add */ );
249
250           if (tm->verbose > 1)
251             {
252               fformat (stdout, "--------------------\n");
253               fformat (stdout, "After adding key %llu value %lld...\n",
254                        tm->keys[i], (u64) (i + 1));
255               fformat (stdout, "%U", BV (format_bihash), h,
256                        2 /* very verbose */ );
257             }
258         }
259
260       if ((acycle % tm->report_every_n) == 0)
261         {
262           fformat (stdout, "%U", BV (format_bihash), h,
263                    0 /* very verbose */ );
264
265           fformat (stdout, "Search for items %d times...\n", tm->search_iter);
266         }
267
268       before = clib_time_now (&tm->clib_time);
269
270       for (j = 0; j < tm->search_iter; j++)
271         {
272           for (i = 0; i < tm->nitems; i++)
273             {
274               kv.key = tm->keys[i];
275               if (BV (clib_bihash_search) (h, &kv, &kv) < 0)
276                 if (BV (clib_bihash_search) (h, &kv, &kv) < 0)
277                   clib_warning
278                     ("[%d] search for key %lld failed unexpectedly\n", i,
279                      tm->keys[i]);
280               if (kv.value != (u64) (i + 1))
281                 clib_warning
282                   ("[%d] search for key %lld returned %lld, not %lld\n", i,
283                    tm->keys, kv.value, (u64) (i + 1));
284             }
285         }
286
287       if ((acycle % tm->report_every_n) == 0)
288         {
289           delta = clib_time_now (&tm->clib_time) - before;
290           total_searches = (uword) tm->search_iter * (uword) tm->nitems;
291
292           if (delta > 0)
293             fformat (stdout, "%.f searches per second\n",
294                      ((f64) total_searches) / delta);
295
296           fformat (stdout, "%lld searches in %.6f seconds\n", total_searches,
297                    delta);
298
299           fformat (stdout, "Standard E-hash search for items %d times...\n",
300                    tm->search_iter);
301         }
302
303       before = clib_time_now (&tm->clib_time);
304
305       for (j = 0; j < tm->search_iter; j++)
306         {
307           for (i = 0; i < tm->nitems; i++)
308             {
309               p = hash_get (tm->key_hash, tm->keys[i]);
310               if (p == 0 || p[0] != (uword) (i + 1))
311                 clib_warning ("ugh, couldn't find %lld\n", tm->keys[i]);
312             }
313         }
314
315       delta = clib_time_now (&tm->clib_time) - before;
316       total_searches = (uword) tm->search_iter * (uword) tm->nitems;
317
318       if ((acycle % tm->report_every_n) == 0)
319         {
320           fformat (stdout, "%lld searches in %.6f seconds\n",
321                    total_searches, delta);
322
323           if (delta > 0)
324             fformat (stdout, "%.f searches per second\n",
325                      ((f64) total_searches) / delta);
326           fformat (stdout, "Delete items...\n");
327         }
328
329       for (i = 0; i < tm->nitems; i++)
330         {
331           int j;
332           int rv;
333
334           kv.key = tm->keys[i];
335           kv.value = (u64) (i + 1);
336           rv = BV (clib_bihash_add_del) (h, &kv, 0 /* is_add */ );
337
338           if (rv < 0)
339             clib_warning ("delete key %lld not ok but should be",
340                           tm->keys[i]);
341
342           if (tm->careful_delete_tests)
343             {
344               for (j = 0; j < tm->nitems; j++)
345                 {
346                   kv.key = tm->keys[j];
347                   rv = BV (clib_bihash_search) (h, &kv, &kv);
348                   if (j <= i && rv >= 0)
349                     {
350                       clib_warning
351                         ("i %d j %d search ok but should not be, value %lld",
352                          i, j, kv.value);
353                     }
354                   if (j > i && rv < 0)
355                     {
356                       clib_warning ("i %d j %d search not ok but should be",
357                                     i, j);
358                     }
359                 }
360             }
361         }
362       if ((acycle % tm->report_every_n) == 0)
363         {
364           struct rusage r_usage;
365           getrusage (RUSAGE_SELF, &r_usage);
366           fformat (stdout, "Kernel RSS: %ld bytes\n", r_usage.ru_maxrss);
367           fformat (stdout, "%U\n", BV (format_bihash), h, 0 /* verbose */ );
368         }
369
370       /* Clean up side-bet hash table and random key vector */
371       hash_free (tm->key_hash);
372       vec_reset_length (tm->keys);
373       /* Recreate hash table if we're going to need it again */
374       if (acycle != (tm->ncycles - 1))
375         tm->key_hash = hash_create (tm->nitems, sizeof (uword));
376     }
377
378   fformat (stdout, "End of run, should be empty...\n");
379
380   fformat (stdout, "%U", BV (format_bihash), h, 0 /* very verbose */ );
381
382   /* ASSERTs if any items remain */
383   BV (clib_bihash_foreach_key_value_pair) (h, count_items, 0);
384
385   return 0;
386 }
387
388 static clib_error_t *
389 test_bihash_command_fn (vlib_main_t * vm,
390                         unformat_input_t * input, vlib_cli_command_t * cmd)
391 {
392   bihash_test_main_t *tm = &bihash_test_main;
393   clib_error_t *error;
394   int which = 0;
395
396   tm->hash_memory_size = 32ULL << 20;
397   tm->nbuckets = 32000;
398   tm->nitems = 100000;
399   tm->ncycles = 10;
400   tm->report_every_n = 50000;
401   tm->seed = 0x1badf00d;
402
403   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
404     {
405       if (unformat (input, "seed %u", &tm->seed))
406         ;
407
408       else if (unformat (input, "nbuckets %d", &tm->nbuckets))
409         ;
410       else if (unformat (input, "non-random-keys"))
411         tm->non_random_keys = 1;
412       else if (unformat (input, "nitems %d", &tm->nitems))
413         ;
414       else if (unformat (input, "ncycles %d", &tm->ncycles))
415         ;
416       else if (unformat (input, "careful %d", &tm->careful_delete_tests))
417         ;
418       else if (unformat (input, "verbose %d", &tm->verbose))
419         ;
420       else if (unformat (input, "search %d", &tm->search_iter))
421         ;
422       else if (unformat (input, "report-every %d", &tm->report_every_n))
423         ;
424       else if (unformat (input, "memory-size %U",
425                          unformat_memory_size, &tm->hash_memory_size))
426         ;
427       else if (unformat (input, "vec64"))
428         which = 1;
429       else if (unformat (input, "threads %u", &tm->nthreads))
430         which = 2;
431       else if (unformat (input, "verbose"))
432         tm->verbose = 1;
433       else
434         return clib_error_return (0, "unknown input '%U'",
435                                   format_unformat_error, input);
436     }
437
438   /* Preallocate hash table, key vector */
439   tm->key_hash = hash_create (tm->nitems, sizeof (uword));
440   vec_validate (tm->keys, tm->nitems - 1);
441   _vec_len (tm->keys) = 0;
442
443   switch (which)
444     {
445     case 0:
446       error = test_bihash (tm);
447       break;
448
449     case 1:
450       error = test_bihash_vec64 (tm);
451       break;
452
453     case 2:
454       error = test_bihash_threads (tm);
455       break;
456
457     default:
458       return clib_error_return (0, "no such test?");
459     }
460
461   return error;
462 }
463
464 /* *INDENT-OFF* */
465 VLIB_CLI_COMMAND (test_bihash_command, static) =
466 {
467   .path = "test bihash",
468   .short_help = "test bihash",
469   .function = test_bihash_command_fn,
470 };
471 /* *INDENT-ON* */
472
473 static clib_error_t *
474 bihash_test_init (vlib_main_t * vm)
475 {
476   bihash_test_main_t *tm = &bihash_test_main;
477
478   tm->vlib_main = vm;
479   return (0);
480 }
481
482 VLIB_INIT_FUNCTION (bihash_test_init);
483
484 /*
485  * fd.io coding-style-patch-verification: ON
486  *
487  * Local Variables:
488  * eval: (c-set-style "gnu")
489  * End:
490  */