perf test: Run parallel tests in two passes
authorIan Rogers <irogers@google.com>
Fri, 25 Oct 2024 19:21:06 +0000 (12:21 -0700)
committerNamhyung Kim <namhyung@kernel.org>
Mon, 28 Oct 2024 16:32:58 +0000 (09:32 -0700)
In pass 1 run all tests that succeed when run in parallel. In pass 2
sequentially run all remaining tests that are flagged as
"exclusive". Sequential and dont_fork tests keep to run in pass 1.
Read the exclusive flag from the shell test descriptions, but remove
from display to avoid >100 characters. Add error handling to finish
tests if starting a later test fails. Mark the task-exit test as
exclusive due to issues reported-by James Clark.

Tested-by: James Clark <james.clark@linaro.org>
Signed-off-by: Ian Rogers <irogers@google.com>
Cc: Colin Ian King <colin.i.king@gmail.com>
Cc: Howard Chu <howardchu95@gmail.com>
Cc: Weilin Wang <weilin.wang@intel.com>
Cc: Ilya Leoshkevich <iii@linux.ibm.com>
Cc: Thomas Richter <tmricht@linux.ibm.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Athira Jajeev <atrajeev@linux.vnet.ibm.com>
Cc: Michael Petlan <mpetlan@redhat.com>
Cc: Veronika Molnarova <vmolnaro@redhat.com>
Link: https://lore.kernel.org/r/20241025192109.132482-8-irogers@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
tools/perf/tests/builtin-test.c
tools/perf/tests/task-exit.c
tools/perf/tests/tests-scripts.c
tools/perf/tests/tests.h

index 8a720ddb03963e1fc54247ad87b894c83fb59397..b997d0a68ca24e6a6d044b75d78240bdedf777f0 100644 (file)
@@ -199,6 +199,14 @@ static test_fnptr test_function(const struct test_suite *t, int subtest)
        return t->test_cases[subtest].run_case;
 }
 
+static bool test_exclusive(const struct test_suite *t, int subtest)
+{
+       if (subtest <= 0)
+               return t->test_cases[0].exclusive;
+
+       return t->test_cases[subtest].exclusive;
+}
+
 static bool perf_test__matches(const char *desc, int curr, int argc, const char *argv[])
 {
        int i;
@@ -242,7 +250,7 @@ static int run_test_child(struct child_process *process)
        const int signals[] = {
                SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM,
        };
-       static struct child_test *child;
+       struct child_test *child = container_of(process, struct child_test, process);
        int err;
 
        err = sigsetjmp(run_test_jmp_buf, 1);
@@ -252,7 +260,6 @@ static int run_test_child(struct child_process *process)
                goto err_out;
        }
 
-       child = container_of(process, struct child_test, process);
        for (size_t i = 0; i < ARRAY_SIZE(signals); i++)
                signal(signals[i], child_test_sig_handler);
 
@@ -305,19 +312,25 @@ static int print_test_result(struct test_suite *t, int i, int subtest, int resul
        return 0;
 }
 
-static int finish_test(struct child_test **child_tests, int running_test, int child_test_num,
-                      int width)
+static void finish_test(struct child_test **child_tests, int running_test, int child_test_num,
+               int width)
 {
        struct child_test *child_test = child_tests[running_test];
-       struct test_suite *t = child_test->test;
-       int i = child_test->test_num;
-       int subi = child_test->subtest;
-       int err = child_test->process.err;
+       struct test_suite *t;
+       int i, subi, err;
        bool err_done = false;
        struct strbuf err_output = STRBUF_INIT;
        int last_running = -1;
        int ret;
 
+       if (child_test == NULL) {
+               /* Test wasn't started. */
+               return;
+       }
+       t = child_test->test;
+       i = child_test->test_num;
+       subi = child_test->subtest;
+       err = child_test->process.err;
        /*
         * For test suites with subtests, display the suite name ahead of the
         * sub test names.
@@ -347,6 +360,8 @@ static int finish_test(struct child_test **child_tests, int running_test, int ch
                        int running = 0;
 
                        for (int y = running_test; y < child_test_num; y++) {
+                               if (child_tests[y] == NULL)
+                                       continue;
                                if (check_if_command_finished(&child_tests[y]->process) == 0)
                                        running++;
                        }
@@ -399,23 +414,32 @@ static int finish_test(struct child_test **child_tests, int running_test, int ch
        print_test_result(t, i, subi, ret, width, /*running=*/0);
        if (err > 0)
                close(err);
