diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt index 156c6e8caa75..f66ccd77bfe7 100644 --- a/tools/perf/Documentation/perf-test.txt +++ b/tools/perf/Documentation/perf-test.txt @@ -29,8 +29,9 @@ REQUISITES ---------- Shell tests run perf, that maps ring buffers, so they are taken to need room in the per user budget set in kernel.perf_event_mlock_kb, shared by the tests -running at the same time. Tests state what they need, or waive, in a comma -separated list in the parens ending their description line, e.g.: +running at the same time, that take turns using it. Tests state what they +need, or waive, in a comma separated list in the parens ending their +description line, e.g.: # perf stat tests (no_mlock) diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index c187772948c4..880eece0bc12 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -33,6 +33,8 @@ #include #include +#include + #include "builtin.h" #include "color.h" #include "config.h" @@ -43,6 +45,9 @@ #include "symbol.h" #include "tests-scripts.h" #include "tests.h" +#include "util/cap.h" +#include "util/event.h" +#include "util/mutex.h" #include "util/rlimit.h" #include "util/strbuf.h" #include "util/term.h" @@ -63,6 +68,17 @@ static bool list_requisites; static unsigned int runs_per_test = 1; /* Number of lines to include in failure snippet. */ static unsigned int failure_snippet_lines = 10; +/* + * The tests mapping ring buffers take turns using their share of + * kernel.perf_event_mlock_kb via this mutex, shared with the forked test + * processes and robust so that a test killed while holding it doesn't leave + * the budget locked, see mutex_init_pshared_robust(). + */ +struct mlock_budget { + struct mutex lock; +}; + +static struct mlock_budget *mlock_budget; const char *dso_to_test; const char *test_objdump_path = "objdump"; static const char *workload_control; @@ -379,12 +395,30 @@ static void child_test_sig_handler(int sig) siglongjmp(run_test_jmp_buf, sig); } +static int run_test_case(struct child_test *child) +{ + return test_function(child->test, child->test_case_num)(child->test, child->test_case_num); +} + +/* The thread safety analysis can't express a lock held conditionally. */ +static int run_test_case_mlocked(struct child_test *child) +{ + int err; + + mutex_lock(&mlock_budget->lock); + err = run_test_case(child); + mutex_unlock(&mlock_budget->lock); + + return err; +} + static int run_test_child(struct child_process *process) { const int signals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM, }; struct child_test *child = container_of(process, struct child_test, process); + bool needs_mlock = mlock_budget && child->test->needs_mlock; int err; close_parent_fds(); @@ -401,7 +435,12 @@ static int run_test_child(struct child_process *process) pr_debug("---- start ----\n"); pr_debug("test child forked, pid %d\n", getpid()); - err = test_function(child->test, child->test_case_num)(child->test, child->test_case_num); + + if (needs_mlock) + err = run_test_case_mlocked(child); + else + err = run_test_case(child); + pr_debug("---- end(%d) ----\n", err); check_leaks(); @@ -1777,6 +1816,92 @@ static struct test_suite **build_suites(void) #undef for_each_suite } +static bool perf_test__shares_mlock_budget(struct test_suite **suites, int argc, + const char *argv[], struct intlist *skiplist) +{ + int sharing = 0; + + /* The selection rules mirrored here are __cmd_test()'s: when they change, change both. */ + for (int curr_suite = 0; suites[curr_suite]; curr_suite++) { + struct test_suite *t = suites[curr_suite]; + int curr_test_case; + bool suite_matched; + + if (!t->needs_mlock || intlist__find(skiplist, curr_suite + 1)) + continue; + + suite_matched = perf_test__matches(test_description(t, /*test_case=*/-1), + curr_suite, argc, argv); + test_suite__for_each_test_case(t, curr_test_case) { + if (test_exclusive(t, curr_test_case)) + continue; + + if (suite_matched || + perf_test__matches(test_description(t, curr_test_case), + curr_suite, argc, argv)) + sharing += runs_per_test; + } + if (sharing > 1) + return true; + } + return false; +} + +/* + * Once the per user budget set in kernel.perf_event_mlock_kb is spent, what + * maps on top of it is charged against RLIMIT_MEMLOCK and the mapping is + * refused with EPERM when that runs out too, but only for the processes the + * kernel is paranoid about and that have no CAP_IPC_LOCK to lift the check, + * see perf_mmap_calc_limits() in the kernel: used by + * perf_test__setup_mlock_budget() below, that has nothing to take turns on + * when neither applies. + */ +static bool perf_test__mlock_budget_exhaustible(void) +{ + if (perf_cap__capable(CAP_IPC_LOCK)) + return false; + + return perf_event_paranoid() > -1; +} + +static void perf_test__setup_mlock_budget(struct test_suite **suites, int argc, + const char *argv[], struct intlist *skiplist) +{ + /* Nothing can be denied the budget, nothing to take turns for. */ + if (!perf_test__mlock_budget_exhaustible()) { + pr_debug("Not taking turns using the perf mlock budget: nothing can exhaust it\n"); + return; + } + + /* Nothing runs at the same time, nothing to share the budget with. */ + if (sequential || !perf_test__shares_mlock_budget(suites, argc, argv, skiplist)) + return; + + mlock_budget = mmap(NULL, sizeof(*mlock_budget), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, /*fd=*/-1, /*offset=*/0); + if (mlock_budget == MAP_FAILED) { + mlock_budget = NULL; + pr_err("Failed to map the shared area used to take turns using the perf mlock budget\n"); + return; + } + + if (!mutex_init_pshared_robust(&mlock_budget->lock)) { + munmap(mlock_budget, sizeof(*mlock_budget)); + mlock_budget = NULL; + pr_debug("Not taking turns using the perf mlock budget\n"); + } +} + +static void perf_test__teardown_mlock_budget(void) +{ + if (!mlock_budget) + return; + + mutex_destroy(&mlock_budget->lock); + munmap(mlock_budget, sizeof(*mlock_budget)); + mlock_budget = NULL; +} + int cmd_test(int argc, const char **argv) { const char *test_usage[] = { @@ -1885,7 +2010,9 @@ int cmd_test(int argc, const char **argv) intlist__delete(skiplist); return err ? -err : -ENOMEM; } + perf_test__setup_mlock_budget(suites, argc, argv, skiplist); ret = __cmd_test(suites, argc, argv, skiplist); + perf_test__teardown_mlock_budget(); free(suites); intlist__delete(skiplist); return ret;