lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20241128133553.823722-2-yangjihong@bytedance.com>
Date: Thu, 28 Nov 2024 21:35:42 +0800
From: Yang Jihong <yangjihong@...edance.com>
To: peterz@...radead.org,
	mingo@...hat.com,
	acme@...nel.org,
	namhyung@...nel.org,
	mark.rutland@....com,
	alexander.shishkin@...ux.intel.com,
	jolsa@...nel.org,
	irogers@...gle.com,
	adrian.hunter@...el.com,
	kan.liang@...ux.intel.com,
	james.clark@....com,
	linux-perf-users@...r.kernel.org,
	linux-kernel@...r.kernel.org
Cc: yangjihong@...edance.com
Subject: [RFC 01/12] perf record: Add event action support

In perf-record, when an event is triggered, default behavior is to
save sample data to perf.data. Sometimes, we may just want to do
some lightweight actions, such as printing a log.

Based on this requirement, add the --action option to the event to
specify the behavior when the event occurs.

Signed-off-by: Yang Jihong <yangjihong@...edance.com>
---
 tools/perf/Documentation/perf-record.txt |   8 +
 tools/perf/builtin-record.c              |  31 +++
 tools/perf/util/Build                    |  18 ++
 tools/perf/util/parse-action.c           | 230 +++++++++++++++++++++++
 tools/perf/util/parse-action.h           |  75 ++++++++
 tools/perf/util/parse-action.l           |  40 ++++
 tools/perf/util/parse-action.y           |  82 ++++++++
 tools/perf/util/record_action.c          |  15 ++
 tools/perf/util/record_action.h          |  24 +++
 9 files changed, 523 insertions(+)
 create mode 100644 tools/perf/util/parse-action.c
 create mode 100644 tools/perf/util/parse-action.h
 create mode 100644 tools/perf/util/parse-action.l
 create mode 100644 tools/perf/util/parse-action.y
 create mode 100644 tools/perf/util/record_action.c
 create mode 100644 tools/perf/util/record_action.h

diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt
index 242223240a08..d0d9e0f69f3d 100644
--- a/tools/perf/Documentation/perf-record.txt
+++ b/tools/perf/Documentation/perf-record.txt
@@ -833,6 +833,14 @@ filtered through the mask provided by -C option.
 	Prepare BPF filter to be used by regular users.  The action should be
 	either "pin" or "unpin".  The filter can be used after it's pinned.
 
+--action=<action>::
+	Actions are the programs that run when the sampling event is triggered.
+	The action is a list of expressions separated by semicolons (;).
+	The sample data is saved by bpf prog attached by the event.
+	The call currently supported is print(); some commonly used built-in special
+	variables are also supported
+	For example:
+	  # perf record -e sched:sched_switch --action 'print("[%llu]comm=%s, cpu=%d, pid=%d, tid=%d\n", time, comm, cpu, pid, tid)' true
 
 include::intel-hybrid.txt[]
 
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index f83252472921..108d98706873 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -51,6 +51,10 @@
 #include "util/clockid.h"
 #include "util/off_cpu.h"
 #include "util/bpf-filter.h"
+#ifdef HAVE_BPF_SKEL
+#include "util/parse-action.h"
+#include "util/record_action.h"
+#endif
 #include "asm/bug.h"
 #include "perf.h"
 #include "cputopo.h"
@@ -182,6 +186,7 @@ struct record {
 	struct pollfd_index_map	*index_map;
 	size_t			index_map_sz;
 	size_t			index_map_cnt;
+	bool			custom_action;
 };
 
 static volatile int done;
@@ -3316,6 +3321,23 @@ static int parse_record_synth_option(const struct option *opt,
 	return 0;
 }
 
