diff mbox series

[v2,bpf-next,2/2] selftests/bpf: Add selftests for cpumask iter

Message ID 20240110060037.4202-3-laoar.shao@gmail.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series bpf: Add bpf_iter_cpumask | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-4 fail Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test
bpf/vmtest-bpf-next-VM_Test-8 fail Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-10 success Logs for s390x-gcc / test
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-12 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-13 fail Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for x86_64-gcc / test
bpf/vmtest-bpf-next-VM_Test-14 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-16 success Logs for x86_64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-17 fail Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-llvm-17 / test
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-21 fail Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-llvm-18 / test
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-llvm-18 / veristat
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next
netdev/ynl success SINGLE THREAD; Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 8 this patch: 8
netdev/cc_maintainers success CCed 0 of 0 maintainers
netdev/build_clang success Errors and warnings before: 8 this patch: 8
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 8 this patch: 8
netdev/checkpatch warning CHECK: Comparison to NULL could be written "!cgrp" WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: externs should be avoided in .c files WARNING: line length of 83 exceeds 80 columns WARNING: line length of 89 exceeds 80 columns WARNING: line length of 91 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-22 fail Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18 and -O2 optimization
bpf/vmtest-bpf-next-PR fail PR summary
bpf/vmtest-bpf-next-VM_Test-18 fail Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17 and -O2 optimization

Commit Message

Yafang Shao Jan. 10, 2024, 6 a.m. UTC
Within the BPF program, we leverage the cgroup iterator to iterate through
percpu runqueue data, specifically the 'nr_running' metric. Subsequently
 we expose this data to userspace by means of a sequence file.

The CPU affinity for the cpumask is determined by the PID of a task:

- PID of the init task (PID 1)
  We typically don't set CPU affinity for init task and thus we can iterate
  across all possible CPUs. However, in scenarios where you've set CPU
  affinity for the init task, you should set the cpumask of your current
  task to full-F. Then proceed to iterate through all possible CPUs using
  the current task.
- PID of a task with defined CPU affinity
  The aim here is to iterate through a specific cpumask. This scenario
  aligns with tasks residing within a cpuset cgroup.
- Invalid PID (e.g., PID -1)
  No cpumask is available in this case.

The result as follows,
  #62/1    cpumask_iter/init_pid:OK
  #62/2    cpumask_iter/invalid_pid:OK
  #62/3    cpumask_iter/self_pid_one_cpu:OK
  #62/4    cpumask_iter/self_pid_multi_cpus:OK
  #62      cpumask_iter:OK
  Summary: 1/4 PASSED, 0 SKIPPED, 0 FAILED

Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
 .../selftests/bpf/prog_tests/cpumask_iter.c   | 134 ++++++++++++++++++
 .../selftests/bpf/progs/cpumask_common.h      |   3 +
 .../selftests/bpf/progs/test_cpumask_iter.c   |  62 ++++++++
 3 files changed, 199 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/cpumask_iter.c
 create mode 100644 tools/testing/selftests/bpf/progs/test_cpumask_iter.c

Comments

