Skip to content

Commit 5b9ef2b

Browse files
wusamuel6Kernel Patches Daemon
authored andcommitted
selftests/bpf: Add tests for wakeup_sources
Sets up the framework to test wakeup_sources iterators using BPF, and adds a few basic tests. Adds several helper functions that for grabbing and releasing a wakelock, abstracting out key functions to setup a framework for testing wakeup_sources. Additionally, adds 3 tests: 1. check_active_count: Checks that stats related to active_count are properly set after several lock/unlock cycles 2. check_sleep_times: Checks that time accounting related to sleep are properly calculated 3. check_no_infinite_reads: Checks that the iterator traversal returns NULL at the end Signed-off-by: Samuel Wu <[email protected]>
1 parent c94c0f2 commit 5b9ef2b

File tree

3 files changed

+352
-0
lines changed

3 files changed

+352
-0
lines changed

tools/testing/selftests/bpf/config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ CONFIG_IP6_NF_IPTABLES=y
111111
CONFIG_IP6_NF_FILTER=y
112112
CONFIG_NF_NAT=y
113113
CONFIG_PACKET=y
114+
CONFIG_PM_WAKELOCKS=y
114115
CONFIG_RC_CORE=y
115116
CONFIG_SAMPLES=y
116117
CONFIG_SAMPLE_LIVEPATCH=m
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2025 Google LLC */
3+
4+
#include <test_progs.h>
5+
#include <bpf/libbpf.h>
6+
#include "wakeup_source_iter.skel.h"
7+
8+
#include <fcntl.h>
9+
#include <stdbool.h>
10+
#include <stdio.h>
11+
#include <stdlib.h>
12+
#include <string.h>
13+
#include <unistd.h>
14+
15+
16+
/* Sleep for 10ms to ensure active time is > 0 after converting ns to ms*/
17+
#define TEST_SLEEP_US 10000
18+
#define TEST_SLEEP_MS (TEST_SLEEP_US / 1000)
19+
#define WAKEUP_SOURCE_NAME_LEN 32
20+
21+
static const char test_ws_name[] = "bpf_selftest_ws";
22+
static bool test_ws_created;
23+
24+
/*
25+
* Creates a new wakeup source by writing to /sys/power/wake_lock.
26+
* This lock persists until explicitly unlocked.
27+
*/
28+
static int lock_ws(const char *name)
29+
{
30+
int fd;
31+
ssize_t bytes;
32+
33+
fd = open("/sys/power/wake_lock", O_WRONLY);
34+
if (!ASSERT_OK_FD(fd, "open /sys/power/wake_lock"))
35+
return -1;
36+
37+
bytes = write(fd, name, strlen(name));
38+
close(fd);
39+
if (!ASSERT_EQ(bytes, strlen(name), "write to wake_lock"))
40+
return -1;
41+
42+
return 0;
43+
}
44+
45+
/*
46+
* Destroys the ws by writing the same name to /sys/power/wake_unlock.
47+
*/
48+
static void unlock_ws(const char *name)
49+
{
50+
int fd;
51+
52+
fd = open("/sys/power/wake_unlock", O_WRONLY);
53+
if (!ASSERT_OK_FD(fd, "open /sys/power/wake_unlock"))
54+
goto cleanup;
55+
56+
write(fd, name, strlen(name));
57+
58+
cleanup:
59+
if (fd)
60+
close(fd);
61+
}
62+
63+
/*
64+
* Setups for testing ws iterators. Will run once prior to suite of tests.
65+
*/
66+
static int setup_test_ws(void)
67+
{
68+
if (lock_ws(test_ws_name))
69+
return -1;
70+
test_ws_created = true;
71+
72+
return 0;
73+
}
74+
75+
/*
76+
* Tears down and cleanups testing ws iterators. WIll run once after the suite
77+
* of tests.
78+
*/
79+
static void teardown_test_ws(void)
80+
{
81+
if (!test_ws_created)
82+
return;
83+
unlock_ws(test_ws_name);
84+
test_ws_created = false;
85+
}
86+
87+
struct WakeupSourceInfo {
88+
char name[WAKEUP_SOURCE_NAME_LEN];
89+
unsigned long active_count;
90+
long active_time_ms;
91+
unsigned long event_count;
92+
unsigned long expire_count;
93+
long last_change_ms;
94+
long max_time_ms;
95+
long prevent_sleep_time_ms;
96+
long total_time_ms;
97+
unsigned long wakeup_count;
98+
};
99+
100+
/*
101+
* Reads and parses one wakeup_source record from the iterator file.
102+
* A record is a single space-delimited line.
103+
* Returns true on success, false on EOF. Asserts internally on errors.
104+
*/
105+
static bool read_ws_info(FILE *iter_file, struct WakeupSourceInfo *ws_info,
106+
char **line)
107+
{
108+
size_t linesize;
109+
int items;
110+
111+
if (getline(line, &linesize, iter_file) == -1)
112+
return false;
113+
114+
(*line)[strcspn(*line, "\n")] = 0;
115+
116+
items = sscanf(*line, "%s %lu %ld %lu %lu %ld %ld %ld %ld %lu",
117+
ws_info->name, &ws_info->active_count,
118+
&ws_info->active_time_ms, &ws_info->event_count,
119+
&ws_info->expire_count, &ws_info->last_change_ms,
120+
&ws_info->max_time_ms, &ws_info->prevent_sleep_time_ms,
121+
&ws_info->total_time_ms, &ws_info->wakeup_count);
122+
123+
if (!ASSERT_EQ(items, 10, "read wakeup source info"))
124+
return false;
125+
126+
if (!ASSERT_LT(strlen(ws_info->name), WAKEUP_SOURCE_NAME_LEN,
127+
"name length"))
128+
return false;
129+
130+
return true;
131+
}
132+
133+
static int get_ws_iter_stream(struct wakeup_source_iter *skel, int *iter_fd,
134+
FILE **iter_file)
135+
{
136+
*iter_fd = bpf_iter_create(
137+
bpf_link__fd(skel->links.wakeup_source_collector));
138+
if (!ASSERT_OK_FD(*iter_fd, "iter_create"))
139+
return -1;
140+
141+
*iter_file = fdopen(*iter_fd, "r");
142+
if (!ASSERT_OK_PTR(*iter_file, "fdopen"))
143+
return -1;
144+
145+
return 0;
146+
}
147+
148+
static void subtest_ws_iter_check_active_count(struct wakeup_source_iter *skel)
149+
{
150+
static const char subtest_ws_name[] = "bpf_selftest_ws_active_count";
151+
const int lock_unlock_cycles = 5;
152+
struct WakeupSourceInfo ws_info;
153+
char *line = NULL;
154+
bool found_ws = false;
155+
FILE *iter_file = NULL;
156+
int iter_fd = -1;
157+
int i;
158+
159+
for (i = 0; i < lock_unlock_cycles; i++) {
160+
if (!ASSERT_OK(lock_ws(subtest_ws_name), "lock_ws"))
161+
goto cleanup;
162+
unlock_ws(subtest_ws_name);
163+
}
164+
165+
if (!get_ws_iter_stream(skel, &iter_fd, &iter_file))
166+
goto cleanup;
167+
168+
while (read_ws_info(iter_file, &ws_info, &line)) {
169+
if (strcmp(ws_info.name, subtest_ws_name) == 0) {
170+
found_ws = true;
171+
ASSERT_EQ(ws_info.active_count, lock_unlock_cycles,
172+
"active_count check");
173+
ASSERT_EQ(ws_info.wakeup_count, lock_unlock_cycles,
174+
"wakeup_count check");
175+
break;
176+
}
177+
}
178+
179+
ASSERT_TRUE(found_ws, "found active_count test ws");
180+
181+
free(line);
182+
cleanup:
183+
if (iter_file)
184+
fclose(iter_file);
185+
else if (iter_fd >= 0)
186+
close(iter_fd);
187+
}
188+
189+
static void subtest_ws_iter_check_sleep_times(struct wakeup_source_iter *skel)
190+
{
191+
bool found_test_ws = false;
192+
struct WakeupSourceInfo ws_info;
193+
char *line = NULL;
194+
FILE *iter_file;
195+
int iter_fd;
196+
197+
if (!get_ws_iter_stream(skel, &iter_fd, &iter_file))
198+
goto cleanup;
199+
200+
while (read_ws_info(iter_file, &ws_info, &line)) {
201+
if (strcmp(ws_info.name, test_ws_name) == 0) {
202+
found_test_ws = true;
203+
ASSERT_GT(ws_info.last_change_ms, 0,
204+
"Expected non-zero last change");
205+
ASSERT_GE(ws_info.active_time_ms, TEST_SLEEP_MS,
206+
"Expected active time >= TEST_SLEEP_MS");
207+
ASSERT_GE(ws_info.max_time_ms, TEST_SLEEP_MS,
208+
"Expected max time >= TEST_SLEEP_MS");
209+
ASSERT_GE(ws_info.total_time_ms, TEST_SLEEP_MS,
210+
"Expected total time >= TEST_SLEEP_MS");
211+
break;
212+
}
213+
}
214+
215+
ASSERT_TRUE(found_test_ws, "found_test_ws");
216+
217+
free(line);
218+
cleanup:
219+
if (iter_file)
220+
fclose(iter_file);
221+
else if (iter_fd >= 0)
222+
close(iter_fd);
223+
}
224+
225+
static void subtest_ws_iter_check_no_infinite_reads(
226+
struct wakeup_source_iter *skel)
227+
{
228+
int iter_fd;
229+
char buf[256];
230+
231+
iter_fd = bpf_iter_create(bpf_link__fd(skel->links.wakeup_source_collector));
232+
if (!ASSERT_OK_FD(iter_fd, "iter_create"))
233+
return;
234+
235+
while (read(iter_fd, buf, sizeof(buf)) > 0)
236+
;
237+
238+
/* Final read should return 0 */
239+
ASSERT_EQ(read(iter_fd, buf, sizeof(buf)), 0, "read");
240+
241+
close(iter_fd);
242+
}
243+
244+
void test_wakeup_source_iter(void)
245+
{
246+
struct wakeup_source_iter *skel = NULL;
247+
248+
if (geteuid() != 0) {
249+
fprintf(stderr,
250+
"Skipping wakeup_source_iter test, requires root\n");
251+
test__skip();
252+
return;
253+
}
254+
255+
skel = wakeup_source_iter__open_and_load();
256+
if (!ASSERT_OK_PTR(skel, "wakeup_source_iter__open_and_load"))
257+
return;
258+
259+
if (!ASSERT_OK(setup_test_ws(), "setup_test_ws"))
260+
goto destroy;
261+
262+
if (!ASSERT_OK(wakeup_source_iter__attach(skel), "skel_attach"))
263+
goto destroy;
264+
265+
/*
266+
* Sleep on O(ms) to ensure that time stats' resolution isn't lost when
267+
* converting from ns to ms
268+
*/
269+
usleep(TEST_SLEEP_US);
270+
271+
if (test__start_subtest("active_count"))
272+
subtest_ws_iter_check_active_count(skel);
273+
if (test__start_subtest("sleep_times"))
274+
subtest_ws_iter_check_sleep_times(skel);
275+
if (test__start_subtest("no_infinite_reads"))
276+
subtest_ws_iter_check_no_infinite_reads(skel);
277+
278+
destroy:
279+
teardown_test_ws();
280+
wakeup_source_iter__destroy(skel);
281+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2025 Google LLC */
3+
#include <vmlinux.h>
4+
#include <bpf/bpf_core_read.h>
5+
#include <bpf/bpf_helpers.h>
6+
7+
#define NSEC_PER_MS 1000000UL
8+
#define WAKEUP_SOURCE_NAME_LEN 32
9+
10+
char _license[] SEC("license") = "GPL";
11+
12+
SEC("iter/wakeup_source")
13+
int wakeup_source_collector(struct bpf_iter__wakeup_source *ctx)
14+
{
15+
const struct wakeup_source *ws = ctx->wakeup_source;
16+
struct seq_file *seq = ctx->meta->seq;
17+
char name[WAKEUP_SOURCE_NAME_LEN] = {'\0'};
18+
const char *pname;
19+
bool active, autosleep_enable;
20+
u64 active_count, event_count, expire_count, wakeup_count;
21+
s64 active_time, curr_time, last_change_time, max_time,
22+
prevent_sleep_time, start_prevent_time, total_time;
23+
24+
if (!ws)
25+
return 0;
26+
27+
active = BPF_CORE_READ_BITFIELD_PROBED(ws, active);
28+
autosleep_enable = BPF_CORE_READ_BITFIELD_PROBED(ws, autosleep_enabled);
29+
if (bpf_core_read(&pname, sizeof(pname), &ws->name) ||
30+
bpf_probe_read_kernel_str(name, sizeof(name), pname) < 0 ||
31+
bpf_core_read(&active_count, sizeof(active_count), &ws->active_count) ||
32+
bpf_core_read(&event_count, sizeof(event_count), &ws->event_count) ||
33+
bpf_core_read(&expire_count, sizeof(expire_count), &ws->expire_count) ||
34+
bpf_core_read(&last_change_time, sizeof(last_change_time), &ws->last_time) ||
35+
bpf_core_read(&max_time, sizeof(max_time), &ws->max_time) ||
36+
bpf_core_read(
37+
&prevent_sleep_time, sizeof(prevent_sleep_time), &ws->prevent_sleep_time) ||
38+
bpf_core_read(
39+
&start_prevent_time, sizeof(start_prevent_time), &ws->start_prevent_time) ||
40+
bpf_core_read(&total_time, sizeof(total_time), &ws->total_time) ||
41+
bpf_core_read(&wakeup_count, sizeof(wakeup_count), &ws->wakeup_count))
42+
return 0;
43+
44+
45+
curr_time = bpf_ktime_get_ns();
46+
active_time = 0;
47+
if (active) {
48+
active_time = curr_time - last_change_time;
49+
total_time += active_time;
50+
if (active_time > max_time)
51+
max_time = active_time;
52+
if (autosleep_enable)
53+
prevent_sleep_time += curr_time - start_prevent_time;
54+
55+
}
56+
57+
BPF_SEQ_PRINTF(seq,
58+
"%s %lu %ld %lu %lu %ld %ld %ld %ld %lu\n",
59+
name,
60+
active_count,
61+
active_time / NSEC_PER_MS,
62+
event_count,
63+
expire_count,
64+
last_change_time / NSEC_PER_MS,
65+
max_time / NSEC_PER_MS,
66+
prevent_sleep_time / NSEC_PER_MS,
67+
total_time / NSEC_PER_MS,
68+
wakeup_count);
69+
return 0;
70+
}

0 commit comments

Comments
 (0)