diff options
Diffstat (limited to 'test/performance/odp_timer_accuracy.c')
-rw-r--r-- | test/performance/odp_timer_accuracy.c | 1438 |
1 files changed, 1438 insertions, 0 deletions
diff --git a/test/performance/odp_timer_accuracy.c b/test/performance/odp_timer_accuracy.c new file mode 100644 index 000000000..a663c894a --- /dev/null +++ b/test/performance/odp_timer_accuracy.c @@ -0,0 +1,1438 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) 2018 Linaro Limited + * Copyright (c) 2019-2023 Nokia + */ + +/** + * @example odp_timer_accuracy.c + * + * ODP timer accuracy test application + * + * @cond _ODP_HIDE_FROM_DOXYGEN_ + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include <stdlib.h> + +#include <unistd.h> +#include <getopt.h> + +#include <odp_api.h> +#include <odp/helper/odph_api.h> + +#define MAX_WORKERS (ODP_THREAD_COUNT_MAX - 1) +#define MAX_QUEUES 1024 +#define MAX_FILENAME 128 + +enum mode_e { + MODE_ONESHOT = 0, + MODE_RESTART_ABS, + MODE_RESTART_REL, + MODE_PERIODIC, +}; + +typedef struct test_opt_t { + int cpu_count; + unsigned long long period_ns; + long long res_ns; + unsigned long long res_hz; + unsigned long long offset_ns; + unsigned long long max_tmo_ns; + unsigned long long num; + unsigned long long num_warmup; + unsigned long long burst; + unsigned long long burst_gap; + odp_fract_u64_t freq; + unsigned long long max_multiplier; + unsigned long long multiplier; + enum mode_e mode; + int clk_src; + odp_queue_type_t queue_type; + int num_queue; + int groups; + int init; + int output; + int early_retry; + uint64_t warmup_timers; + uint64_t tot_timers; + uint64_t alloc_timers; + char filename[MAX_FILENAME]; +} test_opt_t; + +typedef struct timer_ctx_t { + odp_timer_t timer; + odp_event_t event; + uint64_t nsec; + uint64_t count; + uint64_t first_period; + int tmo_tick; + int64_t first_tmo_diff; + int64_t nsec_final; + +} timer_ctx_t; + +typedef struct { + uint64_t nsec_before_sum; + uint64_t nsec_before_min; + uint64_t nsec_before_min_idx; + uint64_t nsec_before_max; + uint64_t nsec_before_max_idx; + + uint64_t nsec_after_sum; + uint64_t nsec_after_min; + uint64_t nsec_after_min_idx; + uint64_t nsec_after_max; + uint64_t nsec_after_max_idx; + + uint64_t num_before; + uint64_t num_exact; + uint64_t num_after; + + uint64_t num_too_near; + +} test_stat_t; + +typedef struct test_log_t { + uint64_t tmo_ns; + int64_t diff_ns; + int tid; + +} test_log_t; + +typedef struct test_global_t { + test_opt_t opt; + + test_stat_t stat[MAX_WORKERS]; + + odp_queue_t queue[MAX_QUEUES]; + odp_schedule_group_t group[MAX_WORKERS]; + odp_timer_pool_t timer_pool; + odp_pool_t timeout_pool; + timer_ctx_t *timer_ctx; + double res_ns; + uint64_t start_tick; + uint64_t start_ns; + uint64_t period_tick; + double period_dbl; + odp_fract_u64_t base_freq; + test_log_t *log; + FILE *file; + odp_barrier_t barrier; + odp_atomic_u64_t events; + odp_atomic_u64_t last_events; + +} test_global_t; + +static void print_usage(void) +{ + printf("\n" + "Timer accuracy test application.\n" + "\n" + "OPTIONS:\n" + " -c, --count <num> CPU count, 0=all available, default=1\n" + " -p, --period <nsec> Timeout period in nsec. Not used in periodic mode. Default: 200 msec\n" + " -r, --res_ns <nsec> Timeout resolution in nsec. Default value is 0. Special values:\n" + " 0: Use period / 10 as the resolution\n" + " -1: In periodic mode, use resolution from capabilities\n" + " -R, --res_hz <hertz> Timeout resolution in hertz. Set resolution either with -r (nsec) or -R (hertz),\n" + " and leave other to 0. Default: 0 (not used)\n" + " -f, --first <nsec> First timer offset in nsec. Default: 0 for periodic mode, otherwise 300 msec\n" + " -x, --max_tmo <nsec> Maximum timeout in nsec. Not used in periodic mode.\n" + " When 0, max tmo is calculated from other options. Default: 0\n" + " -n, --num <number> Number of timeout periods. Default: 50\n" + " -w, --warmup <number> Number of warmup periods. Default: 0\n" + " -b, --burst <number> Number of timers per a timeout period. Default: 1\n" + " -g, --burst_gap <nsec> Gap (in nsec) between timers within a burst. Default: 0\n" + " In periodic mode, first + burst * burst_gap must be less than period length.\n" + " -m, --mode <number> Test mode select (default: 0):\n" + " 0: One-shot. Start all timers at init phase.\n" + " 1: One-shot. Each period, restart timers with absolute time.\n" + " 2: One-shot. Each period, restart timers with relative time.\n" + " 3: Periodic.\n" + " -P, --periodic <freq_integer:freq_numer:freq_denom:max_multiplier>\n" + " Periodic timer pool parameters. Default: 5:0:0:1 (5 Hz)\n" + " -M, --multiplier Periodic timer multiplier. Default: 1\n" + " -o, --output <file> Output file for measurement logs\n" + " -e, --early_retry <num> When timer restart fails due to ODP_TIMER_TOO_NEAR, retry this many times\n" + " with expiration time incremented by the period. Default: 0\n" + " -s, --clk_src Clock source select (default 0):\n" + " 0: ODP_CLOCK_DEFAULT\n" + " 1: ODP_CLOCK_SRC_1, ...\n" + " -t, --queue_type Queue sync type. Default is 0 (PARALLEL).\n" + " 0: PARALLEL\n" + " 1: ATOMIC\n" + " 2: ORDERED\n" + " -q, --num_queue Number of queues. Default is 1.\n" + " -G, --sched_groups Use dedicated schedule group for each worker.\n" + " -i, --init Set global init parameters. Default: init params not set.\n" + " -h, --help Display help and exit.\n\n"); +} + +static int parse_options(int argc, char *argv[], test_opt_t *test_opt) +{ + int opt, long_index; + const struct option longopts[] = { + {"count", required_argument, NULL, 'c'}, + {"period", required_argument, NULL, 'p'}, + {"res_ns", required_argument, NULL, 'r'}, + {"res_hz", required_argument, NULL, 'R'}, + {"first", required_argument, NULL, 'f'}, + {"max_tmo", required_argument, NULL, 'x'}, + {"num", required_argument, NULL, 'n'}, + {"warmup", required_argument, NULL, 'w'}, + {"burst", required_argument, NULL, 'b'}, + {"burst_gap", required_argument, NULL, 'g'}, + {"mode", required_argument, NULL, 'm'}, + {"periodic", required_argument, NULL, 'P'}, + {"multiplier", required_argument, NULL, 'M'}, + {"output", required_argument, NULL, 'o'}, + {"early_retry", required_argument, NULL, 'e'}, + {"clk_src", required_argument, NULL, 's'}, + {"queue_type", required_argument, NULL, 't'}, + {"num_queue", required_argument, NULL, 'q'}, + {"sched_groups", no_argument, NULL, 'G'}, + {"init", no_argument, NULL, 'i'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + const char *shortopts = "+c:p:r:R:f:x:n:w:b:g:m:P:M:o:e:s:t:q:Gih"; + int ret = 0; + + memset(test_opt, 0, sizeof(*test_opt)); + + test_opt->cpu_count = 1; + test_opt->period_ns = 200 * ODP_TIME_MSEC_IN_NS; + test_opt->res_ns = 0; + test_opt->res_hz = 0; + test_opt->offset_ns = UINT64_MAX; + test_opt->max_tmo_ns = 0; + test_opt->num = 50; + test_opt->num_warmup = 0; + test_opt->burst = 1; + test_opt->burst_gap = 0; + test_opt->mode = MODE_ONESHOT; + test_opt->freq.integer = ODP_TIME_SEC_IN_NS / test_opt->period_ns; + test_opt->freq.numer = 0; + test_opt->freq.denom = 0; + test_opt->max_multiplier = 1; + test_opt->multiplier = 1; + test_opt->clk_src = ODP_CLOCK_DEFAULT; + test_opt->queue_type = ODP_SCHED_SYNC_PARALLEL; + test_opt->groups = 0; + test_opt->num_queue = 1; + test_opt->init = 0; + test_opt->output = 0; + test_opt->early_retry = 0; + + while (1) { + opt = getopt_long(argc, argv, shortopts, longopts, &long_index); + + if (opt == -1) + break; /* No more options */ + + switch (opt) { + case 'c': + test_opt->cpu_count = atoi(optarg); + break; + case 'p': + test_opt->period_ns = strtoull(optarg, NULL, 0); + break; + case 'r': + test_opt->res_ns = strtoll(optarg, NULL, 0); + break; + case 'R': + test_opt->res_hz = strtoull(optarg, NULL, 0); + break; + case 'f': + test_opt->offset_ns = strtoull(optarg, NULL, 0); + break; + case 'x': + test_opt->max_tmo_ns = strtoull(optarg, NULL, 0); + break; + case 'n': + test_opt->num = strtoull(optarg, NULL, 0); + break; + case 'w': + test_opt->num_warmup = strtoull(optarg, NULL, 0); + break; + case 'b': + test_opt->burst = strtoull(optarg, NULL, 0); + break; + case 'g': + test_opt->burst_gap = strtoull(optarg, NULL, 0); + break; + case 'm': + test_opt->mode = atoi(optarg); + break; + case 'P': + sscanf(optarg, "%" SCNu64 ":%" SCNu64 ":%" SCNu64 ":%llu", + &test_opt->freq.integer, &test_opt->freq.numer, + &test_opt->freq.denom, &test_opt->max_multiplier); + break; + case 'M': + test_opt->multiplier = strtoull(optarg, NULL, 0); + break; + case 'o': + test_opt->output = 1; + if (strlen(optarg) >= MAX_FILENAME) { + printf("Filename too long\n"); + return -1; + } + odph_strcpy(test_opt->filename, optarg, MAX_FILENAME); + break; + case 'e': + test_opt->early_retry = atoi(optarg); + break; + case 's': + test_opt->clk_src = atoi(optarg); + break; + case 't': + switch (atoi(optarg)) { + case 1: + test_opt->queue_type = ODP_SCHED_SYNC_ATOMIC; + break; + case 2: + test_opt->queue_type = ODP_SCHED_SYNC_ORDERED; + break; + default: + test_opt->queue_type = ODP_SCHED_SYNC_PARALLEL; + break; + } + break; + case 'q': + test_opt->num_queue = atoi(optarg); + break; + case 'G': + test_opt->groups = 1; + break; + case 'i': + test_opt->init = 1; + break; + case 'h': + print_usage(); + ret = -1; + break; + default: + print_usage(); + ret = -1; + break; + } + } + + if (test_opt->mode == MODE_PERIODIC) { + if ((test_opt->freq.integer == 0 && test_opt->freq.numer == 0) || + (test_opt->freq.numer != 0 && test_opt->freq.denom == 0)) { + printf("Bad frequency\n"); + return -1; + } + + test_opt->period_ns = + ODP_TIME_SEC_IN_NS / odp_fract_u64_to_dbl(&test_opt->freq); + + if (test_opt->offset_ns == UINT64_MAX) + test_opt->offset_ns = 0; + } else { + if (test_opt->res_ns < 0) { + printf("Resolution (res_ns) must be >= 0 with single shot timer\n"); + return -1; + } + + if (test_opt->offset_ns == UINT64_MAX) + test_opt->offset_ns = 300 * ODP_TIME_MSEC_IN_NS; + } + + test_opt->warmup_timers = test_opt->num_warmup * test_opt->burst; + test_opt->tot_timers = + test_opt->warmup_timers + test_opt->num * test_opt->burst; + + if (test_opt->mode == MODE_ONESHOT) + test_opt->alloc_timers = test_opt->tot_timers; + else + test_opt->alloc_timers = test_opt->burst; + + return ret; +} + +static int single_shot_params(test_global_t *test_global, odp_timer_pool_param_t *timer_param, + odp_timer_capability_t *timer_capa) +{ + uint64_t res_ns, res_hz; + uint64_t max_res_ns, max_res_hz; + uint64_t period_ns = test_global->opt.period_ns; + uint64_t num_tmo = test_global->opt.num + test_global->opt.num_warmup; + uint64_t offset_ns = test_global->opt.offset_ns; + enum mode_e mode = test_global->opt.mode; + + max_res_ns = timer_capa->max_res.res_ns; + max_res_hz = timer_capa->max_res.res_hz; + + /* Default resolution */ + if (test_global->opt.res_ns == 0 && test_global->opt.res_hz == 0) { + res_ns = test_global->opt.period_ns / 10; + res_hz = 0; + } else if (test_global->opt.res_ns) { + res_ns = test_global->opt.res_ns; + res_hz = 0; + } else { + res_ns = 0; + res_hz = test_global->opt.res_hz; + } + + if (res_ns && res_ns < max_res_ns) { + printf("Resolution %" PRIu64 " nsec too high. Highest resolution %" PRIu64 " nsec. " + "Default resolution is period / 10.\n\n", + res_ns, max_res_ns); + return -1; + } + + if (res_hz && res_hz > max_res_hz) { + printf("Resolution %" PRIu64 " hz too high. Highest resolution %" PRIu64 " hz. " + "Default resolution is period / 10.\n\n", + res_hz, max_res_hz); + return -1; + } + + if (res_ns) + timer_param->res_ns = res_ns; + else + timer_param->res_hz = res_hz; + + if (mode == MODE_ONESHOT) { + timer_param->min_tmo = offset_ns / 2; + timer_param->max_tmo = offset_ns + ((num_tmo + 1) * period_ns); + } else { + timer_param->min_tmo = period_ns / 10; + timer_param->max_tmo = offset_ns + (2 * period_ns); + } + + if (test_global->opt.max_tmo_ns) { + if (test_global->opt.max_tmo_ns < timer_param->max_tmo) { + printf("Max tmo is too small. Must be at least %" PRIu64 " nsec.\n", + timer_param->max_tmo); + return -1; + } + + timer_param->max_tmo = test_global->opt.max_tmo_ns; + } + + printf(" period: %" PRIu64 " nsec\n", period_ns); + printf(" max res nsec: %" PRIu64 "\n", max_res_ns); + printf(" max res hertz: %" PRIu64 "\n", max_res_hz); + + test_global->period_dbl = period_ns; + + return 0; +} + +static int periodic_params(test_global_t *test_global, odp_timer_pool_param_t *timer_param, + odp_timer_capability_t *timer_capa) +{ + int ret; + uint64_t res_ns; + odp_timer_periodic_capability_t capa; + double freq_dbl, min_freq, max_freq; + double opt_freq = odp_fract_u64_to_dbl(&test_global->opt.freq); + odp_fract_u64_t freq = test_global->opt.freq; + uint64_t res_hz = test_global->opt.res_hz; + uint64_t max_multiplier = test_global->opt.max_multiplier; + uint64_t multiplier = test_global->opt.multiplier; + + if (res_hz) { + res_ns = ODP_TIME_SEC_IN_NS / res_hz; + } else { + res_ns = test_global->opt.res_ns; + + /* Default resolution */ + if (res_ns == 0) + res_ns = ODP_TIME_SEC_IN_NS / (10 * multiplier * opt_freq); + } + + if (res_ns == 0) { + printf("Too high resolution\n"); + return -1; + } + + /* Resolution from capa */ + if (test_global->opt.res_ns < 0) + res_ns = 0; + + min_freq = odp_fract_u64_to_dbl(&timer_capa->periodic.min_base_freq_hz); + max_freq = odp_fract_u64_to_dbl(&timer_capa->periodic.max_base_freq_hz); + + capa.base_freq_hz = freq; + capa.max_multiplier = max_multiplier; + capa.res_ns = res_ns; + + ret = odp_timer_periodic_capability(test_global->opt.clk_src, &capa); + + if (ret < 0) { + printf("Requested periodic timer capabilities are not supported.\n" + "Capabilities: min base freq %g Hz, max base freq %g Hz, " + "max res %" PRIu64 " Hz\n", min_freq, max_freq, timer_capa->max_res.res_hz); + return -1; + } + + if (ret == 0) { + printf("Requested base frequency is not met. Using %.2f Hz instead of %.2f Hz.\n", + odp_fract_u64_to_dbl(&capa.base_freq_hz), opt_freq); + + freq = capa.base_freq_hz; + } + + if (res_ns == 0) + res_ns = capa.res_ns; + + freq_dbl = odp_fract_u64_to_dbl(&freq); + test_global->base_freq = freq; + test_global->period_dbl = ODP_TIME_SEC_IN_NS / (multiplier * freq_dbl); + + /* Min/max tmo are ignored, leave those to default values */ + timer_param->timer_type = ODP_TIMER_TYPE_PERIODIC; + timer_param->periodic.base_freq_hz = freq; + timer_param->periodic.max_multiplier = max_multiplier; + + if (res_hz) + timer_param->res_hz = res_hz; + else + timer_param->res_ns = res_ns; + + printf(" min freq capa: %.2f hz\n", min_freq); + printf(" max freq capa: %.2f hz\n", max_freq); + printf(" freq option: %.2f hz\n", opt_freq); + printf(" freq: %.2f hz\n", freq_dbl); + printf(" freq integer: %" PRIu64 "\n", freq.integer); + printf(" freq numer: %" PRIu64 "\n", freq.numer); + printf(" freq denom: %" PRIu64 "\n", freq.denom); + printf(" max_multiplier: %" PRIu64 "\n", max_multiplier); + printf(" multiplier: %" PRIu64 "\n", multiplier); + printf(" timer freq: %.2f hz\n", multiplier * freq_dbl); + printf(" timer period: %.2f nsec\n", test_global->period_dbl); + printf(" resolution capa: %" PRIu64 " nsec\n", capa.res_ns); + + return 0; +} + +static int create_timers(test_global_t *test_global) +{ + odp_pool_t pool; + odp_pool_param_t pool_param; + odp_timer_pool_t timer_pool; + odp_timer_pool_param_t timer_param; + odp_timer_capability_t timer_capa; + odp_timer_t timer; + odp_queue_t *queue; + odp_schedule_group_t *group; + odp_queue_param_t queue_param; + uint64_t offset_ns; + uint32_t max_timers; + odp_event_t event; + odp_timeout_t timeout; + uint64_t i, num_tmo, num_warmup, burst, burst_gap; + uint64_t tot_timers, alloc_timers; + enum mode_e mode; + odp_timer_clk_src_t clk_src; + int ret; + + mode = test_global->opt.mode; + alloc_timers = test_global->opt.alloc_timers; + tot_timers = test_global->opt.tot_timers; + num_warmup = test_global->opt.num_warmup; + num_tmo = num_warmup + test_global->opt.num; + burst = test_global->opt.burst; + burst_gap = test_global->opt.burst_gap; + offset_ns = test_global->opt.offset_ns; + queue = test_global->queue; + group = test_global->group; + + /* Always init globals for destroy calls */ + test_global->timer_pool = ODP_TIMER_POOL_INVALID; + test_global->timeout_pool = ODP_POOL_INVALID; + + for (i = 0; i < alloc_timers; i++) { + test_global->timer_ctx[i].timer = ODP_TIMER_INVALID; + test_global->timer_ctx[i].event = ODP_EVENT_INVALID; + } + + if (test_global->opt.groups) { + /* Create groups */ + + odp_thrmask_t zero; + + odp_thrmask_zero(&zero); + + for (i = 0; i < (uint64_t)test_global->opt.cpu_count; i++) { + group[i] = odp_schedule_group_create(NULL, &zero); + + if (group[i] == ODP_SCHED_GROUP_INVALID) { + printf("Group create failed.\n"); + return -1; + } + } + } + + 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 = test_global->opt.queue_type; + queue_param.sched.group = ODP_SCHED_GROUP_ALL; + + for (i = 0; i < (uint64_t)test_global->opt.num_queue; i++) { + if (test_global->opt.groups) + queue_param.sched.group = group[i % test_global->opt.cpu_count]; + + queue[i] = odp_queue_create(NULL, &queue_param); + if (queue[i] == ODP_QUEUE_INVALID) { + printf("Queue create failed.\n"); + return -1; + } + } + + odp_pool_param_init(&pool_param); + pool_param.type = ODP_POOL_TIMEOUT; + pool_param.tmo.num = alloc_timers; + + pool = odp_pool_create("timeout pool", &pool_param); + + if (pool == ODP_POOL_INVALID) { + printf("Timeout pool create failed.\n"); + return -1; + } + + test_global->timeout_pool = pool; + clk_src = test_global->opt.clk_src; + + if (odp_timer_capability(clk_src, &timer_capa)) { + printf("Timer capa failed\n"); + return -1; + } + + max_timers = timer_capa.max_timers; + + if (mode == MODE_PERIODIC) { + if (timer_capa.periodic.max_pools < 1) { + printf("Error: Periodic timers not supported.\n"); + return -1; + } + max_timers = timer_capa.periodic.max_timers; + } + + if (max_timers && test_global->opt.alloc_timers > max_timers) { + printf("Error: Too many timers: %" PRIu64 ".\n" + " Max timers: %u\n", + test_global->opt.alloc_timers, max_timers); + return -1; + } + + printf("\nTest parameters:\n"); + printf(" clock source: %i\n", clk_src); + printf(" max timers capa: %" PRIu32 "\n", max_timers); + printf(" mode: %i\n", mode); + printf(" queue type: %i\n", test_global->opt.queue_type); + printf(" num queue: %i\n", test_global->opt.num_queue); + printf(" sched groups: %s\n", test_global->opt.groups ? "yes" : "no"); + + odp_timer_pool_param_init(&timer_param); + + if (mode == MODE_PERIODIC) + ret = periodic_params(test_global, &timer_param, &timer_capa); + else + ret = single_shot_params(test_global, &timer_param, &timer_capa); + + if (ret) + return ret; + + if (timer_param.res_hz) { + test_global->res_ns = 1000000000.0 / timer_param.res_hz; + printf(" resolution: %" PRIu64 " Hz\n", timer_param.res_hz); + } else { + test_global->res_ns = timer_param.res_ns; + printf(" resolution: %" PRIu64 " nsec\n", timer_param.res_ns); + } + + timer_param.num_timers = alloc_timers; + timer_param.clk_src = clk_src; + + printf(" restart retries: %i\n", test_global->opt.early_retry); + if (test_global->opt.output) + printf(" log file: %s\n", test_global->opt.filename); + printf(" start offset: %" PRIu64 " nsec\n", offset_ns); + printf(" min timeout: %" PRIu64 " nsec\n", timer_param.min_tmo); + printf(" max timeout: %" PRIu64 " nsec\n", timer_param.max_tmo); + printf(" num timeout: %" PRIu64 "\n", num_tmo); + printf(" num warmup: %" PRIu64 "\n", num_warmup); + printf(" burst size: %" PRIu64 "\n", burst); + printf(" burst gap: %" PRIu64 "\n", burst_gap); + printf(" total timers: %" PRIu64 "\n", tot_timers); + printf(" warmup timers: %" PRIu64 "\n", test_global->opt.warmup_timers); + printf(" alloc timers: %" PRIu64 "\n", alloc_timers); + printf(" warmup time: %.2f sec\n", + (offset_ns + (num_warmup * test_global->period_dbl)) / 1000000000.0); + printf(" test run time: %.2f sec\n\n", + (offset_ns + (num_tmo * test_global->period_dbl)) / 1000000000.0); + + timer_pool = odp_timer_pool_create("timer_accuracy", &timer_param); + + if (timer_pool == ODP_TIMER_POOL_INVALID) { + printf("Timer pool create failed\n"); + return -1; + } + + if (odp_timer_pool_start_multi(&timer_pool, 1) != 1) { + ODPH_ERR("Timer pool start failed\n"); + return -1; + } + + odp_timer_pool_print(timer_pool); + + /* Spend some time so that current tick would not be zero */ + odp_time_wait_ns(100 * ODP_TIME_MSEC_IN_NS); + + test_global->timer_pool = timer_pool; + + for (i = 0; i < alloc_timers; i++) { + timer_ctx_t *ctx = &test_global->timer_ctx[i]; + + timer = odp_timer_alloc(timer_pool, queue[i % test_global->opt.num_queue], ctx); + + if (timer == ODP_TIMER_INVALID) { + printf("Timer alloc failed.\n"); + return -1; + } + + ctx->timer = timer; + + timeout = odp_timeout_alloc(pool); + if (timeout == ODP_TIMEOUT_INVALID) { + printf("Timeout alloc failed\n"); + return -1; + } + + ctx->event = odp_timeout_to_event(timeout); + } + + /* Run scheduler few times to ensure that (software) timer is active */ + for (i = 0; i < 1000; i++) { + event = odp_schedule(NULL, ODP_SCHED_NO_WAIT); + + if (event != ODP_EVENT_INVALID) { + printf("Spurious event received\n"); + odp_event_free(event); + return -1; + } + } + + return 0; +} + +static int start_timers(test_global_t *test_global) +{ + odp_timer_pool_t timer_pool; + uint64_t start_tick; + uint64_t period_ns, start_ns, nsec, offset_ns; + odp_time_t time; + uint64_t i, j, idx, num_tmo, num_warmup, burst, burst_gap; + enum mode_e mode; + + mode = test_global->opt.mode; + num_warmup = test_global->opt.num_warmup; + num_tmo = num_warmup + test_global->opt.num; + burst = test_global->opt.burst; + burst_gap = test_global->opt.burst_gap; + period_ns = test_global->opt.period_ns; + offset_ns = test_global->opt.offset_ns; + timer_pool = test_global->timer_pool; + idx = 0; + + /* Record test start time and tick. Memory barriers forbid compiler and out-of-order + * CPU to move samples apart. */ + odp_mb_full(); + start_tick = odp_timer_current_tick(timer_pool); + time = odp_time_global(); + odp_mb_full(); + + start_ns = odp_time_to_ns(time); + test_global->start_tick = start_tick; + test_global->start_ns = start_ns; + test_global->period_tick = odp_timer_ns_to_tick(timer_pool, period_ns); + + /* When mode is not one-shot, set only one burst of timers initially */ + if (mode != MODE_ONESHOT) + num_tmo = 1; + + for (i = 0; i < num_tmo; i++) { + odp_timer_retval_t retval; + + for (j = 0; j < burst; j++) { + timer_ctx_t *ctx = &test_global->timer_ctx[idx]; + odp_timer_start_t start_param; + + if (mode == MODE_PERIODIC) { + odp_timer_periodic_start_t periodic_start; + + nsec = offset_ns + (j * burst_gap); + + /* By default, timer starts one period after current time. Round + * floating point to closest integer number. */ + ctx->nsec = start_ns + test_global->period_dbl + 0.5; + if (nsec) + ctx->nsec = start_ns + nsec; + + ctx->count = 0; + ctx->first_period = start_tick + + odp_timer_ns_to_tick(timer_pool, + test_global->period_dbl + 0.5); + periodic_start.freq_multiplier = test_global->opt.multiplier; + periodic_start.first_tick = 0; + if (nsec) + periodic_start.first_tick = + start_tick + odp_timer_ns_to_tick(timer_pool, nsec); + periodic_start.tmo_ev = ctx->event; + retval = odp_timer_periodic_start(ctx->timer, &periodic_start); + } else { + nsec = offset_ns + (i * period_ns) + (j * burst_gap); + ctx->nsec = start_ns + nsec; + start_param.tick_type = ODP_TIMER_TICK_ABS; + start_param.tick = + start_tick + odp_timer_ns_to_tick(timer_pool, nsec); + start_param.tmo_ev = ctx->event; + retval = odp_timer_start(ctx->timer, &start_param); + } + + if (retval != ODP_TIMER_SUCCESS) { + printf("Timer[%" PRIu64 "] set failed: %i\n", idx, retval); + return -1; + } + + idx++; + } + } + + printf("\nStarting timers took %" PRIu64 " nsec\n", odp_time_global_ns() - start_ns); + + return 0; +} + +static int destroy_timers(test_global_t *test_global) +{ + uint64_t i, alloc_timers; + odp_timer_t timer; + int ret = 0; + + alloc_timers = test_global->opt.alloc_timers; + + for (i = 0; i < alloc_timers; i++) { + timer = test_global->timer_ctx[i].timer; + + if (timer == ODP_TIMER_INVALID) + break; + + if (odp_timer_free(timer)) { + printf("Timer free failed: %" PRIu64 "\n", i); + ret = -1; + } + } + + if (test_global->timer_pool != ODP_TIMER_POOL_INVALID) + odp_timer_pool_destroy(test_global->timer_pool); + + if (test_global->timeout_pool != ODP_POOL_INVALID) { + if (odp_pool_destroy(test_global->timeout_pool)) { + printf("Pool destroy failed.\n"); + ret = -1; + } + } + + for (i = 0; i < (uint64_t)test_global->opt.num_queue; i++) { + if (odp_queue_destroy(test_global->queue[i])) { + printf("Queue destroy failed.\n"); + ret = -1; + } + } + + if (test_global->opt.groups) { + for (i = 0; i < (uint64_t)test_global->opt.cpu_count; i++) { + if (odp_schedule_group_destroy(test_global->group[i])) { + printf("Group destroy failed.\n"); + ret = -1; + } + } + } + + return ret; +} + +static void print_nsec_error(const char *str, int64_t nsec, double res_ns, + int tid, int idx) +{ + printf(" %s: %12" PRIi64 " / %.3fx resolution", + str, nsec, (double)nsec / res_ns); + if (tid >= 0) + printf(", thread %d", tid); + if (idx >= 0) + printf(", event %d", idx); + printf("\n"); +} + +static void print_stat(test_global_t *test_global) +{ + test_stat_t test_stat; + test_stat_t *stat = &test_stat; + uint64_t tot_timers; + test_stat_t *s = test_global->stat; + test_log_t *log = test_global->log; + double res_ns = test_global->res_ns; + uint64_t ave_after = 0; + uint64_t ave_before = 0; + uint64_t nsec_before_min_tid = 0; + uint64_t nsec_before_max_tid = 0; + uint64_t nsec_after_min_tid = 0; + uint64_t nsec_after_max_tid = 0; + + memset(stat, 0, sizeof(*stat)); + stat->nsec_before_min = UINT64_MAX; + stat->nsec_after_min = UINT64_MAX; + + for (int i = 1; i < test_global->opt.cpu_count + 1; i++) { + stat->nsec_before_sum += s[i].nsec_before_sum; + stat->nsec_after_sum += s[i].nsec_after_sum; + stat->num_before += s[i].num_before; + stat->num_exact += s[i].num_exact; + stat->num_after += s[i].num_after; + stat->num_too_near += s[i].num_too_near; + + if (s[i].nsec_before_min < stat->nsec_before_min) { + stat->nsec_before_min = s[i].nsec_before_min; + stat->nsec_before_min_idx = s[i].nsec_before_min_idx; + nsec_before_min_tid = i; + } + + if (s[i].nsec_after_min < stat->nsec_after_min) { + stat->nsec_after_min = s[i].nsec_after_min; + stat->nsec_after_min_idx = s[i].nsec_after_min_idx; + nsec_after_min_tid = i; + } + + if (s[i].nsec_before_max > stat->nsec_before_max) { + stat->nsec_before_max = s[i].nsec_before_max; + stat->nsec_before_max_idx = s[i].nsec_before_max_idx; + nsec_before_max_tid = i; + } + + if (s[i].nsec_after_max > stat->nsec_after_max) { + stat->nsec_after_max = s[i].nsec_after_max; + stat->nsec_after_max_idx = s[i].nsec_after_max_idx; + nsec_after_max_tid = i; + } + } + + if (stat->num_after) + ave_after = stat->nsec_after_sum / stat->num_after; + else + stat->nsec_after_min = 0; + + if (stat->num_before) + ave_before = stat->nsec_before_sum / stat->num_before; + else + stat->nsec_before_min = 0; + + tot_timers = stat->num_before + stat->num_after + stat->num_exact; + + if (log) { + FILE *file = test_global->file; + + fprintf(file, " Timer thread tmo(ns) diff(ns)\n"); + + for (uint64_t i = 0; i < tot_timers; i++) { + fprintf(file, "%8" PRIu64 " %7u %12" PRIu64 " %10" + PRIi64 "\n", i, log[i].tid, log[i].tmo_ns, log[i].diff_ns); + } + + fprintf(file, "\n"); + } + + printf("\nTest results:\n"); + printf(" num after: %12" PRIu64 " / %.2f%%\n", + stat->num_after, 100.0 * stat->num_after / tot_timers); + printf(" num before: %12" PRIu64 " / %.2f%%\n", + stat->num_before, 100.0 * stat->num_before / tot_timers); + printf(" num exact: %12" PRIu64 " / %.2f%%\n", + stat->num_exact, 100.0 * stat->num_exact / tot_timers); + printf(" num retry: %12" PRIu64 " / %.2f%%\n", + stat->num_too_near, 100.0 * stat->num_too_near / tot_timers); + printf(" error after (nsec):\n"); + print_nsec_error("min", stat->nsec_after_min, res_ns, nsec_after_min_tid, + stat->nsec_after_min_idx); + print_nsec_error("max", stat->nsec_after_max, res_ns, nsec_after_max_tid, + stat->nsec_after_max_idx); + print_nsec_error("ave", ave_after, res_ns, -1, -1); + printf(" error before (nsec):\n"); + print_nsec_error("min", stat->nsec_before_min, res_ns, nsec_before_min_tid, + stat->nsec_before_min_idx); + print_nsec_error("max", stat->nsec_before_max, res_ns, nsec_before_max_tid, + stat->nsec_before_max_idx); + print_nsec_error("ave", ave_before, res_ns, -1, -1); + + if (test_global->opt.mode == MODE_PERIODIC && !test_global->opt.offset_ns) { + int idx = 0; + int64_t max = 0; + + for (int i = 0; i < (int)test_global->opt.alloc_timers; i++) { + timer_ctx_t *t = &test_global->timer_ctx[i]; + int64_t v = t->first_tmo_diff; + + if (ODPH_ABS(v) > ODPH_ABS(max)) { + max = v; + idx = i; + } + } + + printf(" first timeout difference to one period, based on %s (nsec):\n", + test_global->timer_ctx[idx].tmo_tick ? "timeout tick" : "time"); + print_nsec_error("max", max, res_ns, -1, -1); + } + + int64_t max = 0; + + for (int i = 0; i < (int)test_global->opt.alloc_timers; i++) { + timer_ctx_t *t = &test_global->timer_ctx[i]; + int64_t v = t->nsec_final; + + if (ODPH_ABS(v) > ODPH_ABS(max)) + max = v; + } + + printf(" final timeout error (nsec):\n"); + print_nsec_error("max", max, res_ns, -1, -1); + + printf("\n"); +} + +static void cancel_periodic_timers(test_global_t *test_global) +{ + uint64_t i, alloc_timers; + odp_timer_t timer; + + alloc_timers = test_global->opt.alloc_timers; + + for (i = 0; i < alloc_timers; i++) { + timer = test_global->timer_ctx[i].timer; + + if (timer == ODP_TIMER_INVALID) + break; + + if (odp_timer_periodic_cancel(timer)) + printf("Failed to cancel periodic timer.\n"); + } +} + +static int run_test(void *arg) +{ + test_global_t *test_global = (test_global_t *)arg; + odp_event_t ev; + odp_time_t time; + uint64_t time_ns, diff_ns; + odp_timeout_t tmo; + uint64_t tmo_ns; + timer_ctx_t *ctx; + odp_thrmask_t mask; + uint64_t wait = odp_schedule_wait_time(10 * ODP_TIME_MSEC_IN_NS); + odp_schedule_group_t group = ODP_SCHED_GROUP_INVALID; + test_log_t *log = test_global->log; + enum mode_e mode = test_global->opt.mode; + uint64_t tot_timers = test_global->opt.tot_timers; + double period_dbl = test_global->period_dbl; + odp_timer_pool_t tp = test_global->timer_pool; + int tid = odp_thread_id(); + + if (tid > test_global->opt.cpu_count) { + printf("Error: tid %d is larger than cpu_count %d.\n", tid, + test_global->opt.cpu_count); + return 0; + } + + test_stat_t *stat = &test_global->stat[tid]; + + memset(stat, 0, sizeof(*stat)); + stat->nsec_before_min = UINT64_MAX; + stat->nsec_after_min = UINT64_MAX; + + if (test_global->opt.groups) { + odp_thrmask_zero(&mask); + odp_thrmask_set(&mask, tid); + group = test_global->group[tid - 1]; + + if (odp_schedule_group_join(group, &mask)) { + printf("odp_schedule_group_join() failed\n"); + return 0; + } + } + + odp_barrier_wait(&test_global->barrier); + + while (1) { + ev = odp_schedule(NULL, wait); + time = odp_time_global_strict(); + + if (ev == ODP_EVENT_INVALID) { + if (mode == MODE_PERIODIC) { + if (odp_atomic_load_u64(&test_global->last_events) >= + test_global->opt.alloc_timers) + break; + + } else if (odp_atomic_load_u64(&test_global->events) >= tot_timers) { + break; + } + + continue; + } + + time_ns = odp_time_to_ns(time); + tmo = odp_timeout_from_event(ev); + ctx = odp_timeout_user_ptr(tmo); + tmo_ns = ctx->nsec; + + if (mode == MODE_PERIODIC) { + if (!ctx->count && !test_global->opt.offset_ns) { + /* + * If first_tick is zero, the API allows the implementation to + * place the timer where it can, so we have to adjust our + * expectation of the timeout time. + */ + + uint64_t tmo_tick = odp_timeout_tick(tmo); + + if (tmo_tick) { + /* + * Adjust by the difference between one period after start + * time and the timeout tick. + */ + ctx->tmo_tick = 1; + ctx->first_tmo_diff = + (int64_t)odp_timer_tick_to_ns(tp, tmo_tick) - + (int64_t)odp_timer_tick_to_ns(tp, ctx->first_period); + tmo_ns += ctx->first_tmo_diff; + } else { + /* + * Timeout tick is not provided, so the best we can do is + * to just take the current time as a baseline. + */ + ctx->first_tmo_diff = (int64_t)time_ns - (int64_t)tmo_ns; + tmo_ns = ctx->nsec = time_ns; + } + + ctx->nsec = tmo_ns; + } + + /* round to closest integer number */ + tmo_ns += ctx->count * period_dbl + 0.5; + ctx->count++; + } + + uint64_t events = odp_atomic_fetch_inc_u64(&test_global->events); + + if (events >= test_global->opt.warmup_timers && events < tot_timers) { + uint64_t i = events - test_global->opt.warmup_timers; + + ctx->nsec_final = (int64_t)time_ns - (int64_t)tmo_ns; + + if (log) { + log[i].tmo_ns = tmo_ns; + log[i].tid = tid; + } + + if (time_ns > tmo_ns) { + diff_ns = time_ns - tmo_ns; + stat->num_after++; + stat->nsec_after_sum += diff_ns; + if (diff_ns < stat->nsec_after_min) { + stat->nsec_after_min = diff_ns; + stat->nsec_after_min_idx = i; + } + if (diff_ns > stat->nsec_after_max) { + stat->nsec_after_max = diff_ns; + stat->nsec_after_max_idx = i; + } + if (log) + log[i].diff_ns = diff_ns; + + } else if (time_ns < tmo_ns) { + diff_ns = tmo_ns - time_ns; + stat->num_before++; + stat->nsec_before_sum += diff_ns; + if (diff_ns < stat->nsec_before_min) { + stat->nsec_before_min = diff_ns; + stat->nsec_before_min_idx = i; + } + if (diff_ns > stat->nsec_before_max) { + stat->nsec_before_max = diff_ns; + stat->nsec_before_max_idx = i; + } + if (log) + log[i].diff_ns = -diff_ns; + } else { + stat->num_exact++; + } + } + + if ((mode == MODE_RESTART_ABS || mode == MODE_RESTART_REL) && + events < tot_timers - 1) { + /* Reset timer for next period */ + odp_timer_t tim; + uint64_t nsec, tick; + odp_timer_retval_t ret; + unsigned int j; + unsigned int retries = test_global->opt.early_retry; + uint64_t start_ns = test_global->start_ns; + uint64_t period_ns = test_global->opt.period_ns; + odp_timer_start_t start_param; + + tim = ctx->timer; + + /* Depending on the option, retry when expiration + * time is too early */ + for (j = 0; j < retries + 1; j++) { + if (mode == MODE_RESTART_ABS) { + /* Absolute time */ + ctx->nsec += period_ns; + nsec = ctx->nsec - start_ns; + tick = test_global->start_tick + + odp_timer_ns_to_tick(tp, nsec); + start_param.tick_type = ODP_TIMER_TICK_ABS; + } else { + /* Relative time */ + tick = test_global->period_tick; + time = odp_time_local(); + time_ns = odp_time_to_ns(time); + ctx->nsec = time_ns + period_ns; + start_param.tick_type = ODP_TIMER_TICK_REL; + } + + start_param.tmo_ev = ev; + start_param.tick = tick; + + ret = odp_timer_start(tim, &start_param); + if (ret == ODP_TIMER_TOO_NEAR) { + if (events >= test_global->opt.warmup_timers) + stat->num_too_near++; + } else { + break; + } + } + + if (ret != ODP_TIMER_SUCCESS) { + printf("Timer set failed: %i. Timeout nsec " + "%" PRIu64 "\n", ret, ctx->nsec); + return 0; + } + } else if (mode == MODE_PERIODIC) { + int ret = odp_timer_periodic_ack(ctx->timer, ev); + + if (ret < 0) + printf("Failed to ack a periodic timer.\n"); + + if (ret == 2) + odp_atomic_inc_u64(&test_global->last_events); + + if (ret == 2 || ret < 0) + odp_event_free(ev); + } else { + odp_event_free(ev); + } + } + + if (test_global->opt.groups) { + if (odp_schedule_group_leave(group, &mask)) + printf("odp_schedule_group_leave() failed\n"); + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + odp_instance_t instance; + odp_init_t init; + test_opt_t test_opt; + test_global_t *test_global; + odph_helper_options_t helper_options; + odp_init_t *init_ptr = NULL; + 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); + } + + if (parse_options(argc, argv, &test_opt)) + return -1; + + /* List features not to be used (may optimize performance) */ + odp_init_param_init(&init); + init.not_used.feat.cls = 1; + init.not_used.feat.compress = 1; + init.not_used.feat.crypto = 1; + init.not_used.feat.ipsec = 1; + init.not_used.feat.tm = 1; + + init.mem_model = helper_options.mem_model; + + if (test_opt.init) + init_ptr = &init; + + /* Init ODP before calling anything else */ + if (odp_init_global(&instance, init_ptr, NULL)) { + printf("Global init failed.\n"); + return -1; + } + + /* Init this thread */ + if (odp_init_local(instance, ODP_THREAD_CONTROL)) { + printf("Local init failed.\n"); + return -1; + } + + odp_sys_info_print(); + + /* Configure scheduler */ + odp_schedule_config(NULL); + + odp_shm_t shm = ODP_SHM_INVALID, shm_ctx = ODP_SHM_INVALID, shm_log = ODP_SHM_INVALID; + uint64_t size = sizeof(test_global_t); + + shm = odp_shm_reserve("timer_accuracy", size, + ODP_CACHE_LINE_SIZE, ODP_SHM_SINGLE_VA); + + if (shm == ODP_SHM_INVALID) { + printf("Shm alloc failed.\n"); + return -1; + } + + test_global = odp_shm_addr(shm); + memset(test_global, 0, size); + memcpy(&test_global->opt, &test_opt, sizeof(test_opt_t)); + + size = test_global->opt.alloc_timers * sizeof(timer_ctx_t); + shm_ctx = odp_shm_reserve("timer_accuracy_ctx", size, + ODP_CACHE_LINE_SIZE, ODP_SHM_SINGLE_VA); + + if (shm_ctx == ODP_SHM_INVALID) { + printf("Timer context alloc failed.\n"); + ret = -1; + goto quit; + } + + test_global->timer_ctx = odp_shm_addr(shm_ctx); + memset(test_global->timer_ctx, 0, size); + + if (test_global->opt.output) { + test_global->file = fopen(test_global->opt.filename, "w"); + if (test_global->file == NULL) { + printf("Failed to open output file %s: %s\n", + test_global->opt.filename, strerror(errno)); + ret = -1; + goto quit; + } + + size = (test_global->opt.tot_timers - test_global->opt.warmup_timers) * + sizeof(test_log_t); + shm_log = odp_shm_reserve("timer_accuracy_log", size, sizeof(test_log_t), + ODP_SHM_SINGLE_VA); + + if (shm_log == ODP_SHM_INVALID) { + printf("Test log alloc failed.\n"); + ret = -1; + goto quit; + } + + test_global->log = odp_shm_addr(shm_log); + memset(test_global->log, 0, size); + } + + odph_thread_t thread_tbl[MAX_WORKERS]; + int num_workers; + odp_cpumask_t cpumask; + char cpumaskstr[ODP_CPUMASK_STR_SIZE]; + odph_thread_common_param_t thr_common; + odph_thread_param_t thr_param; + + memset(thread_tbl, 0, sizeof(thread_tbl)); + + num_workers = MAX_WORKERS; + if (test_global->opt.cpu_count && test_global->opt.cpu_count < MAX_WORKERS) + num_workers = test_global->opt.cpu_count; + num_workers = odp_cpumask_default_worker(&cpumask, num_workers); + test_global->opt.cpu_count = num_workers; + odp_cpumask_to_str(&cpumask, cpumaskstr, sizeof(cpumaskstr)); + + printf("num worker threads: %i\n", num_workers); + printf("first CPU: %i\n", odp_cpumask_first(&cpumask)); + printf("cpu mask: %s\n", cpumaskstr); + + ret = create_timers(test_global); + if (ret) + goto quit; + + odp_barrier_init(&test_global->barrier, num_workers + 1); + odp_atomic_init_u64(&test_global->events, 0); + odp_atomic_init_u64(&test_global->last_events, 0); + + odph_thread_param_init(&thr_param); + thr_param.start = run_test; + thr_param.arg = (void *)test_global; + thr_param.thr_type = ODP_THREAD_WORKER; + + odph_thread_common_param_init(&thr_common); + thr_common.instance = instance; + thr_common.cpumask = &cpumask; + thr_common.share_param = 1; + + odph_thread_create(thread_tbl, &thr_common, &thr_param, num_workers); + odp_barrier_wait(&test_global->barrier); + + ret = start_timers(test_global); + if (ret) + goto quit; + + if (test_global->opt.mode == MODE_PERIODIC) { + while (odp_atomic_load_u64(&test_global->events) < test_global->opt.tot_timers) + odp_time_wait_ns(10 * ODP_TIME_MSEC_IN_NS); + + cancel_periodic_timers(test_global); + } + + odph_thread_join(thread_tbl, num_workers); + print_stat(test_global); + +quit: + if (test_global->file) + fclose(test_global->file); + + if (destroy_timers(test_global)) + ret = -1; + + if (shm_log != ODP_SHM_INVALID && odp_shm_free(shm_log)) + ret = -1; + + if (shm_ctx != ODP_SHM_INVALID && odp_shm_free(shm_ctx)) + ret = -1; + + if (odp_shm_free(shm)) + ret = -1; + + if (odp_term_local()) { + printf("Term local failed.\n"); + ret = -1; + } + + if (odp_term_global(instance)) { + printf("Term global failed.\n"); + ret = -1; + } + + return ret; +} |