API Reference Manual  1.46.0
odp_random.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright (c) 2021-2024 Nokia
3  */
4 
13 #include <stdio.h>
14 #include <string.h>
15 #include <stdint.h>
16 #include <inttypes.h>
17 #include <getopt.h>
18 
19 #include <odp_api.h>
20 #include <odp/helper/odph_api.h>
21 
22 #define PSEUDO_RANDOM (-1)
23 
24 #define MB (1024ull * 1024ull)
25 
26 typedef struct test_global_t test_global_t;
27 
28 typedef struct thread_arg_t {
29  test_global_t *global;
30  int thread_idx;
31  uint8_t *data;
32 
33 } thread_arg_t;
34 
35 struct test_global_t {
36  odp_barrier_t barrier;
37  odp_random_kind_t type;
38  uint8_t *data;
39  uint32_t rounds;
40 
41  thread_arg_t thread_arg[ODP_THREAD_COUNT_MAX];
42 
43  struct {
44  uint64_t nsec[ODP_THREAD_COUNT_MAX];
45  uint64_t sum[ODP_THREAD_COUNT_MAX];
46  uint64_t min[ODP_THREAD_COUNT_MAX];
47  uint64_t max[ODP_THREAD_COUNT_MAX];
48  } stat;
49 };
50 
51 /* Command line options */
52 typedef struct {
53  int mode;
54  int num_threads;
55  uint32_t size;
56  uint32_t rounds;
57  uint32_t msec;
58  uint64_t delay;
59 
60 } options_t;
61 
62 static options_t options;
63 static const options_t options_def = {
64  .mode = 0,
65  .num_threads = 1,
66  .size = 256,
67  .rounds = 100000,
68  .msec = 500,
69  .delay = 0,
70 };
71 
72 static void print_usage(void)
73 {
74  printf("\n"
75  "random data performance test\n"
76  "\n"
77  "Usage: odp_random [options]\n"
78  "\n"
79  " -m, --mode Test mode select (default: 0):\n"
80  " 0: Data throughput\n"
81  " 1: Data generation latency (size: 8B by default)\n"
82  " -c, --num_cpu Number of CPUs (worker threads). 0: all available CPUs. Default 1.\n"
83  " -s, --size Size of buffer in bytes. Default %u.\n"
84  " -r, --rounds Number of test rounds. Default %u.\n"
85  " Divided by 100 for ODP_RANDOM_TRUE.\n"
86  " -t, --time Target test duration (msec). 0: use rounds from -r option. Default %u.\n"
87  " Based on a measurement on one thread. Test duration may be\n"
88  " significantly longer with multiple threads.\n"
89  " -d, --delay Delay (nsec) between buffer fills. Default %" PRIu64 ".\n"
90  " Affects only latency mode.\n"
91  " -h, --help This help.\n"
92  "\n",
93  options_def.size, options_def.rounds, options_def.msec, options_def.delay);
94 }
95 
96 static int parse_options(int argc, char *argv[])
97 {
98  int opt;
99  int ret = 0;
100 
101  static const struct option longopts[] = {
102  { "mode", required_argument, NULL, 'm' },
103  { "num_cpu", required_argument, NULL, 'c' },
104  { "size", required_argument, NULL, 's' },
105  { "rounds", required_argument, NULL, 'r' },
106  { "time", required_argument, NULL, 't' },
107  { "delay", required_argument, NULL, 'd' },
108  { "help", no_argument, NULL, 'h' },
109  { NULL, 0, NULL, 0 }
110  };
111 
112  static const char *shortopts = "+m:c:s:r:t:d:h";
113 
114  options = options_def;
115  options.size = 0;
116 
117  while (1) {
118  opt = getopt_long(argc, argv, shortopts, longopts, NULL);
119 
120  if (opt == -1)
121  break;
122 
123  switch (opt) {
124  case 'm':
125  options.mode = atoi(optarg);
126  break;
127  case 'c':
128  options.num_threads = atol(optarg);
129  break;
130  case 's':
131  options.size = atol(optarg);
132  break;
133  case 'r':
134  options.rounds = atol(optarg);
135  break;
136  case 't':
137  options.msec = atol(optarg);
138  break;
139  case 'd':
140  options.delay = atol(optarg);
141  break;
142  case 'h':
143  /* fall through */
144  default:
145  print_usage();
146  ret = -1;
147  break;
148  }
149  }
150 
151  if (options.num_threads < 1 || options.num_threads > ODP_THREAD_COUNT_MAX) {
152  ODPH_ERR("Bad number of threads: %i\n", options.num_threads);
153  return -1;
154  }
155 
156  if (options.size == 0) {
157  options.size = options_def.size;
158 
159  if (options.mode)
160  options.size = 8;
161  }
162 
163  printf("\nOptions:\n");
164  printf("------------------------\n");
165  printf(" mode: %i\n", options.mode);
166  printf(" num_cpu: %i\n", options.num_threads);
167  printf(" size: %u\n", options.size);
168  printf(" rounds: %u\n", options.rounds);
169  printf(" time: %u\n", options.msec);
170  printf(" delay: %" PRIu64 "\n", options.delay);
171  printf("\n");
172 
173  return ret;
174 }
175 
176 static inline void random_data_loop(odp_random_kind_t type, uint32_t rounds,
177  uint8_t *data, uint32_t size)
178 {
179  uint32_t i;
180  int32_t ret;
181 
182  if ((int)type == PSEUDO_RANDOM) {
183  uint64_t seed = 0;
184 
185  for (i = 0; i < rounds; i++) {
186  uint32_t pos = 0;
187 
188  while (pos < size) {
189  ret = odp_random_test_data(data + pos, size - pos, &seed);
190 
191  if (ret < 0) {
192  ODPH_ERR("odp_random_test_data() failed\n");
193  exit(EXIT_FAILURE);
194  }
195 
196  pos += ret;
197  }
198  }
199  } else {
200  for (i = 0; i < rounds; i++) {
201  uint32_t pos = 0;
202 
203  while (pos < size) {
204  ret = odp_random_data(data + pos, size - pos, type);
205 
206  if (ret < 0) {
207  ODPH_ERR("odp_random_data() failed\n");
208  exit(EXIT_FAILURE);
209  }
210 
211  pos += ret;
212  }
213  }
214  }
215 }
216 
217 static int test_random_perf(void *ptr)
218 {
219  odp_time_t start;
220  uint64_t nsec;
221  thread_arg_t *thread_arg = ptr;
222  test_global_t *global = thread_arg->global;
223  odp_random_kind_t type = global->type;
224  int thread_idx = thread_arg->thread_idx;
225  uint8_t *data = thread_arg->data;
226  uint32_t size = options.size;
227  uint32_t rounds = global->rounds;
228 
229  /* One warm up round */
230  random_data_loop(type, 1, data, size);
231 
232  odp_barrier_wait(&global->barrier);
233 
234  /* Test run */
235  start = odp_time_local();
236 
237  random_data_loop(type, rounds, data, size);
238 
239  nsec = odp_time_diff_ns(odp_time_local(), start);
240 
241  global->stat.nsec[thread_idx] = nsec;
242 
243  return 0;
244 }
245 
246 static inline void random_data_latency(test_global_t *global, int thread_idx,
247  uint32_t rounds, uint8_t *data, uint32_t size)
248 {
249  uint32_t i;
250  int32_t ret;
251  odp_time_t t1, t2, start;
252  uint64_t nsec;
253  odp_random_kind_t type = global->type;
254  uint64_t delay = options.delay;
255  uint64_t min = UINT64_MAX;
256  uint64_t max = 0;
257  uint64_t sum = 0;
258  uint64_t seed = 0;
259 
260  start = odp_time_local();
261 
262  for (i = 0; i < rounds; i++) {
263  uint32_t pos = 0;
264 
265  if (delay)
266  odp_time_wait_ns(delay);
267 
268  if ((int)type == PSEUDO_RANDOM) {
269  t1 = odp_time_local_strict();
270  while (pos < size) {
271  ret = odp_random_test_data(data + pos, size - pos, &seed);
272 
273  if (ret < 0) {
274  ODPH_ERR("odp_random_test_data() failed\n");
275  exit(EXIT_FAILURE);
276  }
277 
278  pos += ret;
279  }
280  t2 = odp_time_local_strict();
281  } else {
282  t1 = odp_time_local_strict();
283  while (pos < size) {
284  ret = odp_random_data(data + pos, size - pos, type);
285 
286  if (ret < 0) {
287  ODPH_ERR("odp_random_data() failed\n");
288  exit(EXIT_FAILURE);
289  }
290 
291  pos += ret;
292  }
293  t2 = odp_time_local_strict();
294  }
295 
296  nsec = odp_time_diff_ns(t2, t1);
297  sum += nsec;
298 
299  if (nsec > max)
300  max = nsec;
301  if (nsec < min)
302  min = nsec;
303  }
304 
305  nsec = odp_time_diff_ns(odp_time_local(), start);
306 
307  global->stat.nsec[thread_idx] = nsec;
308  global->stat.sum[thread_idx] = sum;
309  global->stat.min[thread_idx] = min;
310  global->stat.max[thread_idx] = max;
311 }
312 
313 static int test_random_latency(void *ptr)
314 {
315  thread_arg_t *thread_arg = ptr;
316  test_global_t *global = thread_arg->global;
317  odp_random_kind_t type = global->type;
318  int thread_idx = thread_arg->thread_idx;
319  uint8_t *data = thread_arg->data;
320  uint32_t size = options.size;
321  uint32_t rounds = global->rounds;
322 
323  /* One warm up round */
324  random_data_loop(type, 1, data, size);
325 
326  odp_barrier_wait(&global->barrier);
327 
328  /* Test run */
329  random_data_latency(global, thread_idx, rounds, data, size);
330 
331  return 0;
332 }
333 
334 static uint32_t type_rounds(test_global_t *global, odp_random_kind_t type)
335 {
336  uint32_t rounds = options.rounds;
337 
338  if (type == ODP_RANDOM_TRUE)
339  rounds /= 100;
340 
341  if (options.msec) {
342  /*
343  * Determine number of rounds to run based on the target test
344  * duration. Random data may be very fast or very slow, so
345  * start with just one round and increase exponentially until
346  * we hit a time limit. Then use the total number of rounds we
347  * reached to calculate a suitable number of rounds for the
348  * actual test.
349  */
350 
351  uint8_t *buf = (uint8_t *)malloc(options.size);
352 
353  if (!buf) {
354  ODPH_ERR("malloc() failed\n");
355  exit(EXIT_FAILURE);
356  }
357 
358  /* One warm up round */
359  random_data_loop(type, 1, buf, options.size);
360 
361  uint32_t r = 1, tr = 0;
362  odp_time_t time;
363  odp_time_t start_time = odp_time_local();
364  odp_time_t end_time = odp_time_add_ns(start_time, 100 * ODP_TIME_MSEC_IN_NS);
365 
366  while (1) {
367  if (options.mode)
368  random_data_latency(global, 0, r, buf, options.size);
369  else
370  random_data_loop(type, r, buf, options.size);
371  tr += r;
372  time = odp_time_local();
373  if (odp_time_cmp(time, end_time) >= 0)
374  break;
375  r *= 2;
376  }
377 
378  rounds = (uint64_t)tr * options.msec * ODP_TIME_MSEC_IN_NS /
379  odp_time_diff_ns(time, start_time);
380  free(buf);
381  }
382 
383  return ODPH_MAX(1u, rounds);
384 }
385 
386 static void test_type(odp_instance_t instance, test_global_t *global, odp_random_kind_t type)
387 {
388  int i;
389  uint32_t rounds;
390  int num_threads = options.num_threads;
391  uint32_t size = options.size;
392 
393  global->type = type;
394  rounds = type_rounds(global, type);
395 
396  memset(&global->stat, 0, sizeof(global->stat));
397  global->rounds = rounds;
398  odp_barrier_init(&global->barrier, num_threads);
399 
400  odp_cpumask_t cpumask;
401  odph_thread_common_param_t thr_common;
402  odph_thread_param_t thr_param[num_threads];
403  odph_thread_t thr_worker[num_threads];
404 
405  if (odp_cpumask_default_worker(&cpumask, num_threads) != num_threads) {
406  ODPH_ERR("Failed to get default CPU mask.\n");
407  exit(EXIT_FAILURE);
408  }
409 
410  odph_thread_common_param_init(&thr_common);
411  thr_common.instance = instance;
412  thr_common.cpumask = &cpumask;
413 
414  for (i = 0; i < num_threads; i++) {
415  odph_thread_param_init(&thr_param[i]);
416  thr_param[i].thr_type = ODP_THREAD_WORKER;
417  thr_param[i].arg = &global->thread_arg[i];
418 
419  if (options.mode == 0)
420  thr_param[i].start = test_random_perf;
421  else
422  thr_param[i].start = test_random_latency;
423  }
424 
425  memset(&thr_worker, 0, sizeof(thr_worker));
426 
427  if (odph_thread_create(thr_worker, &thr_common, thr_param, num_threads) != num_threads) {
428  ODPH_ERR("Failed to create worker threads.\n");
429  exit(EXIT_FAILURE);
430  }
431 
432  odph_thread_join_result_t res[num_threads];
433 
434  if (odph_thread_join_result(thr_worker, res, num_threads) != num_threads) {
435  ODPH_ERR("Failed to join worker threads.\n");
436  exit(EXIT_FAILURE);
437  }
438 
439  for (i = 0; i < num_threads; i++) {
440  if (res[i].is_sig || res[i].ret != 0) {
441  ODPH_ERR("Worker thread failure%s: %d\n", res[i].is_sig ?
442  " (signaled)" : "", res[i].ret);
443  exit(EXIT_FAILURE);
444  }
445  }
446 
447  double mb, seconds, nsec = 0;
448 
449  for (i = 0; i < num_threads; i++)
450  nsec += global->stat.nsec[i];
451 
452  nsec /= num_threads;
453 
454  switch (type) {
455  case ODP_RANDOM_BASIC:
456  printf("ODP_RANDOM_BASIC\n");
457  break;
458  case ODP_RANDOM_CRYPTO:
459  printf("ODP_RANDOM_CRYPTO\n");
460  break;
461  case ODP_RANDOM_TRUE:
462  printf("ODP_RANDOM_TRUE\n");
463  break;
464  default:
465  printf("odp_random_test_data\n");
466  }
467 
468  printf("--------------------\n");
469  printf("threads: %d size: %u B rounds: %u ", num_threads, size, rounds);
470  mb = (uint64_t)num_threads * (uint64_t)size * (uint64_t)rounds;
471  mb /= MB;
472  seconds = (double)nsec / (double)ODP_TIME_SEC_IN_NS;
473  printf("MB: %.3f seconds: %.3f ", mb, seconds);
474  printf("MB/s: %.3f ", mb / seconds);
475  printf("MB/s/thread: %.3f\n", mb / seconds / (double)num_threads);
476 
477  if (options.mode) {
478  double ave;
479  uint64_t min = UINT64_MAX;
480  uint64_t max = 0;
481  uint64_t sum = 0;
482 
483  printf(" latency (nsec)\n");
484  printf(" thread min max ave\n");
485  for (i = 0; i < num_threads; i++) {
486  ave = (double)global->stat.sum[i] / rounds;
487  sum += global->stat.sum[i];
488 
489  if (global->stat.min[i] < min)
490  min = global->stat.min[i];
491 
492  if (global->stat.max[i] > max)
493  max = global->stat.max[i];
494 
495  printf("%8i %8" PRIu64 " %8" PRIu64 " %10.1f\n", i, global->stat.min[i],
496  global->stat.max[i], ave);
497  }
498 
499  printf(" all %8" PRIu64 " %8" PRIu64 " %10.1f\n",
500  min, max, ((double)sum / rounds) / num_threads);
501  }
502 
503  printf("\n");
504 }
505 
506 int main(int argc, char **argv)
507 {
508  odph_helper_options_t helper_options;
509  odp_instance_t instance;
510  odp_init_t init;
511  odp_shm_t shm_glb, shm_data;
512  test_global_t *global;
513  int num_threads, i;
514  uint64_t tot_size, size;
515  uint8_t *addr;
516 
517  /* Let helper collect its own arguments (e.g. --odph_proc) */
518  argc = odph_parse_options(argc, argv);
519 
520  if (odph_options(&helper_options)) {
521  ODPH_ERR("Failed to read ODP helper options.\n");
522  exit(EXIT_FAILURE);
523  }
524 
525  if (parse_options(argc, argv))
526  exit(EXIT_FAILURE);
527 
528  /* List features not to be used */
529  odp_init_param_init(&init);
530  init.not_used.feat.cls = 1;
531  init.not_used.feat.compress = 1;
532  init.not_used.feat.crypto = 1;
533  init.not_used.feat.ipsec = 1;
534  init.not_used.feat.schedule = 1;
535  init.not_used.feat.stash = 1;
536  init.not_used.feat.timer = 1;
537  init.not_used.feat.tm = 1;
538  init.mem_model = helper_options.mem_model;
539 
540  /* Init ODP before calling anything else */
541  if (odp_init_global(&instance, &init, NULL)) {
542  ODPH_ERR("Global init failed.\n");
543  exit(EXIT_FAILURE);
544  }
545 
546  /* Init this thread */
547  if (odp_init_local(instance, ODP_THREAD_CONTROL)) {
548  ODPH_ERR("Local init failed.\n");
549  exit(EXIT_FAILURE);
550  }
551 
553 
554  global = NULL;
555  shm_glb = odp_shm_reserve("test_globals", sizeof(test_global_t), ODP_CACHE_LINE_SIZE, 0);
556 
557  if (shm_glb != ODP_SHM_INVALID)
558  global = (test_global_t *)odp_shm_addr(shm_glb);
559 
560  if (!global) {
561  ODPH_ERR("Failed to reserve shm\n");
562  exit(EXIT_FAILURE);
563  }
564 
565  memset(global, 0, sizeof(test_global_t));
566 
567  num_threads = options.num_threads;
568  addr = NULL;
569  size = ODP_CACHE_LINE_SIZE + ODP_CACHE_LINE_ROUNDUP(options.size);
570  tot_size = num_threads * size;
571  shm_data = odp_shm_reserve("test_data", tot_size, ODP_CACHE_LINE_SIZE, 0);
572 
573  if (shm_data != ODP_SHM_INVALID)
574  addr = odp_shm_addr(shm_data);
575 
576  if (!addr) {
577  ODPH_ERR("Failed to reserve shm: size %" PRIu64 " bytes\n", tot_size);
578  exit(EXIT_FAILURE);
579  }
580 
581  for (i = 0; i < num_threads; i++) {
582  global->thread_arg[i].global = global;
583  global->thread_arg[i].thread_idx = i;
584  global->thread_arg[i].data = addr + i * size;
585  }
586 
587  switch (odp_random_max_kind()) {
588  case ODP_RANDOM_TRUE:
589  test_type(instance, global, ODP_RANDOM_TRUE);
590  /* fall through */
591  case ODP_RANDOM_CRYPTO:
592  test_type(instance, global, ODP_RANDOM_CRYPTO);
593  /* fall through */
594  default:
595  test_type(instance, global, ODP_RANDOM_BASIC);
596  test_type(instance, global, PSEUDO_RANDOM);
597  }
598 
599  if (odp_shm_free(shm_data)) {
600  ODPH_ERR("odp_shm_free() failed\n");
601  exit(EXIT_FAILURE);
602  }
603 
604  if (odp_shm_free(shm_glb)) {
605  ODPH_ERR("odp_shm_free() failed\n");
606  exit(EXIT_FAILURE);
607  }
608 
609  if (odp_term_local()) {
610  ODPH_ERR("Local terminate failed.\n");
611  exit(EXIT_FAILURE);
612  }
613 
614  if (odp_term_global(instance)) {
615  ODPH_ERR("Global terminate failed.\n");
616  exit(EXIT_FAILURE);
617  }
618 
619  return 0;
620 }
void odp_barrier_init(odp_barrier_t *barr, int count)
Initialize barrier with thread count.
void odp_barrier_wait(odp_barrier_t *barr)
Synchronize thread execution on barrier.
#define ODP_CACHE_LINE_ROUNDUP(x)
Round up to cache line size.
int odp_cpumask_default_worker(odp_cpumask_t *mask, int num)
Default CPU mask for worker threads.
void odp_init_param_init(odp_init_t *param)
Initialize the odp_init_t to default values for all fields.
int odp_init_local(odp_instance_t instance, odp_thread_type_t thr_type)
Thread local ODP initialization.
int odp_init_global(odp_instance_t *instance, const odp_init_t *params, const odp_platform_init_t *platform_params)
Global ODP initialization.
int odp_term_local(void)
Thread local ODP termination.
int odp_term_global(odp_instance_t instance)
Global ODP termination.
uint64_t odp_instance_t
ODP instance ID.
odp_random_kind_t odp_random_max_kind(void)
Query random max kind.
int32_t odp_random_data(uint8_t *buf, uint32_t len, odp_random_kind_t kind)
Generate random byte data.
odp_random_kind_t
Random kind selector.
int32_t odp_random_test_data(uint8_t *buf, uint32_t len, uint64_t *seed)
Generate repeatable random data for testing purposes.
@ ODP_RANDOM_BASIC
Basic random, presumably pseudo-random generated by SW.
@ ODP_RANDOM_TRUE
True random, generated from a HW entropy source.
@ ODP_RANDOM_CRYPTO
Cryptographic quality random.
int odp_shm_free(odp_shm_t shm)
Free a contiguous block of shared memory.
#define ODP_SHM_INVALID
Invalid shared memory block.
void * odp_shm_addr(odp_shm_t shm)
Shared memory block address.
odp_shm_t odp_shm_reserve(const char *name, uint64_t size, uint64_t align, uint32_t flags)
Reserve a contiguous block of shared memory.
void odp_sys_info_print(void)
Print system info.
#define ODP_THREAD_COUNT_MAX
Maximum number of threads supported in build time.
@ ODP_THREAD_WORKER
Worker thread.
@ ODP_THREAD_CONTROL
Control thread.
#define ODP_TIME_SEC_IN_NS
A second in nanoseconds.
void odp_time_wait_ns(uint64_t ns)
Wait the specified number of nanoseconds.
odp_time_t odp_time_local(void)
Current local time.
odp_time_t odp_time_add_ns(odp_time_t time, uint64_t ns)
Add nanoseconds into time.
#define ODP_TIME_MSEC_IN_NS
A millisecond in nanoseconds.
odp_time_t odp_time_local_strict(void)
Current local time (strict)
int odp_time_cmp(odp_time_t t2, odp_time_t t1)
Compare two times.
uint64_t odp_time_diff_ns(odp_time_t t2, odp_time_t t1)
Time difference in nanoseconds.
The OpenDataPlane API.
Global initialization parameters.
odp_mem_model_t mem_model
Application memory model.
odp_feature_t not_used
Unused features.
uint32_t tm
Traffic Manager APIs, e.g., odp_tm_xxx()
uint32_t stash
Stash APIs, e.g., odp_stash_xxx()
uint32_t crypto
Crypto APIs, e.g., odp_crypto_xxx()
uint32_t ipsec
IPsec APIs, e.g., odp_ipsec_xxx()
uint32_t timer
Timer APIs, e.g., odp_timer_xxx(), odp_timeout_xxx()
uint32_t cls
Classifier APIs, e.g., odp_cls_xxx(), odp_cos_xxx()
uint32_t schedule
Scheduler APIs, e.g., odp_schedule_xxx()
struct odp_feature_t::@148 feat
Individual feature bits.
uint32_t compress
Compression APIs, e.g., odp_comp_xxx()