Add 'range' mode to test multiple combinations of parameters in rcuscale. The command format is similar to 'once', but allows parameters to be specified as 'name=start[:end:[:step]]', inclusive integer ranges. The default step is 1.
This 'range' mode allows multiple parameters to be ranges, and in that scenario, the benchmark will enumerate all combinations of all ranges. Example usage below running 6 scenarios of [nreaders = 1 or 2] x [writer_cpu_offset = 0 or 1 or 2]: >From the result, we can see that overlapping or non-overlapping reader and writer CPU affinity will affect performance characteristics. $ ./perf bench sync rcu range exp nreaders=1:2 nwriters=1 writer_cpu_offset=0:2 \# Running 'sync/rcu' benchmark: Running experiment with options: gp_exp=1 nwriters=1 nreaders=1 writer_cpu_offset=0 Experiment finished. Average grace-period duration: 297.535 microseconds Minimum grace-period duration: 8.853 50th percentile grace-period duration: 9.044 90th percentile grace-period duration: 9.905 99th percentile grace-period duration: 5724.727 Maximum grace-period duration: 12029.204 Cooling down (3s).. Running experiment with options: gp_exp=1 nwriters=1 nreaders=1 writer_cpu_offset=1 Experiment finished. Average grace-period duration: 15.491 microseconds Minimum grace-period duration: 8.863 50th percentile grace-period duration: 9.354 90th percentile grace-period duration: 21.142 99th percentile grace-period duration: 50.195 Maximum grace-period duration: 319.359 Cooling down (3s).. Running experiment with options: gp_exp=1 nwriters=1 nreaders=1 writer_cpu_offset=2 Experiment finished. Average grace-period duration: 21.439 microseconds Minimum grace-period duration: 11.046 50th percentile grace-period duration: 16.134 90th percentile grace-period duration: 32.819 99th percentile grace-period duration: 53.59 Maximum grace-period duration: 186.71 Cooling down (3s).. Running experiment with options: gp_exp=1 nwriters=1 nreaders=2 writer_cpu_offset=0 Experiment finished. Average grace-period duration: 122.448 microseconds Minimum grace-period duration: 8.934 50th percentile grace-period duration: 9.234 90th percentile grace-period duration: 9.895 99th percentile grace-period duration: 13.31 Maximum grace-period duration: 6024.476 Cooling down (3s).. Running experiment with options: gp_exp=1 nwriters=1 nreaders=2 writer_cpu_offset=1 Experiment finished. Average grace-period duration: 68.765 microseconds Minimum grace-period duration: 8.913 50th percentile grace-period duration: 9.144 90th percentile grace-period duration: 9.384 99th percentile grace-period duration: 10.505 Maximum grace-period duration: 6023.405 Cooling down (3s).. Running experiment with options: gp_exp=1 nwriters=1 nreaders=2 writer_cpu_offset=2 Experiment finished. Average grace-period duration: 12.079 microseconds Minimum grace-period duration: 9.204 50th percentile grace-period duration: 9.344 90th percentile grace-period duration: 11.538 99th percentile grace-period duration: 41.152 Maximum grace-period duration: 78.478 Signed-off-by: Yuzhuo Jing <[email protected]> --- tools/perf/bench/sync-rcu.c | 199 ++++++++++++++++++++++++++++++++++-- 1 file changed, 193 insertions(+), 6 deletions(-) diff --git a/tools/perf/bench/sync-rcu.c b/tools/perf/bench/sync-rcu.c index 934d2416c216..921520a645ae 100644 --- a/tools/perf/bench/sync-rcu.c +++ b/tools/perf/bench/sync-rcu.c @@ -54,6 +54,7 @@ static const char *const bench_rcu_usage[] = { "", "perf bench sync rcu [options..] [-- <command>..]", "perf bench sync rcu [options..] once <gp_type> [<param=value>..] [-- <command>..]", + "perf bench sync rcu [options..] range <gp_type> [<param=range>..] [-- <command>..]", "", " <gp_type>: The type of grace period to use: sync, async, exp (expedited)", " This sets the gp_exp or gp_async kernel module parameters.", @@ -76,11 +77,18 @@ static const char *const bench_rcu_usage[] = { " default: Run 'once sync'.", " once: Run benchmark once, with all parameters passed through to the", " kernel rcuscale module.", + " range: Run benchmark multiple times, with parameters as ranges.", + " Range format is defined as start[:end[:step]], inclusive, non-negative.", + " The benchmark instantiates all combinations of all ranges.", + " If a parameter does not specify end, or start=end, it behaves", + " the same as 'once' mode. The range parameter types are validated", + " agains `modinfo rcuscale` to ensure they are integer.", "", "Examples:", " perf bench sync rcu --hist once exp nreaders=1 nwriters=1 writer_cpu_offset=1", " perf bench sync rcu once", " perf bench sync rcu once sync nreaders=1 nwriters=1 writer_cpu_offset=1", + " perf bench sync rcu range exp nreaders=1:2 nwriters=1 writer_cpu_offset=0:2", "", " perf bench sync rcu once sync nreaders=1 nwriters=1 writer_cpu_offset=1 -- \\", " perf stat -e ipi:ipi_send_cpu,rcu:rcu_grace_period \\", @@ -105,6 +113,7 @@ static const char *const bench_rcu_usage[] = { * pointers could come from: * - string literals (e.g. the "modprobe" and "rcuscale" command name) * - simple_params + * - generated param from ranges */ struct modprobe_cmd { const char *cmd[MODPROBE_CMD_MAX]; @@ -146,6 +155,30 @@ struct modprobe_param { char value[MAX_OPTVALUE]; }; +/* + * Parsed range module parameter. The collected range_params will be + * instantiated to actual values, and then collected into modprobe_cmd. + * + * The range is inclusive. + * + * Example range: start=1 end=9 step=2 will instantiate values 1, 3 5 7 9. + */ +struct range { + int start; + int end; + int step; +}; +struct range_option { + char name[MAX_OPTNAME]; + struct range range; +}; + +/* + * The storage of range parameters. + */ +static struct range_option range_params[MAX_OPTS]; +static int range_params_count; + /* * The storage for simple (i.e. non-range) module parameter strings. */ @@ -346,6 +379,75 @@ static int parse_int(const char *val) return (int)num; } +/* + * Parse a range string into a range struct. The range is inclusive. + * + * The range string is in the format of "start[:end[:step]]". + * The default step is 1. + * + * Example: + * "1:10:2" -> start=1, end=10, step=2 + * "1:10" -> start=1, end=10, step=1 + * "1" -> start=1, end=1, step=1 + */ +static int parse_range(struct range *range, const char *str) +{ +#define MAX_RANGE 5 + + char *token; + char *saveptr = NULL; + int count = 0; + int values[MAX_RANGE]; + + char *str_copy = strdup(str); + + if (!str_copy) + fail("Memory allocation failed"); + + // Split by : or - + token = strtok_r(str_copy, ":", &saveptr); + while (token != NULL && count < MAX_RANGE) { + values[count++] = parse_int(token); + token = strtok_r(NULL, ":", &saveptr); + } + + switch (count) { + case 1: + range->start = values[0]; + range->end = values[0]; + range->step = 1; + break; + case 2: + range->start = values[0]; + range->end = values[1]; + range->step = 1; + break; + case 3: + range->start = values[0]; + range->end = values[1]; + range->step = values[2]; + break; + default: + free(str_copy); + fail("Invalid range format: \"%s\"", str); + } + + if (range->start < 0 || range->end < 0) + fail("Range must be non negative"); + if (range->start > range->end) + fail("Range start must be smaller or equal to end"); + if (range->step <= 0) + fail("Range step must be positive"); + + free(str_copy); + return 0; + +#undef MAX_RANGE +} + +#define param_print_key_value(param, fmt, ...) \ + snprintf((param)->value, MAX_OPTVALUE, fmt, ##__VA_ARGS__) + static void simple_params_add(const char *full) { if (simple_params_count >= MAX_OPTS) @@ -353,6 +455,14 @@ static void simple_params_add(const char *full) strlcpy(simple_params[simple_params_count++].value, full, MAX_OPTVALUE); } +static void range_params_add(const char *name, const struct range *range) +{ + if (range_params_count >= MAX_OPTS) + fail("Too many module parameters"); + strlcpy(range_params[range_params_count].name, name, MAX_OPTNAME); + range_params[range_params_count++].range = *range; +} + static void parse_gp_type(const char *gp_type) { if (strcmp(gp_type, "sync") == 0) { @@ -379,6 +489,10 @@ static bool param_has_conflict(const char *key) && simple_params[i].value[strlen(key)] == '=') return true; } + for (int i = 0; i < range_params_count; ++i) { + if (strcmp(key, range_params[i].name) == 0) + return true; + } /* overridable_params are considered non conflict */ return false; @@ -436,10 +550,12 @@ static void check_param_name(const char *name) * If allow_range is true, params that only has one value will be stored in * params, and range ones will be stored in range_params. */ -static void parse_module_params(int argc, const char *argv[]) +static void parse_module_params(int argc, const char *argv[], bool allow_range) { while (argc) { char *saved_ptr = NULL; + struct range range; + bool is_range = false; char *key; char *value; char buf[MAX_OPTVALUE] = ""; @@ -467,11 +583,26 @@ static void parse_module_params(int argc, const char *argv[]) if (strlen(value) + 1 > MAX_OPTVALUE) fail("Module parameter value too long: \"%s\"", value); - /* Ensure integer type value are integers, but don't need the value. */ - if (modparm_is_int(key)) - parse_int(value); + if (modparm_is_int(key)) { + /* Detect range options. */ + if (allow_range) { + parse_range(&range, value); + is_range = !(range.start == range.end + || range.start + range.step > range.end); + } else { + /* Ensure integer type value are integers, + * but don't need the value. + */ + if (modparm_is_int(key)) + parse_int(value); + } + } - simple_params_add(argv[0]); + /* Store the option. */ + if (is_range) + range_params_add(key, &range); + else + simple_params_add(argv[0]); argc--; argv++; @@ -973,6 +1104,11 @@ static void modprobe_cmd_add(struct modprobe_cmd *cmd, const char *v) cmd->cmd[++cmd->count] = NULL; } +static void modprobe_cmd_pop(struct modprobe_cmd *cmd) +{ + cmd->cmd[--cmd->count] = NULL; +} + /* * Append parameters that are overridable by users. */ @@ -1002,13 +1138,62 @@ static void test_once(int argc, const char *argv[]) { MODPROBE_CMD_INIT; - parse_module_params(argc, argv); + parse_module_params(argc, argv, false); modprobe_collect_simple_options(&modprobe_cmd); runonce(&modprobe_cmd); } +/* + * Recursively generate modprobe options from the range command. + * + * This will modify the global params storage and + * params_count, and also collect new options into modprobe_cmd. + */ +static void test_range_recursive(int range_index, struct modprobe_cmd *cmd) +{ + struct range range; + + if (range_index >= range_params_count) + return runonce(cmd); + + range = range_params[range_index].range; + + for (int i = range.start; i <= range.end; i += range.step) { + struct modprobe_param param; + + param_print_key_value(¶m, "%s=%d", + range_params[range_index].name, i); + modprobe_cmd_add(cmd, param.value); + + test_range_recursive(range_index + 1, cmd); + + modprobe_cmd_pop(cmd); + + if (i + range.step <= range.end) { + printf("Cooling down (%ds)..\n", cooldown); + if (!dryrun) + sleep(cooldown); + puts(""); + } + } +} + +/* + * Test range. Use recursion on all range commands. + */ +static void test_range(int argc, const char *argv[]) +{ + MODPROBE_CMD_INIT; + + parse_module_params(argc, argv, true); + + modprobe_collect_simple_options(&modprobe_cmd); + + test_range_recursive(0, &modprobe_cmd); +} + /* ============================= Entry Point ============================== */ int bench_sync_rcu(int argc, const char **argv) @@ -1041,6 +1226,8 @@ int bench_sync_rcu(int argc, const char **argv) if (strcmp(runmode, "once") == 0) cmd = test_once; + else if (strcmp(runmode, "range") == 0) + cmd = test_range; else usage_with_options(bench_rcu_usage, bench_rcu_options); -- 2.50.1.565.gc32cd1483b-goog

