 
            On 10/29/25 19:08, Andrea Righi wrote:
Add a selftest to validate the correct behavior of the deadline server for the ext_sched_class.
v3: - add a comment to explain the 4% threshold (Emil Tsalapatis) v2: - replaced occurences of CFS in the test with EXT (Joel Fernandes)
Reviewed-by: Emil Tsalapatis emil@etsalapatis.com Co-developed-by: Joel Fernandes joelagnelf@nvidia.com Signed-off-by: Joel Fernandes joelagnelf@nvidia.com Signed-off-by: Andrea Righi arighi@nvidia.com
tools/testing/selftests/sched_ext/Makefile | 1 + .../selftests/sched_ext/rt_stall.bpf.c | 23 ++ tools/testing/selftests/sched_ext/rt_stall.c | 222 ++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 tools/testing/selftests/sched_ext/rt_stall.bpf.c create mode 100644 tools/testing/selftests/sched_ext/rt_stall.c
diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 5fe45f9c5f8fd..c9255d1499b6e 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -183,6 +183,7 @@ auto-test-targets := \ select_cpu_dispatch_bad_dsq \ select_cpu_dispatch_dbl_dsp \ select_cpu_vtime \
- rt_stall \ test_example \
testcase-targets := $(addsuffix .o,$(addprefix $(SCXOBJ_DIR)/,$(auto-test-targets))) diff --git a/tools/testing/selftests/sched_ext/rt_stall.bpf.c b/tools/testing/selftests/sched_ext/rt_stall.bpf.c new file mode 100644 index 0000000000000..80086779dd1eb --- /dev/null +++ b/tools/testing/selftests/sched_ext/rt_stall.bpf.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- A scheduler that verified if RT tasks can stall SCHED_EXT tasks.
- Copyright (c) 2025 NVIDIA Corporation.
- */
+#include <scx/common.bpf.h>
+char _license[] SEC("license") = "GPL";
+UEI_DEFINE(uei);
+void BPF_STRUCT_OPS(rt_stall_exit, struct scx_exit_info *ei) +{
- UEI_RECORD(uei, ei);
+}
+SEC(".struct_ops.link") +struct sched_ext_ops rt_stall_ops = {
- .exit = (void *)rt_stall_exit,
- .name = "rt_stall",
+}; diff --git a/tools/testing/selftests/sched_ext/rt_stall.c b/tools/testing/selftests/sched_ext/rt_stall.c new file mode 100644 index 0000000000000..d0ffa0e72b37b --- /dev/null +++ b/tools/testing/selftests/sched_ext/rt_stall.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright (c) 2025 NVIDIA Corporation.
- */
+#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sched.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <linux/sched.h> +#include <signal.h> +#include <bpf/bpf.h> +#include <scx/common.h> +#include <sys/wait.h> +#include <unistd.h> +#include "rt_stall.bpf.skel.h" +#include "scx_test.h" +#include "../kselftest.h"
+#define CORE_ID 0 /* CPU to pin tasks to */ +#define RUN_TIME 5 /* How long to run the test in seconds */
+/* Simple busy-wait function for test tasks */ +static void process_func(void) +{
- while (1) {
/* Busy wait */
for (volatile unsigned long i = 0; i < 10000000UL; i++)
;- }
+}
+/* Set CPU affinity to a specific core */ +static void set_affinity(int cpu) +{
- cpu_set_t mask;
- CPU_ZERO(&mask);
- CPU_SET(cpu, &mask);
- if (sched_setaffinity(0, sizeof(mask), &mask) != 0) {
perror("sched_setaffinity");
exit(EXIT_FAILURE);- }
+}
+/* Set task scheduling policy and priority */ +static void set_sched(int policy, int priority) +{
- struct sched_param param;
- param.sched_priority = priority;
- if (sched_setscheduler(0, policy, ¶m) != 0) {
perror("sched_setscheduler");
exit(EXIT_FAILURE);- }
+}
+/* Get process runtime from /proc/<pid>/stat */ +static float get_process_runtime(int pid) +{
- char path[256];
- FILE *file;
- long utime, stime;
- int fields;
- snprintf(path, sizeof(path), "/proc/%d/stat", pid);
- file = fopen(path, "r");
- if (file == NULL) {
perror("Failed to open stat file");
return -1;- }
- /* Skip the first 13 fields and read the 14th and 15th */
- fields = fscanf(file,
"%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu",
&utime, &stime);- fclose(file);
- if (fields != 2) {
fprintf(stderr, "Failed to read stat file\n");
return -1;- }
- /* Calculate the total time spent in the process */
- long total_time = utime + stime;
- long ticks_per_second = sysconf(_SC_CLK_TCK);
- float runtime_seconds = total_time * 1.0 / ticks_per_second;
- return runtime_seconds;
+}
+static enum scx_test_status setup(void **ctx) +{
- struct rt_stall *skel;
- skel = rt_stall__open();
- SCX_FAIL_IF(!skel, "Failed to open");
- SCX_ENUM_INIT(skel);
- SCX_FAIL_IF(rt_stall__load(skel), "Failed to load skel");
- *ctx = skel;
- return SCX_TEST_PASS;
+}
+static bool sched_stress_test(void) +{
- /*
* We're expecting the EXT task to get around 5% of CPU time when
* competing with the RT task (small 1% fluctuations are expected).
*
* However, the EXT task should get at least 4% of the CPU to prove
* that the EXT deadline server is working correctly. A percentage
* less than 4% indicates a bug where RT tasks can potentially
* stall SCHED_EXT tasks, causing the test to fail.
*/- const float expected_min_ratio = 0.04; /* 4% */
- float ext_runtime, rt_runtime, actual_ratio;
- int ext_pid, rt_pid;
- ksft_print_header();
- ksft_set_plan(1);
- /* Create and set up a EXT task */
- ext_pid = fork();
- if (ext_pid == 0) {
set_affinity(CORE_ID);
process_func();
exit(0);- } else if (ext_pid < 0) {
perror("fork for EXT task");
ksft_exit_fail();- }
- /* Create an RT task */
- rt_pid = fork();
- if (rt_pid == 0) {
set_affinity(CORE_ID);
set_sched(SCHED_FIFO, 50);
process_func();
exit(0);- } else if (rt_pid < 0) {
perror("fork for RT task");
ksft_exit_fail();- }
- /* Let the processes run for the specified time */
- sleep(RUN_TIME);
- /* Get runtime for the EXT task */
- ext_runtime = get_process_runtime(ext_pid);
- if (ext_runtime == -1)
ksft_exit_fail_msg("Error getting runtime for EXT task (PID %d)\n", ext_pid);- ksft_print_msg("Runtime of EXT task (PID %d) is %f seconds\n",
ext_pid, ext_runtime);- /* Get runtime for the RT task */
- rt_runtime = get_process_runtime(rt_pid);
- if (rt_runtime == -1)
ksft_exit_fail_msg("Error getting runtime for RT task (PID %d)\n", rt_pid);- ksft_print_msg("Runtime of RT task (PID %d) is %f seconds\n", rt_pid, rt_runtime);
- /* Kill the processes */
- kill(ext_pid, SIGKILL);
- kill(rt_pid, SIGKILL);
- waitpid(ext_pid, NULL, 0);
- waitpid(rt_pid, NULL, 0);
- /* Verify that the scx task got enough runtime */
- actual_ratio = ext_runtime / (ext_runtime + rt_runtime);
- ksft_print_msg("EXT task got %.2f%% of total runtime\n", actual_ratio * 100);
- if (actual_ratio >= expected_min_ratio) {
ksft_test_result_pass("PASS: EXT task got more than %.2f%% of runtime\n",
expected_min_ratio * 100);
return true;- }
- ksft_test_result_fail("FAIL: EXT task got less than %.2f%% of runtime\n",
expected_min_ratio * 100);- return false;
+}
+static enum scx_test_status run(void *ctx) +{
- struct rt_stall *skel = ctx;
- struct bpf_link *link;
- bool res;
- link = bpf_map__attach_struct_ops(skel->maps.rt_stall_ops);
- SCX_FAIL_IF(!link, "Failed to attach scheduler");
- res = sched_stress_test();
- SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_NONE));
- bpf_link__destroy(link);
- if (!res)
ksft_exit_fail();- return SCX_TEST_PASS;
+}
+static void cleanup(void *ctx) +{
- struct rt_stall *skel = ctx;
- rt_stall__destroy(skel);
+}
+struct scx_test rt_stall = {
- .name = "rt_stall",
- .description = "Verify that RT tasks cannot stall SCHED_EXT tasks",
- .setup = setup,
- .run = run,
- .cleanup = cleanup,
+}; +REGISTER_SCX_TEST(&rt_stall)
I'd still prefer something like the below to also test if the fair_server stop -> ext_server start -> fair_server start -> ext_server stop flow works correctly, but FWIW Tested-by: Christian Loehle christian.loehle@arm.com
------8<------ @@ -188,19 +188,24 @@ static bool sched_stress_test(void) static enum scx_test_status run(void *ctx) { struct rt_stall *skel = ctx; - struct bpf_link *link; + struct bpf_link *link = NULL; bool res;
- link = bpf_map__attach_struct_ops(skel->maps.rt_stall_ops); - SCX_FAIL_IF(!link, "Failed to attach scheduler"); - - res = sched_stress_test(); - - SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_NONE)); - bpf_link__destroy(link); - - if (!res) - ksft_exit_fail(); + for (int i = 0; i < 4; i++) { + if (i % 2) { + memset(&skel->data->uei, 0, sizeof(skel->data->uei)); + link = bpf_map__attach_struct_ops(skel->maps.rt_stall_ops); + SCX_FAIL_IF(!link, "Failed to attach scheduler"); + } + res = sched_stress_test(); + if (i % 2) { + SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_NONE)); + bpf_link__destroy(link); + } + + if (!res) + ksft_exit_fail(); + }
return SCX_TEST_PASS; }