perf test: Add workload-ctl option

Add a --workload-ctl=fifo:ctl-fifo[,ack-fifo] option for 'perf test
-w'. When set, run_workload() opens the named FIFO, writes enable before
invoking the builtin workload, writes disable before returning, and
waits for ack responses when an ack FIFO is provided to ensure that the
workload doesn't run until the events are enabled.

This can be used to limit the scope of the recording to only the
workload execution and avoid recording Perf setup and teardown code if
Perf record is started with events disabled (-D 1).

Assisted-by: Codex:GPT-5.5
Signed-off-by: James Clark <james.clark@linaro.org>
Cc: Amir Ayupov <aaupov@meta.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Leo Yan <leo.yan@arm.com>
Cc: Mike Leach <mike.leach@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Paschalis Mpeis <Paschalis.Mpeis@arm.com>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Cc: Suzuki Poulouse <suzuki.poulose@arm.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
James Clark
2026-06-09 15:40:07 +01:00
committed by Arnaldo Carvalho de Melo
parent 68ca50bc0f
commit d281fb9dfd
2 changed files with 194 additions and 2 deletions

View File

@@ -69,3 +69,15 @@ OPTIONS
--list-workloads::
List the available workloads to use with -w/--workload.
--record-ctl=fifo:ctl-fifo[,ack-fifo]::
This option is used to communicate with a perf record session in order
to control the recording scope to only the workload and avoid recording
setup and teardown code. When specifying this option, the same FIFO path
must be specified in the record session via:
perf record -D -1 --control=fifo:ctl-fifo[,ack-fifo] ...
Perf test sends 'enable' and 'disable' commands through ctl-fifo to
control event recording. If 'ack-fifo' is provided, the workload runner
waits for an 'ack' response after each command.

View File

@@ -56,6 +56,7 @@ static unsigned int runs_per_test = 1;
static unsigned int failure_snippet_lines = 10;
const char *dso_to_test;
const char *test_objdump_path = "objdump";
static const char *workload_control;
/*
* List of architecture specific tests. Not a weak symbol as the array length is
@@ -168,6 +169,11 @@ static struct test_workload *workloads[] = {
#endif
};
struct workload_control {
int ctl_fd;
int ack_fd;
};
#define workloads__for_each(workload) \
for (unsigned i = 0; i < ARRAY_SIZE(workloads) && ({ workload = workloads[i]; 1; }); i++)
@@ -1387,13 +1393,185 @@ static int workloads__fprintf_list(FILE *fp)
return printed;
}
static int perf_control_open_fifo(struct workload_control *ctl, const char *str)
{
char *s, *p;
int ret;
if (strncmp(str, "fifo:", 5))
return -EINVAL;
str += 5;
if (!*str || *str == ',')
return -EINVAL;
s = strdup(str);
if (!s)
return -ENOMEM;
p = strchr(s, ',');
if (p)
*p = '\0';
ctl->ctl_fd = open(s, O_WRONLY | O_CLOEXEC);
if (ctl->ctl_fd < 0) {
ret = -errno;
pr_err("Failed to open workload control FIFO '%s': %m\n", s);
free(s);
return ret;
}
if (p && *++p) {
ctl->ack_fd = open(p, O_RDONLY | O_CLOEXEC);
if (ctl->ack_fd < 0) {
ret = -errno;
pr_err("Failed to open workload control ack FIFO '%s': %m\n", p);
close(ctl->ctl_fd);
ctl->ctl_fd = -1;
free(s);
return ret;
}
}
free(s);
return 0;
}
static int perf_control_open(struct workload_control *ctl)
{
int ret;
if (!workload_control)
return 0;
ret = perf_control_open_fifo(ctl, workload_control);
if (ret == -EINVAL) {
pr_err("Unsupported workload control spec '%s', expected fifo:ctl-fifo[,ack-fifo]\n",
workload_control);
}
return ret;
}
static void perf_control_close(struct workload_control *ctl)
{
if (ctl->ctl_fd >= 0) {
close(ctl->ctl_fd);
ctl->ctl_fd = -1;
}
if (ctl->ack_fd >= 0) {
close(ctl->ack_fd);
ctl->ack_fd = -1;
}
}
static int perf_control_write_cmd(int fd, const char *cmd)
{
size_t len = strlen(cmd);
ssize_t ret;
while (len) {
ret = write(fd, cmd, len);
if (ret < 0) {
if (errno == EINTR)
continue;
pr_err("Failed to write perf control command: %m\n");
return -1;
}
if (!ret) {
pr_err("Failed to write perf control command: short write\n");
return -1;
}
cmd += ret;
len -= ret;
}
return 0;
}
static int perf_control_read_ack(int fd)
{
char buf[16];
ssize_t ret;
do {
ret = read(fd, buf, sizeof(buf) - 1);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
pr_err("Failed to read perf control ack: %m\n");
return -1;
}
if (!ret) {
pr_err("Unexpected EOF while reading perf control ack\n");
return -1;
}
buf[ret] = '\0';
for (ssize_t i = 0; i < ret; i++) {
if (buf[i] == '\n' || buf[i] == '\0') {
buf[i] = '\0';
break;
}
}
if (strcmp(buf, "ack")) {
pr_err("Unexpected perf control ack: %s\n", buf);
return -1;
}
return 0;
}
static int perf_control_send(struct workload_control *ctl, const char *cmd)
{
if (ctl->ctl_fd < 0)
return 0;
if (perf_control_write_cmd(ctl->ctl_fd, cmd))
return -1;
if (ctl->ack_fd >= 0 && perf_control_read_ack(ctl->ack_fd))
return -1;
return 0;
}
static int run_workload(const char *work, int argc, const char **argv)
{
struct test_workload *twl;
workloads__for_each(twl) {
if (!strcmp(twl->name, work))
return twl->func(argc, argv);
struct workload_control ctl = {
.ctl_fd = -1,
.ack_fd = -1,
};
int control_ret, ret;
if (strcmp(twl->name, work))
continue;
ret = perf_control_open(&ctl);
if (ret)
return ret;
if (perf_control_send(&ctl, "enable\n")) {
perf_control_close(&ctl);
return -1;
}
ret = twl->func(argc, argv);
control_ret = perf_control_send(&ctl, "disable\n");
perf_control_close(&ctl);
if (control_ret)
return -1;
return ret;
}
pr_info("No workload found: %s\n", work);
@@ -1486,6 +1664,8 @@ int cmd_test(int argc, const char **argv)
OPT_UINTEGER('r', "runs-per-test", &runs_per_test,
"Run each test the given number of times, default 1"),
OPT_STRING('w', "workload", &workload, "work", "workload to run for testing, use '--list-workloads' to list the available ones."),
OPT_STRING(0, "record-ctl", &workload_control, "fifo:ctl-fifo[,ack-fifo]",
"Write enable to the fifo just before running the workload and disable after, with optional ack from ack-fifo"),
OPT_BOOLEAN(0, "list-workloads", &list_workloads, "List the available builtin workloads to use with -w/--workload"),
OPT_STRING(0, "dso", &dso_to_test, "dso", "dso to test"),
OPT_STRING(0, "objdump", &test_objdump_path, "path",