[RFC 07/12] perf event action: Add parsing builtin expr support

Yang Jihong
2024-11-28T13:36:53Z

builtin exprs are some built-in special variables (such as cpu, tid, pid).
The bpf program attaches to corresponding event, saves sample data to bpf
perf_event ringuffer in handler, perf-tool read data and run actions.

Signed-off-by: Yang Jihong <yangjihong@bytedance.com>
---
tools/perf/Makefile.perf | 1 +
tools/perf/util/bpf_skel/bpf_record_action.h | 19 +
tools/perf/util/bpf_skel/record_action.bpf.c | 74 ++++
tools/perf/util/parse-action.c | 22 ++
tools/perf/util/parse-action.h | 5 +
tools/perf/util/parse-action.l | 6 +
tools/perf/util/parse-action.y | 11 +-
tools/perf/util/record_action.c | 352 ++++++++++++++++++-
tools/perf/util/record_action.h | 8 +-
9 files changed, 492 insertions(+), 6 deletions(-)
create mode 100644 tools/perf/util/bpf_skel/bpf_record_action.h
create mode 100644 tools/perf/util/bpf_skel/record_action.bpf.c

diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf
index d74241a15131..07bc2f8e2565 100644
--- a/tools/perf/Makefile.perf
+++ b/tools/perf/Makefile.perf
@@ -1173,6 +1173,7 @@ SKELETONS += $(SKEL_OUT)/kwork_trace.skel.h $(SKEL_OUT)/sample_filter.skel.h
SKELETONS += $(SKEL_OUT)/kwork_top.skel.h
SKELETONS += $(SKEL_OUT)/bench_uprobe.skel.h
SKELETONS += $(SKEL_OUT)/augmented_raw_syscalls.skel.h
+SKELETONS += $(SKEL_OUT)/record_action.skel.h

$(SKEL_TMP_OUT) $(LIBAPI_OUTPUT) $(LIBBPF_OUTPUT) $(LIBPERF_OUTPUT) $(LIBSUBCMD_OUTPUT) $(LIBSYMBOL_OUTPUT):
$(Q)$(MKDIR) -p $@
diff --git a/tools/perf/util/bpf_skel/bpf_record_action.h b/tools/perf/util/bpf_skel/bpf_record_action.h
new file mode 100644
index 000000000000..ee4d03848e58
--- /dev/null
+++ b/tools/perf/util/bpf_skel/bpf_record_action.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_UTIL_BPF_SKEL_BPF_RECORD_ACTION_H_
+#define __PERF_UTIL_BPF_SKEL_BPF_RECORD_ACTION_H_
+
+#define __TASK_COMM_MAX_SIZE 16
+
+#define __OUTPUT_FORMATS_MAX_NUM 8
+
+enum __output_format_type {
+ __OUTPUT_FORMAT_TYPE_MAX,
+};
+
+#define __OUTPUT_DATA_MAX_SIZE 256
+struct __output_data_payload {
+ __u32 __size;
+ __u8 __data[__OUTPUT_DATA_MAX_SIZE];
+};
+
+#endif /* __PERF_UTIL_BPF_SKEL_BPF_RECORD_ACTION_H_ */
diff --git a/tools/perf/util/bpf_skel/record_action.bpf.c b/tools/perf/util/bpf_skel/record_action.bpf.c
new file mode 100644
index 000000000000..424fa8c3e6f1
--- /dev/null
+++ b/tools/perf/util/bpf_skel/record_action.bpf.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+
+#include "bpf_record_action.h"
+
+int output_formats[__OUTPUT_FORMATS_MAX_NUM] = { 0 };
+int output_format_num = 0;
+int enabled = 0;
+
+#define MAX_CPUS 1024
+
+/* bpf-output associated map */
+struct __sample_data__ {
+ __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
+ __type(key, int);
+ __type(value, __u32);
+ __uint(max_entries, MAX_CPUS);
+} __sample_data__ SEC(".maps");
+
+struct sample_data_tmp {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __type(key, int);
+ __type(value, struct __output_data_payload);
+ __uint(max_entries, 1);
+} sample_data_tmp SEC(".maps");
+
+static inline struct __output_data_payload *sample_data_payload(void)
+{
+ int key = 0;
+
+ return bpf_map_lookup_elem(&sample_data_tmp, &key);
+}
+
+SEC("xxx")
+int sample_output(u64 *ctx)
+{
+ struct __output_data_payload *sample;
+ int i;
+ int total = 0;
+ int ret = 0;
+
+ if (!enabled)
+ return 0;
+
+ sample = sample_data_payload();
+ if (!sample)
+ return 0;
+
+ for (i = 0; i < output_format_num && i < __OUTPUT_FORMATS_MAX_NUM; i++) {
+ switch (output_formats[i]) {
+ default:
+ ret = -1;
+ break;
+ }
+
+ if (ret < 0)
+ return 0;
+
+ total += ret;
+ if (total > __OUTPUT_DATA_MAX_SIZE)
+ return 0;
+ }
+
+ sample->__size = total;
+ bpf_perf_event_output(ctx, &__sample_data__, BPF_F_CURRENT_CPU,
+ sample, sizeof(__u32) + total);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/perf/util/parse-action.c b/tools/perf/util/parse-action.c
index 95c06cc071ad..e6299de99bc5 100644
--- a/tools/perf/util/parse-action.c
+++ b/tools/perf/util/parse-action.c
@@ -11,6 +11,7 @@
* - string
* - call:
* - print
+ * - builtin:
*/

#include <regex.h>
@@ -19,6 +20,7 @@
#include "util/parse-action.h"
#include "util/parse-action-flex.h"
#include "util/parse-action-bison.h"
+#include "util/record_action.h"

static struct list_head actions_head = LIST_HEAD_INIT(actions_head);

@@ -90,10 +92,30 @@ static int parse_action_option(const char *str)
return ret;
}

