From nobody Sun Oct 5 16:16:38 2025 Received: from mail-pl1-f201.google.com (mail-pl1-f201.google.com [209.85.214.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4B5322C3276 for ; Thu, 31 Jul 2025 13:26:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1753968418; cv=none; b=jyZpZn3WQetUdQnIEfpCmV6EPM0lgB2wqhR1LDQ+5rxKRvI+pu83PO7P8MnqeJQhP+kzhWe2hzP9qiDqWmJ1Ycb3JENP/OF8U7jU7/5m/bXgDWxIVhZpQTTclrr9oZQcdV022eyx8itJ2pF7yXil0t1k/pdyrCxG4u7aHMJjTI0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1753968418; c=relaxed/simple; bh=DHxNzNVWUr+O5o8IyHJuN8fdD6GVaiWbG6x9JXShmp0=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=AIxidifS/cqyrEPMlcjGc596Wt1kIHVeqjgV10XbdnnxZOPwB6wmcmynB1BgqA3PHBA4R11AIX5GZZbtnF70sd1CtNeb9XuKhcEedFXigTmeGoBRAfByXMyuG/yBSesON5YMhqnBdsDVgWzIrkS97YEYei2MtQSs5hailjhUtrY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--yuzhuo.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=EY5BPP2T; arc=none smtp.client-ip=209.85.214.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--yuzhuo.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="EY5BPP2T" Received: by mail-pl1-f201.google.com with SMTP id d9443c01a7336-24011ceafc8so8786105ad.3 for ; Thu, 31 Jul 2025 06:26:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1753968415; x=1754573215; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=bkP7Z4Cqqv/NC5+ok8KneBr4i3bJ/7NR+0dUzs2HMi8=; b=EY5BPP2T7uLDGS8T7JrCdxI6SpxCO3nSoXlqVls2F4J6o45YL5MbeaE7/+yIoTpErl X9JvPt4FPBt0jXyCDmf1Nd5pMf5Boz6Ef/K/qDSphgk9H9NhWhsi2wrgPy3Rdu47YXdV xrsd47wse6ZL5TwDgmphPQGtCburbHN12aM/U14x3SKn7gOa61JSHFdguC18GfsWC87j Sc7ELTWuBWzqmDTMcF0p9+AYKs0X+hjQhxroxwpZFnaIEPgHo3vAWORi06W7HqGlOr3S 9GJm94wEsva/fW7RGkIhqCDayIsiRIUt4rjP4DmXG8dtS5p2f4FgyR1aR3PtScgctQH9 4hpQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1753968415; x=1754573215; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=bkP7Z4Cqqv/NC5+ok8KneBr4i3bJ/7NR+0dUzs2HMi8=; b=oE4a0g0WaMqPHj/hgPMz7vRcYb2NYuvcxlzfyvsCzth5cowRsWS6Ec+/4gTemBf1oF FifQDkzoDe+1CnqaKNMmHQBIKCvlKvHZ3z8Ejw9Gu+Od2wgWonIBUwdfhY1gHvFZAywt gBBe4w9ngBrgaCf3hIn1E4ONg0qgjjz9WP7O3pNGkWP8y0PxQ+M6Sg+s1T5kz/1WNgEA 7WEsETiR3kSkzfkVhM8UOBcOTKO4zXVd8WrceWTevCRANosRhG1DM8n5lusaaDS9PIbR creHiBW2p7KNe0dqnUC7XE3BVtOyWpy5SZpo3ov9lIT7i9/S9PUzQw4SkdpdRw/+YEC1 Hthw== X-Forwarded-Encrypted: i=1; AJvYcCV31QfXzWR/oNJpyyEdkc2vLyhhTiW66YjvZkf/DLT/HXzpjS/RZJ3KeSN5t/zjmoFJEurRNG1BFHSvNRI=@vger.kernel.org X-Gm-Message-State: AOJu0YxDALOzWBgJOmF5QBOnxlYcPNyzVFmK9YUoVbnVt90bg9L4My9z oeGcIDpiZQUAcqB22bicomMC56AY6PRZidCawzUqWmFIsaaYx++QfDQ8OlxduDCZ70ucRgILPK8 1/PBaog== X-Google-Smtp-Source: AGHT+IEwduAehduBW9EWHJOWUGqQXIKJ02sza7Y6mu7qiMI5Yth6Qmr0VERdlGJ8n+sQiOAH1nTf9mvtt4k= X-Received: from plnf7.prod.google.com ([2002:a17:903:1a67:b0:240:1bbf:686b]) (user=yuzhuo job=prod-delivery.src-stubby-dispatcher) by 2002:a17:902:c94b:b0:240:150d:9147 with SMTP id d9443c01a7336-24096a56357mr111905495ad.13.1753968415132; Thu, 31 Jul 2025 06:26:55 -0700 (PDT) Date: Thu, 31 Jul 2025 06:26:11 -0700 In-Reply-To: <20250731132615.938435-1-yuzhuo@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250731132615.938435-1-yuzhuo@google.com> X-Mailer: git-send-email 2.50.1.565.gc32cd1483b-goog Message-ID: <20250731132615.938435-2-yuzhuo@google.com> Subject: [PATCH v1 1/5] perf bench: Add RCU benchmark using rcuscale kernel module From: Yuzhuo Jing To: Davidlohr Bueso , "Paul E . McKenney" , Josh Triplett , Frederic Weisbecker , Neeraj Upadhyay , Joel Fernandes , Boqun Feng , Uladzislau Rezki , Steven Rostedt , Mathieu Desnoyers , Lai Jiangshan , Zqiang , Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , Liang Kan , Yuzhuo Jing , Yuzhuo Jing , Sebastian Andrzej Siewior , linux-kernel@vger.kernel.org, rcu@vger.kernel.org, linux-perf-users@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add 'rcu' to the 'perf bench sync' collection. This benchmark depends on the rcuscale kernel module, and also depends on new features in the rcuscale module that exposes control and internal state through debugfs. This patch adds the basic 'once' mode that runs one combination of rcuscale parameters. Command usage is defined as: perf bench sync rcu [options..] [once [..]] * gp_type is one of "sync", "async", "exp" * Valid params can be found from `modinfo rcuscale`. Except that gp_exp, gp_async, and block_start are managed by the benchmark and cannot be set by user. This benchmark parses the modinfo to validate the existence of user provided parameters. It then executes modprobe to load rcuscale. Experiment start/finish is controlled through /sys/kernel/debug/rcuscale/{should_start,test_complete}. After the experiment finishes, it reads /sys/kernel/debug/rcuscale/writer_durations and outputs statistics. The statistic code (print_writer_duration_stats function) is derived from tools/testing/selftests/rcutorture/bin/kvm-recheck-rcuscale.sh. Example output: ./perf bench sync rcu --hist once exp nreaders=3D1 nwriters=3D1 \# Running 'sync/rcu' benchmark: Running experiment with options: gp_exp=3D1 nreaders=3D1 nwriters=3D1 Experiment finished. Histogram bucket size: 0.1 microseconds 8.8 11 8.9 25 9 27 9.1 13 9.2 6 9.3 3 9.4 2 9.5 2 9.6 3 9.7 1 9.8 1 9.9 1 10 2 10.4 1 10.5 1 11.1 1 6025.6 1 Average grace-period duration: 68.734 microseconds Minimum grace-period duration: 8.813 50th percentile grace-period duration: 9.044 90th percentile grace-period duration: 9.625 99th percentile grace-period duration: 10.516 Maximum grace-period duration: 6025.679 Signed-off-by: Yuzhuo Jing --- tools/perf/bench/Build | 1 + tools/perf/bench/bench.h | 1 + tools/perf/bench/sync-rcu.c | 816 ++++++++++++++++++++++++++++++++++++ tools/perf/builtin-bench.c | 1 + 4 files changed, 819 insertions(+) create mode 100644 tools/perf/bench/sync-rcu.c diff --git a/tools/perf/bench/Build b/tools/perf/bench/Build index 13558279fa0e..f694f8715cfc 100644 --- a/tools/perf/bench/Build +++ b/tools/perf/bench/Build @@ -20,6 +20,7 @@ perf-bench-y +=3D breakpoint.o perf-bench-y +=3D pmu-scan.o perf-bench-y +=3D uprobe.o perf-bench-y +=3D sync.o +perf-bench-y +=3D sync-rcu.o perf-bench-y +=3D qspinlock.o =20 perf-bench-$(CONFIG_X86_64) +=3D mem-memcpy-x86-64-asm.o diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h index 42c0696b05fb..09c5b3af347f 100644 --- a/tools/perf/bench/bench.h +++ b/tools/perf/bench/bench.h @@ -24,6 +24,7 @@ int bench_sched_pipe(int argc, const char **argv); int bench_sched_seccomp_notify(int argc, const char **argv); int bench_sync_qspinlock(int argc, const char **argv); int bench_sync_ticket(int argc, const char **argv); +int bench_sync_rcu(int argc, const char **argv); int bench_syscall_basic(int argc, const char **argv); int bench_syscall_getpgid(int argc, const char **argv); int bench_syscall_fork(int argc, const char **argv); diff --git a/tools/perf/bench/sync-rcu.c b/tools/perf/bench/sync-rcu.c new file mode 100644 index 000000000000..ac85841f0b68 --- /dev/null +++ b/tools/perf/bench/sync-rcu.c @@ -0,0 +1,816 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RCU scale benchmark using rcuscale kernel module. + * + * 2025 Yuzhuo Jing + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bench.h" + +#define MAX_OPTS 64 +#define MAX_OPTNAME 64 +#define MAX_OPTTYPE 16 +#define MAX_OPTVALUE 128 + +#define INIT_CAPACITY 1024UL + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D Global Options =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ + +static bool dryrun; +static unsigned int cooldown =3D 3; +static bool show_hist; +static const char *debugfs =3D "/sys/kernel/debug"; + +static const struct option bench_rcu_options[] =3D { + OPT_BOOLEAN('n', "dryrun", &dryrun, "Dry run mode"), + OPT_UINTEGER('c', "cooldown", &cooldown, + "Sleep time between each run (default: 3 seconds)"), + OPT_BOOLEAN(0, "hist", &show_hist, + "Show histogram of writer durations"), + OPT_STRING(0, "debugfs", &debugfs, "path", + "Debugfs mount point (default: /sys/kernel/debug)"), + OPT_END() +}; + +static const char *const bench_rcu_usage[] =3D { + "RCU benchmark using rcuscale kernel module.", + "", + "perf bench sync rcu [options..]", + "perf bench sync rcu [options..] once [..]", + "", + " : The type of grace period to use: sync, async, exp (expedite= d)", + " This sets the gp_exp or gp_async kernel module parameters.", + " : Any parameter of the rcuscale kernel module, e.g. holdoff= =3D5.", + " Valid options can be found from running `modinfo rcuscale`.= ", + "", + "Notes on param:", + " This benchmark manages gp_exp and gp_async, and sets block_start=3D1.", + " User cannot override those parameters. This benchmark also sets defau= lt", + " values writer_no_print=3D1 and holdoff=3D3, but users may override tho= se.", + " Note that if nwriters=3D0, the rcuscale kernel module will not exit,", + " and the benchmark will sleep indefinitely.", + "", + "Modes:", + " default: Run 'once sync'.", + " once: Run benchmark once, with all parameters passed through to the", + " kernel rcuscale module.", + "", + "Examples:", + " perf bench sync rcu --hist once exp nreaders=3D1 nwriters=3D1 writer_c= pu_offset=3D1", + " perf bench sync rcu once", + " perf bench sync rcu once sync nreaders=3D1 nwriters=3D1 writer_cpu_of= fset=3D1", + "", + "In case perf exited abnormally, user need to unload rcuscale by running:= ", + " modprobe -r rcuscale torture", + "", + "Global options:", // continues to show bench_rcu_options + NULL +}; + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D Runtime Options =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ + +#define MODPROBE_BASE_COUNT 3 +#define MODPROBE_CMD_MAX (2 * MAX_OPTS + MODPROBE_BASE_COUNT + 1) +/* + * The command line builder for modprobe. The cmd array will be directly + * passed to execvp. + * + * Note: the cmd array does not own the pointers in it. Those argument + * pointers could come from: + * - string literals (e.g. the "modprobe" and "rcuscale" command name) + * - simple_params + */ +struct modprobe_cmd { + const char *cmd[MODPROBE_CMD_MAX]; + size_t count; +}; + +#define MODPROBE_CMD_INIT \ + struct modprobe_cmd modprobe_cmd =3D { \ + { "modprobe", "rcuscale", "block_start=3D1", NULL, }, \ + MODPROBE_BASE_COUNT, \ + } +#define MODPROBE_REMOVE_CMD "modprobe -r rcuscale torture" + +/* + * Generic modprobe parameter definition. This is the storage for an + * instantiated module parameter. This may come from parameters directly + * given by user, or generated. + * + * Format must be "key=3Dvalue". + */ +struct modprobe_param { + char value[MAX_OPTVALUE]; +}; + +/* + * The storage for simple (i.e. non-range) module parameter strings. + */ +static struct modprobe_param simple_params[MAX_OPTS]; +static int simple_params_count; + +static bool in_child; + +struct durations { + u64 *values; + size_t count; + size_t capacity; + u64 sum; +}; + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D Override parameters =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D */ + +/* Non-override module parameter. This may be updated by "ratio" command.= */ +struct non_override_param { + char name[MAX_OPTNAME]; +}; +static int non_override_params_count =3D 3; +static struct non_override_param non_override_params[MAX_OPTS] =3D { + { "block_start" }, + { "gp_exp" }, + { "gp_async" }, +}; + +struct overridable_param { + char name[MAX_OPTNAME]; + bool user_overridden; +}; +static struct overridable_param overridable_params[] =3D { + { "writer_no_print", false }, + { "holdoff", false }, +}; +static const int overridable_params_count =3D ARRAY_SIZE(overridable_param= s); + +/* Valid module parameters parsed from modinfo. */ +struct modinfo_parm { + char name[MAX_OPTNAME]; + char type[MAX_OPTTYPE]; +}; +static struct modinfo_parm modinfo_parms[MAX_OPTS]; +static int modinfo_parms_count; + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D Cleanup functions =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ + +static void unload_module(void) +{ + if (system(MODPROBE_REMOVE_CMD) !=3D 0) + fprintf(stderr, "Failed to unload rcuscale kernel module.\n" + "Please manually remove it using `"MODPROBE_REMOVE_CMD"`.\n"); +} + +static void cleanup(void) +{ + if (in_child) + return; + + unload_module(); +} + +static void signal_handler(int sig) +{ + if (sig) + fprintf(stderr, "perf: Signal %d received\n", sig); + /* cleanup is registered in atexit */ + fprintf(stderr, "Cleaning up...\n"); + exit(1); +} + +static void setup_cleanup(void) +{ + atexit(cleanup); + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGSEGV, signal_handler); +} + +/* + * Failure handling. Use this for logical checks, and use "err" for those= with + * external interactions. + */ +#define fail(fmt, ...) \ +do { \ + fprintf(stderr, "perf: "fmt"\n", ##__VA_ARGS__); \ + exit(1); \ +} while (0) + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D Modprobe info =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ + +/* + * Parse modinfo and store the results in modinfo_parms. Used to determine + * whether an option is valid as a range parameter. + * + * The expected format is: + * nreaders:Number of RCU reader threads (int) + * nwriters:Number of RCU updater threads (int) + */ +static void parse_modinfo(void) +{ + char *line =3D NULL; + size_t len =3D 0; + FILE *fp; + + fp =3D popen("modinfo rcuscale -0 -F parm", "r"); + if (!fp) + err(EXIT_FAILURE, "Failed to run modinfo"); + + while (getdelim(&line, &len, '\0', fp) !=3D -1) { + char *type; + char *remaining =3D NULL; + char *name =3D strtok_r(line, ":", &remaining); + + if (!name) + fail("Failed to parse modinfo parameter name"); + + type =3D strrchr(remaining, '('); + if (!type) + fail("Failed to parse modinfo parameter type"); + remaining =3D NULL; + type =3D strtok_r(type + 1, ")", &remaining); + if (!type) + fail("Failed to parse modinfo parameter type"); + + strlcpy(modinfo_parms[modinfo_parms_count].name, name, MAX_OPTNAME); + strlcpy(modinfo_parms[modinfo_parms_count].type, type, MAX_OPTTYPE); + modinfo_parms_count++; + } + if (!modinfo_parms_count) + fail("Failed to read modinfo"); + + free(line); + pclose(fp); +} + +/* + * Check if the module parameter is an integer. + */ +static bool modparm_is_int(const char *name) +{ + for (int i =3D 0; i < modinfo_parms_count; i++) { + if (strcmp(modinfo_parms[i].name, name) =3D=3D 0) + return strcmp(modinfo_parms[i].type, "int") =3D=3D 0; + } + return false; +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D Argument parsing =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ + +/* + * Reserve memory for a pointer. + * + * If current capacity is 0, the minimum capacity is at least INIT_CAPACIT= Y or + * min_capacity. + * + * The current capacity is doubled if it is less than the minimum capacity. + */ +static void raw_reserve_size(void **ptr, size_t elemsize, + size_t *current_capacity, size_t min_capacity) +{ + size_t new_capacity; + + if (min_capacity =3D=3D 0) + min_capacity =3D INIT_CAPACITY; + + if (*current_capacity >=3D min_capacity) + return; + + new_capacity =3D *current_capacity ?: min_capacity; + while (new_capacity < min_capacity) + new_capacity *=3D 2; + + *ptr =3D realloc(*ptr, new_capacity * elemsize); + if (!*ptr) + fail("Failed to allocate memory"); + *current_capacity =3D new_capacity; +} +#define reserve_size(ptr, current_capacity, min_capacity) \ + raw_reserve_size((void **)(ptr), sizeof(**(ptr)), (current_capacity), (mi= n_capacity)) + +static int parse_int(const char *val) +{ + char *endptr; + long num =3D strtol(val, &endptr, 10); + + if (*endptr !=3D '\0') + fail("Invalid integer format: %s", val); + + return (int)num; +} + +static void simple_params_add(const char *full) +{ + if (simple_params_count >=3D MAX_OPTS) + fail("Too many module parameters"); + strlcpy(simple_params[simple_params_count++].value, full, MAX_OPTVALUE); +} + +static void parse_gp_type(const char *gp_type) +{ + if (strcmp(gp_type, "sync") =3D=3D 0) { + // no new option is added + } else if (strcmp(gp_type, "async") =3D=3D 0) + simple_params_add("gp_async=3D1"); + else if (strcmp(gp_type, "exp") =3D=3D 0) + simple_params_add("gp_exp=3D1"); + else + fail("Invalid grace period type: %s", gp_type); +} + +/* + * Check if the option is already set. + */ +static bool param_has_conflict(const char *key) +{ + for (int i =3D 0; i < non_override_params_count; ++i) { + if (strcmp(key, non_override_params[i].name) =3D=3D 0) + return true; + } + for (int i =3D 0; i < simple_params_count; ++i) { + if (strncmp(key, simple_params[i].value, strlen(key)) =3D=3D 0 + && simple_params[i].value[strlen(key)] =3D=3D '=3D') + return true; + } + /* overridable_params are considered non conflict */ + + return false; +} + +static struct overridable_param *overridable_param_get(const char *key) +{ + for (int i =3D 0; i < overridable_params_count; ++i) + if (strcmp(overridable_params[i].name, key) =3D=3D 0) + return overridable_params + i; + return NULL; +} + +/* + * For overridable_params, if user specifies it, set overridden so that it= will + * not be appended to modprobe cmd. + */ +static inline void param_try_set_user_override(const char *key) +{ + struct overridable_param *param =3D overridable_param_get(key); + + if (param) + param->user_overridden =3D true; +} + +/* + * Validate basics about the parameter name. + * + * Note: This is supposed to only be used during parsing user provided + * arguments. This will also update the "user_overridden" flag for overri= dable + * parameters. + */ +static void check_param_name(const char *name) +{ + if (strlen(name) + 1 > MAX_OPTNAME) + fail("Module parameter name too long: %s", name); + if (param_has_conflict(name)) + fail("Module parameter \"%s\" has conflict", name); + /* Set user overridden if possible */ + param_try_set_user_override(name); + + for (int i =3D 0; i < modinfo_parms_count; ++i) { + if (strcmp(modinfo_parms[i].name, name) =3D=3D 0) + return; + } + fail("Module parameter \"%s\" does not exist in modinfo", name); +} + +/* + * Parse module parameter. Results are stored in params and range_params. + * + * If allow_range is false, all params are stored in params, and checks + * the format cannot be range. + * + * 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[]) +{ + while (argc) { + char *saved_ptr =3D NULL; + char *key; + char *value; + char buf[MAX_OPTVALUE] =3D ""; + + if (strnlen(argv[0], MAX_OPTVALUE) >=3D MAX_OPTVALUE - 1) + fail("Module parameter too long: \"%s\"", argv[0]); + strlcpy(buf, argv[0], MAX_OPTVALUE); + + /* Parse keys and values. */ + key =3D strtok_r(buf, "=3D", &saved_ptr); + if (!key) + fail("Failed to parse module option \"%s\"", argv[0]); + check_param_name(key); + + value =3D strtok_r(NULL, "=3D", &saved_ptr); + if (!value || strlen(value) =3D=3D 0) + fail("Cannot find value for module option \"%s\"", key); + 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); + + simple_params_add(argv[0]); + + argc--; + argv++; + } +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Expe= riment Result Handling =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D */ + +static void durations_add(struct durations *durations, u64 duration) +{ + reserve_size(&durations->values, &durations->capacity, durations->count += 1); + durations->values[durations->count++] =3D duration; + durations->sum +=3D duration; +} + +/* + * Parse writer durations from debugfs and push them to durations array. + * + * The expected format is writer_id,duration. + * + * Durations are converted to microseconds and stored in durations array. + */ +static struct durations *parse_durations(void) +{ + char durations_path[PATH_MAX]; + FILE *fp =3D NULL; + char *line =3D NULL; + size_t len =3D 0; + ssize_t read; + u64 duration; + struct durations *durations =3D calloc(1, sizeof(*durations)); + + if (!durations) + fail("Failed to allocate memory for durations"); + + snprintf(durations_path, sizeof(durations_path), "%s/rcuscale/writer_dura= tions", debugfs); + + fp =3D fopen(durations_path, "r"); + if (!fp) + err(EXIT_FAILURE, "Failed to open writer_durations"); + + while ((read =3D getline(&line, &len, fp)) !=3D -1) { + if (sscanf(line, "%*d,%lu", &duration) !=3D 1) + fail("Failed to parse writer duration. Line: %s", line); + durations_add(durations, duration); + } + + free(line); + fclose(fp); + + return durations; +} + +static void free_durations(struct durations *durations) +{ + free(durations->values); + free(durations); +} + +/* + * Helper function for sorting. + */ +static int compare_duration(const void *a, const void *b) +{ + u64 aa =3D *(u64 *)a, bb =3D *(u64 *)b; + + return aa < bb ? -1 : !!(aa > bb); +} + +/* + * Print a trimmed zero converted ns to us, without automatic scientific + * notation like %g. + * e.g. 10000 ns -> 10 us + * e.g. 10001 ns -> 10.001 us + * e.g. 10100 ns -> 10.1 us + */ +static char *print_us(char *buf, u64 ns) +{ + int len; + + sprintf(buf, "%"PRIu64, ns / 1000); + if (ns % 1000 =3D=3D 0) + return buf; + + sprintf(buf + strlen(buf), ".%03"PRIu64, ns % 1000); + len =3D strlen(buf); + while (len && buf[len - 1] =3D=3D '0') + buf[--len] =3D '\0'; + + return buf; +} + +/* + * Print statistics of writer durations. + * + * This function is derived from + * tools/testing/selftests/rcutorture/bin/kvm-recheck-rcuscale.sh + * Note that the durations array are in nanoseconds, and are integers. + */ +static void print_writer_duration_stats(const struct durations *d) +{ + size_t pct50, pct90, pct99; + size_t count; + u64 div, last; + char ms_us_buf[30]; + u64 *durations =3D d->values; + size_t durations_count =3D d->count; + + if (durations_count =3D=3D 0) { + printf("No rcuscale records found.\n"); + return; + } + + qsort(durations, durations_count, sizeof(*durations), compare_duration); + + // Calculate percentiles + pct50 =3D max(durations_count * 50 / 100, 1UL); + pct90 =3D max(durations_count * 90 / 100, 1UL); + pct99 =3D max(durations_count * 99 / 100, 1UL); + +#define US_NS 1000 +#define us(ns) print_us(ms_us_buf, (ns)) + + if (show_hist) { + // Calculate histogram bucket size based on 90th percentile + div =3D pow(10, floor(log10((double)durations[pct90 - 1]) + 0.5)) / 100; + if (div <=3D 0) + div =3D 1; + printf("Histogram bucket size: %s microseconds\n", us(div)); + + last =3D durations[0] - 10 * US_NS; + count =3D 0; + for (size_t i =3D 0; i < durations_count; ++i) { + u64 current =3D durations[i] / div * div; + + if (last =3D=3D current) { + count++; + } else { + if (count > 0) + printf("%s %lu\n", us(last), count); + count =3D 1; + last =3D current; + } + } + if (count > 0) + printf("%s %lu\n", us(last), count); + } + + // Print statistics + printf("Average grace-period duration: %s microseconds\n", us(d->sum / du= rations_count)); + printf("Minimum grace-period duration: %s\n", us(durations[0])); + printf("50th percentile grace-period duration: %s\n", us(durations[pct50 = - 1])); + printf("90th percentile grace-period duration: %s\n", us(durations[pct90 = - 1])); + printf("99th percentile grace-period duration: %s\n", us(durations[pct99 = - 1])); + printf("Maximum grace-period duration: %s\n", us(durations[durations_coun= t - 1])); + +#undef US_NS +#undef us +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D Experiment Functions =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ + +/* + * Trigger the experiment by writing 1 to should_start. + */ +static void start_experiment(void) +{ + char path[PATH_MAX]; + FILE *fp; + + snprintf(path, sizeof(path), "%s/rcuscale/should_start", debugfs); + + fp =3D fopen(path, "w"); + if (!fp) + err(EXIT_FAILURE, "Failed to open %s", path); + + if (fprintf(fp, "1\n") < 0) + err(EXIT_FAILURE, "Failed to write %s", path); + + fclose(fp); +} + +/* + * Wait for the experiment to complete by reading test_complete once every + * second, until it is not 0. + */ +static void wait_experiment(void) +{ + char path[PATH_MAX]; + + snprintf(path, sizeof(path), "%s/rcuscale/test_complete", debugfs); + + while (true) { + int finished; + FILE *fp =3D fopen(path, "r"); + + if (!fp) + err(EXIT_FAILURE, "Failed to open %s", path); + + if (fscanf(fp, "%d", &finished) !=3D 1) { + fclose(fp); + err(EXIT_FAILURE, "Failed to read %s", path); + } + + fclose(fp); + + if (finished) + break; + + sleep(1); + } +} + +/* + * Run the constructed modprobe command. + */ +static void run_modprobe(const struct modprobe_cmd *cmd) +{ + int retval; + pid_t pid; + + if (dryrun) + return; + + pid =3D fork(); + if (pid < 0) + err(EXIT_FAILURE, "Failed to fork child process"); + + if (pid =3D=3D 0) { + execvp(cmd->cmd[0], (char *const *)cmd->cmd); + in_child =3D true; + err(EXIT_FAILURE, "Failed to execute modprobe command"); + } + waitpid(pid, &retval, 0); + if (retval) + fail("modprobe failed, exiting."); +} + +/* + * Print modprobe parameters, but skip the base command line, and also skip + * those overridable params not overridden by user. + */ +static void print_params(const struct modprobe_cmd *cmd) +{ + bool printed =3D false; + char keybuf[MAX_OPTNAME]; + struct overridable_param *param; + + printf("Running experiment with options:"); + for (int i =3D MODPROBE_BASE_COUNT; cmd->cmd[i] !=3D NULL; ++i) { + if (sscanf(cmd->cmd[i], "%[^=3D]=3D", keybuf) !=3D 1) + fail("Invalid generated modprobe parameter: %s", cmd->cmd[i]); + param =3D overridable_param_get(keybuf); + if (param =3D=3D NULL || param->user_overridden) { + printed =3D true; + printf(" %s", cmd->cmd[i]); + } + } + if (!printed) + printf(" (default)\n"); + else + printf("\n"); +} + +/* + * Core Experiment function + */ +static void runonce(const struct modprobe_cmd *modprobe_cmd) +{ + struct durations *durations; + + print_params(modprobe_cmd); + run_modprobe(modprobe_cmd); + + if (dryrun) + return; + + /* Start and wait for experiment */ + start_experiment(); + wait_experiment(); + + /* Parse writer durations */ + /* Wait until all kernel threads enter final wait */ + sleep(1); + durations =3D parse_durations(); + unload_module(); + + printf("Experiment finished.\n"); + + /* Print statistics */ + print_writer_duration_stats(durations); + free_durations(durations); +} + +static void modprobe_cmd_add(struct modprobe_cmd *cmd, const char *v) +{ + // 2 for NULL and v + if (cmd->count + 2 >=3D MODPROBE_CMD_MAX) + fail("Too many module parameters"); + cmd->cmd[cmd->count] =3D v; + cmd->cmd[++cmd->count] =3D NULL; +} + +/* + * Append parameters that are overridable by users. + */ +static void modprobe_cmd_add_overridable(struct modprobe_cmd *cmd) +{ + if (!param_has_conflict("writer_no_print")) + modprobe_cmd_add(cmd, "writer_no_print=3D1"); + if (!param_has_conflict("holdoff")) + modprobe_cmd_add(cmd, "holdoff=3D3"); +} + +/* + * Collect simple options into modprobe_cmd. + */ +static void modprobe_collect_simple_options(struct modprobe_cmd *cmd) +{ + for (int i =3D 0; i < simple_params_count; ++i) + modprobe_cmd_add(cmd, simple_params[i].value); + + modprobe_cmd_add_overridable(cmd); +} + +/* + * Test once. Does not allow ranges. + */ +static void test_once(int argc, const char *argv[]) +{ + MODPROBE_CMD_INIT; + + parse_module_params(argc, argv); + + modprobe_collect_simple_options(&modprobe_cmd); + + runonce(&modprobe_cmd); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D Entry Point =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ + +int bench_sync_rcu(int argc, const char **argv) +{ + void (*cmd)(int argc, const char *argv[]); + const char *runmode, *gp_type; + + /* Reset errno to avoid printing irrelavent error string */ + errno =3D 0; + + /* Parse global options first. */ + argc =3D parse_options(argc, argv, bench_rcu_options, bench_rcu_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + /* The empty case is equivalent to 'once sync'. + * Otherwise, at least two positional options are required: + * once/range/ratio and sync/async/exp + */ + if (argc =3D=3D 0) { + runmode =3D "once"; + gp_type =3D "sync"; + } else if (argc < 2) { + usage_with_options(bench_rcu_usage, bench_rcu_options); + } else { + runmode =3D argv[0]; + gp_type =3D argv[1]; + argc -=3D 2; + argv +=3D 2; + } + + if (strcmp(runmode, "once") =3D=3D 0) + cmd =3D test_once; + else + usage_with_options(bench_rcu_usage, bench_rcu_options); + + parse_gp_type(gp_type); + + parse_modinfo(); + if (system(MODPROBE_REMOVE_CMD) !=3D 0) + err(EXIT_FAILURE, "Unloading existing rcuscale module failed"); + + setup_cleanup(); + + cmd(argc, argv); + + return 0; +} diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c index 8d945b846321..9d2e765c7e16 100644 --- a/tools/perf/builtin-bench.c +++ b/tools/perf/builtin-bench.c @@ -55,6 +55,7 @@ static struct bench sched_benchmarks[] =3D { static struct bench sync_benchmarks[] =3D { { "qspinlock", "Benchmark for queued spinlock", bench_sync_qspinlock }, { "ticket", "Benchmark for ticket spinlock", bench_sync_ticket }, + { "rcu", "Benchmark using rcuscale kernel module", bench_sync_rcu }, { "all", "Run all synchronization benchmarks", NULL }, { NULL, NULL, NULL } }; --=20 2.50.1.565.gc32cd1483b-goog From nobody Sun Oct 5 16:16:38 2025 Received: from mail-pf1-f202.google.com (mail-pf1-f202.google.com [209.85.210.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ACE432C3749 for ; Thu, 31 Jul 2025 13:26:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1753968419; cv=none; b=b3eUiSBUalPBeJVHHsQAy4/XpDLMTO0RGdKqGik7nRRUAliRaOFynbaHaFJylERDQQHnmbKdEnPNBT0GzbJJN7c7NUcJWgzk+Q6sVYs6RzQCQ6I2mLgg9DUaNStWYRl7arBAuRry2ClKfe1BofNFS23FpDVvZxfX5LkJvIC6Ud4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1753968419; c=relaxed/simple; bh=SDLv3vRUm4a7IfRMbe5z27kgDuKQZrYHeqcv2Xeh6+M=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=FS21dYYY9rFwQxldEPXGnRK6Nw+98z15ZKt3v3ulcKzrSL1+06VytTQpFd4BQKtgPiUyyl3UifWihLW7uSn0CMbgeVe+uDwo99EIS3gV54XoGvduTkZXRnzTyRxtZlq/UuJ16sljXyp1Ot50Uwrr9SblMquWg8dqx4BAcmK9RKs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--yuzhuo.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=wbBCNywm; arc=none smtp.client-ip=209.85.210.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--yuzhuo.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="wbBCNywm" Received: by mail-pf1-f202.google.com with SMTP id d2e1a72fcca58-76bcfe2a8a4so607132b3a.0 for ; Thu, 31 Jul 2025 06:26:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1753968417; x=1754573217; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=wx1jGBaHmAOK84M8vVvUKtj0B4hkxXN2QqsHHZy6RfE=; b=wbBCNywmmpqQQ81W3dFaBcU308v0MLBz+V0Y//o5I/jCfTJs7kmdqLIXQxvMVmdcBJ +lDjAg14SAVnG1yoq7uGDltRvaKfORXU59Y+j1bHFcpWM2yKu0Ptjx/Kx2gd273THlWc BocD6kagQixb95YOooZ+yYrH+ZKw6VUqkMS6jvO0cvIEj515vEH7yt4ZakofnOWKtaaM CrJWX4hbcBnF5tpby+IK7nQa/Yfsv1lWZlAsiATWMr+kLG+QqxkEURvTiwpngaVRam0+ 5aVJ/XbIz2NGSxuXP78oYVsiAdECyAa+GAqLOWipacyYCNTRksCDcfxBiRJQwCGTj4t3 DkVw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1753968417; x=1754573217; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=wx1jGBaHmAOK84M8vVvUKtj0B4hkxXN2QqsHHZy6RfE=; b=LBKGytGKQgvRwgAqZtvHrBSZoC0hVyj8Dw7nss28OnLu+3neBcyGsJmpK6AMmTMuZK oliwkqYHFxMWmikKHrm8ggPznK5ore2LrjtVNLE0Vqf0qcOWZxpNu4zXWlIaEhRYkv8p iOiUZpZGAERQRw56tREYGC47CAzwvJBfV5f8YByo1tOXKaQf4yfZsqa7nOQGAZVqHcIo R5kVPs7WYNwAiNvcKKKU3Dy9mlp4+xjeuaiejR5YPT+QcW6AZDpauWU2FEpvpSfTGNgW 8Oln/2mZK+xUzuQ/LvwyJH9sWrBJprkmxTkM3BhymihffFHXYXsPAQqnwy9HGKu9aAU3 wgkQ== X-Forwarded-Encrypted: i=1; AJvYcCX7F8XmZKEn8+F4RO/vf9IRQSt7UpgFzL77xdW83GqpPCX/IiDCV+9tlm05AXAOPKSPxVF2HKKWxxD1918=@vger.kernel.org X-Gm-Message-State: AOJu0Yw4i1ehtHPTN3AxsETNNt2hEaOi7h3B36Xg7hNIs/z8dPjYtYdd PDgV5OklcH3wNEVbP/mF8iG6QpBoDOKzS4qAmaIUAKlxpaWnW5DnEiQbDV3AZ6bqFp1SVAhF7MZ Iz2Gj0Q== X-Google-Smtp-Source: AGHT+IHy977OGKGUfQxfPzzgX/dUwkN3T5owF6V3wWPLDAGGgMWB1YafYG5tVw6JssVvWq6hdTG9MJzAQrY= X-Received: from pfll8.prod.google.com ([2002:a05:6a00:1588:b0:747:9faf:ed39]) (user=yuzhuo job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a21:339d:b0:218:96ad:720d with SMTP id adf61e73a8af0-23dc0cf784dmr9622346637.1.1753968416873; Thu, 31 Jul 2025 06:26:56 -0700 (PDT) Date: Thu, 31 Jul 2025 06:26:12 -0700 In-Reply-To: <20250731132615.938435-1-yuzhuo@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250731132615.938435-1-yuzhuo@google.com> X-Mailer: git-send-email 2.50.1.565.gc32cd1483b-goog Message-ID: <20250731132615.938435-3-yuzhuo@google.com> Subject: [PATCH v1 2/5] perf bench: Implement subprocess execution for 'sync rcu' From: Yuzhuo Jing To: Davidlohr Bueso , "Paul E . McKenney" , Josh Triplett , Frederic Weisbecker , Neeraj Upadhyay , Joel Fernandes , Boqun Feng , Uladzislau Rezki , Steven Rostedt , Mathieu Desnoyers , Lai Jiangshan , Zqiang , Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , Liang Kan , Yuzhuo Jing , Yuzhuo Jing , Sebastian Andrzej Siewior , linux-kernel@vger.kernel.org, rcu@vger.kernel.org, linux-perf-users@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Monitor system state is useful for understanding performance impact. This patch enables running external tool during the benchmark. It provides a similar semantic to 'perf record -- perf bench mem', except that the order is reversed. Because the benchmark threads are kernel module created kthreads, perf cannot directly attach to them. This patch propose a method to execute the attach command from a child process, using command line substitution. If any of the command string contains "{READER,WRITER,KFREE}_TASKS" placeholder, they are replaced with the real value upon startup. The thread ID information comes from /sys/kernel/debug/rcuscale/{reader,writer,kfree}_tasks. Example usage of running 'perf stat' to attach kernel threads: $ ./perf bench sync rcu once sync nreaders=3D1 nwriters=3D1 writer_cpu_off= set=3D1 -- \ perf stat -e ipi:ipi_send_cpu,rcu:rcu_grace_period \ -t READER_TASKS,WRITER_TASKS \# Running 'sync/rcu' benchmark: Running experiment with options: nreaders=3D1 nwriters=3D1 writer_cpu_offse= t=3D1 Running child command: perf stat -e ipi:ipi_send_cpu,rcu:rcu_grace_period -= t 1682932,1682933 Performance counter stats for thread id '1682932,1682933': 20105 ipi:ipi_send_cpu 702 rcu:rcu_grace_period 25.023871111 seconds time elapsed Experiment finished. Waiting for child process to exit. Average grace-period duration: 188128.652 microseconds Minimum grace-period duration: 9000.221 50th percentile grace-period duration: 217996.932 90th percentile grace-period duration: 218001.019 99th percentile grace-period duration: 218153.558 Maximum grace-period duration: 326999.705 Signed-off-by: Yuzhuo Jing --- tools/perf/bench/sync-rcu.c | 252 +++++++++++++++++++++++++++++++++++- 1 file changed, 247 insertions(+), 5 deletions(-) diff --git a/tools/perf/bench/sync-rcu.c b/tools/perf/bench/sync-rcu.c index ac85841f0b68..934d2416c216 100644 --- a/tools/perf/bench/sync-rcu.c +++ b/tools/perf/bench/sync-rcu.c @@ -5,6 +5,7 @@ * 2025 Yuzhuo Jing */ #include +#include #include #include #include @@ -32,6 +33,7 @@ static bool dryrun; static unsigned int cooldown =3D 3; static bool show_hist; +static unsigned int child_delay =3D 1; static const char *debugfs =3D "/sys/kernel/debug"; =20 static const struct option bench_rcu_options[] =3D { @@ -40,6 +42,8 @@ static const struct option bench_rcu_options[] =3D { "Sleep time between each run (default: 3 seconds)"), OPT_BOOLEAN(0, "hist", &show_hist, "Show histogram of writer durations"), + OPT_UINTEGER(0, "child-delay", &child_delay, + "Wait for child startup before starting experiment (default: 1 second)"), OPT_STRING(0, "debugfs", &debugfs, "path", "Debugfs mount point (default: /sys/kernel/debug)"), OPT_END() @@ -48,13 +52,18 @@ static const struct option bench_rcu_options[] =3D { static const char *const bench_rcu_usage[] =3D { "RCU benchmark using rcuscale kernel module.", "", - "perf bench sync rcu [options..]", - "perf bench sync rcu [options..] once [..]", + "perf bench sync rcu [options..] [-- ..]", + "perf bench sync rcu [options..] once [..] [-- = ..]", "", " : The type of grace period to use: sync, async, exp (expedite= d)", " This sets the gp_exp or gp_async kernel module parameters.", " : Any parameter of the rcuscale kernel module, e.g. holdoff= =3D5.", " Valid options can be found from running `modinfo rcuscale`.= ", + " : A child command to run during the experiment. This is usef= ul", + " for running tools that monitor system metrics during the", + " experiment. If the command line string contains", + " {READER,WRITER,KFREE}_TASKS placeholders, they will be subs= tituted", + " with the tasks PIDs, separated by comma.", "", "Notes on param:", " This benchmark manages gp_exp and gp_async, and sets block_start=3D1.", @@ -73,6 +82,10 @@ static const char *const bench_rcu_usage[] =3D { " perf bench sync rcu once", " perf bench sync rcu once sync nreaders=3D1 nwriters=3D1 writer_cpu_of= fset=3D1", "", + " perf bench sync rcu once sync nreaders=3D1 nwriters=3D1 writer_cpu_of= fset=3D1 -- \\", + " perf stat -e ipi:ipi_send_cpu,rcu:rcu_grace_period \\", + " -t READER_TASKS,WRITER_TASKS", + "", "In case perf exited abnormally, user need to unload rcuscale by running:= ", " modprobe -r rcuscale torture", "", @@ -105,6 +118,23 @@ struct modprobe_cmd { } #define MODPROBE_REMOVE_CMD "modprobe -r rcuscale torture" =20 +/* + * Generated subprocess command. + * + * Different from modprobe_cmd, this struct is owns the argv array and all + * strings in the array. The only exception is child_cmd_template, which + * contains the remainder of argv parsing. + * + * Upon each runonce(), generate_child_command will make a copy of the str= ings + * in child_cmd_template and also substitute placeholders to actual value. + */ +struct child_cmd { + int argc; + char **argv; +}; + +static struct child_cmd child_cmd_template; + /* * Generic modprobe parameter definition. This is the storage for an * instantiated module parameter. This may come from parameters directly @@ -122,6 +152,7 @@ struct modprobe_param { static struct modprobe_param simple_params[MAX_OPTS]; static int simple_params_count; =20 +static pid_t child_pid; static bool in_child; =20 struct durations { @@ -177,6 +208,12 @@ static void cleanup(void) return; =20 unload_module(); + + if (child_pid) { + kill(child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); + child_pid =3D 0; + } } =20 static void signal_handler(int sig) @@ -407,6 +444,13 @@ static void parse_module_params(int argc, const char *= argv[]) char *value; char buf[MAX_OPTVALUE] =3D ""; =20 + /* Handle child command. */ + if (strcmp(argv[0], "--") =3D=3D 0) { + child_cmd_template.argc =3D argc - 1; + child_cmd_template.argv =3D (char **)argv + 1; + break; + } + if (strnlen(argv[0], MAX_OPTVALUE) >=3D MAX_OPTVALUE - 1) fail("Module parameter too long: \"%s\"", argv[0]); strlcpy(buf, argv[0], MAX_OPTVALUE); @@ -434,6 +478,162 @@ static void parse_module_params(int argc, const char = *argv[]) } } =20 +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D Child Command Handling =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D */ + +/* + * Read reader, writer, or kfree tasks from debugfs, and return a comma + * separated string. + */ +static char *get_tids(const char *debugfs_filename) +{ + char path[PATH_MAX]; + FILE *fp; + + char *tids =3D calloc(INIT_CAPACITY, sizeof(char)); + size_t tids_len =3D 0; + size_t tids_capacity =3D INIT_CAPACITY; + + char *line =3D NULL; + size_t line_buf_size =3D 0; + + if (!tids) + fail("Failed to allocate memory for substitute string"); + + snprintf(path, sizeof(path), "%s/rcuscale/%s", debugfs, debugfs_filename); + + fp =3D fopen(path, "r"); + if (!fp) + err(EXIT_FAILURE, "Failed to open %s", path); + + while (getline(&line, &line_buf_size, fp) !=3D -1) { + size_t line_len =3D strlen(line); + bool is_first =3D (tids_len =3D=3D 0); + + // trim white space and new line characters + while (line_len && isspace(line[line_len - 1])) + line[--line_len] =3D '\0'; + + // 2 for NUL-terminator and "," + reserve_size(&tids, &tids_capacity, tids_len + line_len + 2); + // skip "," for the first value + if (!is_first) + strlcpy(tids + tids_len, ",", 2); + strcat(tids + tids_len, line); + tids_len +=3D line_len + !is_first; + } + + free(line); + fclose(fp); + + return tids; +} + +/* + * Replace the placeholder with the actual value. Modifies the given new = string. + */ +static void replace_child_arg(char **arg, const char *placeholder, + const char *debugfs_filename, char **replacement) +{ + size_t str_capacity =3D strlen(*arg) + 1; + size_t placeholder_len =3D strlen(placeholder); + + while (true) { + size_t replacement_len; + const char *found =3D strstr(*arg, placeholder); + size_t placeholder_off, suffix_off; + + if (found =3D=3D NULL) + return; + + placeholder_off =3D found - *arg; + found =3D NULL; + + /* Replacement is calculated lazily upon encountering placeholder */ + if (*replacement =3D=3D NULL) + *replacement =3D get_tids(debugfs_filename); + + replacement_len =3D strlen(*replacement); + + reserve_size(arg, &str_capacity, + str_capacity - placeholder_len + replacement_len + 1); + + suffix_off =3D placeholder_off + placeholder_len; + + /* Move: v suffix_off + * PREFIX PLACEHOLDER SUFFIX + * ^ placeholder_off + * To: PREFIX _______ SUFFIX + * Or: PREFIX _______________ SUFFIX + * ^ placeholder_off+replacement_len + */ + memmove(*arg + placeholder_off + replacement_len, + *arg + suffix_off, strlen(*arg + suffix_off) + 1); + /* Fill in the replacement */ + memcpy(*arg + placeholder_off, *replacement, replacement_len); + } +} + +/* + * Generate child command by replacing {READER,WRITER,KFREE}_TASKS with th= e actual + * values, comma separated. Caller must call free_child_command(). + */ +static struct child_cmd *generate_child_command(void) +{ + char *reader_tasks_string =3D NULL; + char *writer_tasks_string =3D NULL; + char *kfree_tasks_string =3D NULL; + struct child_cmd *cmd =3D calloc(1, sizeof(*cmd)); + + if (!cmd) + fail("Failed to allocate memory for child command"); + + cmd->argc =3D child_cmd_template.argc; + if (cmd->argc =3D=3D 0) { + cmd->argv =3D NULL; + return cmd; + } + + cmd->argv =3D malloc((cmd->argc + 1) * sizeof(char *)); + if (!cmd->argv) + fail("Failed to allocate memory for child command"); + + for (int i =3D 0; i < cmd->argc; ++i) { + char *arg =3D strdup(child_cmd_template.argv[i]); + + if (!arg) + fail("Failed to allocate memory for child command"); + + if (dryrun) { + cmd->argv[i] =3D arg; + continue; + } + + replace_child_arg(&arg, "READER_TASKS", "reader_tasks", &reader_tasks_st= ring); + replace_child_arg(&arg, "WRITER_TASKS", "writer_tasks", &writer_tasks_st= ring); + replace_child_arg(&arg, "KFREE_TASKS", "kfree_tasks", &kfree_tasks_strin= g); + + cmd->argv[i] =3D arg; + } + + cmd->argv[cmd->argc] =3D NULL; + + free(reader_tasks_string); + free(writer_tasks_string); + free(kfree_tasks_string); + + return cmd; +} + +/* + * Free the child command. + */ +static void free_child_command(struct child_cmd *cmd) +{ + for (int i =3D 0; i < cmd->argc; i++) + free(cmd->argv[i]); + free(cmd->argv); +} + /* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Expe= riment Result Handling =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D */ =20 static void durations_add(struct durations *durations, u64 duration) @@ -692,18 +892,53 @@ static void print_params(const struct modprobe_cmd *c= md) printf("\n"); } =20 +static void print_child_command(const struct child_cmd *cmd) +{ + if (cmd->argc =3D=3D 0) + return; + printf("Running child command:"); + for (int i =3D 0; i < cmd->argc; ++i) + printf(" %s", cmd->argv[i]); + printf("\n"); +} + /* * Core Experiment function */ static void runonce(const struct modprobe_cmd *modprobe_cmd) { + struct child_cmd *child_cmd; struct durations *durations; =20 print_params(modprobe_cmd); run_modprobe(modprobe_cmd); =20 - if (dryrun) + child_cmd =3D generate_child_command(); + print_child_command(child_cmd); + + if (dryrun) { + free_child_command(child_cmd); return; + } + + if (child_cmd->argc !=3D 0) { + // Start command in background + child_pid =3D fork(); + if (child_pid < 0) + err(EXIT_FAILURE, "Failed to fork child process"); + + if (child_pid =3D=3D 0) { + execvp(child_cmd->argv[0], child_cmd->argv); + in_child =3D true; + err(EXIT_FAILURE, "Failed to execute child command"); + } + // otherwise, parent process + } + free_child_command(child_cmd); + child_cmd =3D NULL; + + /* Wait for child process to initialize */ + sleep(child_delay); =20 /* Start and wait for experiment */ start_experiment(); @@ -717,6 +952,13 @@ static void runonce(const struct modprobe_cmd *modprob= e_cmd) =20 printf("Experiment finished.\n"); =20 + /* Wait for child to finish */ + if (child_pid !=3D 0) { + printf("Waiting for child process to exit.\n"); + waitpid(child_pid, NULL, 0); + child_pid =3D 0; + } + /* Print statistics */ print_writer_duration_stats(durations); free_durations(durations); @@ -779,13 +1021,13 @@ int bench_sync_rcu(int argc, const char **argv) =20 /* Parse global options first. */ argc =3D parse_options(argc, argv, bench_rcu_options, bench_rcu_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_KEEP_DASHDASH); =20 /* The empty case is equivalent to 'once sync'. * Otherwise, at least two positional options are required: * once/range/ratio and sync/async/exp */ - if (argc =3D=3D 0) { + if (argc =3D=3D 0 || strcmp(argv[0], "--") =3D=3D 0) { runmode =3D "once"; gp_type =3D "sync"; } else if (argc < 2) { --=20 2.50.1.565.gc32cd1483b-goog From nobody Sun Oct 5 16:16:38 2025 Received: from mail-pj1-f73.google.com (mail-pj1-f73.google.com [209.85.216.73]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6082B2D0281 for ; Thu, 31 Jul 2025 13:26:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1753968421; cv=none; b=MZelFP276a3TxLTeFRXJ081tWunCvdMtGyNyFFH8cQTGbtUD38mJiUVbFO/BU5aF6crtGavOJrlsGQPNDz4EGYeqENyMHVMPVou0aaR9dkR+2yvR9c6/Apa2b9ViYyC8PJUFus58DeMDxP6iyHAQOTGD/pWep7ZgnPTmHkCWaDA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1753968421; c=relaxed/simple; bh=uAaOsx58SAIc0DaiyhmSbn/jax0B8hzvHf4y49AyUtE=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=KuB6GUz/WrszbCMmBYprLxgZki8aphrPLzKOSQqS+GW3Hlk3pF8P20b0u+FaWHzS4Ez6IDNzEhoAz6lrqYqEcByQBnn5X7xiy9J8L83ezyQzPBS5uYkpkRmcwfImd5LtsKnWu8wXJ/qG2sP5fGxtSZOrFr46eRmiu3apqqj3S+E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--yuzhuo.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=A+v83ibV; arc=none smtp.client-ip=209.85.216.73 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--yuzhuo.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="A+v83ibV" Received: by mail-pj1-f73.google.com with SMTP id 98e67ed59e1d1-31f4d0f60caso1046518a91.1 for ; Thu, 31 Jul 2025 06:26:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1753968419; x=1754573219; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=eIx8Yrl2dRhsZZNWCwwoUy/r9MaHjZd4+Pz8DrLT0Nw=; b=A+v83ibVcZaLAOM4rrbMoRTKoada3CZhdpB1OzHfXKZgKR9N54nBlGbsVYHjV5fdCU J1eUyfUDcjRrfK7VU771emr4l9uWebjczgJbPFrdhacdQDKF3RWOx0rFdkomwSHeBGOt 1Ge6qx+WgAeARn7QmpLxVfUVPrd4ahI/4RTAwzeZqdqrOBajpSKXEaWm0kRNXkFZfjmV WJnbILMVFIyvrLA/MfvDZWLdK5yB9I4zF4gL+u0+sdvjTWVWePFrjwmDH7Hgi1AkAC5o QGEkXx7+krk3+juZTwXPhBwOSJ5ewRiLeWFqwZ0Jh9ht9t4Ghm0tih2W6y+ZQ/HpwMlW xDDA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1753968419; x=1754573219; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=eIx8Yrl2dRhsZZNWCwwoUy/r9MaHjZd4+Pz8DrLT0Nw=; b=qn7z1/iAbMDATWfdKMl6GpXhuZNT3KOooZv9cGkb9RioJ4d5ZxjAJYHFd3om33BToY hc01jh1eV3uDwz8YvaT4N19YS1bHnFZWUVbbvdufLSlYhvZAAqGUluvY5tcHNPSpkVUY kr4rzxKAiYEFGbFn+6tjSpja88ozoucfoYfLa0T4xbf5vyELG59ZNvFUG6RdaaY0hIql XnOS1AfS78xDqGgFWurktlQ7X2QN5MuVB0jdsBdmHI9v/3GDYZf9sl1rCQRy/hwsJMau JvWD6YQj/kZfyNV7P5HGN5886NvhJna8SB9fdE58UR1/uhIYkLBMyrnFq1ZfH/vZZLUH y4kw== X-Forwarded-Encrypted: i=1; AJvYcCVhc8N3U6HAKlz4GDx41+i7wHwYAIi1UdjzYx/6y9CDqf5UMSB66V5HW5odv32Z9ePrbXHqWFoI3nnU3hc=@vger.kernel.org X-Gm-Message-State: AOJu0YyOErfY4wMVyFbLKKUC795M+WN8Ley2NDvfckght5lBth5vzN28 5ginL30wNwR0vffLVHahsTULZAdhbJ8oLjIbFIC9wFlOjNUX3AHXEXJuK+4EpYdpVC5cklLC7vY tyJZixQ== X-Google-Smtp-Source: AGHT+IEh/5sOTgA8qE5uzJr4lbFyhYU/sDirMMjY/1aldSGmBNHAtwFN7iym8avJdbqOTRFHWAEdbnNnqLE= X-Received: from pjqx17.prod.google.com ([2002:a17:90a:b011:b0:314:29b4:453]) (user=yuzhuo job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:4c48:b0:316:3972:b9d0 with SMTP id 98e67ed59e1d1-31f5dc953d4mr9913423a91.0.1753968418498; Thu, 31 Jul 2025 06:26:58 -0700 (PDT) Date: Thu, 31 Jul 2025 06:26:13 -0700 In-Reply-To: <20250731132615.938435-1-yuzhuo@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250731132615.938435-1-yuzhuo@google.com> X-Mailer: git-send-email 2.50.1.565.gc32cd1483b-goog Message-ID: <20250731132615.938435-4-yuzhuo@google.com> Subject: [PATCH v1 3/5] perf bench: Add 'range' mode to 'sync rcu' From: Yuzhuo Jing To: Davidlohr Bueso , "Paul E . McKenney" , Josh Triplett , Frederic Weisbecker , Neeraj Upadhyay , Joel Fernandes , Boqun Feng , Uladzislau Rezki , Steven Rostedt , Mathieu Desnoyers , Lai Jiangshan , Zqiang , Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , Liang Kan , Yuzhuo Jing , Yuzhuo Jing , Sebastian Andrzej Siewior , linux-kernel@vger.kernel.org, rcu@vger.kernel.org, linux-perf-users@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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=3Dstart[: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 =3D 1 or 2] x [writer_cpu_offset =3D 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=3D1:2 nwriters=3D1 writer_cpu_o= ffset=3D0:2 \# Running 'sync/rcu' benchmark: Running experiment with options: gp_exp=3D1 nwriters=3D1 nreaders=3D1 write= r_cpu_offset=3D0 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=3D1 nwriters=3D1 nreaders=3D1 write= r_cpu_offset=3D1 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=3D1 nwriters=3D1 nreaders=3D1 write= r_cpu_offset=3D2 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=3D1 nwriters=3D1 nreaders=3D2 write= r_cpu_offset=3D0 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=3D1 nwriters=3D1 nreaders=3D2 write= r_cpu_offset=3D1 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=3D1 nwriters=3D1 nreaders=3D2 write= r_cpu_offset=3D2 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 --- 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[] =3D { "", "perf bench sync rcu [options..] [-- ..]", "perf bench sync rcu [options..] once [..] [-- = ..]", + "perf bench sync rcu [options..] range [..] [-- = ..]", "", " : The type of grace period to use: sync, async, exp (expedite= d)", " This sets the gp_exp or gp_async kernel module parameters.", @@ -76,11 +77,18 @@ static const char *const bench_rcu_usage[] =3D { " 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-n= egative.", + " The benchmark instantiates all combinations of all ranges.", + " If a parameter does not specify end, or start=3Dend, it behaves= ", + " the same as 'once' mode. The range parameter types are validat= ed", + " agains `modinfo rcuscale` to ensure they are integer.", "", "Examples:", " perf bench sync rcu --hist once exp nreaders=3D1 nwriters=3D1 writer_c= pu_offset=3D1", " perf bench sync rcu once", " perf bench sync rcu once sync nreaders=3D1 nwriters=3D1 writer_cpu_of= fset=3D1", + " perf bench sync rcu range exp nreaders=3D1:2 nwriters=3D1 writer_cpu_= offset=3D0:2", "", " perf bench sync rcu once sync nreaders=3D1 nwriters=3D1 writer_cpu_of= fset=3D1 -- \\", " perf stat -e ipi:ipi_send_cpu,rcu:rcu_grace_period \\", @@ -105,6 +113,7 @@ static const char *const bench_rcu_usage[] =3D { * 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]; }; =20 +/* + * 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=3D1 end=3D9 step=3D2 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; } =20 +/* + * 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=3D1, end=3D10, step=3D2 + * "1:10" -> start=3D1, end=3D10, step=3D1 + * "1" -> start=3D1, end=3D1, step=3D1 + */ +static int parse_range(struct range *range, const char *str) +{ +#define MAX_RANGE 5 + + char *token; + char *saveptr =3D NULL; + int count =3D 0; + int values[MAX_RANGE]; + + char *str_copy =3D strdup(str); + + if (!str_copy) + fail("Memory allocation failed"); + + // Split by : or - + token =3D strtok_r(str_copy, ":", &saveptr); + while (token !=3D NULL && count < MAX_RANGE) { + values[count++] =3D parse_int(token); + token =3D strtok_r(NULL, ":", &saveptr); + } + + switch (count) { + case 1: + range->start =3D values[0]; + range->end =3D values[0]; + range->step =3D 1; + break; + case 2: + range->start =3D values[0]; + range->end =3D values[1]; + range->step =3D 1; + break; + case 3: + range->start =3D values[0]; + range->end =3D values[1]; + range->step =3D 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 <=3D 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 >=3D MAX_OPTS) @@ -353,6 +455,14 @@ static void simple_params_add(const char *full) strlcpy(simple_params[simple_params_count++].value, full, MAX_OPTVALUE); } =20 +static void range_params_add(const char *name, const struct range *range) +{ + if (range_params_count >=3D MAX_OPTS) + fail("Too many module parameters"); + strlcpy(range_params[range_params_count].name, name, MAX_OPTNAME); + range_params[range_params_count++].range =3D *range; +} + static void parse_gp_type(const char *gp_type) { if (strcmp(gp_type, "sync") =3D=3D 0) { @@ -379,6 +489,10 @@ static bool param_has_conflict(const char *key) && simple_params[i].value[strlen(key)] =3D=3D '=3D') return true; } + for (int i =3D 0; i < range_params_count; ++i) { + if (strcmp(key, range_params[i].name) =3D=3D 0) + return true; + } /* overridable_params are considered non conflict */ =20 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_r= ange) { while (argc) { char *saved_ptr =3D NULL; + struct range range; + bool is_range =3D false; char *key; char *value; char buf[MAX_OPTVALUE] =3D ""; @@ -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); =20 - /* 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 =3D !(range.start =3D=3D 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); + } + } =20 - simple_params_add(argv[0]); + /* Store the option. */ + if (is_range) + range_params_add(key, &range); + else + simple_params_add(argv[0]); =20 argc--; argv++; @@ -973,6 +1104,11 @@ static void modprobe_cmd_add(struct modprobe_cmd *cmd= , const char *v) cmd->cmd[++cmd->count] =3D NULL; } =20 +static void modprobe_cmd_pop(struct modprobe_cmd *cmd) +{ + cmd->cmd[--cmd->count] =3D NULL; +} + /* * Append parameters that are overridable by users. */ @@ -1002,13 +1138,62 @@ static void test_once(int argc, const char *argv[]) { MODPROBE_CMD_INIT; =20 - parse_module_params(argc, argv); + parse_module_params(argc, argv, false); =20 modprobe_collect_simple_options(&modprobe_cmd); =20 runonce(&modprobe_cmd); } =20 +/* + * 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 >=3D range_params_count) + return runonce(cmd); + + range =3D range_params[range_index].range; + + for (int i =3D range.start; i <=3D range.end; i +=3D range.step) { + struct modprobe_param param; + + param_print_key_value(¶m, "%s=3D%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 <=3D 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); +} + /* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D Entry Point =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ =20 int bench_sync_rcu(int argc, const char **argv) @@ -1041,6 +1226,8 @@ int bench_sync_rcu(int argc, const char **argv) =20 if (strcmp(runmode, "once") =3D=3D 0) cmd =3D test_once; + else if (strcmp(runmode, "range") =3D=3D 0) + cmd =3D test_range; else usage_with_options(bench_rcu_usage, bench_rcu_options); =20 --=20 2.50.1.565.gc32cd1483b-goog From nobody Sun Oct 5 16:16:38 2025 Received: from mail-pl1-f201.google.com (mail-pl1-f201.google.com [209.85.214.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E7B6F2D0C73 for ; Thu, 31 Jul 2025 13:27:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1753968422; cv=none; b=Pm8s+s3R9H7flK6/h+ucyAQOth+Krbvf5x5otvjOAuumwP9PxKJ8KTRqDMqUIRmJtP6wKoMhwi9J9Bt7XDHO/utBiBRfqwXYrDOCaIlKtN3uZIO+f62DcqJiugSvnTbgDNG8shwy6Oy8wUAYMJFPEMOuzcrHPcz2g1gpLE4ifnE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1753968422; c=relaxed/simple; bh=U2Q/nA8/fs3IaxhsmxtBPWhflIIPydiW0Xcing3X6TQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=jLTqKOt/hrbNpE7Q4WMsH0GtGeV/6yC68t5WlIcfzIROVhDHoDAgejR6aDWXuYi5+tu6RKF237Gn4BAPk1uz1F/BUFsqlrPeDtugIBQS7ndaoHjV3ISq3bAnfQN/nADPW6Eci14MV38N7870WJIc+S/qXSyp6EYRvKS9CW63EkA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--yuzhuo.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=Jg4T17Fu; arc=none smtp.client-ip=209.85.214.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--yuzhuo.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="Jg4T17Fu" Received: by mail-pl1-f201.google.com with SMTP id d9443c01a7336-2400cbd4241so12638835ad.3 for ; Thu, 31 Jul 2025 06:27:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1753968420; x=1754573220; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=cS0kX7TvRYgQTsqu0GMqG/8vvD7pFz5fsy9ObfHAWm4=; b=Jg4T17FugmVHZwRiwS3QLHVhMZkjjr/LQTLtSfLnY8fYp6RWCCP1q1G9cAWNeV9PoO 19/Ez/k0IFszuunhz30CmIKDUmIWeVX4IZAkxtxUHe4Ic685tzJCD0Zfb22HggSuhvCL 3XsKBZ1CxoldgPPe3YrDWDairLV94zT5dPcLMSqxo2xiRUtPoYshTI0hdovKfw/s58gf YbYx3ivr9YD0gu+Rp9eDB+q4JDv1UmgsnxC7BgtZRhX0vMcBjCUiwYIJORK+108xXmpl anpDjuGEfxWc49HoVZv/4KoM+pTk79di7/msXig6WlK/5lDpDUW4UradQd4mHjPho+md 7Iog== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1753968420; x=1754573220; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=cS0kX7TvRYgQTsqu0GMqG/8vvD7pFz5fsy9ObfHAWm4=; b=Tn1vRK+IBa3mI+HwxHRo9GzdnL2u/sLVvqsSytDybmaIx7P/8TsPmfMFqhXHfP9qnk XIuUftW4UEPWmHYop1QZNMQGOvIgEubPSZyBR08pb/kC7NfpHALKLqE9pxf5mgtYorer EFmNaANRvBnPsWpy0PnixYvbPn+LATu5iTRi+3RQt5ieWI9It+w4RbkRC+9q4sGsrV0g wcplp1QSU4vLx/an4iVsqv3+RN0oqNyA5ytbMwI10Db4ky09LJQoWHXXTqZbOac7VEmD H7sImkHlsobocNxziRgpLwq9774MxASzXb91nfM4+jxKyudCrcfhbfzLjCbM/WOzb3X0 H61w== X-Forwarded-Encrypted: i=1; AJvYcCVij2MQNwNI8PYq1psihSgewrMVr1PQDDL75HgELE4V3DtHrdH0+48x5W4wHZpzqRVfqsa3PGI48RgQMNE=@vger.kernel.org X-Gm-Message-State: AOJu0YzZXkRFfobj8Pw74we9beTHObRsvZ2YcDBWwnUKoIMuDhHDZ2fr xZa1/gEexP4qjnBULpz2sF1NlaNT9LyV6PF4Ej7lcI0lUM9tBNcAGZN7BJlrs3cMJPidCtvtpSo DqhQ+XA== X-Google-Smtp-Source: AGHT+IEwFWYPRxHNlmybj0gexk68rMr7gVOb927INT9mYj/fa8gmdq1czl+fytYCMaogstSXj8WleWU/75s= X-Received: from plrj13.prod.google.com ([2002:a17:903:28d:b0:23f:cd94:8c5e]) (user=yuzhuo job=prod-delivery.src-stubby-dispatcher) by 2002:a17:903:4b43:b0:240:8ec:fe2 with SMTP id d9443c01a7336-24096a4fa57mr127525435ad.4.1753968420421; Thu, 31 Jul 2025 06:27:00 -0700 (PDT) Date: Thu, 31 Jul 2025 06:26:14 -0700 In-Reply-To: <20250731132615.938435-1-yuzhuo@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250731132615.938435-1-yuzhuo@google.com> X-Mailer: git-send-email 2.50.1.565.gc32cd1483b-goog Message-ID: <20250731132615.938435-5-yuzhuo@google.com> Subject: [PATCH v1 4/5] perf bench: Add 'ratio' mode to 'sync rcu' From: Yuzhuo Jing To: Davidlohr Bueso , "Paul E . McKenney" , Josh Triplett , Frederic Weisbecker , Neeraj Upadhyay , Joel Fernandes , Boqun Feng , Uladzislau Rezki , Steven Rostedt , Mathieu Desnoyers , Lai Jiangshan , Zqiang , Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , Liang Kan , Yuzhuo Jing , Yuzhuo Jing , Sebastian Andrzej Siewior , linux-kernel@vger.kernel.org, rcu@vger.kernel.org, linux-perf-users@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a 'ratio' mode to RCU benchmark. This mode helps investigate performance effects on the ratio between selected two parameters. The command is defined as: ratio is the sum of and . specifies the range of param1's values, and thus param2's values can be calculated as 'total-param1'. Example usage: $ ./perf bench sync rcu ratio sync 10 0:10:3 nreaders nwriters \# Running 'sync/rcu' benchmark: Running experiment with options: nreaders=3D0 nwriters=3D10 Experiment finished. Average grace-period duration: 16494.413 microseconds Minimum grace-period duration: 7994.842 50th percentile grace-period duration: 17999.439 90th percentile grace-period duration: 18001.923 99th percentile grace-period duration: 23999.068 Maximum grace-period duration: 24000.441 Cooling down (3s).. Running experiment with options: nreaders=3D3 nwriters=3D7 Experiment finished. Average grace-period duration: 140018.793 microseconds Minimum grace-period duration: 11987.02 50th percentile grace-period duration: 120999.879 90th percentile grace-period duration: 218000.011 99th percentile grace-period duration: 218006.372 Maximum grace-period duration: 219002.024 Cooling down (3s).. Running experiment with options: nreaders=3D6 nwriters=3D4 Experiment finished. Average grace-period duration: 210481.539 microseconds Minimum grace-period duration: 5999.579 50th percentile grace-period duration: 217999.902 90th percentile grace-period duration: 218000.529 99th percentile grace-period duration: 218998.809 Maximum grace-period duration: 219000.652 Cooling down (3s).. Running experiment with options: nreaders=3D9 nwriters=3D1 Experiment finished. Average grace-period duration: 210782.119 microseconds Minimum grace-period duration: 89997.154 50th percentile grace-period duration: 217999.829 90th percentile grace-period duration: 218001.299 99th percentile grace-period duration: 219003.072 Maximum grace-period duration: 324116.763 Signed-off-by: Yuzhuo Jing --- tools/perf/bench/sync-rcu.c | 74 +++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tools/perf/bench/sync-rcu.c b/tools/perf/bench/sync-rcu.c index 921520a645ae..73142fd5be21 100644 --- a/tools/perf/bench/sync-rcu.c +++ b/tools/perf/bench/sync-rcu.c @@ -55,6 +55,7 @@ static const char *const bench_rcu_usage[] =3D { "perf bench sync rcu [options..] [-- ..]", "perf bench sync rcu [options..] once [..] [-- = ..]", "perf bench sync rcu [options..] range [..] [-- = ..]", + "perf bench sync rcu [options..] ratio <= param1_name> [..] [-- ..]", "", " : The type of grace period to use: sync, async, exp (expedite= d)", " This sets the gp_exp or gp_async kernel module parameters.", @@ -83,12 +84,17 @@ static const char *const bench_rcu_usage[] =3D { " If a parameter does not specify end, or start=3Dend, it behaves= ", " the same as 'once' mode. The range parameter types are validat= ed", " agains `modinfo rcuscale` to ensure they are integer.", + " ratio: Run benchmark that changes the ratio between two parameters.", + " specifies the sum of param1 and param2, and ", + " is the range of param1 values. param2 is calculated by total-p= aram1.", + " Additional non-range parameters may also be specified.", "", "Examples:", " perf bench sync rcu --hist once exp nreaders=3D1 nwriters=3D1 writer_c= pu_offset=3D1", " perf bench sync rcu once", " perf bench sync rcu once sync nreaders=3D1 nwriters=3D1 writer_cpu_of= fset=3D1", " perf bench sync rcu range exp nreaders=3D1:2 nwriters=3D1 writer_cpu_= offset=3D0:2", + " perf bench sync rcu ratio sync 10 0:10:3 nreaders nwriters", "", " perf bench sync rcu once sync nreaders=3D1 nwriters=3D1 writer_cpu_of= fset=3D1 -- \\", " perf stat -e ipi:ipi_send_cpu,rcu:rcu_grace_period \\", @@ -1194,6 +1200,72 @@ static void test_range(int argc, const char *argv[]) test_range_recursive(0, &modprobe_cmd); } =20 +/* + * Test ratio. Use loop on two range options. + * + * Does not allow ranges for other options. + * + * Example: + * perf bench rcu ratio sync 10 1:10:2 nreaders nwriters + * will run the following experiments: + * nreaders=3D1, nwriters=3D9 + * nreaders=3D2, nwriters=3D8 + * nreaders=3D3, nwriters=3D7 + * ... + * nreaders=3D9, nwriters=3D1 + */ +static void test_ratio(int argc, const char *argv[]) +{ + MODPROBE_CMD_INIT; + + int total; + struct range option1_range; + const char *option1_name; + const char *option2_name; + + if (argc < 4) + usage_with_options(bench_rcu_usage, bench_rcu_options); + + total =3D parse_int(argv[0]); + parse_range(&option1_range, argv[1]); + option1_name =3D argv[2]; + option2_name =3D argv[3]; + + check_param_name(option1_name); + check_param_name(option2_name); + + if (total < option1_range.start || total < option1_range.end) + fail("Total must be greater than or equal to the range bounary"); + + parse_module_params(argc - 4, argv + 4, false); + + modprobe_collect_simple_options(&modprobe_cmd); + + for (int i =3D option1_range.start; i <=3D option1_range.end; i +=3D opti= on1_range.step) { + int j =3D total - i; + + struct modprobe_param param1, param2; + + param_print_key_value(¶m1, "%s=3D%d", option1_name, i); + param_print_key_value(¶m2, "%s=3D%d", option2_name, j); + + modprobe_cmd_add(&modprobe_cmd, param1.value); + modprobe_cmd_add(&modprobe_cmd, param2.value); + + runonce(&modprobe_cmd); + + modprobe_cmd_pop(&modprobe_cmd); + modprobe_cmd_pop(&modprobe_cmd); + + if (i + option1_range.step <=3D option1_range.end) { + printf("Cooling down (%ds)..\n", cooldown); + if (!dryrun) + sleep(cooldown); + puts(""); + } + } +} + /* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D Entry Point =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ =20 int bench_sync_rcu(int argc, const char **argv) @@ -1228,6 +1300,8 @@ int bench_sync_rcu(int argc, const char **argv) cmd =3D test_once; else if (strcmp(runmode, "range") =3D=3D 0) cmd =3D test_range; + else if (strcmp(runmode, "ratio") =3D=3D 0) + cmd =3D test_ratio; else usage_with_options(bench_rcu_usage, bench_rcu_options); =20 --=20 2.50.1.565.gc32cd1483b-goog From nobody Sun Oct 5 16:16:38 2025 Received: from mail-pf1-f202.google.com (mail-pf1-f202.google.com [209.85.210.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DF2A92D0C9B for ; Thu, 31 Jul 2025 13:27:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1753968424; cv=none; b=TUOujHZGRijmEpKW7L4gAmBaajjA1RkFS3DMxfXfADskSYSz8W0yYqDrB20qdXcqXFjyoZjp4LOuoB2cHzF5ARbHbB9Vu5Ska+btyOdTMoY00Jqkqh/UVGCuK29dD6VAubsAfQ/0gz1c43alVx383TShsYyPRhAWAxUOd+7ZoE8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1753968424; c=relaxed/simple; bh=IvE7wbOfiky5gP1/JdKf1HJsEXUXuz12TG0IoOAqx3U=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=P74hg9UeeG6cn/JE7XmRD/XK3UWqhWVxI1TdbervJJZQAPL4KXpot2uxXh8/q701FegUHbNvywMIGrZIfmGU70xsDDa9Nr/TJa+c+SWrD5oLoL8iumZ0OuJuAWnDJs4cXXcuQ4zpe8T11Iw6M9gnYYWYECLMF8F8YwU6DMcyijE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--yuzhuo.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=tMfqJ82Z; arc=none smtp.client-ip=209.85.210.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--yuzhuo.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="tMfqJ82Z" Received: by mail-pf1-f202.google.com with SMTP id d2e1a72fcca58-74928291bc3so1058042b3a.0 for ; Thu, 31 Jul 2025 06:27:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1753968422; x=1754573222; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=9D+1SkLUHFp3CL6p6s9XNodFOnizBfge5u1xSj5q/5M=; b=tMfqJ82Z05i8Mg0BYhuYG0kBYmCOeFUL9mvvU5abyEG1YqbgDAxn1ApFiy6E90mfDn EN/6DbGJmy1JLTksnZbnAIz3nDfHvP7/hjDEIKnxcQXuNERzy78jFsSlLXVs+2aVsDTS f2fnllV8pqwibDbRCkrTxVOJlMoyLheMZBDiPaPy7XledeP7RvT21t9DZlGXDiL1Wp4U gWQRbO8qs6IZ6K6cqCQKxiL0TQ7+ODh1GLTwyO+NnMNvPKLJKn4Fjn5iITzpP0XqL9kc qqIDt44Osc3ADUKRae4vJGb31ektm+jLmWTr0Eda/xROokUGvO1Jv/eYelG3R3uKi73q 2InA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1753968422; x=1754573222; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=9D+1SkLUHFp3CL6p6s9XNodFOnizBfge5u1xSj5q/5M=; b=efgZQEi3pM7uHhpexn8fBn8JFfMY3l0DOEfoOKNjP8pQwJMoB49QlxuG+APvif3fh/ 9JKWGA1N4pL51WqPdffM5HsmgoZRCFyMpRNDW+DkVM5O1VUTORpCBJgMhqqP+f9KEzPm +yBGwlPNYg7MYLE3bpw6owwwUHTWTK/Zzi2LcENFpL67v5frFiXusW0g6YxBL0Z2/w1M 9ZJbmDeBOsmwx7brJb4tSZUntTsJWlJOJVdpxyaNHESWKizGj5A6mTT7R47hFX6DIGEa gbMJqi+PdPt/lF0MrmG6TI458BY+p92Hx5DPo4HFCFYTiS33SU1ZYclthlRRPAHquZo6 c/jg== X-Forwarded-Encrypted: i=1; AJvYcCWZ5QzffFucPQdT54pfPgkqERNf3+6L8uFtXNjoSXj5JZuHl2ND1uTXzDww676HfPR8CDsi6AhLg+0nb0g=@vger.kernel.org X-Gm-Message-State: AOJu0YwuVW4+zQDlVb+zSq8koQAQimZyugFkK9jfbOl31Q0T/ltcf6Rc uNxzM9w6xD3uF/rn/gtSsqRzSxoTWwqvMAxRRAKcZcob4odjG+ZaBgwvxrBYQ/smGOz5X/YsU3C NU253WA== X-Google-Smtp-Source: AGHT+IHobDSGcus+S94g4r7tqFvwsqp38RnMHKYwOWfldZixh8KyWZx+bTZ3gtJnbMQKBH3kowT2EzQj/94= X-Received: from pgar7.prod.google.com ([2002:a05:6a02:2e87:b0:b2c:4548:13d0]) (user=yuzhuo job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a20:258a:b0:232:ddc1:f22b with SMTP id adf61e73a8af0-23dc0d5f6bdmr10780967637.19.1753968422122; Thu, 31 Jul 2025 06:27:02 -0700 (PDT) Date: Thu, 31 Jul 2025 06:26:15 -0700 In-Reply-To: <20250731132615.938435-1-yuzhuo@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250731132615.938435-1-yuzhuo@google.com> X-Mailer: git-send-email 2.50.1.565.gc32cd1483b-goog Message-ID: <20250731132615.938435-6-yuzhuo@google.com> Subject: [PATCH v1 5/5] perf bench: Add documentation for 'sync rcu' suite From: Yuzhuo Jing To: Davidlohr Bueso , "Paul E . McKenney" , Josh Triplett , Frederic Weisbecker , Neeraj Upadhyay , Joel Fernandes , Boqun Feng , Uladzislau Rezki , Steven Rostedt , Mathieu Desnoyers , Lai Jiangshan , Zqiang , Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , Liang Kan , Yuzhuo Jing , Yuzhuo Jing , Sebastian Andrzej Siewior , linux-kernel@vger.kernel.org, rcu@vger.kernel.org, linux-perf-users@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add documentation for 'perf bench sync rcu'. This benchmark has three modes or subcommands that takes positional arguments. In addition, *kernel* module parameters are directly specified in the form of "name=3Dvalue", without "--name". Multiple subsections are thus added to the 'sync' section for illustration. Signed-off-by: Yuzhuo Jing --- tools/perf/Documentation/perf-bench.txt | 131 ++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documenta= tion/perf-bench.txt index 8331bd28b10e..786c6e6880f5 100644 --- a/tools/perf/Documentation/perf-bench.txt +++ b/tools/perf/Documentation/perf-bench.txt @@ -49,6 +49,9 @@ SUBSYSTEM 'sched':: Scheduler and IPC mechanisms. =20 +'sync':: + Synchronization primitives. + 'syscall':: System call performance (throughput). =20 @@ -162,6 +165,134 @@ Example of *pipe* =20 --------------------- =20 +SUITES FOR 'sync' +~~~~~~~~~~~~~~~~~ +*rcu*:: +Suite for RCU performance. Depends on rcuscale kernel module. +This benchmark has three modes: once, range, ratio. Usage is defined as b= elow. + +'perf bench sync rcu' [options..] [-- ..] +'perf bench sync rcu' [options..] once [..] [-- = ..] +'perf bench sync rcu' [options..] range [..] [-- = ..] +'perf bench sync rcu' [options..] ratio <= param1_name> [..] [-- ..] + +Modes for *rcu* +^^^^^^^^^^^^^^^ + +default:: +Run 'once sync'.", + +once:: +Run benchmark once, with all parameters passed through to the kernel rcusc= ale +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. + +ratio:: +Run benchmark that changes the ratio between two parameters. 'total' speci= fies +the sum of 'param1' and 'param2', and 'param1_range' is the range of 'para= m1' +values. 'param2' is calculated by 'total-param1'. Additional non-range +parameters may also be specified. + +Positional arguments for *rcu* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +gp_type:: +The type of grace period to use: sync, async, exp (expedited) + +param:: +Any parameter of the rcuscale kernel module, except for "gp_exp", +"gp_async" and "block_start" that are managed by this benchmark. +Valid options can be found from "modinfo rcuscale". + +command:: +A child command to run during the experiment. If the command line +string contains {READER,WRITER,KFREE}_TASKS placeholders, they will be +substituted with the tasks PIDs, separated by comma. + +Options for *rcu* +^^^^^^^^^^^^^^^^^ +-c:: +--cooldown:: +Sleep time between each experiment (default: 3 seconds). + +-n:: +--dryrun:: +Dry run mode. Do not run experiments, but instead print what parameter +combination will run. + +--child-delay=3D:: +Wait for child startup before starting experiment (default: 1 second). + +--debugfs=3D:: +Debugfs mount point used to interact with the rcuscale kernel module. (def= ault: +/sys/kernel/debug). + +--hist:: +Show the histogram of writer durations. + +Example of *rcu* +^^^^^^^^^^^^^^^^ + +--------------------- +% perf bench sync rcu once exp nreaders=3D1 nwriters=3D1 +# Running 'sync/rcu' benchmark: +Running experiment with options: gp_exp=3D1 nreaders=3D1 nwriters=3D1 +Experiment finished. +Average grace-period duration: 124.236 microseconds +Minimum grace-period duration: 8.783 +50th percentile grace-period duration: 9.033 +90th percentile grace-period duration: 9.665 +99th percentile grace-period duration: 20.911 +Maximum grace-period duration: 6025.167 + +% perf bench sync rcu range exp nreaders=3D1 nwriters=3D1 writer_cpu_offse= t=3D0:1 -- \ + perf stat -e ipi:ipi_send_cpu,rcu:rcu_grace_period \ + -t READER_TASKS,WRITER_TASKS +# Running 'sync/rcu' benchmark: +Running experiment with options: gp_exp=3D1 nreaders=3D1 nwriters=3D1 writ= er_cpu_offset=3D0 +Running child command: perf stat -e ipi:ipi_send_cpu,rcu:rcu_grace_period = -t 2061441,2061442 + + Performance counter stats for thread id '2061441,2061442': + + 2400 ipi:ipi_send_cpu + 100 rcu:rcu_grace_period + + 6.006040148 seconds time elapsed + +Experiment finished. +Waiting for child process to exit. +Average grace-period duration: 301.177 microseconds +Minimum grace-period duration: 9.064 +50th percentile grace-period duration: 9.394 +90th percentile grace-period duration: 10.977 +99th percentile grace-period duration: 5926.781 +Maximum grace-period duration: 6011.067 +Cooling down (3s).. + +Running experiment with options: gp_exp=3D1 nreaders=3D1 nwriters=3D1 writ= er_cpu_offset=3D1 +Running child command: perf stat -e ipi:ipi_send_cpu,rcu:rcu_grace_period = -t 2061461,2061462 + + Performance counter stats for thread id '2061461,2061462': + + 2144 ipi:ipi_send_cpu + 201 rcu:rcu_grace_period + + 6.006110747 seconds time elapsed + +Experiment finished. +Waiting for child process to exit. +Average grace-period duration: 12.23 microseconds +Minimum grace-period duration: 9.134 +50th percentile grace-period duration: 9.475 +90th percentile grace-period duration: 11.897 +99th percentile grace-period duration: 38.057 +Maximum grace-period duration: 67.19 +--------------------- + SUITES FOR 'syscall' ~~~~~~~~~~~~~~~~~~ *basic*:: --=20 2.50.1.565.gc32cd1483b-goog