Add unit tests in parse-events.c for parsing lock_held(),
lock_held_read(), and lock_held_exclusive() BPF filter expressions
(standalone predicates, negated predicates, and compound group
expressions) and extend record_bpf_filter.sh with lock_held(),
lock_held_read()/lock_held_write(), and ':R'/':W' recording tests.
Example output:
# perf test -v 'Parsing of BPF sample filters'
5: Parse event definition strings :
5.7: Parsing of BPF sample filters : Ok
# perf test -v 'perf record sample filtering (by BPF) tests'
112: perf record sample filtering (by BPF) tests :
Checking BPF-filter privilege
Basic bpf-filter test
Basic bpf-filter test [Success]
Failing bpf-filter test
Failing bpf-filter test [Success]
Group bpf-filter test
Group bpf-filter test [Success]
Multiple bpf-filter test
Multiple bpf-filter test [Success]
Cgroup bpf-filter test
Cgroup bpf-filter test [Success]
Lock-held bpf-filter test
Lock-held bpf-filter test [Success]
112: perf record sample filtering (by BPF) tests : Ok
Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/tests/parse-events.c | 108 +++++++++++++++++++
tools/perf/tests/shell/record_bpf_filter.sh | 114 ++++++++++++++++++++
2 files changed, 222 insertions(+)
diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c
index 5f6f95c8a5b4..4cf9192762f2 100644
--- a/tools/perf/tests/parse-events.c
+++ b/tools/perf/tests/parse-events.c
@@ -8,9 +8,11 @@
#include "debug.h"
#include "pmu.h"
#include "pmus.h"
+#include "bpf-filter.h"
#include "strbuf.h"
#include <dirent.h>
#include <errno.h>
+#include <string.h>
#include "fncache.h"
#include <sys/types.h>
#include <sys/stat.h>
@@ -2938,6 +2940,111 @@ static int test__pmu_events_alias2(struct test_suite
*test __maybe_unused,
return ret;
}
+#ifdef HAVE_BPF_SKEL
+static void clear_bpf_filter_exprs(struct list_head *expr_head)
+{
+ struct perf_bpf_filter_expr *expr, *tmp;
+
+ list_for_each_entry_safe(expr, tmp, expr_head, list) {
+ list_del(&expr->list);
+ perf_bpf_filter_expr__delete(expr);
+ }
+}
+
+static int do_test_bpf_filters(struct list_head *expr_head)
+{
+ struct perf_bpf_filter_expr *expr, *group;
+ const char *compound = "lock_held(rwsem:R) == 1 || lock_held(&dentry)
!= 0, period > 100";
+
+ /* Simple lock_held(&cgroup_mutex) */
+ TEST_ASSERT_VAL("parse lock_held(&cgroup_mutex)",
+ __perf_bpf_filter__parse(expr_head,
"lock_held(&cgroup_mutex)") == 0);
+ TEST_ASSERT_VAL("not empty", !list_empty(expr_head));
+ expr = list_first_entry(expr_head, struct perf_bpf_filter_expr, list);
+ TEST_ASSERT_VAL("term is PBF_TERM_LOCK_HELD", expr->term ==
PBF_TERM_LOCK_HELD);
+ TEST_ASSERT_VAL("op is PBF_OP_EQ", expr->op == PBF_OP_EQ);
+ TEST_ASSERT_VAL("val is 1", expr->val == 1);
+ TEST_ASSERT_VAL("lock_name is &cgroup_mutex",
+ expr->lock_name && !strcmp(expr->lock_name,
"&cgroup_mutex"));
+ clear_bpf_filter_exprs(expr_head);
+
+ /* Negated !lock_held(cgroup_mutex) */
+ TEST_ASSERT_VAL("parse !lock_held(cgroup_mutex)",
+ __perf_bpf_filter__parse(expr_head,
"!lock_held(cgroup_mutex)") == 0);
+ TEST_ASSERT_VAL("not empty", !list_empty(expr_head));
+ expr = list_first_entry(expr_head, struct perf_bpf_filter_expr, list);
+ TEST_ASSERT_VAL("term is PBF_TERM_LOCK_HELD", expr->term ==
PBF_TERM_LOCK_HELD);
+ TEST_ASSERT_VAL("op is PBF_OP_EQ", expr->op == PBF_OP_EQ);
+ TEST_ASSERT_VAL("val is 0", expr->val == 0);
+ TEST_ASSERT_VAL("lock_name is cgroup_mutex",
+ expr->lock_name && !strcmp(expr->lock_name,
"cgroup_mutex"));
+ clear_bpf_filter_exprs(expr_head);
+
+ /* Read/shared and write/exclusive predicates */
+ TEST_ASSERT_VAL("parse lock_held_read(&mmap_lock)",
+ __perf_bpf_filter__parse(expr_head,
"lock_held_read(&mmap_lock)") == 0);
+ TEST_ASSERT_VAL("not empty", !list_empty(expr_head));
+ expr = list_first_entry(expr_head, struct perf_bpf_filter_expr, list);
+ TEST_ASSERT_VAL("term is PBF_TERM_LOCK_HELD", expr->term ==
PBF_TERM_LOCK_HELD);
+ TEST_ASSERT_VAL("part is PBF_LOCK_MODE_READ", expr->part ==
PBF_LOCK_MODE_READ);
+ TEST_ASSERT_VAL("op is PBF_OP_EQ", expr->op == PBF_OP_EQ);
+ TEST_ASSERT_VAL("val is 1", expr->val == 1);
+ TEST_ASSERT_VAL("lock_name is &mmap_lock",
+ expr->lock_name && !strcmp(expr->lock_name,
"&mmap_lock"));
+ clear_bpf_filter_exprs(expr_head);
+
+ TEST_ASSERT_VAL("parse !lock_held_exclusive(mmap_lock)",
+ __perf_bpf_filter__parse(expr_head,
+
"!lock_held_exclusive(mmap_lock)") == 0);
+ TEST_ASSERT_VAL("not empty", !list_empty(expr_head));
+ expr = list_first_entry(expr_head, struct perf_bpf_filter_expr, list);
+ TEST_ASSERT_VAL("term is PBF_TERM_LOCK_HELD", expr->term ==
PBF_TERM_LOCK_HELD);
+ TEST_ASSERT_VAL("part is PBF_LOCK_MODE_WRITE", expr->part ==
PBF_LOCK_MODE_WRITE);
+ TEST_ASSERT_VAL("op is PBF_OP_EQ", expr->op == PBF_OP_EQ);
+ TEST_ASSERT_VAL("val is 0", expr->val == 0);
+ TEST_ASSERT_VAL("lock_name is mmap_lock",
+ expr->lock_name && !strcmp(expr->lock_name,
"mmap_lock"));
+ clear_bpf_filter_exprs(expr_head);
+
+ /*
+ * Comparison and compound group:
+ * lock_held(rwsem:R) == 1 || lock_held(&dentry) != 0, period > 100
+ */
+ TEST_ASSERT_VAL("parse compound lock_held filter",
+ __perf_bpf_filter__parse(expr_head, compound) == 0);
+ TEST_ASSERT_VAL("not empty", !list_empty(expr_head));
+ expr = list_first_entry(expr_head, struct perf_bpf_filter_expr, list);
+ TEST_ASSERT_VAL("group op", expr->op == PBF_OP_GROUP_BEGIN);
+ TEST_ASSERT_VAL("groups not empty", !list_empty(&expr->groups));
+ group = list_first_entry(&expr->groups, struct perf_bpf_filter_expr,
list);
+ TEST_ASSERT_VAL("first group member lock_name",
+ group->lock_name && !strcmp(group->lock_name,
"rwsem:R"));
+ TEST_ASSERT_VAL("first group member op", group->op == PBF_OP_EQ &&
group->val == 1);
+ group = list_last_entry(&expr->groups, struct perf_bpf_filter_expr,
list);
+ TEST_ASSERT_VAL("second group member lock_name",
+ group->lock_name && !strcmp(group->lock_name,
"&dentry"));
+ TEST_ASSERT_VAL("second group member op", group->op == PBF_OP_NEQ &&
group->val == 0);
+ expr = list_last_entry(expr_head, struct perf_bpf_filter_expr, list);
+ TEST_ASSERT_VAL("second term is period", expr->term == PBF_TERM_PERIOD
&& expr->val == 100);
+
+ return TEST_OK;
+}
+#endif
+
+static int test__bpf_filters(struct test_suite *test __maybe_unused,
+ int subtest __maybe_unused)
+{
+#ifdef HAVE_BPF_SKEL
+ struct list_head expr_head = LIST_HEAD_INIT(expr_head);
+ int ret = do_test_bpf_filters(&expr_head);
+
+ clear_bpf_filter_exprs(&expr_head);
+ return ret;
+#else
+ return TEST_SKIP;
+#endif
+}
+
static struct test_case tests__parse_events[] = {
TEST_CASE_REASON("Test event parsing",
events2,
@@ -2952,6 +3059,7 @@ static struct test_case tests__parse_events[] = {
"no aliases in sysfs"),
TEST_CASE("Parsing of aliased events", pmu_events_alias2),
TEST_CASE("Parsing of terms (event modifiers)", terms2),
+ TEST_CASE("Parsing of BPF sample filters", bpf_filters),
{ .name = NULL, }
};
diff --git a/tools/perf/tests/shell/record_bpf_filter.sh
b/tools/perf/tests/shell/record_bpf_filter.sh
index 383574cb3bd3..9334b8b9b1c2 100755
--- a/tools/perf/tests/shell/record_bpf_filter.sh
+++ b/tools/perf/tests/shell/record_bpf_filter.sh
@@ -176,6 +176,116 @@ test_bpf_filter_cgroup() {
echo "Cgroup bpf-filter test [Success]"
}
+test_bpf_filter_lock_held() {
+ echo "Lock-held bpf-filter test"
+ lock_sym="cgroup_mutex"
+
+ if [ -r /proc/kallsyms ] && ! grep -qw "${lock_sym}" /proc/kallsyms
+ then
+ lock_sym="slab_mutex"
+ fi
+
+ if ! perf record -e task-clock --filter "lock_held(&${lock_sym})" \
+ -o "${perfdata}" true 2> /dev/null
+ then
+ if [ "$(id -u)" != 0 ]
+ then
+ echo "Lock-held bpf-filter test [Skipped permission]"
+ return
+ fi
+ if [ -r /proc/kallsyms ] && ! grep -qw "${lock_sym}" /proc/kallsyms
+ then
+ echo "Lock-held bpf-filter test [Skipped missing ${lock_sym}]"
+ return
+ fi
+ echo "Lock-held bpf-filter test [Failed &${lock_sym}]"
+ err=1
+ return
+ fi
+
+ if ! perf record -e task-clock --filter "!lock_held(${lock_sym})" \
+ -o "${perfdata}" true 2> /dev/null
+ then
+ echo "Lock-held bpf-filter test [Failed !lock_held(${lock_sym})]"
+ err=1
+ return
+ fi
+
+ if ! perf record -e task-clock --filter 'lock_held(mmap_lock)' \
+ -o "${perfdata}" perf bench mem mmap -t 2 -l 10 2> /dev/null
+ then
+ echo "Lock-held bpf-filter test [Failed mmap_lock]"
+ err=1
+ return
+ fi
+
+ if ! perf record -e task-clock --filter 'lock_held(rq_lock) ||
lock_held(spinlock)' \
+ -o "${perfdata}" true 2> /dev/null
+ then
+ if ! perf record -e task-clock --filter 'lock_held(rq_lock) ||
lock_held(spinlock)' \
+ -o /dev/null true 2>&1 | grep -q "Kernel BTF lacks required"
+ then
+ echo "Lock-held bpf-filter test [Failed rq_lock/spinlock]"
+ err=1
+ return
+ fi
+ fi
+
+ if ! perf record -e task-clock --filter 'lock_held(mutex) ||
lock_held(rwsem)' \
+ -o "${perfdata}" true 2> /dev/null
+ then
+ echo "Lock-held bpf-filter test [Failed lock type]"
+ err=1
+ return
+ fi
+
+ if ! perf record -e task-clock \
+ --filter 'lock_held_read(&mmap_lock) || lock_held_write(&mmap_lock)' \
+ -o "${perfdata}" true 2> /dev/null
+ then
+ echo "Lock-held bpf-filter test [Failed lock_held_read/write(&mmap_lock)]"
+ err=1
+ return
+ fi
+
+ if ! perf record -e task-clock --filter 'lock_held(&mmap_lock:R) ||
lock_held(&mmap_lock:W)' \
+ -o "${perfdata}" true 2> /dev/null
+ then
+ echo "Lock-held bpf-filter test [Failed &mmap_lock:R/:W]"
+ err=1
+ return
+ fi
+
+ if perf list tracepoint 2> /dev/null | grep -q kmem:mm_page_alloc
+ then
+ if ! perf record -e kmem:mm_page_alloc --filter 'lock_held(mutex) ||
lock_held(rwsem)' \
+ -o "${perfdata}" true 2> /dev/null
+ then
+ echo "Lock-held bpf-filter test [Failed tracepoint kmem:mm_page_alloc]"
+ err=1
+ return
+ fi
+ fi
+
+ if perf record -e task-clock --filter 'lock_held_read(rq_lock)' \
+ -o /dev/null true 2> /dev/null
+ then
+ echo "Lock-held bpf-filter test [Failed lock_held_read(rq_lock) should
fail]"
+ err=1
+ return
+ fi
+
+ if perf record -e task-clock --filter 'lock_held(nonexistent_lock_xyz)' \
+ -o /dev/null true 2> /dev/null
+ then
+ echo "Lock-held bpf-filter test [Failed nonexistent lock should fail]"
+ err=1
+ return
+ fi
+
+ echo "Lock-held bpf-filter test [Success]"
+}
+
test_bpf_filter_priv
if [ $err = 0 ]; then
@@ -198,5 +308,9 @@ if [ $err = 0 ]; then
test_bpf_filter_cgroup
fi
+if [ $err = 0 ]; then
+ test_bpf_filter_lock_held
+fi
+
cleanup
exit $err
--
2.56.0.212.g129ede20d9-goog