selftests/cgroup: add PSI pressure trigger and validation tests
PATCH v3 1/1 selftests/cgroup: add PSI pressure trigger and validation tests
From: Tao Cui <[email protected]>
The cgroup selftests have no PSI coverage. Add test_psi.c: per-resource trigger smoke tests (one trigger per fd, IRQ full-only), a cgroup.pressure hide/show toggle test, and a CPU-pressure trigger test using over-subscription. Skips when PSI is disabled or a resource is absent.
---
Changes since v2 (Suren Baghdasaryan, Michal Koutny review):
- Restructure the trigger test into per-resource cases (io, memory, cpu, irq) so a failure points at the specific resource; irq is skipped when /proc/pressure/irq is absent.
- Spawn the CPU hogs with cg_run_nowait() instead of open-coding fork(), and arm the trigger with a 2s window so unprivileged users can set it.
- Address the remaining review comments on cleanup and robustness:
guard teardown with a "created" flag, use cg_read_strcmp() instead of atoi(), report strerror() on errors, and fix the unused-parameter and sign-compare nits.
Changes since v1 (Michal Koutny, sashiko review):
- Keep trigger tests smoke-level; switch the firing test from memory to CPU pressure; drop churn_memory().
- Keep the runner out of the cgroup; add PSI/IRQ skip-guards and a .gitignore entry.
--- tools/testing/selftests/cgroup/.gitignore | 1 + tools/testing/selftests/cgroup/Makefile | 2 + tools/testing/selftests/cgroup/config | 1 + tools/testing/selftests/cgroup/test_psi.c | 296 ++++++++++++++++++++++ 4 files changed, 300 insertions(+) create mode 100644 tools/testing/selftests/cgroup/test_psi.c
tools/testing/selftests/cgroup/.gitignore +1 -0
@@ -8,5 +8,6 @@ test_kill test_kmem test_memcontrol test_pids +test_psi test_zswap wait_inotify
tools/testing/selftests/cgroup/Makefile +2 -0
@@ -16,6 +16,7 @@ TEST_GEN_PROGS += test_kill TEST_GEN_PROGS += test_kmem TEST_GEN_PROGS += test_memcontrol TEST_GEN_PROGS += test_pids +TEST_GEN_PROGS += test_psi TEST_GEN_PROGS += test_zswap LOCAL_HDRS += $(selfdir)/clone3/clone3_selftests.h $(selfdir)/pidfd/pidfd.h
@@ -32,4 +33,5 @@ $(OUTPUT)/test_kill: $(LIBCGROUP_O) $(OUTPUT)/test_kmem: $(LIBCGROUP_O) $(OUTPUT)/test_memcontrol: $(LIBCGROUP_O) $(OUTPUT)/test_pids: $(LIBCGROUP_O) +$(OUTPUT)/test_psi: $(LIBCGROUP_O) $(OUTPUT)/test_zswap: $(LIBCGROUP_O)
tools/testing/selftests/cgroup/config +1 -0
@@ -4,3 +4,4 @@ CONFIG_CGROUP_FREEZER=y CONFIG_CGROUP_SCHED=y CONFIG_MEMCG=y CONFIG_PAGE_COUNTER=y +CONFIG_PSI=y
tools/testing/selftests/cgroup/test_psi.c new file +296 -0
296 lines not on this page · raw message
Signature · 1 line
2.43.0
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 32 lines · 在 2026/8/13 21:54, [email protected] 写道:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The child processes spawned to generate CPU pressure execute an unconditional infinite loop (for (;;);). If the test is interrupted (e.g., via SIGINT or a CI timeout sending SIGTERM), the parent process will exit immediately without executing the cleanup block (cg_killall()). This leavesncpus + 1child processes running forever, consuming 100% CPU on all cores and effectively locking up the system. ----- Patch [1]: [PATCH v3] selftests/cgroup: add PSI pressure trigger and validation tests --- commit
a3c72bd52fb11f36beba6ee125a9c7100e986a5a
Author: Tao Cui <[email protected]>
selftests/cgroup: add PSI pressure trigger and validation testsThis commit adds per-resource trigger smoke tests, a cgroup.pressure hide/show toggle test, and a CPU-pressure trigger test using over-subscription to the cgroup selftests.
Quoted 5 lines
tools/testing/selftests/cgroup/test_psi.c new file +0
-0[ ... ]
tools/testing/selftests/cgroup/test_psi.c line 141
Quoted 5 lines
tools/testing/selftests/cgroup/test_psi.c new file +5
-0+/* A child that burns CPU forever; stopped by cg_killall() in the parent. */ +static int hog_cpu (const char *cgroup,void *arg) +{ +for (;;) + ;[Severity: Medium]
Will this leave runaway CPU hogs consuming 100% CPU on all cores if the test is interrupted?
Quoted 8 lines
If the parent process is interrupted by a signal (like SIGINT or a CI runner timeout sending SIGTERM) while waiting for
poll(), it will exit immediately without executing thecg_killall()cleanup block.Could a fallback termination condition be added here, such as
prctl(PR_SET_PDEATHSIG, SIGKILL)or checkinggetppid(), to prevent orphaned processes from locking up the system?
Agreed. The hogs run forever, so interrupting the parent during poll() skips cg_killall() and orphans them. I'll add prctl(PR_SET_PDEATHSIG, SIGKILL) at the start of each hog so the kernel reaps them if the parent dies -- for v4.
Quoted 3 lines
Quoted 2 lines
tools/testing/selftests/cgroup/test_psi.c new file +2
-0+return 0 ; +}
Quoted 152 lines · Tao Cui
From: Tao Cui <[email protected]>
The cgroup selftests have no PSI coverage. Add
test_psi.c: per-resource trigger smoke tests (one trigger per fd, IRQ full-only), a cgroup.pressure hide/show toggle test, and a CPU-pressure trigger test using over-subscription. Skips when PSI is disabled or a resource is absent.Signed-off-by Tao Cui <[email protected]>---
Changes since v2 (Suren Baghdasaryan, Michal Koutny review):
- Restructure the trigger test into per-resource cases (io, memory, cpu, irq) so a failure points at the specific resource; irq is skipped when/proc/pressure/irqis absent.
- Spawn the CPU hogs withcg_run_nowait()instead of open-codingfork(), and arm the trigger with a 2s window so unprivileged users can set it.
- Address the remaining review comments on cleanup and robustness:
guard teardown with a "created" flag, usecg_read_strcmp()instead ofatoi(), reportstrerror()on errors, and fix the unused-parameter and sign-compare nits.Changes since v1 (Michal Koutny, sashiko review):
- Keep trigger tests smoke-level; switch the firing test from memory to CPU pressure; dropchurn_memory().
- Keep the runner out of the cgroup; add PSI/IRQ skip-guards and a .gitignore entry.--- tools/testing/selftests/cgroup/.gitignore | 1 + tools/testing/selftests/cgroup/Makefile | 2 + tools/testing/selftests/cgroup/config | 1 + tools/testing/selftests/cgroup/test_psi.c | 296 ++++++++++++++++++++++ 4 files changed, 300 insertions(+) create mode 100644 tools/testing/selftests/cgroup/test_psi.ctools/testing/selftests/cgroup/.gitignore +1
-0@@ -8,5 +8,6 @@ test_kill test_kmem test_memcontrol test_pids +test_psi test_zswap wait_inotifytools/testing/selftests/cgroup/Makefile +2
-0@@ -16,6 +16,7 @@ TEST_GEN_PROGS += test_kill TEST_GEN_PROGS += test_kmem TEST_GEN_PROGS += test_memcontrol TEST_GEN_PROGS += test_pids +TEST_GEN_PROGS += test_psi TEST_GEN_PROGS += test_zswap LOCAL_HDRS += $(selfdir)/clone3/clone3_selftests.h $(selfdir)/pidfd/pidfd.h@@ -32,4 +33,5 @@ $(OUTPUT)/test_kill: $(LIBCGROUP_O) $(OUTPUT)/test_kmem: $(LIBCGROUP_O) $(OUTPUT)/test_memcontrol: $(LIBCGROUP_O) $(OUTPUT)/test_pids: $(LIBCGROUP_O) +$(OUTPUT)/test_psi: $(LIBCGROUP_O) $(OUTPUT)/test_zswap: $(LIBCGROUP_O)tools/testing/selftests/cgroup/config +1
-0@@ -4,3 +4,4 @@ CONFIG_CGROUP_FREEZER=y CONFIG_CGROUP_SCHED=y CONFIG_MEMCG=y CONFIG_PAGE_COUNTER=y +CONFIG_PSI=ytools/testing/selftests/cgroup/test_psi.c new file +69
-0@@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/limits.h> + +#include "kselftest.h" +#include "cgroup_util.h" + +#define PSI_POLL_TIMEOUhas this check T_MS 5000 + +/* PSI triggers are written with a trailing NUL the kernel parser expects. */ +static ssize_t write_trigger (int fd,const char *trigger) +{ +return write (fd, trigger,strlen (trigger) +1 ); +} + +static int pressure_open (const char *resource) +{ +char path[PATH_MAX ]; +int fd; + +snprintf (path,sizeof (path),"/proc/pressure/%s" , resource); + fd =open (path,O_RDWR ); +if (fd <0 ) +ksft_print_msg ("open %s: %s\n" , path,strerror (errno)); +return fd; +} + +/* +* /proc/pressure/<resource> accepts exactly one trigger per file +* descriptor. For io, memory and cpu verify that a "some" trigger arms +* and that a second trigger on the same fd is rejected with EBUSY. For +* irq, which only tracks "full", verify that "some" is rejected and +* "full" arms. irq is optional -- it only exists with IRQ-time +* accounting -- so a missing /proc/pressure/irq is SKIP, not FAIL. +*/ +static int test_proc_trigger (const char *resource,bool full_only) +{ +int fd, ret =KSFT_FAIL ; + + fd =pressure_open (resource); +if (fd <0 ) +return full_only ?KSFT_SKIP :KSFT_FAIL ; + +if (!full_only) { +if (write_trigger (fd,"some 150000 2000000" ) <=0 ) { +ksft_print_msg ("%s: 'some' trigger rejected: %s\n" , + resource,strerror (errno)); + goto out; + } +if (write_trigger (fd,"full 150000 2000000" ) !=-1 || + errno !=EBUSY ) { +ksft_print_msg ("%s: second trigger not EBUSY\n" , + resource); + goto out; + } + }else { +if (write_trigger (fd,"some 150000 2000000" ) !=-1 ) { +ksft_print_msg ("irq: 'some' trigger unexpectedly accepted\n" ); + goto out; + } +if (write_trigger (fd,"full 150000 2000000" ) <=0 ) {
tools/testing/selftests/cgroup/test_psi.c line 79
Quoted 10 lines
tools/testing/selftests/cgroup/test_psi.c new file +10
-0+ksft_print_msg ("irq: 'full' trigger rejected: %s\n" , +strerror (errno)); + goto out; + } + } + + ret =KSFT_PASS ; +out: +close (fd); +return ret;
You don't really need "ret" variable. Just "return KSFT_FAIL;" here is enough.
tools/testing/selftests/cgroup/test_psi.c line 92
Quoted 13 lines
tools/testing/selftests/cgroup/test_psi.c new file +13
-0+} + +/* +* cgroup.pressure gates visibility of the per-resource *.pressure files +* inside a cgroup: writing 0 hides them, writing 1 shows them again. +* Drive one hide/show cycle and check that memory.pressure appears and +* disappears along with it. +*/ +static int test_cgroup_pressure_toggle (const char *root) +{ +char buf[BUF_SIZE ]; +char *cg =NULL ; +int ret =KSFT_FAIL , created =0 ;
Again, you don't need "ret" variable here.
tools/testing/selftests/cgroup/test_psi.c line 101
Quoted 9 lines
tools/testing/selftests/cgroup/test_psi.c new file +9
-0+ + cg =cg_name (root,"psi_toggle_test" ); +if (!cg) + goto cleanup; +if (cg_create (cg)) { +ksft_print_msg ("cg_create: %s\n" ,strerror (errno)); + goto cleanup; + } + created =1 ;
Nope. The way we usually do the cleanup is by jumping to the appropriate label instead of adding extra state flags. In this case that would look something like this:
cg = cg_name(root, "psi_toggle_test");
if (!cg)
return KSFT_FAIL;
if (cg_create(cg)) {
ksft_print_msg("cg_create: %s\n", strerror(errno));
goto free_cg;
}
if (cg_write(cg, "cgroup.pressure", "0")) {
ksft_print_msg("write cgroup.pressure=0: %s\n", strerror(errno));
goto cleanup;
}
...
return KSFT_PASS;
cleanup:
cg_destroy(cg);
free_cg:
free(cg);
return KSFT_FAIL;
}Quoted 30 lines
tools/testing/selftests/cgroup/test_psi.c new file +30
-0+ +if (cg_write (cg,"cgroup.pressure" ,"0" )) { +ksft_print_msg ("write cgroup.pressure=0: %s\n" ,strerror (errno)); + goto cleanup; + } +if (cg_read_strcmp (cg,"cgroup.pressure" ,"0\n" )) { +ksft_print_msg ("cgroup.pressure readback != 0\n" ); + goto cleanup; + } +if (cg_read (cg,"memory.pressure" , buf,sizeof (buf)) >=0 ) { +ksft_print_msg ("memory.pressure readable while hidden\n" ); + goto cleanup; + } + +if (cg_write (cg,"cgroup.pressure" ,"1" )) { +ksft_print_msg ("write cgroup.pressure=1: %s\n" ,strerror (errno)); + goto cleanup; + } +if (cg_read_strcmp (cg,"cgroup.pressure" ,"1\n" )) { +ksft_print_msg ("cgroup.pressure readback != 1\n" ); + goto cleanup; + } +if (cg_read (cg,"memory.pressure" , buf,sizeof (buf)) <0 ) { +ksft_print_msg ("memory.pressure unreadable after enabling\n" ); + goto cleanup; + } + + ret =KSFT_PASS ; +cleanup: +if (created)
tools/testing/selftests/cgroup/test_psi.c line 141
Quoted 10 lines
tools/testing/selftests/cgroup/test_psi.c new file +10
-0+cg_destroy (cg); +free (cg); +return ret; +} + +/* A child that burns CPU forever; stopped by cg_killall() in the parent. */ +static int hog_cpu (const char *cgroup,void *arg) +{ +for (;;) + ;
This loop looks strange. Maybe:
for (;;) {}tools/testing/selftests/cgroup/test_psi.c line 153
Quoted 12 lines
tools/testing/selftests/cgroup/test_psi.c new file +12
-0+return 0 ; +} + +/* +* Arm a "some" trigger on a cgroup's cpu.pressure, oversubscribe the +* cgroup with more spinning hogs than there are CPUs, and check that the +* trigger fires once the cgroup stalls on CPU. +*/ +static int test_cgroup_trigger_fire (const char *root) +{ +char *cg =NULL , *cpupress =NULL ; +int fd =-1 , ret =KSFT_FAIL , created =0 , i;
Please define each variable on a separate line.
tools/testing/selftests/cgroup/test_psi.c line 163
Quoted 10 lines
tools/testing/selftests/cgroup/test_psi.c new file +10
-0+long ncpus; + + cg =cg_name (root,"psi_trigger_test" ); +if (!cg) + goto cleanup; +if (cg_create (cg)) { +ksft_print_msg ("cg_create: %s\n" ,strerror (errno)); + goto cleanup; + } + created =1 ;
Same as above. Jump to the appropriate label.
Quoted 11 lines
tools/testing/selftests/cgroup/test_psi.c new file +11
-0+ + cpupress =cg_control (cg,"cpu.pressure" ); +if (!cpupress) + goto cleanup; + fd =open (cpupress,O_RDWR ); +if (fd <0 ) { +ksft_print_msg ("open cpu.pressure: %s\n" ,strerror (errno)); + goto cleanup; + } + +/*
tools/testing/selftests/cgroup/test_psi.c line 184
Quoted 10 lines
tools/testing/selftests/cgroup/test_psi.c new file +10
-0+* 1usec threshold over a 2s window: any CPU stall fires it. The 2s +* window is the smallest unprivileged users are allowed to arm. +*/ +if (write_trigger (fd,"some 1 2000000" ) <=0 ) { +ksft_print_msg ("arm trigger: %s\n" ,strerror (errno)); + goto cleanup; + } + + ncpus =sysconf (_SC_NPROCESSORS_ONLN); +if (ncpus <=0 ) {
I don't think this syscall will ever return 0, so you just need to check for -1. Also reporting -1 in the error log isn't very informative. Instead report strerror(errno).
Quoted 9 lines
tools/testing/selftests/cgroup/test_psi.c new file +9
-0+ksft_print_msg ("sysconf(_SC_NPROCESSORS_ONLN) returned %ld\n" , + ncpus); + goto cleanup; + } + +/* ncpus+1 hogs guarantee CPU contention inside the cgroup. */ +for (i =0 ; i < ncpus +1 ; i++) { +if (cg_run_nowait (cg, hog_cpu,NULL ) <0 ) { +ksft_print_msg ("spawn hog %d: %s\n" , i,strerror (errno));
tools/testing/selftests/cgroup/test_psi.c line 203
Quoted 10 lines
tools/testing/selftests/cgroup/test_psi.c new file +10
-0+ goto cleanup; + } + } + +struct pollfd pfd = { .fd = fd, .events =POLLPRI }; + +switch (poll (&pfd,1 ,PSI_POLL_TIMEOUT_MS )) { +case -1 : +ksft_print_msg ("poll: %s\n" ,strerror (errno)); + goto cleanup;
So, goto cleanup here but break everywhere else? Consistency would be nice, so please use break here as well.
tools/testing/selftests/cgroup/test_psi.c line 206
Quoted 3 lines
tools/testing/selftests/cgroup/test_psi.c new file +3
-0+case 0 : +ksft_print_msg ("trigger did not fire (could not induce CPU pressure)\n" ); + ret =KSFT_SKIP ;
This means your trigger timed out. Why is this treated as KSFT_SKIP and not as KSFT_FAIL?
Quoted 77 lines
tools/testing/selftests/cgroup/test_psi.c new file +77
-0+break ; +default : +if (pfd.revents &POLLPRI ) + ret =KSFT_PASS ; +else +ksft_print_msg ("poll returned 0x%x\n" , pfd.revents); +break ; + } + +cleanup: +if (fd >=0 ) +close (fd); +if (created) { +cg_killall (cg); +cg_destroy (cg); + } +free (cpupress); +free (cg); +return ret; +} + +struct psi_proc_test { +const char *name; +const char *resource; +bool full_only; +}; +static const struct psi_proc_test proc_tests[] = { + {"proc_trigger_io" ,"io" , false }, + {"proc_trigger_memory" ,"memory" , false }, + {"proc_trigger_cpu" ,"cpu" , false }, + {"proc_trigger_irq" ,"irq" , true }, +}; + +struct psi_cg_test { +const char *name; +int (*fn)(const char *root); +}; +static const struct psi_cg_test cg_tests[] = { + {"cgroup_pressure_toggle" , test_cgroup_pressure_toggle }, + {"cgroup_trigger_fire" , test_cgroup_trigger_fire }, +}; + +int main (int argc,char **argv) +{ +char root[PATH_MAX ]; +int psi_fd, i; + +ksft_print_header (); +ksft_set_plan (ARRAY_SIZE (proc_tests) +ARRAY_SIZE (cg_tests)); + +if (cg_find_unified_root (root,sizeof (root),NULL )) +ksft_exit_skip ("cgroup v2 isn't mounted\n" ); + +/* PSI must be enabled (CONFIG_PSI=y, not disabled on the cmdline). */ + psi_fd =open ("/proc/pressure/memory" ,O_RDONLY ); +if (psi_fd <0 ) +ksft_exit_skip ("PSI unavailable (CONFIG_PSI=n or psi=0)\n" ); +close (psi_fd); + +for (i =0 ; i <ARRAY_SIZE (proc_tests); i++) { +switch (test_proc_trigger (proc_tests[i].resource, + proc_tests[i].full_only)) { +case KSFT_PASS : +ksft_test_result_pass ("%s\n" , proc_tests[i].name); +break ; +case KSFT_SKIP : +ksft_test_result_skip ("%s\n" , proc_tests[i].name); +break ; +default : +ksft_test_result_fail ("%s\n" , proc_tests[i].name); +break ; + } + } + +for (i =0 ; i <ARRAY_SIZE (cg_tests); i++) { +switch (cg_tests[i].fn (root)) { +case KSFT_PASS :
tools/testing/selftests/cgroup/test_psi.c line 293
Quoted 10 lines
tools/testing/selftests/cgroup/test_psi.c new file +10
-0+ksft_test_result_pass ("%s\n" , cg_tests[i].name); +break ; +case KSFT_SKIP : +ksft_test_result_skip ("%s\n" , cg_tests[i].name); +break ; +default : +ksft_test_result_fail ("%s\n" , cg_tests[i].name); +break ; + } + }
Please convert this to use
https://docs.kernel.org/dev-tools/kselftest.html#test-harness as I suggested before. This code will look much simpler. Even if some other tests are not using it, there is no reason for new tests not to take advantage of the existing harness. You will find planty of examples which use them.
Quoted 6 lines
tools/testing/selftests/cgroup/test_psi.c new file +3
-1+ +ksft_finished (); +} --2.43.0
Hi, Suren
Quoted 157 lines · Suren Baghdasaryan
Quoted 156 lines · Tao Cui
From: Tao Cui <[email protected]>
The cgroup selftests have no PSI coverage. Add
test_psi.c: per-resource trigger smoke tests (one trigger per fd, IRQ full-only), a cgroup.pressure hide/show toggle test, and a CPU-pressure trigger test using over-subscription. Skips when PSI is disabled or a resource is absent.Signed-off-by Tao Cui <[email protected]>---
Changes since v2 (Suren Baghdasaryan, Michal Koutny review):
- Restructure the trigger test into per-resource cases (io, memory, cpu, irq) so a failure points at the specific resource; irq is skipped when/proc/pressure/irqis absent.
- Spawn the CPU hogs withcg_run_nowait()instead of open-codingfork(), and arm the trigger with a 2s window so unprivileged users can set it.
- Address the remaining review comments on cleanup and robustness:
guard teardown with a "created" flag, usecg_read_strcmp()instead ofatoi(), reportstrerror()on errors, and fix the unused-parameter and sign-compare nits.Changes since v1 (Michal Koutny, sashiko review):
- Keep trigger tests smoke-level; switch the firing test from memory to CPU pressure; dropchurn_memory().
- Keep the runner out of the cgroup; add PSI/IRQ skip-guards and a .gitignore entry.--- tools/testing/selftests/cgroup/.gitignore | 1 + tools/testing/selftests/cgroup/Makefile | 2 + tools/testing/selftests/cgroup/config | 1 + tools/testing/selftests/cgroup/test_psi.c | 296 ++++++++++++++++++++++ 4 files changed, 300 insertions(+) create mode 100644 tools/testing/selftests/cgroup/test_psi.ctools/testing/selftests/cgroup/.gitignore +1
-0@@ -8,5 +8,6 @@ test_kill test_kmem test_memcontrol test_pids +test_psi test_zswap wait_inotifytools/testing/selftests/cgroup/Makefile +2
-0@@ -16,6 +16,7 @@ TEST_GEN_PROGS += test_kill TEST_GEN_PROGS += test_kmem TEST_GEN_PROGS += test_memcontrol TEST_GEN_PROGS += test_pids +TEST_GEN_PROGS += test_psi TEST_GEN_PROGS += test_zswap LOCAL_HDRS += $(selfdir)/clone3/clone3_selftests.h $(selfdir)/pidfd/pidfd.h@@ -32,4 +33,5 @@ $(OUTPUT)/test_kill: $(LIBCGROUP_O) $(OUTPUT)/test_kmem: $(LIBCGROUP_O) $(OUTPUT)/test_memcontrol: $(LIBCGROUP_O) $(OUTPUT)/test_pids: $(LIBCGROUP_O) +$(OUTPUT)/test_psi: $(LIBCGROUP_O) $(OUTPUT)/test_zswap: $(LIBCGROUP_O)tools/testing/selftests/cgroup/config +1
-0@@ -4,3 +4,4 @@ CONFIG_CGROUP_FREEZER=y CONFIG_CGROUP_SCHED=y CONFIG_MEMCG=y CONFIG_PAGE_COUNTER=y +CONFIG_PSI=ytools/testing/selftests/cgroup/test_psi.c new file +73
-0@@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/limits.h> + +#include "kselftest.h" +#include "cgroup_util.h" + +#define PSI_POLL_TIMEOUhas this check T_MS 5000 + +/* PSI triggers are written with a trailing NUL the kernel parser expects. */ +static ssize_t write_trigger (int fd,const char *trigger) +{ +return write (fd, trigger,strlen (trigger) +1 ); +} + +static int pressure_open (const char *resource) +{ +char path[PATH_MAX ]; +int fd; + +snprintf (path,sizeof (path),"/proc/pressure/%s" , resource); + fd =open (path,O_RDWR ); +if (fd <0 ) +ksft_print_msg ("open %s: %s\n" , path,strerror (errno)); +return fd; +} + +/* +* /proc/pressure/<resource> accepts exactly one trigger per file +* descriptor. For io, memory and cpu verify that a "some" trigger arms +* and that a second trigger on the same fd is rejected with EBUSY. For +* irq, which only tracks "full", verify that "some" is rejected and +* "full" arms. irq is optional -- it only exists with IRQ-time +* accounting -- so a missing /proc/pressure/irq is SKIP, not FAIL. +*/ +static int test_proc_trigger (const char *resource,bool full_only) +{ +int fd, ret =KSFT_FAIL ; + + fd =pressure_open (resource); +if (fd <0 ) +return full_only ?KSFT_SKIP :KSFT_FAIL ; + +if (!full_only) { +if (write_trigger (fd,"some 150000 2000000" ) <=0 ) { +ksft_print_msg ("%s: 'some' trigger rejected: %s\n" , + resource,strerror (errno)); + goto out; + } +if (write_trigger (fd,"full 150000 2000000" ) !=-1 || + errno !=EBUSY ) { +ksft_print_msg ("%s: second trigger not EBUSY\n" , + resource); + goto out; + } + }else { +if (write_trigger (fd,"some 150000 2000000" ) !=-1 ) { +ksft_print_msg ("irq: 'some' trigger unexpectedly accepted\n" ); + goto out; + } +if (write_trigger (fd,"full 150000 2000000" ) <=0 ) { +ksft_print_msg ("irq: 'full' trigger rejected: %s\n" , +strerror (errno)); + goto out; + }
Quoted 9 lines
tools/testing/selftests/cgroup/test_psi.c line 79
Quoted 6 lines
tools/testing/selftests/cgroup/test_psi.c new file +6
-0+ } + + ret =KSFT_PASS ; +out: +close (fd); +return ret;You don't really need "ret" variable. Just "return
KSFT_FAIL;" here is enough.
Done. The harness conversion took care of this one: the switch/case reporting is gone and the failure paths are just ASSERTs now.
Quoted 43 lines
tools/testing/selftests/cgroup/test_psi.c line 92
Quoted 13 lines
tools/testing/selftests/cgroup/test_psi.c new file +13
-0+} + +/* +* cgroup.pressure gates visibility of the per-resource *.pressure files +* inside a cgroup: writing 0 hides them, writing 1 shows them again. +* Drive one hide/show cycle and check that memory.pressure appears and +* disappears along with it. +*/ +static int test_cgroup_pressure_toggle (const char *root) +{ +char buf[BUF_SIZE ]; +char *cg =NULL ; +int ret =KSFT_FAIL , created =0 ;Again, you don't need "ret" variable here.
tools/testing/selftests/cgroup/test_psi.c line 101
Quoted 9 lines
tools/testing/selftests/cgroup/test_psi.c new file +9
-0+ + cg =cg_name (root,"psi_toggle_test" ); +if (!cg) + goto cleanup; +if (cg_create (cg)) { +ksft_print_msg ("cg_create: %s\n" ,strerror (errno)); + goto cleanup; + } + created =1 ;Nope. The way we usually do the cleanup is by jumping to the appropriate label instead of adding extra state flags. In this case that would look something like this:
cg = cg_name(root, "psi_toggle_test"); if (!cg) return KSFT_FAIL; if (cg_create(cg)) { ksft_print_msg("cg_create: %s\n", strerror(errno)); goto free_cg; } if (cg_write(cg, "cgroup.pressure", "0")) { ksft_print_msg("write cgroup.pressure=0: %s\n", strerror(errno)); goto cleanup; }
Quoted 9 lines
...
return KSFT_PASS; cleanup: cg_destroy(cg); free_cg: free(cg); return KSFT_FAIL; }
You're right, that reads better. I ended up going one step further and moving the teardown into FIXTURE_TEARDOWN() instead: the cgroup is created inside the test and self->cg is NULL until then, so the teardown is a no-op for the tests that don't create one. That way there are no labels and no state flags left.
Quoted 43 lines
Quoted 30 lines
tools/testing/selftests/cgroup/test_psi.c new file +30
-0+ +if (cg_write (cg,"cgroup.pressure" ,"0" )) { +ksft_print_msg ("write cgroup.pressure=0: %s\n" ,strerror (errno)); + goto cleanup; + } +if (cg_read_strcmp (cg,"cgroup.pressure" ,"0\n" )) { +ksft_print_msg ("cgroup.pressure readback != 0\n" ); + goto cleanup; + } +if (cg_read (cg,"memory.pressure" , buf,sizeof (buf)) >=0 ) { +ksft_print_msg ("memory.pressure readable while hidden\n" ); + goto cleanup; + } + +if (cg_write (cg,"cgroup.pressure" ,"1" )) { +ksft_print_msg ("write cgroup.pressure=1: %s\n" ,strerror (errno)); + goto cleanup; + } +if (cg_read_strcmp (cg,"cgroup.pressure" ,"1\n" )) { +ksft_print_msg ("cgroup.pressure readback != 1\n" ); + goto cleanup; + } +if (cg_read (cg,"memory.pressure" , buf,sizeof (buf)) <0 ) { +ksft_print_msg ("memory.pressure unreadable after enabling\n" ); + goto cleanup; + } + + ret =KSFT_PASS ; +cleanup: +if (created)tools/testing/selftests/cgroup/test_psi.c line 141
Quoted 10 lines
tools/testing/selftests/cgroup/test_psi.c new file +10
-0+cg_destroy (cg); +free (cg); +return ret; +} + +/* A child that burns CPU forever; stopped by cg_killall() in the parent. */ +static int hog_cpu (const char *cgroup,void *arg) +{ +for (;;) + ;This loop looks strange. Maybe:
Quoted 2 lines
for (;;) {}Fixed.
Quoted 6 lines
Quoted 6 lines
tools/testing/selftests/cgroup/test_psi.c new file +6
-0+return 0 ; +} + +/* +* Arm a "some" trigger on a cgroup's cpu.pressure, oversubscribe the +* cgroup with more spinning hogs than there are CPUs, and check that the
Quoted 9 lines
tools/testing/selftests/cgroup/test_psi.c line 153
Quoted 6 lines
+ * trigger fires once the cgroup stalls on CPU.
+ */tools/testing/selftests/cgroup/test_psi.c new file +4
-0+static int test_cgroup_trigger_fire (const char *root) +{ +char *cg =NULL , *cpupress =NULL ; +int fd =-1 , ret =KSFT_FAIL , created =0 , i;Please define each variable on a separate line.
Fixed.
Quoted 30 lines
tools/testing/selftests/cgroup/test_psi.c line 163
Quoted 10 lines
tools/testing/selftests/cgroup/test_psi.c new file +10
-0+long ncpus; + + cg =cg_name (root,"psi_trigger_test" ); +if (!cg) + goto cleanup; +if (cg_create (cg)) { +ksft_print_msg ("cg_create: %s\n" ,strerror (errno)); + goto cleanup; + } + created =1 ;Same as above. Jump to the appropriate label.
Quoted 17 lines
tools/testing/selftests/cgroup/test_psi.c new file +17
-0+ + cpupress =cg_control (cg,"cpu.pressure" ); +if (!cpupress) + goto cleanup; + fd =open (cpupress,O_RDWR ); +if (fd <0 ) { +ksft_print_msg ("open cpu.pressure: %s\n" ,strerror (errno)); + goto cleanup; + } + +/* +* 1usec threshold over a 2s window: any CPU stall fires it. The 2s +* window is the smallest unprivileged users are allowed to arm. +*/ +if (write_trigger (fd,"some 1 2000000" ) <=0 ) { +ksft_print_msg ("arm trigger: %s\n" ,strerror (errno)); + goto cleanup;
Quoted 9 lines
tools/testing/selftests/cgroup/test_psi.c line 184
Quoted 4 lines
tools/testing/selftests/cgroup/test_psi.c new file +4
-0+ } + + ncpus =sysconf (_SC_NPROCESSORS_ONLN); +if (ncpus <=0 ) {I don't think this syscall will ever return 0, so you just need to check for -1. Also reporting -1 in the error log isn't very informative. Instead report strerror(errno).
Agreed, now checks only for -1 and reports strerror(errno) via TH_LOG.
Quoted 14 lines
Quoted 14 lines
tools/testing/selftests/cgroup/test_psi.c new file +14
-0+ksft_print_msg ("sysconf(_SC_NPROCESSORS_ONLN) returned %ld\n" , + ncpus); + goto cleanup; + } + +/* ncpus+1 hogs guarantee CPU contention inside the cgroup. */ +for (i =0 ; i < ncpus +1 ; i++) { +if (cg_run_nowait (cg, hog_cpu,NULL ) <0 ) { +ksft_print_msg ("spawn hog %d: %s\n" , i,strerror (errno)); + goto cleanup; + } + } + +struct pollfd pfd = { .fd = fd, .events =POLLPRI };
Quoted 9 lines
tools/testing/selftests/cgroup/test_psi.c line 203
Quoted 5 lines
tools/testing/selftests/cgroup/test_psi.c new file +5
-0+ +switch (poll (&pfd,1 ,PSI_POLL_TIMEOUT_MS )) { +case -1 : +ksft_print_msg ("poll: %s\n" ,strerror (errno)); + goto cleanup;So, goto cleanup here but break everywhere else? Consistency would be nice, so please use break here as well.
The switch is gone along with the ret bookkeeping, so this is moot
now: poll() collapsed into a single ASSERT_EQ(1, poll(...)).
Quoted 6 lines
tools/testing/selftests/cgroup/test_psi.c line 206
Quoted 3 lines
tools/testing/selftests/cgroup/test_psi.c new file +3
-0+case 0 : +ksft_print_msg ("trigger did not fire (could not induce CPU pressure)\n" ); + ret =KSFT_SKIP ;This means your trigger timed out. Why is this treated as
KSFT_SKIPand not asKSFT_FAIL?
Fair point, I agree. With ncpus+1 hogs and a 1usec threshold over a 2s window the cgroup must stall, so if the trigger doesn't fire something is actually broken. I made it a FAIL.
Quoted 87 lines
Quoted 87 lines
tools/testing/selftests/cgroup/test_psi.c new file +86
-0+break ; +default : +if (pfd.revents &POLLPRI ) + ret =KSFT_PASS ; +else +ksft_print_msg ("poll returned 0x%x\n" , pfd.revents); +break ; + } + +cleanup: +if (fd >=0 ) +close (fd); +if (created) { +cg_killall (cg); +cg_destroy (cg); + } +free (cpupress); +free (cg); +return ret; +} + +struct psi_proc_test { +const char *name; +const char *resource; +bool full_only; +}; +static const struct psi_proc_test proc_tests[] = { + {"proc_trigger_io" ,"io" , false }, + {"proc_trigger_memory" ,"memory" , false }, + {"proc_trigger_cpu" ,"cpu" , false }, + {"proc_trigger_irq" ,"irq" , true }, +}; + +struct psi_cg_test { +const char *name; +int (*fn)(const char *root); +}; +static const struct psi_cg_test cg_tests[] = { + {"cgroup_pressure_toggle" , test_cgroup_pressure_toggle }, + {"cgroup_trigger_fire" , test_cgroup_trigger_fire }, +}; + +int main (int argc,char **argv) +{ +char root[PATH_MAX ]; +int psi_fd, i; + +ksft_print_header (); +ksft_set_plan (ARRAY_SIZE (proc_tests) +ARRAY_SIZE (cg_tests)); + +if (cg_find_unified_root (root,sizeof (root),NULL )) +ksft_exit_skip ("cgroup v2 isn't mounted\n" ); + +/* PSI must be enabled (CONFIG_PSI=y, not disabled on the cmdline). */ + psi_fd =open ("/proc/pressure/memory" ,O_RDONLY ); +if (psi_fd <0 ) +ksft_exit_skip ("PSI unavailable (CONFIG_PSI=n or psi=0)\n" ); +close (psi_fd); + +for (i =0 ; i <ARRAY_SIZE (proc_tests); i++) { +switch (test_proc_trigger (proc_tests[i].resource, + proc_tests[i].full_only)) { +case KSFT_PASS : +ksft_test_result_pass ("%s\n" , proc_tests[i].name); +break ; +case KSFT_SKIP : +ksft_test_result_skip ("%s\n" , proc_tests[i].name); +break ; +default : +ksft_test_result_fail ("%s\n" , proc_tests[i].name); +break ; + } + } + +for (i =0 ; i <ARRAY_SIZE (cg_tests); i++) { +switch (cg_tests[i].fn (root)) { +case KSFT_PASS : +ksft_test_result_pass ("%s\n" , cg_tests[i].name); +break ; +case KSFT_SKIP : +ksft_test_result_skip ("%s\n" , cg_tests[i].name); +break ; +default : +ksft_test_result_fail ("%s\n" , cg_tests[i].name); +break ; + }
Quoted 9 lines
Quoted 1 line
+ }
Please convert this to use
https://docs.kernel.org/dev-tools/kselftest.html#test-harness as I suggested before. This code will look much simpler. Even if some other tests are not using it, there is no reason for new tests not to take advantage of the existing harness. You will find planty of examples which use them.
Converted, thanks for the pointer. Each case is a TEST_F(psi, ...) now; the cgroup v2 and PSI availability checks live in FIXTURE_SETUP() and SKIP there, and the hog killing and cgroup destruction live in FIXTURE_TEARDOWN(). It does make the code simpler, as you predicted. The hogs also set PR_SET_PDEATHSIG on themselves now, so an interrupted run doesn't leave orphans pinning every CPU.
With that the file shrinks from 296 to 206 lines. I tested it on a few setups (PSI on and off, with and without /proc/pressure/irq): 6/6 pass where everything is available, clean skips otherwise.
Thanks a lot for the review and for your patience with all the iterations.
Thanks,
Tao>> +
Quoted 5 lines
Quoted 5 lines
tools/testing/selftests/cgroup/test_psi.c new file +2
-1+ksft_finished (); +} --2.43.0