Skip to content

Commit 80213f3

Browse files
rgushchinKernel Patches Daemon
authored andcommitted
bpf: selftests: PSI struct ops test
Add a PSI struct ops test. The test creates a cgroup with two child sub-cgroups, sets up memory.high for one of those and puts there a memory hungry process (initially frozen). Then it creates 2 PSI triggers from within a init() BPF callback and attaches them to these cgroups. Then it deletes the first cgroup, creates another one and runs the memory hungry task. From the cgroup creation callback the test is creating another trigger. The memory hungry task is creating a high memory pressure in one memory cgroup, which triggers a PSI event. The PSI BPF handler declares a memcg oom in the corresponding cgroup. Finally the checks that both handle_cgroup_free() and handle_psi_event() handlers were executed, the correct process was killed and oom counters were updated. Signed-off-by: Roman Gushchin <[email protected]>
1 parent 05f4901 commit 80213f3

File tree

2 files changed

+320
-0
lines changed

2 files changed

+320
-0
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
#include <test_progs.h>
3+
#include <bpf/btf.h>
4+
#include <bpf/bpf.h>
5+
6+
#include "cgroup_helpers.h"
7+
#include "test_psi.skel.h"
8+
9+
enum psi_res {
10+
PSI_IO,
11+
PSI_MEM,
12+
PSI_CPU,
13+
PSI_IRQ,
14+
NR_PSI_RESOURCES,
15+
};
16+
17+
struct cgroup_desc {
18+
const char *path;
19+
unsigned long long id;
20+
int pid;
21+
int fd;
22+
size_t target;
23+
size_t high;
24+
bool victim;
25+
};
26+
27+
#define MB (1024 * 1024)
28+
29+
static struct cgroup_desc cgroups[] = {
30+
{ .path = "/psi_test" },
31+
{ .path = "/psi_test/cg1" },
32+
{ .path = "/psi_test/cg2", .target = 500 * MB,
33+
.high = 40 * MB, .victim = true },
34+
};
35+
36+
static int spawn_task(struct cgroup_desc *desc)
37+
{
38+
char *ptr;
39+
int pid;
40+
41+
pid = fork();
42+
if (pid < 0)
43+
return pid;
44+
45+
if (pid > 0) {
46+
/* parent */
47+
desc->pid = pid;
48+
return 0;
49+
}
50+
51+
/* child */
52+
ptr = (char *)malloc(desc->target);
53+
if (!ptr)
54+
return -ENOMEM;
55+
56+
memset(ptr, 'a', desc->target);
57+
58+
while (1)
59+
sleep(1000);
60+
61+
return 0;
62+
}
63+
64+
static void setup_environment(void)
65+
{
66+
int i, err;
67+
68+
err = setup_cgroup_environment();
69+
if (!ASSERT_OK(err, "setup_cgroup_environment"))
70+
goto cleanup;
71+
72+
for (i = 0; i < ARRAY_SIZE(cgroups); i++) {
73+
cgroups[i].fd = create_and_get_cgroup(cgroups[i].path);
74+
if (!ASSERT_GE(cgroups[i].fd, 0, "create_and_get_cgroup"))
75+
goto cleanup;
76+
77+
cgroups[i].id = get_cgroup_id(cgroups[i].path);
78+
if (!ASSERT_GT(cgroups[i].id, 0, "get_cgroup_id"))
79+
goto cleanup;
80+
81+
/* Freeze the top-level cgroup and enable the memory controller */
82+
if (i == 0) {
83+
err = write_cgroup_file(cgroups[i].path, "cgroup.freeze", "1");
84+
if (!ASSERT_OK(err, "freeze cgroup"))
85+
goto cleanup;
86+
87+
err = write_cgroup_file(cgroups[i].path, "cgroup.subtree_control",
88+
"+memory");
89+
if (!ASSERT_OK(err, "enable memory controller"))
90+
goto cleanup;
91+
}
92+
93+
/* Set memory.high */
94+
if (cgroups[i].high) {
95+
char buf[256];
96+
97+
snprintf(buf, sizeof(buf), "%lu", cgroups[i].high);
98+
err = write_cgroup_file(cgroups[i].path, "memory.high", buf);
99+
if (!ASSERT_OK(err, "set memory.high"))
100+
goto cleanup;
101+
102+
snprintf(buf, sizeof(buf), "0");
103+
write_cgroup_file(cgroups[i].path, "memory.swap.max", buf);
104+
}
105+
106+
/* Spawn tasks creating memory pressure */
107+
if (cgroups[i].target) {
108+
char buf[256];
109+
110+
err = spawn_task(&cgroups[i]);
111+
if (!ASSERT_OK(err, "spawn task"))
112+
goto cleanup;
113+
114+
snprintf(buf, sizeof(buf), "%d", cgroups[i].pid);
115+
err = write_cgroup_file(cgroups[i].path, "cgroup.procs", buf);
116+
if (!ASSERT_OK(err, "put child into a cgroup"))
117+
goto cleanup;
118+
}
119+
}
120+
121+
return;
122+
123+
cleanup:
124+
cleanup_cgroup_environment();
125+
}
126+
127+
static int run_and_wait_for_oom(void)
128+
{
129+
int ret = -1;
130+
bool first = true;
131+
char buf[4096] = {};
132+
size_t size;
133+
134+
/* Unfreeze the top-level cgroup */
135+
ret = write_cgroup_file(cgroups[0].path, "cgroup.freeze", "0");
136+
if (!ASSERT_OK(ret, "unfreeze cgroup"))
137+
return -1;
138+
139+
for (;;) {
140+
int i, status;
141+
pid_t pid = wait(&status);
142+
143+
if (pid == -1) {
144+
if (errno == EINTR)
145+
continue;
146+
/* ECHILD */
147+
break;
148+
}
149+
150+
if (!first)
151+
continue;
152+
first = false;
153+
154+
/* Check which process was terminated first */
155+
for (i = 0; i < ARRAY_SIZE(cgroups); i++) {
156+
if (!ASSERT_OK(cgroups[i].victim !=
157+
(pid == cgroups[i].pid),
158+
"correct process was killed")) {
159+
ret = -1;
160+
break;
161+
}
162+
163+
if (!cgroups[i].victim)
164+
continue;
165+
166+
/* Check the memcg oom counter */
167+
size = read_cgroup_file(cgroups[i].path, "memory.events",
168+
buf, sizeof(buf));
169+
if (!ASSERT_OK(size <= 0, "read memory.events")) {
170+
ret = -1;
171+
break;
172+
}
173+
174+
if (!ASSERT_OK(strstr(buf, "oom_kill 1") == NULL,
175+
"oom_kill count check")) {
176+
ret = -1;
177+
break;
178+
}
179+
}
180+
181+
/* Kill all remaining tasks */
182+
for (i = 0; i < ARRAY_SIZE(cgroups); i++)
183+
if (cgroups[i].pid && cgroups[i].pid != pid)
184+
kill(cgroups[i].pid, SIGKILL);
185+
}
186+
187+
return ret;
188+
}
189+
190+
void test_psi(void)
191+
{
192+
struct test_psi *skel;
193+
u64 deleted_cgroup_id;
194+
int new_cgroup_fd;
195+
u64 new_cgroup_id;
196+
int err;
197+
198+
setup_environment();
199+
200+
skel = test_psi__open_and_load();
201+
err = libbpf_get_error(skel);
202+
if (CHECK_FAIL(err))
203+
goto cleanup;
204+
205+
skel->bss->deleted_cgroup_id = cgroups[1].id;
206+
skel->bss->high_pressure_cgroup_id = cgroups[2].id;
207+
208+
err = test_psi__attach(skel);
209+
if (CHECK_FAIL(err))
210+
goto cleanup;
211+
212+
/* Delete the first cgroup, it should trigger handle_cgroup_offline() */
213+
remove_cgroup(cgroups[1].path);
214+
215+
new_cgroup_fd = create_and_get_cgroup("/psi_test_new");
216+
if (!ASSERT_GE(new_cgroup_fd, 0, "create_and_get_cgroup"))
217+
goto cleanup;
218+
219+
new_cgroup_id = get_cgroup_id("/psi_test_new");
220+
if (!ASSERT_GT(new_cgroup_id, 0, "get_cgroup_id"))
221+
goto cleanup;
222+
223+
/* Unfreeze all child tasks and create the memory pressure */
224+
err = run_and_wait_for_oom();
225+
CHECK_FAIL(err);
226+
227+
/* Check the result of the handle_cgroup_offline() handler */
228+
deleted_cgroup_id = skel->bss->deleted_cgroup_id;
229+
ASSERT_EQ(deleted_cgroup_id, cgroups[1].id, "deleted cgroup id");
230+
231+
/* Check the result of the handle_cgroup_online() handler */
232+
ASSERT_EQ(skel->bss->new_cgroup_id, new_cgroup_id,
233+
"new cgroup id");
234+
235+
cleanup:
236+
cleanup_cgroup_environment();
237+
test_psi__destroy(skel);
238+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
#include "vmlinux.h"
3+
#include <bpf/bpf_helpers.h>
4+
#include <bpf/bpf_tracing.h>
5+
6+
char _license[] SEC("license") = "GPL";
7+
8+
#define PSI_FULL 0x80000000
9+
10+
/* cgroup which will experience the high memory pressure */
11+
u64 high_pressure_cgroup_id;
12+
13+
/* cgroup which will be deleted */
14+
u64 deleted_cgroup_id;
15+
16+
/* cgroup which will be created */
17+
u64 new_cgroup_id;
18+
19+
/* cgroup which was deleted */
20+
u64 deleted_cgroup_id;
21+
22+
char constraint_name[] = "CONSTRAINT_BPF_PSI_MEM";
23+
24+
SEC("struct_ops.s/init")
25+
int BPF_PROG(psi_init, struct bpf_psi *bpf_psi)
26+
{
27+
int ret;
28+
29+
ret = bpf_psi_create_trigger(bpf_psi, high_pressure_cgroup_id,
30+
PSI_MEM | PSI_FULL, 100000, 1000000);
31+
if (ret)
32+
return ret;
33+
34+
return bpf_psi_create_trigger(bpf_psi, deleted_cgroup_id,
35+
PSI_IO, 100000, 1000000);
36+
}
37+
38+
SEC("struct_ops.s/handle_psi_event")
39+
void BPF_PROG(handle_psi_event, struct bpf_psi *bpf_psi, struct psi_trigger *t)
40+
{
41+
u64 cgroup_id = t->cgroup_id;
42+
struct mem_cgroup *memcg;
43+
struct cgroup *cgroup;
44+
45+
cgroup = bpf_cgroup_from_id(cgroup_id);
46+
if (!cgroup)
47+
return;
48+
49+
memcg = bpf_get_mem_cgroup(&cgroup->self);
50+
if (!memcg) {
51+
bpf_cgroup_release(cgroup);
52+
return;
53+
}
54+
55+
bpf_out_of_memory(memcg, 0, BPF_OOM_FLAGS_WAIT_ON_OOM_LOCK,
56+
constraint_name);
57+
58+
bpf_put_mem_cgroup(memcg);
59+
bpf_cgroup_release(cgroup);
60+
}
61+
62+
SEC("struct_ops.s/handle_cgroup_online")
63+
void BPF_PROG(handle_cgroup_online, struct bpf_psi *bpf_psi, u64 cgroup_id)
64+
{
65+
new_cgroup_id = cgroup_id;
66+
67+
bpf_psi_create_trigger(bpf_psi, cgroup_id, PSI_IO, 100000, 1000000);
68+
}
69+
70+
SEC("struct_ops.s/handle_cgroup_offline")
71+
void BPF_PROG(handle_cgroup_offline, struct bpf_psi *bpf_psi, u64 cgroup_id)
72+
{
73+
deleted_cgroup_id = cgroup_id;
74+
}
75+
76+
SEC(".struct_ops.link")
77+
struct bpf_psi_ops test_bpf_psi = {
78+
.init = (void *)psi_init,
79+
.handle_psi_event = (void *)handle_psi_event,
80+
.handle_cgroup_online = (void *)handle_cgroup_online,
81+
.handle_cgroup_offline = (void *)handle_cgroup_offline,
82+
};

0 commit comments

Comments
 (0)