Alexei Starovoitov Jan. 10, 2024, 5:52 p.m. UTC | #1
On Tue, Jan 9, 2024 at 10:00 PM Yafang Shao <laoar.shao@gmail.com> wrote:
> +
> +SEC("iter/cgroup")
> +int BPF_PROG(cpu_cgroup, struct bpf_iter_meta *meta, struct cgroup *cgrp)
> +{
> +       u32 *cpu, nr_running = 0, psi_nr_running = 0, nr_cpus = 0;
> +       unsigned int tasks[NR_PSI_TASK_COUNTS];
> +       struct psi_group_cpu *groupc;
> +       struct bpf_cpumask *mask;
> +       struct task_struct *p;
> +       struct rq *rq;
> +
> +       /* epilogue */
> +       if (cgrp == NULL)
> +               return 0;
> +
> +       mask = bpf_cpumask_create();
> +       if (!mask)
> +               return 1;
> +
> +       p = bpf_task_from_pid(target_pid);
> +       if (!p) {
> +               bpf_cpumask_release(mask);
> +               return 1;
> +       }
> +
> +       bpf_cpumask_copy(mask, p->cpus_ptr);
> +       bpf_for_each(cpumask, cpu, &mask->cpumask) {
> +               rq = (struct rq *)bpf_per_cpu_ptr(&runqueues, *cpu);
> +               if (!rq)
> +                       continue;
> +               nr_running += rq->nr_running;
> +               nr_cpus += 1;
> +
> +               groupc = (struct psi_group_cpu *)bpf_per_cpu_ptr(&system_group_pcpu, *cpu);
> +               if (!groupc)
> +                       continue;
> +               bpf_probe_read_kernel(&tasks, sizeof(tasks), &groupc->tasks);
> +               psi_nr_running += tasks[NR_RUNNING];
> +       }

Instead of probe_read_kernel (which is not fast) please use
bpf_rdonly_cast() and access groups->tasks.
array should already be recognized by the verifier, but if not let's
fix the verifier instead of fallback to probe_read.
Yafang Shao Jan. 11, 2024, 2:31 a.m. UTC | #2
On Thu, Jan 11, 2024 at 1:52 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Tue, Jan 9, 2024 at 10:00 PM Yafang Shao <laoar.shao@gmail.com> wrote:
> > +
> > +SEC("iter/cgroup")
> > +int BPF_PROG(cpu_cgroup, struct bpf_iter_meta *meta, struct cgroup *cgrp)
> > +{
> > +       u32 *cpu, nr_running = 0, psi_nr_running = 0, nr_cpus = 0;
> > +       unsigned int tasks[NR_PSI_TASK_COUNTS];
> > +       struct psi_group_cpu *groupc;
> > +       struct bpf_cpumask *mask;
> > +       struct task_struct *p;
> > +       struct rq *rq;
> > +
> > +       /* epilogue */
> > +       if (cgrp == NULL)
> > +               return 0;
> > +
> > +       mask = bpf_cpumask_create();
> > +       if (!mask)
> > +               return 1;
> > +
> > +       p = bpf_task_from_pid(target_pid);
> > +       if (!p) {
> > +               bpf_cpumask_release(mask);
> > +               return 1;
> > +       }
> > +
> > +       bpf_cpumask_copy(mask, p->cpus_ptr);
> > +       bpf_for_each(cpumask, cpu, &mask->cpumask) {
> > +               rq = (struct rq *)bpf_per_cpu_ptr(&runqueues, *cpu);
> > +               if (!rq)
> > +                       continue;
> > +               nr_running += rq->nr_running;
> > +               nr_cpus += 1;
> > +
> > +               groupc = (struct psi_group_cpu *)bpf_per_cpu_ptr(&system_group_pcpu, *cpu);
> > +               if (!groupc)
> > +                       continue;
> > +               bpf_probe_read_kernel(&tasks, sizeof(tasks), &groupc->tasks);
> > +               psi_nr_running += tasks[NR_RUNNING];
> > +       }
>
> Instead of probe_read_kernel (which is not fast) please use
> bpf_rdonly_cast() and access groups->tasks.
> array should already be recognized by the verifier, but if not let's
> fix the verifier instead of fallback to probe_read.

Thanks for your suggestion.
Will do it.
kernel test robot Jan. 15, 2024, 1:52 a.m. UTC | #3
Hi Yafang,

kernel test robot noticed the following build errors:

[auto build test ERROR on bpf-next/master]

url:    https://github.com/intel-lab-lkp/linux/commits/Yafang-Shao/bpf-Add-bpf_iter_cpumask-kfuncs/20240110-140322
base:   https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master
patch link:    https://lore.kernel.org/r/20240110060037.4202-3-laoar.shao%40gmail.com
patch subject: [PATCH v2 bpf-next 2/2] selftests/bpf: Add selftests for cpumask iter
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240115/202401150914.Rcl50Ct9-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202401150914.Rcl50Ct9-lkp@intel.com/

All errors (new ones prefixed by >>):

>> progs/test_cpumask_iter.c:20:21: error: use of undeclared identifier 'NR_PSI_TASK_COUNTS'
      20 |         unsigned int tasks[NR_PSI_TASK_COUNTS];
         |                            ^
>> progs/test_cpumask_iter.c:41:2: error: assigning to 'u32 *' (aka 'unsigned int *') from 'int *' converts between pointers to integer types with different sign [-Werror,-Wpointer-sign]
      41 |         bpf_for_each(cpumask, cpu, &mask->cpumask) {
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   /tools/include/bpf/bpf_helpers.h:348:10: note: expanded from macro 'bpf_for_each'
     348 |         (((cur) = bpf_iter_##type##_next(&___it)));                                             \
         |                 ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> progs/test_cpumask_iter.c:51:55: error: incomplete definition of type 'struct psi_group_cpu'
      51 |                 bpf_probe_read_kernel(&tasks, sizeof(tasks), &groupc->tasks);
         |                                                               ~~~~~~^
   progs/test_cpumask_iter.c:11:21: note: forward declaration of 'struct psi_group_cpu'
      11 | extern const struct psi_group_cpu system_group_pcpu __ksym __weak;
         |                     ^
>> progs/test_cpumask_iter.c:52:27: error: use of undeclared identifier 'NR_RUNNING'; did you mean 'T_RUNNING'?
      52 |                 psi_nr_running += tasks[NR_RUNNING];
         |                                         ^~~~~~~~~~
         |                                         T_RUNNING
   /tools/include/vmlinux.h:11216:3: note: 'T_RUNNING' declared here
    11216 |                 T_RUNNING = 0,
          |                 ^
   4 errors generated.
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/prog_tests/cpumask_iter.c b/tools/testing/selftests/bpf/prog_tests/cpumask_iter.c
new file mode 100644
index 000000000000..689ccc4d3c3b
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/cpumask_iter.c
@@ -0,0 +1,134 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2023 Yafang Shao <laoar.shao@gmail.com> */
+
+#define _GNU_SOURCE
+#include <sched.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <test_progs.h>
+#include "cgroup_helpers.h"
+#include "test_cpumask_iter.skel.h"
+
+static void verify_percpu_data(struct bpf_link *link, int nr_cpu_exp, int nr_running_exp)
+{
+	int iter_fd, len, item, nr_running, psi_running, nr_cpus;
+	static char buf[128];
+	size_t left;
+	char *p;
+
+	iter_fd = bpf_iter_create(bpf_link__fd(link));
+	if (!ASSERT_GE(iter_fd, 0, "iter_fd"))
+		return;
+
+	memset(buf, 0, sizeof(buf));
+	left = ARRAY_SIZE(buf);
+	p = buf;
+	while ((len = read(iter_fd, p, left)) > 0) {
+		p += len;
+		left -= len;
+	}
+
+	item = sscanf(buf, "nr_running %u nr_cpus %u psi_running %u\n",
+		      &nr_running, &nr_cpus, &psi_running);
+	if (nr_cpu_exp == -1) {
+		ASSERT_EQ(item, -1, "seq_format");
+		goto out;
+	}
+
+	ASSERT_EQ(item, 3, "seq_format");
+	ASSERT_GE(nr_running, nr_running_exp, "nr_running");
+	ASSERT_GE(psi_running, nr_running_exp, "nr_running");
+	ASSERT_EQ(nr_cpus, nr_cpu_exp, "nr_cpus");
+
+	/* read() after iter finishes should be ok. */
+	if (len == 0)
+		ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read");
+
+out:
+	close(iter_fd);
+}
+
+void test_cpumask_iter(void)
+{
+	DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
+	int nr_possible, cgrp_fd, pid, err, cnt, i;
+	struct test_cpumask_iter *skel = NULL;
+	union bpf_iter_link_info linfo;
+	int cpu_ids[] = {1, 3, 4, 5};
+	struct bpf_link *link;
+	cpu_set_t set;
+
+	skel = test_cpumask_iter__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "test_for_each_cpu__open_and_load"))
+		return;
+
+	if (setup_cgroup_environment())
+		goto destroy;
+
+	/* Utilize the cgroup iter */
+	cgrp_fd = get_root_cgroup();
+	if (!ASSERT_GE(cgrp_fd, 0, "create cgrp"))
+		goto cleanup;
+
+	memset(&linfo, 0, sizeof(linfo));
+	linfo.cgroup.cgroup_fd = cgrp_fd;
+	linfo.cgroup.order = BPF_CGROUP_ITER_SELF_ONLY;
+	opts.link_info = &linfo;
+	opts.link_info_len = sizeof(linfo);
+
+	link = bpf_program__attach_iter(skel->progs.cpu_cgroup, &opts);
+	if (!ASSERT_OK_PTR(link, "attach_iter"))
+		goto close_fd;
+
+	skel->bss->target_pid = 1;
+	/* In case init task is set CPU affinity */
+	err = sched_getaffinity(1, sizeof(set), &set);
+	if (!ASSERT_OK(err, "setaffinity"))
+		goto close_fd;
+
+	cnt = CPU_COUNT(&set);
+	nr_possible = bpf_num_possible_cpus();
+	if (test__start_subtest("init_pid"))
+		/* curent task is running. */
+		verify_percpu_data(link, cnt, cnt == nr_possible ? 1 : 0);
+
+	skel->bss->target_pid = -1;
+	if (test__start_subtest("invalid_pid"))
+		verify_percpu_data(link, -1, -1);
+
+	pid = getpid();
+	skel->bss->target_pid = pid;
+	CPU_ZERO(&set);
+	CPU_SET(0, &set);
+	err = sched_setaffinity(pid, sizeof(set), &set);
+	if (!ASSERT_OK(err, "setaffinity"))
+		goto free_link;
+
+	if (test__start_subtest("self_pid_one_cpu"))
+		verify_percpu_data(link, 1, 1);
+
+	/* Assume there are at least 8 CPUs on the testbed */
+	if (nr_possible < 8)
+		goto free_link;
+
+	CPU_ZERO(&set);
+	/* Set the CPU affinitiy: 1,3-5 */
+	for (i = 0; i < ARRAY_SIZE(cpu_ids); i++)
+		CPU_SET(cpu_ids[i], &set);
+	err = sched_setaffinity(pid, sizeof(set), &set);
+	if (!ASSERT_OK(err, "setaffinity"))
+		goto free_link;
+
+	if (test__start_subtest("self_pid_multi_cpus"))
+		verify_percpu_data(link, ARRAY_SIZE(cpu_ids), 1);
+
+free_link:
+	bpf_link__destroy(link);
+close_fd:
+	close(cgrp_fd);
+cleanup:
+	cleanup_cgroup_environment();
+destroy:
+	test_cpumask_iter__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/cpumask_common.h b/tools/testing/selftests/bpf/progs/cpumask_common.h
index 0cd4aebb97cf..5f2f44eca4c4 100644
--- a/tools/testing/selftests/bpf/progs/cpumask_common.h
+++ b/tools/testing/selftests/bpf/progs/cpumask_common.h
@@ -55,6 +55,9 @@  void bpf_cpumask_copy(struct bpf_cpumask *dst, const struct cpumask *src) __ksym
 u32 bpf_cpumask_any_distribute(const struct cpumask *src) __ksym;
 u32 bpf_cpumask_any_and_distribute(const struct cpumask *src1, const struct cpumask *src2) __ksym;
 u32 bpf_cpumask_weight(const struct cpumask *cpumask) __ksym;
+int bpf_iter_cpumask_new(struct bpf_iter_cpumask *it, struct cpumask *mask) __ksym;
+int *bpf_iter_cpumask_next(struct bpf_iter_cpumask *it) __ksym;
+void bpf_iter_cpumask_destroy(struct bpf_iter_cpumask *it) __ksym;
 
 void bpf_rcu_read_lock(void) __ksym;
 void bpf_rcu_read_unlock(void) __ksym;
diff --git a/tools/testing/selftests/bpf/progs/test_cpumask_iter.c b/tools/testing/selftests/bpf/progs/test_cpumask_iter.c
new file mode 100644
index 000000000000..68ebfa0963c7
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_cpumask_iter.c
@@ -0,0 +1,62 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2023 Yafang Shao <laoar.shao@gmail.com> */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+#include "task_kfunc_common.h"
+#include "cpumask_common.h"
+
+extern const struct psi_group_cpu system_group_pcpu __ksym __weak;
+extern const struct rq runqueues __ksym __weak;
+
+int target_pid;
+
+SEC("iter/cgroup")
+int BPF_PROG(cpu_cgroup, struct bpf_iter_meta *meta, struct cgroup *cgrp)
+{
+	u32 *cpu, nr_running = 0, psi_nr_running = 0, nr_cpus = 0;
+	unsigned int tasks[NR_PSI_TASK_COUNTS];
+	struct psi_group_cpu *groupc;
+	struct bpf_cpumask *mask;
+	struct task_struct *p;
+	struct rq *rq;
+
+	/* epilogue */
+	if (cgrp == NULL)
+		return 0;
+
+	mask = bpf_cpumask_create();
+	if (!mask)
+		return 1;
+
+	p = bpf_task_from_pid(target_pid);
+	if (!p) {
+		bpf_cpumask_release(mask);
+		return 1;
+	}
+
+	bpf_cpumask_copy(mask, p->cpus_ptr);
+	bpf_for_each(cpumask, cpu, &mask->cpumask) {
+		rq = (struct rq *)bpf_per_cpu_ptr(&runqueues, *cpu);
+		if (!rq)
+			continue;
+		nr_running += rq->nr_running;
+		nr_cpus += 1;
+
+		groupc = (struct psi_group_cpu *)bpf_per_cpu_ptr(&system_group_pcpu, *cpu);
+		if (!groupc)
+			continue;
+		bpf_probe_read_kernel(&tasks, sizeof(tasks), &groupc->tasks);
+		psi_nr_running += tasks[NR_RUNNING];
+	}
+	BPF_SEQ_PRINTF(meta->seq, "nr_running %u nr_cpus %u psi_running %u\n",
+		       nr_running, nr_cpus, psi_nr_running);
+
+	bpf_task_release(p);
+	bpf_cpumask_release(mask);
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";