[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250731132615.938435-4-yuzhuo@google.com>
Date: Thu, 31 Jul 2025 06:26:13 -0700
From: Yuzhuo Jing <yuzhuo@...gle.com>
To: Davidlohr Bueso <dave@...olabs.net>, "Paul E . McKenney" <paulmck@...nel.org>,
Josh Triplett <josh@...htriplett.org>, Frederic Weisbecker <frederic@...nel.org>,
Neeraj Upadhyay <neeraj.upadhyay@...nel.org>, Joel Fernandes <joelagnelf@...dia.com>,
Boqun Feng <boqun.feng@...il.com>, Uladzislau Rezki <urezki@...il.com>,
Steven Rostedt <rostedt@...dmis.org>, Mathieu Desnoyers <mathieu.desnoyers@...icios.com>,
Lai Jiangshan <jiangshanlai@...il.com>, Zqiang <qiang.zhang@...ux.dev>,
Peter Zijlstra <peterz@...radead.org>, Ingo Molnar <mingo@...hat.com>,
Arnaldo Carvalho de Melo <acme@...nel.org>, Namhyung Kim <namhyung@...nel.org>,
Mark Rutland <mark.rutland@....com>,
Alexander Shishkin <alexander.shishkin@...ux.intel.com>, Jiri Olsa <jolsa@...nel.org>,
Ian Rogers <irogers@...gle.com>, Adrian Hunter <adrian.hunter@...el.com>,
Liang Kan <kan.liang@...ux.intel.com>, Yuzhuo Jing <yzj@...ch.edu>,
Yuzhuo Jing <yuzhuo@...gle.com>, Sebastian Andrzej Siewior <bigeasy@...utronix.de>, linux-kernel@...r.kernel.org,
rcu@...r.kernel.org, linux-perf-users@...r.kernel.org
Subject: [PATCH v1 3/5] perf bench: Add 'range' mode to 'sync rcu'
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 <yuzhuo@...gle.com>
---
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
Powered by blists - more mailing lists