+#ifdef HAVE_BPF_SKEL
+static int parse_record_action_option(const struct option *opt,
+				      const char *str,
+				      int unset __maybe_unused)
+{
+	int ret;
+	struct record *rec = (struct record *)opt->value;
+
+	ret = parse_record_action(rec->evlist, str);
+	if (ret)
+		return ret;
+
+	rec->custom_action = true;
+	return 0;
+}
+#endif
+
 /*
  * XXX Ideally would be local to cmd_record() and passed to a record__new
  * because we need to have access to it in record__exit, that is called
@@ -3564,6 +3586,9 @@ static struct option __record_options[] = {
 	OPT_BOOLEAN(0, "off-cpu", &record.off_cpu, "Enable off-cpu analysis"),
 	OPT_STRING(0, "setup-filter", &record.filter_action, "pin|unpin",
 		   "BPF filter action"),
+#ifdef HAVE_BPF_SKEL
+	OPT_CALLBACK(0, "action", &record, "action", "event action", parse_record_action_option),
+#endif
 	OPT_END()
 };
 
@@ -4001,6 +4026,12 @@ int cmd_record(int argc, const char **argv)
 	if (quiet)
 		perf_quiet_option();
 
+#ifdef HAVE_BPF_SKEL
+	/* Currently, event actions only supported using bpf prog. */
+	if (rec->custom_action)
+		return bpf_perf_record(rec->evlist, argc, argv);
+#endif
+
 	err = symbol__validate_sym_arguments();
 	if (err)
 		return err;
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index c06d2ee9024c..db4c4cabc5f8 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -249,6 +249,12 @@ perf-util-$(CONFIG_LIBBPF) += bpf-utils.o
 
 perf-util-$(CONFIG_LIBPFM4) += pfm.o
 
+# perf record event action
+perf-util-$(CONFIG_PERF_BPF_SKEL) += parse-action.o
+perf-util-$(CONFIG_PERF_BPF_SKEL) += parse-action-flex.o
+perf-util-$(CONFIG_PERF_BPF_SKEL) += parse-action-bison.o
+perf-util-$(CONFIG_PERF_BPF_SKEL) += record_action.o
+
 CFLAGS_config.o   += -DETC_PERFCONFIG="BUILD_STR($(ETC_PERFCONFIG_SQ))"
 
 # avoid compiler warnings in 32-bit mode
@@ -294,6 +300,16 @@ $(OUTPUT)util/bpf-filter-bison.c $(OUTPUT)util/bpf-filter-bison.h: util/bpf-filt
 	$(Q)$(call echo-cmd,bison)$(BISON) -v $< -d $(PARSER_DEBUG_BISON) $(BISON_FILE_PREFIX_MAP) \
 		-o $(OUTPUT)util/bpf-filter-bison.c -p perf_bpf_filter_
 
+$(OUTPUT)util/parse-action-flex.c $(OUTPUT)util/parse-action-flex.h: util/parse-action.l $(OUTPUT)util/parse-action-bison.c util/parse-action.h
+	$(call rule_mkdir)
+	$(Q)$(call echo-cmd,flex)$(FLEX) -o $(OUTPUT)util/parse-action-flex.c \
+		--header-file=$(OUTPUT)util/parse-action-flex.h $(PARSER_DEBUG_FLEX) $<
+
+$(OUTPUT)util/parse-action.c $(OUTPUT)util/parse-action-bison.h: util/parse-action.y util/parse-action.h
+	$(call rule_mkdir)
+	$(Q)$(call echo-cmd,bison)$(BISON) -v $< -d $(PARSER_DEBUG_BISON) $(BISON_FILE_PREFIX_MAP) \
+		-o $(OUTPUT)util/parse-action-bison.c -p parse_action_
+
 FLEX_VERSION := $(shell $(FLEX) --version | cut -d' ' -f2)
 
 FLEX_GE_260 := $(call version-ge3,$(FLEX_VERSION),2.6.0)
@@ -345,11 +361,13 @@ CFLAGS_parse-events-flex.o  += $(flex_flags) -Wno-unused-label
 CFLAGS_pmu-flex.o           += $(flex_flags)
 CFLAGS_expr-flex.o          += $(flex_flags)
 CFLAGS_bpf-filter-flex.o    += $(flex_flags)
+CFLAGS_parse-action-flex.o  += $(flex_flags)
 
 CFLAGS_parse-events-bison.o += $(bison_flags)
 CFLAGS_pmu-bison.o          += -DYYLTYPE_IS_TRIVIAL=0 $(bison_flags)
 CFLAGS_expr-bison.o         += -DYYLTYPE_IS_TRIVIAL=0 $(bison_flags)
 CFLAGS_bpf-filter-bison.o   += -DYYLTYPE_IS_TRIVIAL=0 $(bison_flags)
+CFLAGS_parse-action-bison.o += -DYYLTYPE_IS_TRIVIAL=0 $(bison_flags)
 
 $(OUTPUT)util/parse-events.o: $(OUTPUT)util/parse-events-flex.c $(OUTPUT)util/parse-events-bison.c
 $(OUTPUT)util/pmu.o: $(OUTPUT)util/pmu-flex.c $(OUTPUT)util/pmu-bison.c