-       return 0;
+       zfree(&child_tests[running_test]);
 }
 
 static int start_test(struct test_suite *test, int i, int subi, struct child_test **child,
-                     int width)
+               int width, int pass)
 {
        int err;
 
        *child = NULL;
        if (dont_fork) {
-               pr_debug("--- start ---\n");
-               err = test_function(test, subi)(test, subi);
-               pr_debug("---- end ----\n");
-               print_test_result(test, i, subi, err, width, /*running=*/0);
+               if (pass == 1) {
+                       pr_debug("--- start ---\n");
+                       err = test_function(test, subi)(test, subi);
+                       pr_debug("---- end ----\n");
+                       print_test_result(test, i, subi, err, width, /*running=*/0);
+               }
+               return 0;
+       }
+       if (pass == 1 && !sequential && test_exclusive(test, subi)) {
+               /* When parallel, skip exclusive tests on the first pass. */
+               return 0;
+       }
+       if (pass != 1 && (sequential || !test_exclusive(test, subi))) {
+               /* Sequential and non-exclusive tests were run on the first pass. */
                return 0;
        }
-
        *child = zalloc(sizeof(**child));
        if (!*child)
                return -ENOMEM;
@@ -434,10 +458,14 @@ static int start_test(struct test_suite *test, int i, int subi, struct child_tes
                (*child)->process.err = -1;
        }
        (*child)->process.no_exec_cmd = run_test_child;
-       err = start_command(&(*child)->process);
-       if (err || !sequential)
-               return  err;
-       return finish_test(child, /*running_test=*/0, /*child_test_num=*/1, width);
+       if (sequential || pass == 2) {
+               err = start_command(&(*child)->process);
+               if (err)
+                       return err;
+               finish_test(child, /*running_test=*/0, /*child_test_num=*/1, width);
+               return 0;
+       }
+       return start_command(&(*child)->process);
 }
 
 #define for_each_test(j, k, t)                                 \
