Hi! > +Dependencies > +------------ > +CPU partitioning is performed by the partrt tool, which is available as a git > +submodule. Before building, do the following from the LTP root directory to > +include rt-tools in the build: > + > + git submodule update utils/rt-tools
I think that you have to do git submodule init prior to this (at least if you do this right after you cloned git repository). > +The following build time kernel configurations must be enabled: > + > + CONFIG_NO_HZ_FULL=y > + CONFIG_CPUSETS=y > + > +These kernel configuration parameters are also documented in the > +READ.kernel_config file in LTP root directory. > + > +The following kernel boot parameter needs to be set: > + > + nohz_full=<cpu list> > + > +There might be other parameters needed to actually get nohz_full working, but > +this is largely dependent on architecture and kernel version. > + > +How to run > +---------- > + > +The partrt_nohz_full test is run by runltp like so: > + > + ./runltp -f partrt_nohz_full > + > +To make life interesting, you can play with "-n" and "-i" options to runltp, > to > +generate some load in the non-realtime partition: > + > + ./runltp -n -i 5 -f partrt_nohz_full > + > +To get some more help with the cause of a failing test, do this as root > before > +running the test: > + > + echo 1 > /sys/kernel/debug/tracing/events/enable > + > +and then look at trace after test failed: > + > + cat /sys/kernel/debug/tracing/trace > + > +Since "count_ticks" also uses ftrace and will update tracing_cpumask to only > +trace the nohz_full CPUs, the trace should be close to empty if the run was > +successful. > diff --git a/testcases/kernel/partrt/nohz_full/.gitignore > b/testcases/kernel/partrt/nohz_full/.gitignore > new file mode 100644 > index 0000000..5848b21 > --- /dev/null > +++ b/testcases/kernel/partrt/nohz_full/.gitignore > @@ -0,0 +1 @@ > +/test_partrt_nohz_full > diff --git a/testcases/kernel/partrt/nohz_full/Makefile > b/testcases/kernel/partrt/nohz_full/Makefile > new file mode 100644 > index 0000000..5528dc0 > --- /dev/null > +++ b/testcases/kernel/partrt/nohz_full/Makefile > @@ -0,0 +1,23 @@ > +# > +# Copyright (C) 2014, Enea AB. > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 2 of the License, or > +# (at your option) any later version. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See > +# the GNU General Public License for more details. > +# > + > +top_srcdir ?= ../../../.. > + > +include $(top_srcdir)/include/mk/testcases.mk > + > +LDLIBS += -lrt > + > +MAKE_TARGETS := test_partrt_nohz_full > + > +include $(top_srcdir)/include/mk/generic_leaf_target.mk > diff --git a/testcases/kernel/partrt/nohz_full/test_partrt_nohz_full.c > b/testcases/kernel/partrt/nohz_full/test_partrt_nohz_full.c > new file mode 100644 > index 0000000..1343b71 > --- /dev/null > +++ b/testcases/kernel/partrt/nohz_full/test_partrt_nohz_full.c > @@ -0,0 +1,551 @@ > +/* > + * Copyright (C) 2014, Enea AB. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See > + * the GNU General Public License for more details. > + */ > + > +#define _GNU_SOURCE > + > +#include <errno.h> > +#include <string.h> > +#include <getopt.h> > +#include <sys/types.h> > +#include <sys/wait.h> > +#include <sys/syscall.h> > +#include <sys/stat.h> > +#include <sys/timerfd.h> > +#include <stdarg.h> > +#include <inttypes.h> > +#include <limits.h> > +#include <time.h> > +#include <sched.h> > +#include <fcntl.h> > + > +#include <test.h> > +#include <usctest.h> > +#include <safe_macros.h> > +#include <safe_stdio.h> > + > +const char *TCID = "partrt_nohz_full"; > +const int TST_TOTAL = 1; > + > +/* Used for RT load */ > +volatile int dummy_value; > + > +/* When true (1), all children will terminate */ > +static volatile int time_is_up; > + > +/* Verbosity level chosen from program command line. > + * 0 = No extra information from called sub-programs > + * 1 = Request verbose information from called sub-programs > + * 2 = Request more verbose inormation from called sub-programs. > + */ > +static int verbose; The verbosity level doesn't seem to be used anywhere (apart from being set while parsing command line options). > +/* Name of application as given in argv[0] */ > +static const char *appname; > + > +/* Boolean describing whether RT partition in cpuset needs to be created */ > +int need_partrt_create = 0; > + > +/* RT load function prototype */ > +typedef void (child_func)(void); > + > +/* Amount of stack for RT load threads */ > +#define STACK_SZ (64 * 1024) > + > +/* Default path to cgroups/cpuset */ > +#define CPUSET_ROOT "/sys/fs/cgroup/cpuset" > + > +/* Expected path to cpuset RT partition */ > +#define CPUSET_RT_PATH CPUSET_ROOT "/rt" > + > +/* Expected path to RT partition tasks list file */ > +#define CPUSET_RT_TASKS_FILE CPUSET_RT_PATH "/tasks" > + > +/* Expected path to RT partition CPU list file */ > +#define CPUSET_RT_CPUS_FILE CPUSET_RT_PATH "/cpuset.cpus" > + > +/* File to look for to determine whether true 0Hz can be achieved */ > +#define SCHED_TICK_MAX_FILE "/sys/kernel/debug/sched_tick_max_deferment" > + > +/* File containing a list of filesystems supported by the kernel */ > +#define PROC_FILESYSTEMS_FILE "/proc/filesystems" > + > +static void cleanup(void); > + > +/* > + * Check exit status value. > + * If any signs of error is found, report error and abort testing. > + */ > +static void assert_exit_status(const char *cmd, int status) > +{ > + if (status == -1) > + tst_brkm(TBROK | TERRNO, cleanup, > + "%s: Could not execute command", cmd); > + if (WIFSIGNALED(status)) > + tst_brkm(TBROK, cleanup, > + "%s: Child terminated unexpectedly due to signal nr > %d", > + cmd, > + WTERMSIG(status)); I think that it's more readbale to add the curly braces around block that spawns to multiple lines even if it's technically one function call. > + if (!WIFEXITED(status)) > + tst_brkm(TBROK, cleanup, "%s: Child terminated unexpectedly", > + cmd); > + if (WEXITSTATUS(status) != 0) > + tst_brkm(TBROK, cleanup, "%s: Child exited with exit status %d", > + cmd, > + WEXITSTATUS(status)); > +} > + > +/* > + * Execute given command using shell, ensure success (0) is returned. > + */ > +static void shell(const char *cmd, ...) > +{ > + char *cmd_buf; > + va_list va; > + > + va_start(va, cmd); > + if (vasprintf(&cmd_buf, cmd, va) == -1) > + tst_brkm(TBROK, cleanup, > + "%s: Not valid printf format, or out of memory", cmd); > + va_end(va); > + > + tst_resm(TINFO, "%s: Executing", cmd_buf); > + assert_exit_status(cmd_buf, system(cmd_buf)); > + tst_resm(TINFO, "%s: Returns success", cmd_buf); > + > + free(cmd_buf); > +} > + > +/* > + * Convert string to unsigned long. > + * Error prefix expressed as string. > + */ > +static unsigned long str_to_ulong( > + const char *str, > + const char *err_prefix > + ) Why not just: static unsigned long str_to_ulong(const char *str, const char *err_prefix) As far as I can the line fits into 80 chars. > +{ > + char *endptr; > + unsigned long val; > + val = strtoull(str, &endptr, 0); > + if (endptr == str) > + tst_brkm(TBROK, cleanup, > + "%s: %s: Expected unsigned decimal value", > + err_prefix, str); > + if (*endptr != '\0') > + tst_brkm(TBROK, cleanup, > + "%s: %s: Garbage character '%c' found after decimal > value", > + err_prefix, str, *endptr); > + > + return val; > +} > + > +/* > + * Call a command using shell. > + * Command line expressed as va_list. > + * Returns the command's first line of output. > + */ > +static char *shell_str(char *dest, int size, const char *cmd, ...) > +{ > + int len; > + char *cmd_buf; > + FILE *file; > + va_list va; > + > + /* Build command line */ > + va_start(va, cmd); > + if (vasprintf(&cmd_buf, cmd, va) == -1) > + tst_brkm(TBROK, cleanup, > + "%s: Not valid printf format, or out of memory", cmd); > + va_end(va); > + > + /* Launch command */ > + errno = 0; > + file = popen(cmd_buf, "r"); > + if (file == NULL) { > + if (errno == 0) > + tst_brkm(TBROK, cleanup, "%s: popen(): Out of memory", > + cmd_buf); > + else > + tst_brkm(TBROK | TERRNO, cleanup, > + "%s: popen() failed", cmd_buf); > + } This may be worth of SAFE_POPEN(), I can add it if you want. > + tst_resm(TINFO, "%s: Executing", cmd_buf); > + > + /* Read commands stdout */ > + if (fgets(dest, size, file) == NULL) { > + if (feof(file)) > + tst_brkm(TBROK, cleanup, > + "%s: Expected output from the command, but got > nothing", > + cmd_buf); > + else > + tst_brkm(TBROK | TERRNO, cleanup, > + "%s: Could not read command output", > + cmd_buf); > + } > + > + /* Get rid of terminating newline, if any */ > + len = strlen(dest); > + if (dest[len-1] == '\n') > + dest[len-1] = '\0'; > + > + /* Wait until command execution finish. > + * Main reason for doing this even though we've got what we want is for > + * better error detection. The alternative could be to let cleanup() > + * handle it if process hasn't finished by then. */ > + assert_exit_status(cmd_buf, pclose(file)); > + > + tst_resm(TINFO, "%s: Returns: %s", cmd_buf, dest); > + > + free(cmd_buf); > + > + return dest; > +} > + > +static int child_entry(void *arg) > +{ > + (void) arg; > + > + while (!time_is_up) > + dummy_value = rand(); > + > + return 0; > +} > + > +/* > + * Start a new thread executing the given function, on the given cpuset > + * partition and CPU ID. Note that the CPU ID must be one of the > + * available CPUs in the cpuset partition. > + */ > +static void launch_child(int cpu) > +{ > + char *stack = SAFE_MALLOC(cleanup, STACK_SZ); > + int tid; > + const int cpuset_tasks_fd = SAFE_OPEN(cleanup, CPUSET_RT_TASKS_FILE, > + O_WRONLY); > + char *tid_str; > + cpu_set_t cpu_set; > + > + tid = ltp_clone(CLONE_VM, child_entry, NULL, STACK_SZ, stack); > + if (tid == -1) > + tst_brkm(TBROK | TERRNO, cleanup, "clone() failed"); > + > + /* Move child to RT partition */ > + SAFE_ASPRINTF(cleanup, &tid_str, "%ld", (long) tid); > + SAFE_WRITE(cleanup, 1, cpuset_tasks_fd, tid_str, strlen(tid_str)); > + > + free(tid_str); > + SAFE_CLOSE(cleanup, cpuset_tasks_fd); SAFE_FILE_PRINTF(cleanup, CPUSET_RT_TASKS_FILE, "%ld", (long)tid) ? Or is there a good reason to use open(), asprintf(), write() and close() instead? > + > + /* Move child to indicated CPU within the RT partition */ > + CPU_ZERO(&cpu_set); > + CPU_SET(cpu, &cpu_set); > + if (sched_setaffinity(tid, sizeof(cpu_set), &cpu_set) < 0) > + tst_brkm(TBROK | TERRNO, cleanup, > + "pid %u: sched_setaffinity() failed", > + tid); > + > + tst_resm(TINFO, "pid %d: Starting RT load on cpu %d", > + tid, cpu); > +} > + > +static void cleanup(void) > +{ > + static int cleanup_entered; > + > + pid_t pid; > + int status; > + > + if (cleanup_entered) > + return; /* Called from cleanup() */ > + > + cleanup_entered = 1; Why is this still needed? It shouldn't be if only parent process uses the tst_* interface. > + tst_resm(TINFO, "Cleanup: Terminating children"); > + > + time_is_up = 1; > + > + do { > + pid = wait(&status); > + if (pid != -1) { > + char cmd[64]; > + snprintf(cmd, sizeof(cmd), "Pid %lu", > + (unsigned long) pid); > + assert_exit_status(cmd, status); > + tst_resm(TINFO, > + "Cleanup: %s: Has terminated successfully", > + cmd); > + } > + } while (pid != -1); > + > + if (errno != ECHILD) > + tst_brkm(TBROK | TERRNO, NULL, "Cleanup: wait() failed"); > + > + if (need_partrt_create) > + shell("partrt undo"); > + > + tst_resm(TINFO, "Cleanup: Done"); > +} > + > +static unsigned long determine_nohz_mask(void) > +{ > + int range_first; > + int range_last; > + int bit; > + unsigned long mask = 0; > + FILE *stream = SAFE_FOPEN(cleanup, CPUSET_RT_CPUS_FILE, "r"); > + int nr_matches; > + > + for (nr_matches = fscanf(stream, "%d-%d", &range_first, &range_last); > + nr_matches > 0; > + nr_matches = fscanf(stream, ",%d-%d", &range_first, &range_last)) { > + if (nr_matches == 1) > + range_last = range_first; > + > + /* Set all bits in range */ > + for (bit = range_first; bit <= range_last; bit++) > + mask |= (1 << bit); Are you sure that this would not overflow? I guess that it depends on how you have partitioned your CPUs. > + } > + > + SAFE_FCLOSE(cleanup, stream); > + > + if (nr_matches == -1) > + tst_brkm(TBROK | TERRNO, cleanup, "%s: fscanf() failed", > + CPUSET_RT_CPUS_FILE); > + > + return mask; > +} > + > +static void tool_available(char *tool_name, const char *env_path) > +{ > + int success = 0; > + > + if (strchr(tool_name, '/') != NULL) { > + if (access(tool_name, X_OK) == 0) > + success = 1; > + } else { > + char full_path[PATH_MAX]; > + const char *curr; > + char * const alloced_path = strdup(env_path); > + char *next = alloced_path; > + > + for (curr = strsep(&next, ":"); > + curr != NULL; > + curr = strsep(&next, ":")) { > + if (*curr == '\0') > + curr = "."; > + if (snprintf(full_path, sizeof(full_path), > + "%s/%s", curr, tool_name > + ) >= (int)sizeof(full_path)) > + continue; > + if (access(full_path, X_OK) == 0) { > + success = 1; > + break; > + } > + } > + free(alloced_path); > + } > + > + if (!success) > + tst_brkm(TCONF, cleanup, > + "%s tool not found, skipping test. Use 'git submodule > update utils/rt-tools' to include needed tools in the build.", > + tool_name); > +} > + > +static void tools_check(void) > +{ > + const char *env_path = getenv("PATH"); > + > + if (env_path == NULL) > + env_path = ""; > + > + tool_available("partrt", env_path); > + tool_available("list2mask", env_path); > + tool_available("count_ticks", env_path); We have tst_get_path() that could be used as: char buf[2048]; if (!tst_get_path("partrt", buf, sizeof(buf))) tst_brkm(TCONF, cleanup, "Tool partrt not available"); > +} > + > +static void cpuset_check(void) > +{ > + FILE * const stream = SAFE_FOPEN(cleanup, PROC_FILESYSTEMS_FILE, "r"); > * > + char *name; > + > + while (fscanf(stream, "%as", &name) > 0) { > + if (strcmp(name, "cpuset") == 0) { > + free(name); > + return; > + } > + free(name); > + } > + > + tst_brkm(TCONF, cleanup, "CPUSET not configured in kernel"); > +} > + > +/* > + * Setup and return number of child threads > + */ > +static int setup_children(void) > +{ > + unsigned long nohz_mask; > + int bit; > + int nr_children = 0; > + > + time_is_up = 0; > + > + tools_check(); > + tst_require_root(NULL); > + cpuset_check(); > + > + if (access(CPUSET_RT_PATH, F_OK) == -1) > + need_partrt_create = 1; > + > + if (need_partrt_create) > + shell("partrt create $(list2mask --nohz)"); > + > + nohz_mask = determine_nohz_mask(); > + tst_resm(TINFO, "Nohz CPU mask: %#lx", nohz_mask); > + > + for (bit = 0; bit < (int) (sizeof(nohz_mask) * 8); bit++) { > + if (nohz_mask & (1lu << bit)) { > + launch_child(bit); > + nr_children++; > + } > + } > + > + tst_resm(TINFO, "All children started"); > + return nr_children; > +} > + > +/* > + * Perform tick measurement for the given number of seconds. > + */ > +static void test(time_t duration, unsigned nr_children) > +{ > + uint64_t nr_ticks; > + time_t time_finished; > + /* If sched_tick_max_deferment patch has been applied, expect that the > + * partitioning has disabled ticks completely. Otherwise, expect > + * 1Hz ticks */ > + const int expect_0hz = > + access(SCHED_TICK_MAX_FILE, F_OK) == 0; > + const time_t current_time = time(NULL); > + > + const uint64_t expected_ticks = expect_0hz ? 0 : duration * nr_children; > + char val_str[64]; > + static const char count_ticks_end_cmd[] = > + "count_ticks --cpu rt --batch --end"; > + int timer_fd; > + struct itimerspec timeout = { {0}, {0} }; > + uint64_t nr_timeouts; > + > + shell("count_ticks --cpu rt --start"); > + > + time_finished = time(NULL) + duration; > + > + tst_resm(TINFO, "Execution started: %s", ctime(¤t_time)); > + tst_resm(TINFO, "Execution ends : %s", ctime(&time_finished)); What is this information good for? As far as I can see it says how long it took to prepare for the actual test. > + timer_fd = timerfd_create(CLOCK_MONOTONIC, 0); > + timeout.it_value.tv_sec = duration; > + timerfd_settime(timer_fd, 0, &timeout, NULL); > + > + /* Wait for timeout */ > + SAFE_READ(cleanup, 1, timer_fd, &nr_timeouts, sizeof(nr_timeouts)); > + if (nr_timeouts != 1) > + tst_brkm(TBROK, cleanup, > + "Multiple timeouts when single timeout was requested"); > + > + shell_str(val_str, sizeof(val_str), count_ticks_end_cmd); > + nr_ticks = str_to_ulong(val_str, count_ticks_end_cmd); > + > + if (expect_0hz) { > + if (nr_ticks != 0) > + tst_resm(TFAIL, "Expected no ticks, but got %" PRIu64, > + nr_ticks); > + else > + tst_resm(TPASS, "No ticks occurred"); > + } else { > + if (nr_ticks > expected_ticks) > + tst_resm(TFAIL, > + "Expected maximum %" PRIu64 > + " ticks, but got %" PRIu64, > + expected_ticks, nr_ticks); > + else > + tst_resm(TPASS, > + "%" PRIu64 > + " ticks occurred, which was expected", > + nr_ticks); > + } > +} > + > +static void usage(void) > +{ > + printf( > + "Usage: %s [options]\n" > + " %s --help\n" > + "Test whether a CPU can be isolated and made tickless even > under load.\n" > + "\n" > + " -h Display this usage text and exit.\n" > + " -v <level> Set verbose level, 0 = default.\n" > + " -d <secs> Number of seconds to run the test.\n" > + , > + appname, appname > + ); > + > + exit(1); The library exits after usage is printed, the exit(1) here will not be reached. > +} > + > +int main(int argc, char *argv[]) > +{ > + long duration = 0; > + int lc; > + unsigned int nr_children; > + char *error_msg; > + char *verbose_str = NULL; > + char *duration_str = NULL; > + > + const option_t options[] = { > + {"v:", NULL, &verbose_str}, > + {"d:", NULL, &duration_str}, > + {NULL, NULL, NULL} > + }; > + > + verbose = 0; > + appname = basename(argv[0]); > + > + error_msg = parse_opts(argc, argv, options, usage); > + if (error_msg != NULL) > + tst_brkm(TBROK, NULL, "Error parsing command line: %s", > + error_msg); > + > + if (verbose_str != NULL) > + verbose = str_to_ulong(verbose_str, "-v"); > + > + if (duration_str == NULL) > + tst_brkm(TBROK, cleanup, > + "No duration specified, nothing to do"); > + > + duration = (long) str_to_ulong(duration_str, "-d"); > + > + tst_resm(TINFO, "%s: Compiled %s %s", __FILE__, __DATE__, __TIME__); I would say that this is not very useful information, or is it? > + nr_children = setup_children(); > + > + for (lc = 0; TEST_LOOPING(lc); lc++) > + test(duration, nr_children); > + > + cleanup(); > + tst_exit(); > + > + /* Should not end up here */ > + return 1; Remove the return from here. The tst_exit() is marked as __attribute__((noreturn)) and the compiler knows that you cannot get here. > +} > diff --git a/utils/.gitignore b/utils/.gitignore > index a582748..f63e51a 100644 > --- a/utils/.gitignore > +++ b/utils/.gitignore > @@ -48,3 +48,6 @@ > /sctp/func_tests/test_tcp_style_v6 > /sctp/func_tests/test_timetolive > /sctp/func_tests/test_timetolive_v6 > +/count_ticks > +/list2mask > +/partrt > diff --git a/utils/Makefile b/utils/Makefile > index 1508b35..8c2fc7e 100644 > --- a/utils/Makefile > +++ b/utils/Makefile > @@ -22,7 +22,11 @@ top_srcdir ?= .. > > include $(top_srcdir)/include/mk/env_pre.mk > > +ifneq ($(wildcard rt-tools),) > +MAKE_TARGETS += ffsb partrt list2mask count_ticks > +else > MAKE_TARGETS += ffsb > +endif This would be better as: MAKE_TARGETS += ffsb ifneq ($(wildcard rt-tools),) MAKE_TARGETS += partrt list2mask count_ticks endif because otherwise we would need to add any new tool into two places which is prone to mistakes. > FFSBDIR := ffsb-6.0-rc2 > FILTER_OUT_DIRS := $(FFSBDIR) > @@ -35,6 +39,9 @@ $(FFSB): $(abs_srcdir)/$(FFSBDIR) > ffsb: $(FFSB) > cp $(FFSB) ffsb > > +partrt list2mask count_ticks: > + cp rt-tools/install/bin/$@ $@ > + > trunk-all: $(FFSB) > > trunk-clean:: | ffsb-clean > diff --git a/utils/rt-tools b/utils/rt-tools > new file mode 160000 > index 0000000..f75b334 > --- /dev/null > +++ b/utils/rt-tools > @@ -0,0 +1 @@ > +Subproject commit f75b334922a2243d0b757c0627c6f1c8440818c0 > -- > 1.7.10.4 > > > ------------------------------------------------------------------------------ > "Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE > Instantly run your Selenium tests across 300+ browser/OS combos. Get > unparalleled scalability from the best Selenium testing platform available. > Simple to use. Nothing to install. Get started now for free." > http://p.sf.net/sfu/SauceLabs > _______________________________________________ > Ltp-list mailing list > Ltp-list@lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/ltp-list -- Cyril Hrubis chru...@suse.cz ------------------------------------------------------------------------------ Is your legacy SCM system holding you back? Join Perforce May 7 to find out: • 3 signs your SCM is hindering your productivity • Requirements for releasing software faster • Expert tips and advice for migrating your SCM now http://p.sf.net/sfu/perforce _______________________________________________ Ltp-list mailing list Ltp-list@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/ltp-list