From 31daca45ba2d5c7ff31280742c463331870c3eb3 Mon Sep 17 00:00:00 2001 From: Stepan Pressl Date: Thu, 7 Nov 2024 19:27:07 +0100 Subject: [PATCH] benchmarks/cyclictest: the rt-tests cyclictest NuttX Port Despite the existence of the patch in benchmarks/rt-tests, this commit adds the NuttX Official cyclictest utility. The main difference is the introduction of different waiting methods next to POSIX clock_nanosleep: - The thread can wait for a g_waitsem, posted by board_timerhook() if CONFIG_SYSTEMTICK_HOOK is defined. Since the semaphore is only one, only one thread can wait. - The thread can wait for a Timer Device to timeout. The timer's timeout determines the waiting time of the thread. Since the timer is only one, again, only one thread can wait. The user can measure the elapsed time using clock_gettime or the timer device itself. The different waiting and measuring methods were introduced because NuttX, by default, does not offer fine measuring capabilities using POSIX time functions (as of Feb 25). Signed-off-by: Stepan Pressl --- benchmarks/cyclictest/CMakeLists.txt | 55 ++ benchmarks/cyclictest/Kconfig | 29 + benchmarks/cyclictest/Make.defs | 23 + benchmarks/cyclictest/Makefile | 39 + benchmarks/cyclictest/cyclictest.c | 1083 ++++++++++++++++++++++++++ 5 files changed, 1229 insertions(+) create mode 100644 benchmarks/cyclictest/CMakeLists.txt create mode 100644 benchmarks/cyclictest/Kconfig create mode 100644 benchmarks/cyclictest/Make.defs create mode 100644 benchmarks/cyclictest/Makefile create mode 100644 benchmarks/cyclictest/cyclictest.c 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; +}