@@ -447,12 +475,11 @@ static int start_test(struct test_suite *test, int i, int subi, struct child_tes
 static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist)
 {
        struct test_suite *t;
-       unsigned int j, k;
-       int i = 0;
        int width = 0;
+       unsigned int j, k;
        size_t num_tests = 0;
        struct child_test **child_tests;
-       int child_test_num = 0;
+       int err = 0;
 
        for_each_test(j, k, t) {
                int len = strlen(test_description(t, -1));
@@ -475,62 +502,73 @@ static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist)
        if (!child_tests)
                return -ENOMEM;
 
-       for_each_test(j, k, t) {
-               int curr = i++;
-
-               if (!perf_test__matches(test_description(t, -1), curr, argc, argv)) {
-                       bool skip = true;
+       /*
+        * In parallel mode pass 1 runs non-exclusive tests in parallel, pass 2
+        * runs the exclusive tests sequentially. In other modes all tests are
+        * run in pass 1.
+        */
+       for (int pass = 1; pass <= 2; pass++) {
+               int child_test_num = 0;
+               int i = 0;
+
+               for_each_test(j, k, t) {
+                       int curr = i++;
+
+                       if (!perf_test__matches(test_description(t, -1), curr, argc, argv)) {
+                               /*
+                                * Test suite shouldn't be run based on
+                                * description. See if subtest should.
+                                */
+                               bool skip = true;
+
+                               for (int subi = 0, subn = num_subtests(t); subi < subn; subi++) {
+                                       if (perf_test__matches(test_description(t, subi),
+                                                               curr, argc, argv))
+                                               skip = false;
+                               }
 
-                       for (int subi = 0, subn = num_subtests(t); subi < subn; subi++) {
-                               if (perf_test__matches(test_description(t, subi),
-                                                       curr, argc, argv))
-                                       skip = false;
+                               if (skip)
+                                       continue;
                        }
 
-                       if (skip)
+                       if (intlist__find(skiplist, i)) {
+                               pr_info("%3d: %-*s:", curr + 1, width, test_description(t, -1));
+                               color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip (user override)\n");
                                continue;
-               }
-
-               if (intlist__find(skiplist, i)) {
-                       pr_info("%3d: %-*s:", curr + 1, width, test_description(t, -1));
-                       color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip (user override)\n");
-                       continue;
-               }
-
-               if (!has_subtests(t)) {
-                       int err = start_test(t, curr, -1, &child_tests[child_test_num++], width);
+                       }
 
-                       if (err) {
-                               /* TODO: if !sequential waitpid the already forked children. */
-                               free(child_tests);
-                               return err;
+                       if (!has_subtests(t)) {
+                               err = start_test(t, curr, -1, &child_tests[child_test_num++],
+                                                width, pass);
+                               if (err)
+                                       goto err_out;
+                               continue;
                        }
-               } else {
                        for (int subi = 0, subn = num_subtests(t); subi < subn; subi++) {
-                               int err;
-
                                if (!perf_test__matches(test_description(t, subi),
                                                        curr, argc, argv))
                                        continue;
 
                                err = start_test(t, curr, subi, &child_tests[child_test_num++],
-                                                width);
+                                                width, pass);
                                if (err)
-                                       return err;
+                                       goto err_out;
                        }
                }
-       }
-       for (i = 0; i < child_test_num; i++) {
                if (!sequential) {
-                       int ret  = finish_test(child_tests, i, child_test_num, width);
-
-                       if (ret)
-                               return ret;
+                       /* Parallel mode starts tests but doesn't finish them. Do that now. */
+                       for (size_t x = 0; x < num_tests; x++)
+                               finish_test(child_tests, x, num_tests, width);
                }
-               free(child_tests[i]);
+       }
+err_out:
+       if (err) {
+               pr_err("Internal test harness failure. Completing any started tests:\n:");
+               for (size_t x = 0; x < num_tests; x++)
+                       finish_test(child_tests, x, num_tests, width);
        }
        free(child_tests);
-       return 0;
+       return err;
 }
 
 static int perf_test__list(int argc, const char **argv)
@@ -656,6 +694,7 @@ int cmd_test(int argc, const char **argv)
        symbol_conf.priv_size = sizeof(int);
        symbol_conf.try_vmlinux_path = true;
 
+
        if (symbol__init(NULL) < 0)
                return -1;
 
index d33d0952025cf5b65e80fb7e8464f634b5c313c1..8e328bbd509dc66ef3c6322e5d19aed20932d5fa 100644 (file)
@@ -152,4 +152,11 @@ out_delete_evlist:
        return err;
 }
 
-DEFINE_SUITE("Number of exit events of a simple workload", task_exit);
+struct test_case tests__task_exit[] = {
+       TEST_CASE_EXCLUSIVE("Number of exit events of a simple workload", task_exit),
+       {       .name = NULL, }
+};
+struct test_suite suite__task_exit = {
+       .desc = "Number of exit events of a simple workload",
+       .test_cases = tests__task_exit,
+};
index ed114b044293659ef65537036fe55fb1ae77eec2..cf3ae0c1d871742be0d8cc616cb2a9223a4009ec 100644 (file)
@@ -175,6 +175,7 @@ static void append_script(int dir_fd, const char *name, char *desc,
        struct test_suite *test_suite, **result_tmp;
        struct test_case *tests;
        size_t len;
+       char *exclusive;
 
        snprintf(link, sizeof(link), "/proc/%d/fd/%d", getpid(), dir_fd);
        len = readlink(link, filename, sizeof(filename));
@@ -191,9 +192,13 @@ static void append_script(int dir_fd, const char *name, char *desc,
                return;
        }
        tests[0].name = strdup_check(name);
+       exclusive = strstr(desc, " (exclusive)");
+       if (exclusive != NULL) {
+               tests[0].exclusive = true;
+               exclusive[0] = '\0';
+       }
        tests[0].desc = strdup_check(desc);
        tests[0].run_case = shell_test__run;
-
        test_suite = zalloc(sizeof(*test_suite));
        if (!test_suite) {
                pr_err("Out of memory while building script test suite list\n");
index 1ed76d4156b6af55af04a380eeb7abd45ea1f666..af284dd47e5c7855c508393da765356d8389f623 100644 (file)
@@ -36,6 +36,7 @@ struct test_case {
        const char *desc;
        const char *skip_reason;
        test_fnptr run_case;
+       bool exclusive;
 };
 
 struct test_suite {
@@ -62,6 +63,14 @@ struct test_suite {
                .skip_reason = _reason,                 \
        }
 
+#define TEST_CASE_EXCLUSIVE(description, _name)                \
+       {                                               \
+               .name = #_name,                         \
+               .desc = description,                    \
+               .run_case = test__##_name,              \
+               .exclusive = true,                      \
+       }
+
 #define DEFINE_SUITE(description, _name)               \
        struct test_case tests__##_name[] = {           \
                TEST_CASE(description, _name),          \