/* Copyright (c) 2023, Nokia * All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #ifndef _GNU_SOURCE #define _GNU_SOURCE /* Needed for sigaction */ #endif #include #include #include #include #include #include #include /* Number of API function calls per test case */ #define REPEAT_COUNT 1000 /* Default number of rounds per test case */ #define ROUNDS 1000u /** User area size in bytes */ #define UAREA_SIZE 8 /** Timer duration in nsec */ #define TIMER_NSEC 50000000 #define BENCH_INFO(run, max, name) \ {#run, run, max, name} /* Run benchmark, returns >0 on success */ typedef int (*bench_run_fn_t)(void); /* Benchmark data */ typedef struct { /* Default test name */ const char *name; /* Test function to run */ bench_run_fn_t run; /* Test specific limit for rounds (tuning for slow implementation) */ uint32_t max_rounds; /* Override default test name */ const char *desc; } bench_info_t; typedef struct { /* Command line options */ struct { /* Clock source to be used */ int clk_src; /* Measure time vs CPU cycles */ int time; /* Benchmark index to run indefinitely */ int bench_idx; /* Rounds per test case */ uint32_t rounds; } opt; odp_timer_pool_t timer_pool; odp_timer_t timer; odp_queue_t queue; odp_pool_t pool; odp_timeout_t timeout; odp_event_t event; uint64_t timer_nsec; uint64_t tick; uint64_t nsec; double tick_hz; int plain_queue; /* Benchmark functions */ bench_info_t *bench; /* Number of benchmark functions */ int num_bench; /* Break worker loop if set to 1 */ odp_atomic_u32_t exit_thread; /* Test case input / output data */ uint64_t a1[REPEAT_COUNT]; odp_event_t ev[REPEAT_COUNT]; odp_timeout_t tmo[REPEAT_COUNT]; odp_timer_t tim[REPEAT_COUNT]; /* Dummy result */ uint64_t dummy; /* Benchmark run failed */ int bench_failed; /* CPU mask as string */ char cpumask_str[ODP_CPUMASK_STR_SIZE]; } gbl_args_t; static gbl_args_t *gbl_args; static void sig_handler(int signo ODP_UNUSED) { if (gbl_args == NULL) return; odp_atomic_store_u32(&gbl_args->exit_thread, 1); } static int setup_sig_handler(void) { struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = sig_handler; /* No additional signals blocked. By default, the signal which triggered * the handler is blocked. */ if (sigemptyset(&action.sa_mask)) return -1; if (sigaction(SIGINT, &action, NULL)) return -1; return 0; } /* Run given benchmark indefinitely */ static void run_indef(gbl_args_t *args, int idx) { const char *desc; const bench_info_t *bench = &args->bench[idx]; desc = bench->desc != NULL ? bench->desc : bench->name; printf("Running odp_%s test indefinitely\n", desc); while (!odp_atomic_load_u32(&gbl_args->exit_thread)) { int ret; ret = bench->run(); if (!ret) ODPH_ABORT("Benchmark %s failed\n", desc); } } static int run_benchmarks(void *arg) { int i, j; uint64_t c1, c2; odp_time_t t1, t2; gbl_args_t *args = arg; const int meas_time = args->opt.time; printf("\nAverage %s per function call\n", meas_time ? "time (nsec)" : "CPU cycles"); printf("-------------------------------------------------\n"); /* Run each test twice. Results from the first warm-up round are ignored. */ for (i = 0; i < 2; i++) { uint64_t total = 0; uint32_t round = 1; for (j = 0; j < gbl_args->num_bench; round++) { int ret; const char *desc; const bench_info_t *bench = &args->bench[j]; uint32_t max_rounds = args->opt.rounds; if (bench->max_rounds && max_rounds > bench->max_rounds) max_rounds = bench->max_rounds; /* Run selected test indefinitely */ if (args->opt.bench_idx) { if ((j + 1) != args->opt.bench_idx) { j++; continue; } run_indef(args, j); return 0; } desc = bench->desc != NULL ? bench->desc : bench->name; if (meas_time) t1 = odp_time_local(); else c1 = odp_cpu_cycles(); ret = bench->run(); if (meas_time) t2 = odp_time_local(); else c2 = odp_cpu_cycles(); if (!ret) { ODPH_ERR("Benchmark odp_%s failed\n", desc); args->bench_failed = -1; return -1; } if (meas_time) total += odp_time_diff_ns(t2, t1); else total += odp_cpu_cycles_diff(c2, c1); for (i = 0; i < REPEAT_COUNT; i++) args->dummy += args->a1[i]; if (round >= max_rounds) { double result; /* Each benchmark runs internally REPEAT_COUNT times. */ result = ((double)total) / (max_rounds * REPEAT_COUNT); /* No print from warm-up round */ if (i > 0) printf("[%02d] odp_%-26s: %12.2f\n", j + 1, desc, result); j++; total = 0; round = 1; } } } /* Print dummy result to prevent compiler to optimize it away*/ printf("\n(dummy result: 0x%" PRIx64 ")\n\n", args->dummy); return 0; } static int timer_current_tick(void) { int i; odp_timer_pool_t timer_pool = gbl_args->timer_pool; uint64_t *a1 = gbl_args->a1; for (i = 0; i < REPEAT_COUNT; i++) a1[i] = odp_timer_current_tick(timer_pool); return i; } static int timer_tick_to_ns(void) { int i; odp_timer_pool_t timer_pool = gbl_args->timer_pool; uint64_t *a1 = gbl_args->a1; uint64_t tick = gbl_args->tick; for (i = 0; i < REPEAT_COUNT; i++) a1[i] = odp_timer_tick_to_ns(timer_pool, tick); return i; } static int timer_ns_to_tick(void) { int i; odp_timer_pool_t timer_pool = gbl_args->timer_pool; uint64_t *a1 = gbl_args->a1; uint64_t nsec = gbl_args->nsec; for (i = 0; i < REPEAT_COUNT; i++) a1[i] = odp_timer_ns_to_tick(timer_pool, nsec); return i; } static int timeout_to_event(void) { int i; odp_event_t *ev = gbl_args->ev; odp_timeout_t timeout = gbl_args->timeout; for (i = 0; i < REPEAT_COUNT; i++) ev[i] = odp_timeout_to_event(timeout); gbl_args->dummy += odp_event_to_u64(ev[0]); return i; } static int timeout_from_event(void) { int i; odp_event_t ev = gbl_args->event; odp_timeout_t *tmo = gbl_args->tmo; for (i = 0; i < REPEAT_COUNT; i++) tmo[i] = odp_timeout_from_event(ev); gbl_args->dummy += odp_timeout_to_u64(tmo[0]); return i; } static int timeout_fresh(void) { int i; odp_timeout_t timeout = gbl_args->timeout; uint64_t *a1 = gbl_args->a1; for (i = 0; i < REPEAT_COUNT; i++) a1[i] = odp_timeout_fresh(timeout); return i; } static int timeout_timer(void) { int i; odp_timeout_t timeout = gbl_args->timeout; odp_timer_t *tim = gbl_args->tim; for (i = 0; i < REPEAT_COUNT; i++) tim[i] = odp_timeout_timer(timeout); gbl_args->dummy += odp_timer_to_u64(tim[0]); return i; } static int timeout_tick(void) { int i; odp_timeout_t timeout = gbl_args->timeout; uint64_t *a1 = gbl_args->a1; for (i = 0; i < REPEAT_COUNT; i++) a1[i] = odp_timeout_tick(timeout); return i; } static int timeout_user_ptr(void) { int i; odp_timeout_t timeout = gbl_args->timeout; uint64_t *a1 = gbl_args->a1; for (i = 0; i < REPEAT_COUNT; i++) a1[i] = (uintptr_t)odp_timeout_user_ptr(timeout); return i; } static int timeout_user_area(void) { int i; odp_timeout_t timeout = gbl_args->timeout; uint64_t *a1 = gbl_args->a1; for (i = 0; i < REPEAT_COUNT; i++) a1[i] = (uintptr_t)odp_timeout_user_area(timeout); return i; } static int timeout_to_u64(void) { int i; odp_timeout_t timeout = gbl_args->timeout; uint64_t *a1 = gbl_args->a1; for (i = 0; i < REPEAT_COUNT; i++) a1[i] = odp_timeout_to_u64(timeout); return i; } static int timer_to_u64(void) { int i; odp_timer_t timer = gbl_args->timer; uint64_t *a1 = gbl_args->a1; for (i = 0; i < REPEAT_COUNT; i++) a1[i] = odp_timer_to_u64(timer); return i; } static int timer_pool_to_u64(void) { int i; odp_timer_pool_t tp = gbl_args->timer_pool; uint64_t *a1 = gbl_args->a1; for (i = 0; i < REPEAT_COUNT; i++) a1[i] = odp_timer_pool_to_u64(tp); return i; } bench_info_t test_suite[] = { BENCH_INFO(timer_current_tick, 0, NULL), BENCH_INFO(timer_tick_to_ns, 0, NULL), BENCH_INFO(timer_ns_to_tick, 0, NULL), BENCH_INFO(timeout_to_event, 0, NULL), BENCH_INFO(timeout_from_event, 0, NULL), BENCH_INFO(timeout_fresh, 0, NULL), BENCH_INFO(timeout_timer, 0, NULL), BENCH_INFO(timeout_tick, 0, NULL), BENCH_INFO(timeout_user_ptr, 0, NULL), BENCH_INFO(timeout_user_area, 0, NULL), BENCH_INFO(timeout_to_u64, 0, NULL), BENCH_INFO(timer_to_u64, 0, NULL), BENCH_INFO(timer_pool_to_u64, 0, NULL), }; /* Print usage information */ static void usage(void) { printf("\n" "ODP timer API micro benchmarks\n" "\n" "Options:\n" " -s, --clk_src Clock source select (default 0):\n" " 0: ODP_CLOCK_DEFAULT\n" " 1: ODP_CLOCK_SRC_1, ...\n" " -t, --time Time measurement. 0: measure CPU cycles (default), 1: measure time\n" " -i, --index Benchmark index to run indefinitely.\n" " -r, --rounds Run each test case 'num' times (default %u).\n" " -h, --help Display help and exit.\n\n" "\n", ROUNDS); } /* Parse command line arguments */ static int parse_args(int argc, char *argv[]) { int opt; int long_index; static const struct option longopts[] = { {"clk_src", required_argument, NULL, 's'}, {"time", required_argument, NULL, 't'}, {"index", required_argument, NULL, 'i'}, {"rounds", required_argument, NULL, 'r'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; static const char *shortopts = "s:t:i:r:h"; gbl_args->opt.clk_src = ODP_CLOCK_DEFAULT; gbl_args->opt.time = 0; /* Measure CPU cycles */ gbl_args->opt.bench_idx = 0; /* Run all benchmarks */ gbl_args->opt.rounds = ROUNDS; while (1) { opt = getopt_long(argc, argv, shortopts, longopts, &long_index); if (opt == -1) break; /* No more options */ switch (opt) { case 's': gbl_args->opt.clk_src = atoi(optarg); break; case 't': gbl_args->opt.time = atoi(optarg); break; case 'i': gbl_args->opt.bench_idx = atoi(optarg); break; case 'r': gbl_args->opt.rounds = atoi(optarg); break; case 'h': usage(); return 1; default: ODPH_ERR("Bad option. Use -h for help.\n"); return -1; } } if (gbl_args->opt.rounds < 1) { ODPH_ERR("Invalid test cycle repeat count: %u\n", gbl_args->opt.rounds); return -1; } if (gbl_args->opt.bench_idx < 0 || gbl_args->opt.bench_idx > gbl_args->num_bench) { ODPH_ERR("Bad bench index %i\n", gbl_args->opt.bench_idx); return -1; } optind = 1; /* Reset 'extern optind' from the getopt lib */ return 0; } /* Print system and application info */ static void print_info(void) { odp_sys_info_print(); printf("\n" "odp_bench_timer options\n" "-----------------------\n"); printf("CPU mask: %s\n", gbl_args->cpumask_str); printf("Clock source: %i\n", gbl_args->opt.clk_src); printf("Measurement unit: %s\n", gbl_args->opt.time ? "nsec" : "CPU cycles"); printf("Test rounds: %u\n", gbl_args->opt.rounds); printf("Timer duration: %" PRIu64 " nsec\n", gbl_args->timer_nsec); printf("Timer tick freq: %.2f Hz\n", gbl_args->tick_hz); printf("\n"); } static int create_timer(void) { odp_pool_capability_t pool_capa; odp_timer_capability_t timer_capa; odp_timer_clk_src_t clk_src; odp_timer_pool_param_t tp_param; odp_timer_pool_t tp; odp_pool_t pool; odp_pool_param_t pool_param; odp_timeout_t tmo; odp_queue_param_t queue_param; odp_queue_t queue; odp_timer_t timer; uint64_t t1, t2, diff, tick1, tick2; if (odp_pool_capability(&pool_capa)) { ODPH_ERR("Pool capa failed\n"); return -1; } clk_src = gbl_args->opt.clk_src; if (odp_timer_capability(clk_src, &timer_capa)) { ODPH_ERR("Timer capa failed\n"); return -1; } odp_timer_pool_param_init(&tp_param); tp_param.clk_src = clk_src; tp_param.res_ns = timer_capa.max_res.res_ns; tp_param.min_tmo = timer_capa.max_res.min_tmo; tp_param.max_tmo = timer_capa.max_res.max_tmo; tp_param.num_timers = 10; tp = odp_timer_pool_create("bench_timer", &tp_param); if (tp == ODP_TIMER_POOL_INVALID) { ODPH_ERR("Timer pool create failed\n"); return -1; } gbl_args->timer_pool = tp; odp_timer_pool_start(); gbl_args->timer_nsec = TIMER_NSEC; if (TIMER_NSEC < tp_param.min_tmo) gbl_args->timer_nsec = tp_param.min_tmo; else if (TIMER_NSEC > tp_param.max_tmo) gbl_args->timer_nsec = tp_param.max_tmo; odp_pool_param_init(&pool_param); pool_param.type = ODP_POOL_TIMEOUT; pool_param.tmo.num = 10; pool_param.tmo.uarea_size = UAREA_SIZE; if (UAREA_SIZE > pool_capa.tmo.max_uarea_size) pool_param.tmo.uarea_size = pool_capa.tmo.max_uarea_size; pool = odp_pool_create("bench_timer", &pool_param); if (pool == ODP_POOL_INVALID) { ODPH_ERR("Timeout pool create failed\n"); return -1; } gbl_args->pool = pool; tmo = odp_timeout_alloc(pool); if (tmo == ODP_TIMEOUT_INVALID) { ODPH_ERR("Timeout alloc failed\n"); return -1; } gbl_args->timeout = tmo; gbl_args->tick = odp_timer_current_tick(tp); gbl_args->nsec = odp_timer_tick_to_ns(tp, gbl_args->tick); /* Measure timer tick frequency for test information */ t1 = odp_time_global_strict_ns(); tick1 = odp_timer_current_tick(tp); odp_time_wait_ns(200 * ODP_TIME_MSEC_IN_NS); tick2 = odp_timer_current_tick(tp); t2 = odp_time_global_strict_ns(); diff = t2 - t1; if (diff) gbl_args->tick_hz = (tick2 - tick1) / ((double)diff / ODP_TIME_SEC_IN_NS); odp_queue_param_init(&queue_param); queue_param.type = ODP_QUEUE_TYPE_SCHED; queue_param.sched.prio = odp_schedule_default_prio(); queue_param.sched.sync = ODP_SCHED_SYNC_ATOMIC; queue_param.sched.group = ODP_SCHED_GROUP_ALL; if (timer_capa.queue_type_sched == 0) { queue_param.type = ODP_QUEUE_TYPE_PLAIN; gbl_args->plain_queue = 1; } queue = odp_queue_create("bench_timer", &queue_param); if (queue == ODP_QUEUE_INVALID) { ODPH_ERR("Queue create failed\n"); return -1; } gbl_args->queue = queue; timer = odp_timer_alloc(tp, queue, (void *)(uintptr_t)0xdeadbeef); if (timer == ODP_TIMER_INVALID) { ODPH_ERR("Timer alloc failed\n"); return -1; } gbl_args->timer = timer; return 0; } static int wait_timer(void) { odp_timer_start_t start_param; odp_timer_t timer = gbl_args->timer; odp_timer_pool_t tp = gbl_args->timer_pool; uint64_t wait_nsec = 2 * gbl_args->timer_nsec; uint64_t sched_wait = odp_schedule_wait_time(wait_nsec); odp_event_t ev; uint64_t start; start_param.tick_type = ODP_TIMER_TICK_REL; start_param.tick = odp_timer_ns_to_tick(tp, gbl_args->timer_nsec); start_param.tmo_ev = odp_timeout_to_event(gbl_args->timeout); if (odp_timer_start(timer, &start_param) != ODP_TIMER_SUCCESS) { ODPH_ERR("Timer start failed\n"); return -1; } gbl_args->timeout = ODP_TIMEOUT_INVALID; gbl_args->event = ODP_EVENT_INVALID; /* Wait for timeout */ if (gbl_args->plain_queue) { start = odp_time_global_ns(); while (1) { ev = odp_queue_deq(gbl_args->queue); if (ev != ODP_EVENT_INVALID) break; if ((odp_time_global_ns() - start) > wait_nsec) { ODPH_ERR("Timeout event missing\n"); return -1; } } gbl_args->event = ev; } else { ev = odp_schedule(NULL, sched_wait); if (ev == ODP_EVENT_INVALID) { ODPH_ERR("Timeout event missing\n"); return -1; } gbl_args->event = ev; /* Free schedule context */ if (odp_schedule(NULL, ODP_SCHED_NO_WAIT) != ODP_EVENT_INVALID) { ODPH_ERR("Extra timeout event\n"); return -1; } } if (odp_event_type(gbl_args->event) != ODP_EVENT_TIMEOUT) { ODPH_ERR("Bad event type\n"); return -1; } gbl_args->timeout = odp_timeout_from_event(gbl_args->event); return 0; } int main(int argc, char *argv[]) { odph_helper_options_t helper_options; odph_thread_t worker_thread; odph_thread_common_param_t thr_common; odph_thread_param_t thr_param; int cpu, i; odp_shm_t shm; odp_cpumask_t cpumask, default_mask; odp_instance_t instance; odp_init_t init_param; int ret = 0; /* Let helper collect its own arguments (e.g. --odph_proc) */ argc = odph_parse_options(argc, argv); if (odph_options(&helper_options)) { ODPH_ERR("Reading ODP helper options failed\n"); exit(EXIT_FAILURE); } odp_init_param_init(&init_param); init_param.mem_model = helper_options.mem_model; /* Init ODP before calling anything else */ if (odp_init_global(&instance, &init_param, NULL)) { ODPH_ERR("Global init failed\n"); exit(EXIT_FAILURE); } /* Init this thread */ if (odp_init_local(instance, ODP_THREAD_CONTROL)) { ODPH_ERR("Local init failed\n"); exit(EXIT_FAILURE); } if (setup_sig_handler()) { ODPH_ERR("Signal handler setup failed\n"); exit(EXIT_FAILURE); } odp_schedule_config(NULL); /* Reserve memory for args from shared mem */ shm = odp_shm_reserve("shm_args", sizeof(gbl_args_t), ODP_CACHE_LINE_SIZE, 0); if (shm == ODP_SHM_INVALID) { ODPH_ERR("Shared mem reserve failed\n"); exit(EXIT_FAILURE); } gbl_args = odp_shm_addr(shm); if (gbl_args == NULL) { ODPH_ERR("Shared mem alloc failed\n"); exit(EXIT_FAILURE); } memset(gbl_args, 0, sizeof(gbl_args_t)); odp_atomic_init_u32(&gbl_args->exit_thread, 0); gbl_args->timer_pool = ODP_TIMER_POOL_INVALID; gbl_args->timer = ODP_TIMER_INVALID; gbl_args->queue = ODP_QUEUE_INVALID; gbl_args->pool = ODP_POOL_INVALID; gbl_args->timeout = ODP_TIMEOUT_INVALID; gbl_args->bench = test_suite; gbl_args->num_bench = sizeof(test_suite) / sizeof(test_suite[0]); for (i = 0; i < REPEAT_COUNT; i++) { gbl_args->a1[i] = i; gbl_args->ev[i] = ODP_EVENT_INVALID; gbl_args->tmo[i] = ODP_TIMEOUT_INVALID; gbl_args->tim[i] = ODP_TIMER_INVALID; } /* Parse and store the application arguments */ ret = parse_args(argc, argv); if (ret) goto exit; /* Get default worker cpumask */ if (odp_cpumask_default_worker(&default_mask, 1) != 1) { ODPH_ERR("Unable to allocate worker thread\n"); ret = -1; goto exit; } (void)odp_cpumask_to_str(&default_mask, gbl_args->cpumask_str, sizeof(gbl_args->cpumask_str)); /* Create timer and other resources */ ret = create_timer(); if (ret) goto exit; print_info(); /* Start one timer and wait for the timeout event. Timer expiration fills in * timeout event metadata. */ ret = wait_timer(); if (ret) goto exit; memset(&worker_thread, 0, sizeof(odph_thread_t)); /* Create worker thread */ cpu = odp_cpumask_first(&default_mask); odp_cpumask_zero(&cpumask); odp_cpumask_set(&cpumask, cpu); odph_thread_common_param_init(&thr_common); thr_common.instance = instance; thr_common.cpumask = &cpumask; thr_common.share_param = 1; odph_thread_param_init(&thr_param); thr_param.start = run_benchmarks; thr_param.arg = gbl_args; thr_param.thr_type = ODP_THREAD_WORKER; odph_thread_create(&worker_thread, &thr_common, &thr_param, 1); odph_thread_join(&worker_thread, 1); ret = gbl_args->bench_failed; exit: if (gbl_args->timeout != ODP_TIMEOUT_INVALID) odp_timeout_free(gbl_args->timeout); if (gbl_args->pool != ODP_POOL_INVALID) odp_pool_destroy(gbl_args->pool); if (gbl_args->timer != ODP_TIMER_INVALID) odp_timer_free(gbl_args->timer); if (gbl_args->timer_pool != ODP_TIMER_POOL_INVALID) odp_timer_pool_destroy(gbl_args->timer_pool); if (gbl_args->queue != ODP_QUEUE_INVALID) { if (odp_queue_destroy(gbl_args->queue)) { ODPH_ERR("Queue destroy failed\n"); exit(EXIT_FAILURE); } } if (odp_shm_free(shm)) { ODPH_ERR("Shared mem free failed\n"); exit(EXIT_FAILURE); } if (odp_term_local()) { ODPH_ERR("Local term failed\n"); exit(EXIT_FAILURE); } if (odp_term_global(instance)) { ODPH_ERR("Global term failed\n"); exit(EXIT_FAILURE); } if (ret < 0) return EXIT_FAILURE; return EXIT_SUCCESS; }