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 | 103 +++++++++++++++++++
 2 files changed, 211 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..adaa5363d0b7 100755
--- a/tools/perf/tests/shell/record_bpf_filter.sh
+++ b/tools/perf/tests/shell/record_bpf_filter.sh
@@ -176,6 +176,105 @@ 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 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 +297,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


Reply via email to