diff --git a/benchmarks/cyclictest/CMakeLists.txt b/benchmarks/cyclictest/CMakeLists.txt new file mode 100644 index 000000000..59cfd2cf8 --- /dev/null +++ b/benchmarks/cyclictest/CMakeLists.txt @@ -0,0 +1,55 @@ +# ############################################################################## +# apps/benchmarks/cyclictest/CMakeLists.txt +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_BENCHMARK_CYCLICTEST) + + # ############################################################################ + # Config and Fetch Coremark application + # ############################################################################ + + set(CYCLICTEST_DIR ${CMAKE_CURRENT_LIST_DIR}) + + # ############################################################################ + # Sources + # ############################################################################ + + set(CSRCS ${CYCLICTEST_DIR}/cyclictest.c) + + # ############################################################################ + # Applications Configuration + # ############################################################################ + + nuttx_add_application( + NAME + cyclictest + PRIORITY + ${CONFIG_CYCLICTEST_PRIORITY} + STACKSIZE + ${CONFIG_CYCLICTEST_STACKSIZE} + MODULE + ${CONFIG_BENCHMARK_CYCLICTEST} + COMPILE_FLAGS + ${CFLAGS} + SRCS + ${CSRCS} + INCLUDE_DIRECTORIES + ${INCDIR}) + +endif() diff --git a/benchmarks/cyclictest/Kconfig b/benchmarks/cyclictest/Kconfig new file mode 100644 index 000000000..10f80734b --- /dev/null +++ b/benchmarks/cyclictest/Kconfig @@ -0,0 +1,29 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +menuconfig BENCHMARK_CYCLICTEST + bool "Cyclictest" + default n + ---help--- + Enable the cyclictest application. + +if BENCHMARK_CYCLICTEST + +config BENCHMARK_CYCLICTEST_PROGNAME + string "Cyclictest App Name" + default "cyclictest" + ---help--- + This is the name of the program that will be used when the NSH ELF + program is installed. + +config BENCHMARK_CYCLICTEST_PRIORITY + int "Cyclictest task priority" + default 100 + +config BENCHMARK_CYCLICTEST_STACKSIZE + int "Cyclictest task stack size" + default 4096 + +endif # BENCHMARK_CYCLICTEST diff --git a/benchmarks/cyclictest/Make.defs b/benchmarks/cyclictest/Make.defs new file mode 100644 index 000000000..ecfced80f --- /dev/null +++ b/benchmarks/cyclictest/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/benchmarks/cyclictest/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_BENCHMARK_CYCLICTEST),) +CONFIGURED_APPS += $(APPDIR)/benchmarks/cyclictest +endif diff --git a/benchmarks/cyclictest/Makefile b/benchmarks/cyclictest/Makefile new file mode 100644 index 000000000..b7f516919 --- /dev/null +++ b/benchmarks/cyclictest/Makefile @@ -0,0 +1,39 @@ +############################################################################ +# apps/benchmarks/cyclictest/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +# spinlock_bench application + +############################################################################ +# Applications Configuration +############################################################################ + +MODULE = $(CONFIG_BENCHMARK_CYCLICTEST) + +PROGNAME += $(CONFIG_BENCHMARK_CYCLICTEST_PROGNAME) +PRIORITY += $(CONFIG_BENCHMARK_CYCLICTEST_PRIORITY) +STACKSIZE += $(CONFIG_BENCHMARK_CYCLICTEST_STACKSIZE) + +MAINSRC += cyclictest.c + +# Build with WebAssembly when CONFIG_INTERPRETERS_WAMR is enabled + +include $(APPDIR)/Application.mk diff --git a/benchmarks/cyclictest/cyclictest.c b/benchmarks/cyclictest/cyclictest.c new file mode 100644 index 000000000..2ab0f1208 --- /dev/null +++ b/benchmarks/cyclictest/cyclictest.c @@ -0,0 +1,1083 @@ +/**************************************************************************** + * apps/benchmarks/cyclictest/cyclictest.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * A NuttX port of the cyclictest rt-tests utility for Linux. + * As of writing this piece of software (Feb 2025), clock_gettime + * and clock_nanosleep are tightly tied to systemtick by default. + * Yes, the time resolution can be achieved by using TICKLESS, but for high + * resolution waiting and measurement we can use other methods. + * + * This piece of software includes configurable waiting methods: + * - clock_nanosleep + * - systemtick hook: it is assumed your BSP supports a board_timerhook + * function where a sem_t g_waitsem is posted. + * - WARNING: only one task (thread) can wait for the semaphore. + * - NuttX Timer API: waiting for the timer to expire. + * - WARNING: only one task (thread) can wait for the timer to expire. + * + * Available time measuring methods: + * - clock_gettime + * - NuttX Timer API + * + * Authors of the NuttX port: Stepan Pressl + * + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +enum meas_method_e +{ + M_GETTIME = 0, + M_TIMER_API, + M_COUNT +}; + +enum wait_method_e +{ + W_NANOSLEEP = 0, + W_DEVTIMER, + W_COUNT +}; + +struct cyclictest_config_s +{ + int clock; + int distance; + int duration; + int histogram; + int histofall; + unsigned long interval; + unsigned long loops; + int threads; + int policy; + int prio; + char *timer_dev; + enum meas_method_e meas_method; + enum wait_method_e wait_method; +}; + +struct thread_param_s +{ + int prio; + int policy; + unsigned long interval; + unsigned long max_cycles; + struct thread_stats_s *stats; + int clock; +}; + +struct thread_stats_s +{ + long *hist_array; + long hist_overflow; + long min; + long max; + double avg; + unsigned long cycles; + pthread_t id; + bool ended; +}; + +static bool running; +static struct cyclictest_config_s config; +static int timerfd; +static struct pollfd polltimer[1]; + +static const struct option optargs[] = +{ + {"clock", optional_argument, 0, 'c'}, + {"distance", optional_argument, 0, 'd'}, + {"duration", optional_argument, 0, 'D'}, + {"help", optional_argument, 0, 'e'}, + {"histogram", optional_argument, 0, 'h'}, + {"histofall", optional_argument, 0, 'H'}, + {"interval", optional_argument, 0, 'i'}, + {"loops", optional_argument, 0, 'l'}, + {"measurement", optional_argument, 0, 'm'}, + {"nanosleep", optional_argument, 0, 'n'}, + {"prio", optional_argument, 0, 'p'}, + {"threads", optional_argument, 0, 't'}, + {"timer-device", optional_argument, 0, 'T'}, + {"policy", optional_argument, 0, 'y'}, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void print_help(void) +{ + puts( + "The Cyclictest Benchmark Utility\n" + "Usage:\n" + " -c --clock [CLOCK]: selects the clock: 0 selects CLOCK_REALTIME, " + "1 selects CLOCK_MONOTONIC (default).\n" + " -d --distance [US]: The distance of thread intervals. " + "Default is 500us.\n" + " -D --duration [TIME]: Test duration length in seconds. " + "Default is 0 (endless).\n" + " -e --help: Display this help and quit.\n" + " -h --histogram [US]: Output the histogram data to stdout. " + "US is the maximum value to be printed.\n" + " -H --histofall: Same as -h except that an additional histogram " + "column\n" + " is displayed at the right that contains summary data of all thread" + " histograms.\n" + " If cyclictest runs a single thread only, the -H option is " + "equivalent to -h.\n" + " -i --interval [US]: The thread interval. Default is 1000us.\n" + " -l --loops [N]: The number of measurement loops. Default is 0 " + "(endless).\n" + " -m --measurement [METHOD]: Set the time measurement method:\n" + " 0 selects clock_gettime, 1 uses the NuttX timer API.\n" + " WARNING:\n" + " If METHOD 1 is selected, you need to specify a timer device " + "(e.g. /dev/timer0) in -T.\n" + " -n --nanosleep [METHOD]: Set the waiting method: 0 selects " + "clock_nanosleep,\n" + " 1 waits for the POLLIN flag on a timer device. Default is 0.\n" + " WARNING:\n" + " Choosing 1 works only with one thread, " + "the -t value is therefore set to 1.\n" + " If METHOD 1 is selected, you need to specify a timer device " + "(e.g. /dev/timer0) in -T.\n" + " -p --prio: Set the priority of the first thread.\n" + " -t --threads [N]: The number of test threads to be created. " + "Default is 1.\n" + " -T --timer-device [DEV]: The measuring timer device.\n" + " Must be specified when -m=1 or -n=1.\n" + " -y --policy [NAME]: Set the scheduler policy, where NAME is \n" + " fifo, rr, batch, idle, normal, other.\n" + ); +} + +static long arg_decimal(char *arg) +{ + long ret; + char *endptr; + ret = strtol(arg, &endptr, 10); + if (endptr == arg) + { + return -1; + } + + return ret; +} + +static bool parse_args(int argc, char * const argv[]) +{ + int longindex; + int opt; + long decimal; + while ((opt = getopt_long(argc, argv, "c:d:D:h:Hi:l:m:n:p:t:T:", + optargs, &longindex)) != -1) + { + switch (opt) + { + case 'c': + decimal = arg_decimal(optarg); + if (decimal < 0) + { + return false; + } + else if (decimal == CLOCK_MONOTONIC || decimal == CLOCK_REALTIME) + { + config.clock = decimal; + } + break; + case 'd': + decimal = arg_decimal(optarg); + if (decimal >= 0) + { + config.distance = decimal; + } + else + { + return false; + } + break; + case 'D': + decimal = arg_decimal(optarg); + if (decimal >= 0) + { + config.duration = decimal; + } + else + { + return false; + } + break; + case 'e': + return true; + case 'h': + decimal = arg_decimal(optarg); + if (decimal >= 0) + { + config.histogram = decimal; + } + else + { + return false; + } + break; + case 'H': + config.histofall = true; + break; + case 'i': + decimal = arg_decimal(optarg); + if (decimal >= 0) + { + config.interval = decimal; + } + else + { + return false; + } + break; + case 'l': + decimal = arg_decimal(optarg); + if (decimal >= 0) + { + config.loops = decimal; + } + else + { + return false; + } + break; + case 'm': + decimal = arg_decimal(optarg); + if (decimal >= 0) + { + config.meas_method = decimal; + } + else + { + return false; + } + break; + case 'n': + decimal = arg_decimal(optarg); + if (decimal >= 0) + { + config.wait_method = decimal; + } + else + { + return false; + } + break; + case 'p': + decimal = arg_decimal(optarg); + if (decimal >= 0 && decimal <= 255) + { + config.prio = decimal; + } + else + { + return false; + } + break; + case 't': + decimal = arg_decimal(optarg); + if (decimal > 0) + { + config.threads = decimal; + } + else + { + return false; + } + break; + case 'T': + config.timer_dev = optarg; + break; + case 'y': + if (strcmp(optarg, "other") == 0) + { + config.policy = SCHED_OTHER; + } + else if (strcmp(optarg, "normal") == 0) + { + config.policy = SCHED_NORMAL; + } + else if (strcmp(optarg, "batch") == 0) + { + config.policy = SCHED_BATCH; + } + else if (strcmp(optarg, "idle") == 0) + { + config.policy = SCHED_IDLE; + } + else if (strcmp(optarg, "fifo") == 0) + { + config.policy = SCHED_FIFO; + } + else if (strcmp(optarg, "rr") == 0) + { + config.policy = SCHED_RR; + } + else + { + return false; + } + break; + case '?': + return false; + default: + break; + } + } + + if (optind < argc) + { + return false; + } + + return true; +} + +static bool check_args_logic(void) +{ + /* Check if -T option was passed */ + + if (config.wait_method == W_DEVTIMER || config.meas_method == M_TIMER_API) + { + if (config.timer_dev == NULL) + { + fprintf(stderr, "Specify timer device!\n"); + return false; + } + } + + /* Works only with one thread */ + + if (config.wait_method == W_DEVTIMER) + { + config.threads = 1; + } + + /* If the priority was not loaded, default to number of threads. */ + + if (config.prio == 0) + { + config.prio = config.threads; + } + + if (config.wait_method >= W_COUNT) + { + return false; + } + + if (config.meas_method >= M_COUNT) + { + return false; + } + + return true; +} + +static inline void tsnorm(struct timespec *ts) +{ + while (ts->tv_nsec >= NSEC_PER_SEC) + { + ts->tv_nsec -= NSEC_PER_SEC; + ts->tv_sec++; + } +} + +static inline int64_t timediff_us(struct timespec t1, struct timespec t2) +{ + int64_t ret; + ret = 1000000 * (int64_t) ((int) t1.tv_sec - (int) t2.tv_sec); + ret += (int64_t) ((int) t1.tv_nsec - (int) t2.tv_nsec) / 1000; + return ret; +} + +static inline int64_t timediff_us_timer(struct timer_status_s after, + struct timer_status_s before) +{ + int64_t ret = 0; + uint32_t t1; + uint32_t t2; + t1 = before.timeleft; + t2 = after.timeleft; + if (t2 < t1) + { + ret = (int64_t) (t1 - t2); + } + else + { + ret = (int64_t) (after.timeout - (t2 - t1)); + } + + return ret; +} + +static inline int timerdev_getstatus(struct timer_status_s *st) +{ + return ioctl(timerfd, TCIOC_GETSTATUS, (unsigned long)((uintptr_t)st)); +} + +static void *testthread(void *arg) +{ + int ret; + int32_t newint; + int64_t diff = 0; + struct timer_status_s stamp1; + struct timer_status_s stamp2; + struct timespec now; + struct timespec next; + struct timespec interval; + struct timespec endtime; + struct sched_param schedp; + struct thread_param_s *param = (struct thread_param_s *)arg; + struct thread_stats_s *stats = param->stats; + + stats->min = LONG_MAX; + interval.tv_sec = param->interval / 1000000; + interval.tv_nsec = (param->interval % 1000000) * 1000; + + /* Set priority and policy */ + + schedp.sched_priority = param->prio; + ret = pthread_setschedparam(pthread_self(), param->policy, &schedp); + if (ret < 0) + { + goto threadend; + } + + if (config.wait_method == W_DEVTIMER) + { + /* Start the timer here (we know the thread is only one) */ + + ret = ioctl(timerfd, TCIOC_START); + if (ret < 0) + { + perror("TCIOC_START"); + goto threadend; + } + } + + /* We can use clock_gettime for the endtime. */ + + if ((ret = clock_gettime(param->clock, &now)) < 0) + { + goto threadend; + } + + endtime.tv_sec = now.tv_sec + config.duration; + endtime.tv_nsec = now.tv_nsec; + + while (running) + { + /* This inicializes the stamp1.timeout field */ + + if (config.meas_method == M_TIMER_API) + { + ret = timerdev_getstatus(&stamp1); + if (ret < 0) + { + perror("TCIOC_GETSTAUS"); + goto threadend; + } + } + + switch (config.wait_method) + { + case W_NANOSLEEP: + if (config.meas_method == M_TIMER_API) + { + /* If we measure using the TIMER_API, compute + * the expected timestamp when the thread should wake up. + */ + + newint = stamp1.timeleft - param->interval; + if (newint < 0) + { + stamp1.timeleft = stamp1.timeout + newint; + } + } + + next = now; + next.tv_sec += interval.tv_sec; + next.tv_nsec += interval.tv_nsec; + tsnorm(&next); + ret = clock_nanosleep(param->clock, TIMER_ABSTIME, &next, NULL); + if (ret < 0) + { + goto threadend; + } + break; + case W_DEVTIMER: + if (config.meas_method == M_TIMER_API) + { + /* We suppose the timer resets itself when it + * overflows. So the first timestamp is timeout. + */ + + stamp1.timeleft = stamp1.timeout; + } + else if (config.meas_method == M_GETTIME) + { + /* If we measure using the POSIX API, we must get the + * microseconds in which the currently running timer + * is supposed to timeout. We then convert this + * to the timespec struct, indicating the start. + */ + + ret = timerdev_getstatus(&stamp1); + if (ret < 0) + { + perror("TCIOC_GETSTATUS"); + goto threadend; + } + + ret = clock_gettime(param->clock, &next); + if (ret < 0) + { + goto threadend; + } + + next.tv_sec += (stamp1.timeleft) / 1000000; + next.tv_nsec += (stamp1.timeleft % 1000000) * 1000; + tsnorm(&next); + } + + if ((ret = poll(polltimer, 1, -1)) < 0) + { + goto threadend; + } + break; + default: + break; + } + + /* Time Stamp 2 */ + + switch (config.meas_method) + { + case M_GETTIME: + if ((ret = clock_gettime(param->clock, &now)) < 0) + { + goto threadend; + } + + diff = timediff_us(now, next); + break; + case M_TIMER_API: + ret = timerdev_getstatus(&stamp2); + if (ret < 0) + { + perror("TCIOC_GETSTAUS"); + goto threadend; + } + + diff = timediff_us_timer(stamp2, stamp1); + break; + default: + break; + } + + if (diff < stats->min) + { + stats->min = diff; + } + + if (diff > stats->max) + { + stats->max = diff; + } + + stats->avg += (double) diff; + + if (config.histogram) + { + if (diff < config.histogram && diff >= 0) + { + stats->hist_array[diff] += 1; + } + else + { + stats->hist_overflow += 1; + } + } + + ++stats->cycles; + if (param->max_cycles != 0 && stats->cycles >= param->max_cycles) + { + stats->ended = true; + break; + } + + if (config.duration != 0) + { + if ((ret = clock_gettime(param->clock, &now)) < 0) + { + goto threadend; + } + + if (timediff_us(now, endtime) >= 0) + { + stats->ended = true; + break; + } + } + } + +threadend: + return NULL; +} + +static inline void init_thread_param(struct thread_param_s *param, + unsigned long interval, + unsigned long max_cycles, + int policy, + int prio, + struct thread_stats_s *stats, + int clock) +{ + stats->avg = 0.0; + stats->cycles = 0; + stats->max = -LONG_MAX; + stats->min = LONG_MAX; + stats->hist_overflow = 0; + stats->ended = false; + + param->interval = interval; + param->max_cycles = max_cycles; + param->policy = policy; + param->prio = prio; + param->stats = stats; + param->clock = clock; +} + +/* Copied from the original rt-tests/cyclictest. + * This way, the output is compatible with the original cyclictest. + */ + +static void print_hist(struct thread_param_s *par[], int nthreads) +{ + int i; + int j; + unsigned long long int log_entries[nthreads + 1]; + unsigned long maxmax; + unsigned long alloverflows; + + bzero(log_entries, sizeof(log_entries)); + + printf("# Histogram\n"); + for (i = 0; i < config.histogram; i++) + { + unsigned long long int allthreads = 0; + + printf("%06d ", i); + + for (j = 0; j < nthreads; j++) + { + unsigned long curr_latency = par[j]->stats->hist_array[i]; + printf("%06lu", curr_latency); + if (j < nthreads - 1) + { + printf("\t"); + } + + log_entries[j] += curr_latency; + allthreads += curr_latency; + } + + if (config.histofall && nthreads > 1) + { + printf("\t%06llu", allthreads); + log_entries[nthreads] += allthreads; + } + + printf("\n"); + } + + printf("# Total:"); + + for (j = 0; j < nthreads; j++) + { + printf(" %09llu", log_entries[j]); + } + + if (config.histofall && nthreads > 1) + { + printf(" %09llu", log_entries[nthreads]); + } + + printf("\n"); + printf("# Min Latencies:"); + + for (j = 0; j < nthreads; j++) + { + printf(" %05lu", par[j]->stats->min); + } + + printf("\n"); + printf("# Avg Latencies:"); + + for (j = 0; j < nthreads; j++) + { + printf(" %05lu", par[j]->stats->cycles ? + (long)(par[j]->stats->avg / par[j]->stats->cycles) : 0); + } + + printf("\n"); + printf("# Max Latencies:"); + + maxmax = 0; + for (j = 0; j < nthreads; j++) + { + printf(" %05lu", par[j]->stats->max); + if (par[j]->stats->max > maxmax) + { + maxmax = par[j]->stats->max; + } + } + + if (config.histofall && nthreads > 1) + { + printf(" %05lu", maxmax); + } + + printf("\n"); + printf("# Histogram Overflows:"); + + alloverflows = 0; + for (j = 0; j < nthreads; j++) + { + printf(" %05lu", par[j]->stats->hist_overflow); + alloverflows += par[j]->stats->hist_overflow; + } + + if (config.histofall && nthreads > 1) + { + printf(" %05lu", alloverflows); + } + + printf("\n"); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, char *argv[]) +{ + int i; + int ret; + struct thread_param_s **params = NULL; + struct thread_stats_s **stats = NULL; + struct sigevent event; + struct timer_notify_s tnotify; + uint32_t maxtimeout_timer; + uint32_t reqtimeout_timer; + + running = true; + config.clock = CLOCK_MONOTONIC; + config.distance = 500; + config.duration = 0; + config.histogram = 0; + config.histofall = 0; + config.interval = 1000; + config.loops = 0; + config.threads = 1; + config.prio = 0; + config.policy = SCHED_FIFO; + config.meas_method = M_GETTIME; + config.wait_method = W_NANOSLEEP; + config.timer_dev = NULL; + + if (!parse_args(argc, argv)) + { + print_help(); + return ERROR; + } + + if (!check_args_logic()) + { + print_help(); + return ERROR; + } + + /* Timer must be configured */ + + if (config.wait_method == W_DEVTIMER || config.meas_method == M_TIMER_API) + { + timerfd = open(config.timer_dev, O_RDWR); + if (timerfd < 0) + { + perror("Failed to open the device timer"); + return ERROR; + } + + /* Configure the timer notification */ + + polltimer[0].fd = timerfd; + polltimer[0].events = POLLIN; + + /* Fill in the notify struct + * We do not want any signalling. But we must configure it, + * because without the timer will not start. + */ + + memset(&event, 0, sizeof(event)); + event.sigev_notify = SIGEV_NONE; + + tnotify.periodic = true; + tnotify.pid = getpid(); + tnotify.event = event; + + /* Now set timeout of the timer. + * This depends on several factors. + * + * If wait_method == W_DEVTIMER, the timeout is set to config.interval + * (to achieve periodic operation). The extra time is measured by + * NANOSLEEP or the timer itself. If the timer is used, the timer + * zeroes itself when the timeout is reached, so we just get + * the timer value after poll has stopped blocking. + * + * If wait_method != W_DEVTIMER, we must set the timeout to at least + * the double of the maximum of all thread intervals + * (if you're not sure, please consult Claude Shannon). + * + * This raises the question: what if wait_method == W_DEVTIMER + * and meas_method == W_TIMER_API and the thread wakes up later + * then the timer's timeout? The solution is to have a different + * timer which runs slower and can measure overruns. + * But this would overcomplicate things. + */ + + if (config.wait_method == W_DEVTIMER) + { + reqtimeout_timer = config.interval; + } + else if (config.wait_method == W_NANOSLEEP) + { + /* Multiply by 3 instead of 2, just to be sure */ + + reqtimeout_timer = 3 * (config.interval + + (config.threads - 1) * config.distance); + } + else + { + reqtimeout_timer = 2 * CONFIG_USEC_PER_TICK; + } + + ret = ioctl(timerfd, TCIOC_MAXTIMEOUT, + (unsigned long)((uintptr_t)&maxtimeout_timer)); + if (ret < 0) + { + perror("TCIOC_MAXTIMEOUT"); + goto errtimer; + } + + if (reqtimeout_timer > maxtimeout_timer) + { + fprintf(stderr, "The timer cannot measure such periods!\n"); + goto errtimer; + } + + ret = ioctl(timerfd, TCIOC_SETTIMEOUT, + (unsigned long)reqtimeout_timer); + if (ret < 0) + { + perror("TCIOC_SETTIMEOUT"); + goto errtimer; + } + + ret = ioctl(timerfd, TCIOC_NOTIFICATION, + (unsigned long)((uintptr_t)&tnotify)); + if (ret < 0) + { + perror("TCIOC_NOTIFICATION"); + goto errtimer; + } + + /* If the timer is used only for measurement, start it here, otherwise + * start it only in one thread. + */ + + if (config.wait_method != W_DEVTIMER) + { + ret = ioctl(timerfd, TCIOC_START); + if (ret < 0) + { + perror("TCIOC_START"); + goto errtimer; + } + } + } + + params = calloc(config.threads, sizeof(struct thread_param_s *)); + if (params == NULL) + { + perror("params"); + ret = ERROR; + goto main_error; + } + + stats = calloc(config.threads, sizeof(struct thread_stats_s *)); + if (stats == NULL) + { + perror("stats"); + ret = ERROR; + goto main_error; + } + + for (i = 0; i < config.threads; ++i) + { + params[i] = malloc(sizeof(struct thread_param_s)); + if (params == NULL) + { + perror("params[i]"); + ret = ERROR; + goto main_error; + } + + stats[i] = malloc(sizeof(struct thread_stats_s)); + if (params == NULL) + { + perror("stats[i]"); + ret = ERROR; + goto main_error; + } + + stats[i]->hist_array = calloc(config.histogram, sizeof(long)); + if (stats[i]->hist_array == NULL) + { + perror("hist_array"); + ret = ERROR; + goto main_error; + } + + init_thread_param(params[i], config.interval, config.loops, + config.policy, config.prio, stats[i], config.clock); + + pthread_create(&stats[i]->id, NULL, testthread, params[i]); + config.interval += config.distance; + if (config.prio > 1) + { + config.prio--; + } + } + + while (running) + { + /* Periodically update the output */ + + usleep(100 * 1000); + int ended = 0; + for (i = 0; i < config.threads; ++i) + { + if (stats[i]->ended) + { + ended += 1; + } + } + + if (ended == config.threads) + { + running = false; + } + } + + for (i = 0; i < config.threads; ++i) + { + pthread_join(stats[i]->id, NULL); + } + + if (config.histogram) + { + print_hist(params, config.threads); + } + + ret = OK; + if (config.wait_method == W_DEVTIMER || config.meas_method == M_TIMER_API) + { + ret = ioctl(timerfd, TCIOC_STOP); + if (ret < 0) + { + perror("TCIOC_STOP"); + ret = ERROR; + } + + close(timerfd); + } + +main_error: + if (stats != NULL) + { + for (i = 0; i < config.threads; ++i) + { + if (params[i] != NULL) + { + free(params[i]); + } + + if (stats[i] != NULL) + { + if (stats[i]->hist_array != NULL) + { + free(stats[i]->hist_array); + } + + free(stats[i]); + } + } + } + + free(stats); + return ret; + +errtimer: + close(timerfd); + return ERROR; +}