+static bool initialized = false;
+static int parse_action_init(void)
+{
+ int ret;
+
+ if (initialized)
+ return 0;
+
+ ret = bpf_perf_record_init();
+ if (ret)
+ return ret;
+
+ initialized = true;
+ return 0;
+}
+
int parse_record_action(struct evlist *evlist, const char *str)
{
int ret;

+ ret = parse_action_init();
+ if (ret)
+ return ret;
+
if (evlist == NULL) {
pr_err("--action option should follow a tracer option\n");
return -1;
diff --git a/tools/perf/util/parse-action.h b/tools/perf/util/parse-action.h
index f8aece601a84..b1a33a77c558 100644
--- a/tools/perf/util/parse-action.h
+++ b/tools/perf/util/parse-action.h
@@ -11,6 +11,7 @@
enum evtact_expr_type {
EVTACT_EXPR_TYPE_CONST,
EVTACT_EXPR_TYPE_CALL,
+ EVTACT_EXPR_TYPE_BUILTIN,
EVTACT_EXPR_TYPE_MAX,
};

@@ -25,6 +26,10 @@ enum evtact_expr_call_type {
EVTACT_EXPR_CALL_TYPE_MAX,
};

+enum evtact_expr_builtin_type {
+ EVTACT_EXPR_BUILTIN_TYPE_MAX,
+};
+
struct evtact_expr;
struct evtact_expr_ops {
int (*new)(struct evtact_expr *expr, void *data, int size);
diff --git a/tools/perf/util/parse-action.l b/tools/perf/util/parse-action.l
index 5e75383ba3e7..1c729b7a0248 100644
--- a/tools/perf/util/parse-action.l
+++ b/tools/perf/util/parse-action.l
@@ -82,6 +82,12 @@ static int call(u32 opcode)
return CALL;
}

+static int builtin(u32 opcode)
+{
+ parse_action_lval.opcode = opcode;
+ return BUILTIN;
+}
+
%}

num_dec [0-9]+
diff --git a/tools/perf/util/parse-action.y b/tools/perf/util/parse-action.y
index 1b162c694218..78a15f146ade 100644
--- a/tools/perf/util/parse-action.y
+++ b/tools/perf/util/parse-action.y
@@ -38,14 +38,14 @@ static void parse_action_error(struct list_head *expr __maybe_unused,
u32 opcode;
}

-%token IDENT ERROR NUMBER STRING CALL
+%token IDENT ERROR NUMBER STRING CALL BUILTIN
%token SEMI LP RP COM
%type <expr> action_term expr_term expr_call_term
%destructor { parse_action_expr__free($$); } <expr>
%type <str> IDENT
%type <num> NUMBER
%type <str> STRING
-%type <opcode> CALL
+%type <opcode> CALL BUILTIN
%type opnds

%%
@@ -132,6 +132,13 @@ STRING
YYERROR;
}
|
+BUILTIN
+{
+ $$ = parse_action_expr__new(evtact_expr_id_encode(EVTACT_EXPR_TYPE_BUILTIN, $1), NULL, NULL, 0);
+ if ($$ == NULL)
+ YYERROR;
+}
+|
IDENT
{
$$ = NULL;
diff --git a/tools/perf/util/record_action.c b/tools/perf/util/record_action.c
index 44789e0d4678..634d6b199bae 100644
--- a/tools/perf/util/record_action.c
+++ b/tools/perf/util/record_action.c
@@ -1,15 +1,361 @@
// SPDX-License-Identifier: GPL-2.0
/**
- * Read event sample data and execute the specified actions.
+ * Read perf event output sample data and execute the specified actions.
*/

+#include <errno.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <linux/err.h>
+
#include "util/debug.h"
+#include "util/target.h"
#include "util/parse-action.h"
#include "util/record_action.h"

-int bpf_perf_record(struct evlist *evlist __maybe_unused,
- int argc __maybe_unused, const char **argv __maybe_unused)
+#include "util/bpf_counter.h"
+#include "util/bpf_skel/bpf_record_action.h"
+#include "util/bpf_skel/record_action.skel.h"
+
+static struct perf_buffer *pb;
+static struct record_action_bpf *skel;
+
+struct expr_builtin_output_priv {
+ int offset;
+ int size;
+};
+
+static int bpf_expr_builtin_new(struct evtact_expr *expr,
+ void *data __maybe_unused, int size __maybe_unused)
+{
+ struct expr_builtin_output_priv *priv;
+
+ priv = malloc(sizeof(*priv));
+ if (priv == NULL) {
+ pr_err("bpf expr builtin priv malloc failed\n");
+ return -ENOMEM;
+ }
+
+ expr->priv = priv;
+ return 0;
+}
+
+static void bpf_expr_builtin_free(struct evtact_expr *expr)
+{
+ zfree(&expr->priv);
+}
+
+static int bpf_expr_builtin_eval(struct evtact_expr *expr,
+ void *in, int in_size, void **out, int *out_size)
+{
+ struct expr_builtin_output_priv *priv = expr->priv;
+
+ if (in_size < priv->size)
+ return -EINVAL;
+
+ *out = (u8 *)in + priv->offset;
+ *out_size = priv->size;
+ return 0;
+}
+
+static struct evtact_expr_ops bpf_expr_builtin_common = {
+ .new = bpf_expr_builtin_new,
+ .free = bpf_expr_builtin_free,
+ .eval = bpf_expr_builtin_eval,
+};
+
+static int bpf_expr_builtin_set_ops(struct evtact_expr *expr, u32 opcode)
+{
+ if (opcode >= EVTACT_EXPR_BUILTIN_TYPE_MAX) {
+ pr_err("bpf expr_builtin opcode invalid: %u\n", opcode);
+ return -EINVAL;
+ }
+
+ expr->ops = &bpf_expr_builtin_common;
+ return 0;
+}
+
+static struct evtact_expr_class bpf_expr_builtin = {
+ .set_ops = bpf_expr_builtin_set_ops,
+};
+
+int bpf_perf_record_init(void)
+{
+ return parse_action_expr__set_class(EVTACT_EXPR_TYPE_BUILTIN,
+ &bpf_expr_builtin);
+}
+
+static int set_expr_builtin_output_format(struct evtact_expr *expr,
+ u32 opcode, int *offset,
+ int *format __maybe_unused)
+{
+ int size = 0;
+ struct expr_builtin_output_priv *priv = expr->priv;
+
+ switch (opcode) {
+ default:
+ pr_err("set expr builtin output format unknown opcode: %u\n", opcode);
+ return -1;
+ }
+
+ priv->offset = *offset;
+ priv->size = size;
+ *offset += size;
+ return 0;
+}
+
+struct output_args {
+ int *num;
+ int *offset;
+ int *formats;
+};
+
+static int do_set_output_format(struct evtact_expr *expr, void *data)
+{
+ int ret;
+ u32 type, opcode;
+ struct output_args *args = data;
+ int num = *(args->num);
+
+ evtact_expr_id_decode(expr->id, &type, &opcode);
+ if (type == EVTACT_EXPR_TYPE_BUILTIN) {
+ if (num >= __OUTPUT_FORMATS_MAX_NUM) {
+ pr_err("bpf record action output formats too many\n");
+ return -1;
+ }
+
+ ret = set_expr_builtin_output_format(expr, opcode, args->offset,
+ args->formats + num);
+ if (ret)
+ return ret;
+ num++;
+ }
+
+ *(args->num) = num;
+ return 0;
+}
+
+static int bpf_set_output_format(int *formats)
+{
+ int ret;
+ int offset = 0;
+ int num = 0;
+ struct output_args args = {
+ .num = &num,
+ .offset = &offset,
+ .formats = formats,
+ };
+
+ ret = event_actions__for_each_expr(do_set_output_format, &args, true);
+ if (ret)
+ return ret;
+
+ if (offset > __OUTPUT_DATA_MAX_SIZE) {
+ pr_err("bpf record action output too large\n");
+ return -1;
+ }
+
+ skel->bss->output_format_num = num;
+ return 0;
+}
+
+
+struct eval_args {
+ void *data;
+ __u32 size;
+};
+
+static int do_sample_handler(struct evtact_expr *expr, void *data)
+{
+ int ret;
+ struct eval_args *args = data;
+
+ if (expr != NULL && expr->ops->eval != NULL) {
+ ret = expr->ops->eval(expr, args->data, args->size, NULL, NULL);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void sample_callback(void *ctx __maybe_unused, int cpu __maybe_unused,
+ void *data, __u32 size __maybe_unused)
+{
+ struct __output_data_payload *sample_data = data;
+ struct eval_args args = {
+ .data = sample_data->__data,
+ .size = sample_data->__size,
+ };
+ (void)event_actions__for_each_expr(do_sample_handler, &args, false);
+}
+
+static void lost_callback(void *ctx __maybe_unused, int cpu, __u64 cnt)
+{
+ fprintf(stderr, "Lost %llu events on CPU #%d\n", cnt, cpu);
+}
+
+static int bpf_record_prepare(const char *subsystem, const char *event_name)
+{
+ int ret, map_fd;
+
+ skel = record_action_bpf__open();
+ if (!skel) {
+ pr_err("open record-action BPF skeleton failed\n");
+ return -1;
+ }
+
+ set_max_rlimit();
+
+ ret = bpf_program__set_type(skel->progs.sample_output, BPF_PROG_TYPE_TRACEPOINT);
+ if (ret) {
+ pr_err("set type record-action BPF skeleton failed\n");
+ goto out;
+ }
+
+ ret = record_action_bpf__load(skel);
+ if (ret) {
+ pr_err("load record-action BPF skeleton failed\n");
+ goto out;
+ }
+
+ ret = bpf_set_output_format(skel->bss->output_formats);
+ if (ret)
+ goto out;
+
+ map_fd = bpf_map__fd(skel->maps.__sample_data__);
+ if (map_fd < 0) {
+ pr_err("map fd record-action BPF skeleton failed\n");
+ goto out;
+ }
+
+ skel->links.sample_output = bpf_program__attach_tracepoint(
+ skel->progs.sample_output, subsystem, event_name);
+ if (IS_ERR(skel->links.sample_output)) {
+ pr_err("attach record-action BPF skeleton failed\n");
+ goto out;
+ }
+
+ pb = perf_buffer__new(map_fd, 8, sample_callback,
+ lost_callback, NULL, NULL);
+ ret = libbpf_get_error(pb);
+ if (ret) {
+ pr_err("setup record-action perf_buffer failed: %d\n", ret);
+ goto out;
+ }
+
+ return 0;
+
+out:
+ record_action_bpf__destroy(skel);
+ return -1;
+}
+
+static inline void bpf_record_start(void)
+{
+ skel->bss->enabled = 1;
+}
+
+static inline void bpf_record_stop(void)
{
+ skel->bss->enabled = 0;
+}
+
+static volatile int done;
+static volatile sig_atomic_t child_finished;
+static void sig_handler(int sig)
+{
+ if (sig == SIGCHLD)
+ child_finished = 1;
+
+ done = 1;
+}
+
+static bool is_bpf_record_supported(struct evlist *evlist,
+ char **subsystem, char **event_name)
+{
+ struct evsel *evsel;
+
+ if (evlist == NULL) {
+ pr_err("--action option should follow a tracer option\n");
+ return false;
+ }
+
+ /* only one fixed bpf prog and can only be attached to one event. */
+ if (evlist->core.nr_entries > 1) {
+ pr_err("too many events for specified action\n");
+ return false;
+ }
+
+ evsel = evlist__last(evlist);
+ if (evsel == NULL) {
+ pr_err("evlist for bpf record action is empty\n");
+ return false;
+ }
+
+ if (evsel->core.attr.type != PERF_TYPE_TRACEPOINT) {
+ pr_err("bpf record action only supports specifying for tracepoint tracer\n");
+ return false;
+ }
+
+ *subsystem = strtok_r(evsel->name, ":", event_name);
+ if (*subsystem == NULL || event_name == NULL) {
+ pr_err("bpf record action tracepoint name format incorrect\n");
+ return false;
+ }
+
+ return true;
+}
+
+int bpf_perf_record(struct evlist *evlist, int argc, const char **argv)
+{
+ int ret;
+ char *subsystem, *event_name;
+ struct target target = {
+ .system_wide = true,
+ };
+
+ if (!is_bpf_record_supported(evlist, &subsystem, &event_name))
+ goto out_free_event_actions;
+
+ ret = bpf_record_prepare(subsystem, event_name);
+ if (ret)
+ goto out_free_event_actions;
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+ signal(SIGCHLD, sig_handler);
+
+ if (argc > 0) {
+ ret = evlist__prepare_workload(evlist, &target, argv, false, NULL);
+ if (ret < 0) {
+ pr_err("evlist workload create failed\n");
+ goto out_destroy_record_action_bpf;
+ }
+ }
+
+ bpf_record_start();
+ evlist__start_workload(evlist);
+
+ while ((ret = perf_buffer__poll(pb, 1000)) >= 0) {
+ if (done == 1)
+ break;
+ }
+
+ bpf_record_stop();
+
+ if (argc > 0) {
+ int exit_status;
+
+ if (!child_finished)
+ kill(evlist->workload.pid, SIGTERM);
+
+ wait(&exit_status);
+ }
+
+out_destroy_record_action_bpf:
+ record_action_bpf__destroy(skel);
+out_free_event_actions:
event_actions__free();
return 0;
}
diff --git a/tools/perf/util/record_action.h b/tools/perf/util/record_action.h
index 289be4befa97..6cd578af2ccb 100644
--- a/tools/perf/util/record_action.h
+++ b/tools/perf/util/record_action.h
@@ -7,8 +7,9 @@

#ifdef HAVE_BPF_SKEL

-int bpf_perf_record(struct evlist *evlist, int argc, const char **argv);
+int bpf_perf_record_init(void);

+int bpf_perf_record(struct evlist *evlist, int argc, const char **argv);

#else /* !HAVE_BPF_SKEL */

@@ -19,6 +20,11 @@ static inline int bpf_perf_record(struct evlist *evlist __maybe_unused,
return -EOPNOTSUPP;
}

+static inline int bpf_perf_record_init(void)
+{
+ return 0;
+}
+
#endif /* !HAVE_BPF_SKEL */

#endif /* __PERF_UTIL_RECORD_ACTION_H_ */
--
2.25.1