diff --git a/tools/perf/util/parse-action.c b/tools/perf/util/parse-action.c
new file mode 100644
index 000000000000..01c8c7fdea59
--- /dev/null
+++ b/tools/perf/util/parse-action.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Generic actions for sampling events
+ * Actions are the programs that run when the sampling event is triggered.
+ * The action is a list of expressions separated by semicolons (;).
+ * Each action is an expression, added to actions_head node as list_head node.
+ */
+
+#include "util/debug.h"
+#include "util/parse-action.h"
+#include "util/parse-action-flex.h"
+#include "util/parse-action-bison.h"
+
+static struct list_head actions_head = LIST_HEAD_INIT(actions_head);
+
+int event_actions__for_each_expr(int (*func)(struct evtact_expr *, void *arg),
+				 void *arg, bool recursive)
+{
+	int ret;
+	struct evtact_expr *expr, *opnd;
+
+	if (list_empty(&actions_head))
+		return (*func)(NULL, arg);
+
+	list_for_each_entry(expr, &actions_head, list) {
+		ret = (*func)(expr, arg);
+		if (ret)
+			return ret;
+
+		if (recursive && !list_empty(&expr->opnds)) {
+			list_for_each_entry(opnd, &expr->opnds, list) {
+				ret = (*func)(opnd, arg);
+				if (ret)
+					return ret;
+			}
+		}
+	}
+
+	return 0;
+}
+
+int event_actions__for_each_expr_safe(int (*func)(struct evtact_expr *, void *arg),
+				      void *arg, bool recursive)
+{
+	int ret;
+	struct evtact_expr *expr, *tmp;
+	struct evtact_expr *opnd, *opnd_tmp;
+
+	if (list_empty(&actions_head))
+		return (*func)(NULL, arg);
+
+	list_for_each_entry_safe(expr, tmp, &actions_head, list) {
+		ret = (*func)(expr, arg);
+		if (ret)
+			return ret;
+
+		if (recursive && !list_empty(&expr->opnds)) {
+			list_for_each_entry_safe(opnd, opnd_tmp, &expr->opnds, list) {
+				ret = (*func)(opnd, arg);
+				if (ret)
+					return ret;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int parse_action_option(const char *str)
+{
+	int ret;
+	YY_BUFFER_STATE buffer;
+
+	buffer = parse_action__scan_string(str);
+	ret = parse_action_parse(&actions_head);
+
+	parse_action__flush_buffer(buffer);
+	parse_action__delete_buffer(buffer);
+	parse_action_lex_destroy();
+
+	return ret;
+}
+
+int parse_record_action(struct evlist *evlist, const char *str)
+{
+	int ret;
+
+	if (evlist == NULL) {
+		pr_err("--action option should follow a tracer option\n");
+		return -1;
+	}
+
+	ret = parse_action_option(str);
+	if (ret) {
+		event_actions__free();
+		pr_err("parse action option failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int do_action_free(struct evtact_expr *action, void *data __maybe_unused)
+{
+	if (action == NULL)
+		return 0;
+
+	list_del(&action->list);
+	parse_action_expr__free(action);
+	return 0;
+}
+
+void event_actions__free(void)
+{
+	(void)event_actions__for_each_expr_safe(do_action_free, NULL, false);
+}
+
+static struct evtact_expr_class *expr_class_list[EVTACT_EXPR_TYPE_MAX] = {
+};
+
+int parse_action_expr__set_class(enum evtact_expr_type type,
+				 struct evtact_expr_class *class)
+{
+	if (type >= EVTACT_EXPR_TYPE_MAX) {
+		pr_err("action expr set class ops type invalid\n");
+		return -EINVAL;
+	}
+
+	if (expr_class_list[type] != NULL) {
+		pr_err("action expr set class ops type already exists\n");
+		return -EEXIST;
+	}
+
+	expr_class_list[type] = class;
+	return 0;
+}
+
+static int expr_set_type(struct evtact_expr *expr)
+{
+	u64 id;
+	int ret;
+	u32 type, opcode;
+	struct evtact_expr_class *class;
+
+	id = expr->id;
+	evtact_expr_id_decode(id, &type, &opcode);
+
+	if (type >= EVTACT_EXPR_TYPE_MAX) {
+		pr_err("parse_action_expr type invalid: %u\n", type);
+		return -EINVAL;
+	}
+
+	class = expr_class_list[type];
+	if (class == NULL) {
+		pr_err("parse_action_expr class not supported: %u\n", type);
+		return -ENOTSUP;
+	}
+
+	if (class->set_ops != NULL) {
+		ret = class->set_ops(expr, opcode);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+struct evtact_expr *parse_action_expr__new(u64 id, struct list_head *opnds,
+					   void *data, int size)
+{
+	int ret;
+	struct evtact_expr *expr;
+
+	expr = malloc(sizeof(*expr));
+	if (expr == NULL) {
+		pr_err("parse_action_expr malloc failed\n");
+		goto out_free_opnds;
+	}
+	expr->id = id;
+
+	if (opnds != NULL)
+		list_add_tail(&expr->opnds, opnds);
+	else
+		INIT_LIST_HEAD(&expr->opnds);
+
+	ret = expr_set_type(expr);
+	if (ret)
+		goto out_list_del_opnds;
+
+	if (expr->ops->new != NULL) {
+		ret = expr->ops->new(expr, data, size);
+		if (ret)
+			goto out_free_expr;
+	}
+
+	return expr;
+
+out_free_expr:
+	free(expr);
+out_list_del_opnds:
+	list_del(&expr->opnds);
+out_free_opnds:
+	parse_action_expr__free_opnds(opnds);
+
+	return NULL;
+}
+
+void parse_action_expr__free_opnds(struct list_head *opnds)
+{
+	struct evtact_expr *opnd, *tmp;
+
+	if (opnds != NULL && !list_empty(opnds)) {
+		list_for_each_entry_safe(opnd, tmp, opnds, list) {
+			list_del(&opnd->list);
+			parse_action_expr__free(opnd);
+		}
+	}
+}
+
+void parse_action_expr__free(struct evtact_expr *expr)
+{
+	if (expr == NULL)
+		return;
+
+	if (expr->ops->free != NULL)
+		expr->ops->free(expr);
+
+	parse_action_expr__free_opnds(&expr->opnds);
+	free(expr);
+}
diff --git a/tools/perf/util/parse-action.h b/tools/perf/util/parse-action.h
new file mode 100644
index 000000000000..71a0a166959e
--- /dev/null
+++ b/tools/perf/util/parse-action.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_UTIL_PARSE_ACTION_H_
+#define __PERF_UTIL_PARSE_ACTION_H_
+
+#include <linux/types.h>
+
+#include <subcmd/parse-options.h>
+
+#include "evlist.h"
+
+enum evtact_expr_type {
+	EVTACT_EXPR_TYPE_MAX,
+};
+
+struct evtact_expr;
+struct evtact_expr_ops {
+	int (*new)(struct evtact_expr *expr, void *data, int size);
+	int (*eval)(struct evtact_expr *expr,
+		    void *in, int in_size, void **out, int *out_size);
+	void (*free)(struct evtact_expr *expr);
+};
+
+struct evtact_expr_class {
+	int (*set_ops)(struct evtact_expr *expr, u32 opcode);
+};
+
+struct evtact_expr {
+	struct list_head list;
+	u64 id;
+	struct evtact_expr_ops *ops;
+	struct list_head opnds;
+	void *priv;
+};
+
+/*
+ * The expr id contains two fileds:
+ * |--------------|----------------|
+ * |     type     |     opcode     |
+ * |--------------|----------------|
+ *      32-bit           32-bit
+ */
+#define EVTACT_EXPR_ID_TYPE_BITS_SHIFT 32
+static inline u64 evtact_expr_id_encode(u32 type, u32 opcode)
+{
+	return (u64)type << EVTACT_EXPR_ID_TYPE_BITS_SHIFT | opcode;
+}
+
+static inline void evtact_expr_id_decode(u64 id, u32 *type, u32 *opcode)
+{
+	if (type != NULL)
+		*type = id >> EVTACT_EXPR_ID_TYPE_BITS_SHIFT;
+
+	if (opcode != NULL)
+		*opcode = id & GENMASK(EVTACT_EXPR_ID_TYPE_BITS_SHIFT, 0);
+}
+
+int parse_record_action(struct evlist *evlist, const char *str);
+void event_actions__free(void);
+
+int event_actions__for_each_expr(int (*func)(struct evtact_expr *, void *arg),
+				 void *arg, bool recursive);
+
+int event_actions__for_each_expr_safe(int (*func)(struct evtact_expr *, void *arg),
+				      void *arg, bool recursive);
+
+struct evtact_expr *parse_action_expr__new(u64 id, struct list_head *opnds,
+					   void *data, int size);
+
+void parse_action_expr__free_opnds(struct list_head *opnds);
+void parse_action_expr__free(struct evtact_expr *expr);
+
+int parse_action_expr__set_class(enum evtact_expr_type type,
+				 struct evtact_expr_class *ops);
+
+#endif /* __PERF_UTIL_PARSE_ACTION_H_ */
diff --git a/tools/perf/util/parse-action.l b/tools/perf/util/parse-action.l
new file mode 100644
index 000000000000..3cb72de50372
--- /dev/null
+++ b/tools/perf/util/parse-action.l
@@ -0,0 +1,40 @@
+%option prefix="parse_action_"
+%option noyywrap
+%option stack
+
+%{
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util/debug.h"
+
+#include "parse-action.h"
+#include "parse-action-bison.h"
+
+%}
+
+space		[ \t]
+ident		[_a-zA-Z][_a-zA-Z0-9]*
+
+%%
+
+{space}		{ }
+
+";"		{ return SEMI; }
+
+{ident}		{
+			parse_action_lval.str = strdup(parse_action_text);
+			if (parse_action_lval.str == NULL) {
+				pr_err("parse_action malloc ident string failed\n");
+				return ERROR;
+			}
+			return IDENT;
+		}
+.		{
+			pr_err("invalid character: '%s'\n", parse_action_text);
+			return ERROR;
+		}
+
+%%
diff --git a/tools/perf/util/parse-action.y b/tools/perf/util/parse-action.y
new file mode 100644
index 000000000000..fade9d093d4a
--- /dev/null
+++ b/tools/perf/util/parse-action.y
@@ -0,0 +1,82 @@
+%parse-param {struct list_head *actions_head}
+%define parse.error verbose
+
+%{
+
+#ifndef NDEBUG
+#define YYDEBUG 1
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <linux/compiler.h>
+#include <linux/list.h>
+
+#include "util/debug.h"
+#include "util/parse-action.h"
+
+int parse_action_lex(void);
+
+static void parse_action_error(struct list_head *expr __maybe_unused,
+			       char const *msg)
+{
+	pr_err("parse_action: %s\n", msg);
+}
+
+%}
+
+%union
+{
+	char *str;
+	struct evtact_expr *expr;
+	struct list_head *list;
+}
+
+%token IDENT ERROR
+%token SEMI
+%type <expr> action_term expr_term
+%destructor { parse_action_expr__free($$); } <expr>
+%type <str> IDENT
+
+%%
+
+actions:
+action_term SEMI actions
+{
+	list_add(&$1->list, actions_head);
+}
+|
+action_term SEMI
+{
+	list_add(&$1->list, actions_head);
+}
+|
+action_term
+{
+	list_add(&$1->list, actions_head);
+}
+
+action_term:
+expr_term
+{
+	$$ = $1;
+}
+
+expr_term:
+IDENT
+{
+	$$ = NULL;
+	pr_err("unsupported ident: '%s'\n", $1);
+	free($1);
+	YYERROR;
+}
+|
+ERROR
+{
+	$$ = NULL;
+	YYERROR;
+}
+
+%%
diff --git a/tools/perf/util/record_action.c b/tools/perf/util/record_action.c
new file mode 100644
index 000000000000..44789e0d4678
--- /dev/null
+++ b/tools/perf/util/record_action.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Read event sample data and execute the specified actions.
+ */
+
+#include "util/debug.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)
+{
+	event_actions__free();
+	return 0;
+}
diff --git a/tools/perf/util/record_action.h b/tools/perf/util/record_action.h
new file mode 100644
index 000000000000..289be4befa97
--- /dev/null
+++ b/tools/perf/util/record_action.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_UTIL_RECORD_ACTION_H_
+#define __PERF_UTIL_RECORD_ACTION_H_
+
+#include <errno.h>
+#include "evlist.h"
+
+#ifdef HAVE_BPF_SKEL
+
+int bpf_perf_record(struct evlist *evlist, int argc, const char **argv);
+
+
+#else /* !HAVE_BPF_SKEL */
+
+static inline int bpf_perf_record(struct evlist *evlist __maybe_unused,
+				  int argc __maybe_unused,
+				  const char **argv __maybe_unused)
+{
+	return -EOPNOTSUPP;
+}
+
+#endif /* !HAVE_BPF_SKEL */
+
+#endif /* __PERF_UTIL_RECORD_ACTION_H_ */
-- 
2.25.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