From: wen.yang@linux.dev
To: Gabriele Monaco <gmonaco@redhat.com>,
Steven Rostedt <rostedt@goodmis.org>
Cc: linux-trace-kernel@vger.kernel.org, linux-kernel@vger.kernel.org,
Wen Yang <wen.yang@linux.dev>
Subject: [RFC PATCH v2 09/10] rv/tlob: add KUnit tests for the tlob monitor
Date: Tue, 12 May 2026 02:24:55 +0800 [thread overview]
Message-ID: <a12d14297b33b9b8d425bc1b813a8aecbd54bcc6.1778522945.git.wen.yang@linux.dev> (raw)
In-Reply-To: <cover.1778522945.git.wen.yang@linux.dev>
From: Wen Yang <wen.yang@linux.dev>
Add five KUnit test suites gated behind CONFIG_TLOB_KUNIT_TEST
(depends on RV_MON_TLOB && KUNIT; default KUNIT_ALL_TESTS) with a
.kunitconfig fragment for the kunit.py runner.
tlob_task_api tests the start/stop API, error returns (-EEXIST,
-ESRCH, -EOVERFLOW, -ENOSPC, -ERANGE).
tlob_sched_integration covers context-switch accounting and monitoring
a kthread. tlob_parse_uprobe exercises the uprobe line parser.
tlob_trace_output checks sched_switch and error_env_tlob field layout.
tlob_violation_react verifies error_env_tlob fires once on budget
expiry and zero times when the budget is not exceeded.
Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
kernel/trace/rv/monitors/tlob/.kunitconfig | 5 +
kernel/trace/rv/monitors/tlob/tlob.c | 26 +
kernel/trace/rv/monitors/tlob/tlob_kunit.c | 881 +++++++++++++++++++++
3 files changed, 912 insertions(+)
create mode 100644 kernel/trace/rv/monitors/tlob/.kunitconfig
create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.c
diff --git a/kernel/trace/rv/monitors/tlob/.kunitconfig b/kernel/trace/rv/monitors/tlob/.kunitconfig
new file mode 100644
index 000000000000..977c58601ab7
--- /dev/null
+++ b/kernel/trace/rv/monitors/tlob/.kunitconfig
@@ -0,0 +1,5 @@
+CONFIG_FTRACE=y
+CONFIG_KUNIT=y
+CONFIG_RV=y
+CONFIG_RV_MON_TLOB=y
+CONFIG_TLOB_KUNIT_TEST=y
diff --git a/kernel/trace/rv/monitors/tlob/tlob.c b/kernel/trace/rv/monitors/tlob/tlob.c
index 475e972ae9aa..90e7035a0b55 100644
--- a/kernel/trace/rv/monitors/tlob/tlob.c
+++ b/kernel/trace/rv/monitors/tlob/tlob.c
@@ -1024,6 +1024,7 @@ EXPORT_SYMBOL_IF_KUNIT(tlob_num_monitored_read);
/* Tracepoint probes for KUnit; rv_trace.h is only included here. */
static struct tlob_captured_event tlob_kunit_last_event;
static struct tlob_captured_error_env tlob_kunit_last_error_env;
+static struct tlob_captured_detail tlob_kunit_last_detail;
static atomic_t tlob_kunit_event_cnt = ATOMIC_INIT(0);
static atomic_t tlob_kunit_error_env_cnt = ATOMIC_INIT(0);
@@ -1054,6 +1055,17 @@ static void tlob_kunit_error_env_probe(void *data, int id, char *state,
atomic_inc(&tlob_kunit_error_env_cnt);
}
+static void tlob_kunit_detail_probe(void *data, int pid, u64 threshold_us,
+ u64 running_ns, u64 waiting_ns,
+ u64 sleeping_ns)
+{
+ tlob_kunit_last_detail.pid = pid;
+ tlob_kunit_last_detail.threshold_us = threshold_us;
+ tlob_kunit_last_detail.running_ns = running_ns;
+ tlob_kunit_last_detail.waiting_ns = waiting_ns;
+ tlob_kunit_last_detail.sleeping_ns = sleeping_ns;
+}
+
int tlob_register_kunit_probes(void)
{
int ret;
@@ -1069,6 +1081,12 @@ int tlob_register_kunit_probes(void)
unregister_trace_event_tlob(tlob_kunit_event_probe, NULL);
return ret;
}
+ ret = register_trace_detail_env_tlob(tlob_kunit_detail_probe, NULL);
+ if (ret) {
+ unregister_trace_error_env_tlob(tlob_kunit_error_env_probe, NULL);
+ unregister_trace_event_tlob(tlob_kunit_event_probe, NULL);
+ return ret;
+ }
return 0;
}
EXPORT_SYMBOL_IF_KUNIT(tlob_register_kunit_probes);
@@ -1077,6 +1095,7 @@ void tlob_unregister_kunit_probes(void)
{
unregister_trace_event_tlob(tlob_kunit_event_probe, NULL);
unregister_trace_error_env_tlob(tlob_kunit_error_env_probe, NULL);
+ unregister_trace_detail_env_tlob(tlob_kunit_detail_probe, NULL);
tracepoint_synchronize_unregister();
}
EXPORT_SYMBOL_IF_KUNIT(tlob_unregister_kunit_probes);
@@ -1105,6 +1124,7 @@ void tlob_error_env_count_reset(void)
}
EXPORT_SYMBOL_IF_KUNIT(tlob_error_env_count_reset);
+
const struct tlob_captured_event *tlob_last_event_read(void)
{
return &tlob_kunit_last_event;
@@ -1117,6 +1137,12 @@ const struct tlob_captured_error_env *tlob_last_error_env_read(void)
}
EXPORT_SYMBOL_IF_KUNIT(tlob_last_error_env_read);
+const struct tlob_captured_detail *tlob_last_detail_read(void)
+{
+ return &tlob_kunit_last_detail;
+}
+EXPORT_SYMBOL_IF_KUNIT(tlob_last_detail_read);
+
#endif /* CONFIG_KUNIT */
VISIBLE_IF_KUNIT int tlob_enable_hooks(void)
diff --git a/kernel/trace/rv/monitors/tlob/tlob_kunit.c b/kernel/trace/rv/monitors/tlob/tlob_kunit.c
new file mode 100644
index 000000000000..ed2e7c7abaf8
--- /dev/null
+++ b/kernel/trace/rv/monitors/tlob/tlob_kunit.c
@@ -0,0 +1,881 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for the tlob RV monitor.
+ *
+ * tlob_task_api: start/stop lifecycle, error paths, violations.
+ * tlob_sched_integration: per-state accounting across real context switches.
+ * tlob_uprobe_format: uprobe binding format; add/remove acceptance and rejection.
+ * tlob_trace_output: trace event format for event_tlob, error_env_tlob.
+ * tlob_violation_react: error count per budget expiry; per-state breakdown.
+ *
+ * tlob_add_uprobe() duplicate-(binary, offset_start) constraint is not covered
+ * here: kern_path() requires a real filesystem; see selftests instead.
+ */
+#include <kunit/test.h>
+#include <linux/atomic.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/ktime.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/sched/rt.h>
+#include <linux/sched/task.h>
+
+#include "tlob.h"
+
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+
+/*
+ * Kthread cleanup guard: registers a kunit action that stops a kthread on
+ * test exit, even when a KUNIT_ASSERT fires before normal teardown code runs.
+ *
+ * Caller must call get_task_struct() before registering the guard.
+ * Set guard->task = NULL before normal-path teardown to prevent double-stop.
+ * Pass the completion to unblock on early exit, or NULL if not needed.
+ */
+struct tlob_kthread_guard {
+ struct task_struct *task;
+ struct completion *unblock;
+};
+
+static void kthread_guard_fn(void *arg)
+{
+ struct tlob_kthread_guard *g = arg;
+
+ if (!g->task)
+ return;
+ if (g->unblock)
+ complete(g->unblock);
+ kthread_stop(g->task);
+ put_task_struct(g->task);
+}
+
+static struct tlob_kthread_guard *
+tlob_guard_kthread(struct kunit *test, struct task_struct *task,
+ struct completion *unblock)
+{
+ struct tlob_kthread_guard *g;
+
+ g = kunit_kzalloc(test, sizeof(*g), GFP_KERNEL);
+ if (!g)
+ return NULL;
+ g->task = task;
+ g->unblock = unblock;
+ if (kunit_add_action_or_reset(test, kthread_guard_fn, g))
+ return NULL;
+ return g;
+}
+
+/* Suite 1: task API - lifecycle, error paths, violations. */
+
+/* Basic start/stop cycle */
+static void tlob_start_stop_ok(struct kunit *test)
+{
+ int ret;
+
+ ret = tlob_start_task(current, 10000000ULL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(current), 0);
+ KUNIT_EXPECT_EQ(test, tlob_num_monitored_read(), 0);
+}
+
+/* Double start must return -EALREADY; double stop must return -ESRCH. */
+static void tlob_double_start(struct kunit *test)
+{
+ KUNIT_ASSERT_EQ(test, tlob_start_task(current, 10000000ULL), 0);
+ KUNIT_EXPECT_EQ(test, tlob_start_task(current, 10000000ULL), -EALREADY);
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(current), 0);
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(current), -ESRCH);
+ KUNIT_EXPECT_EQ(test, tlob_num_monitored_read(), 0);
+}
+
+/* Stop without start must return -ESRCH. */
+static void tlob_stop_without_start(struct kunit *test)
+{
+ tlob_stop_task(current);
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(current), -ESRCH);
+ KUNIT_EXPECT_EQ(test, tlob_num_monitored_read(), 0);
+}
+
+/* threshold_us == 0 is invalid and must return -ERANGE. */
+static void tlob_zero_threshold(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, tlob_start_task(current, 0), -ERANGE);
+}
+
+/* 1 ns budget: timer almost certainly fires before tlob_stop_task(). */
+static void tlob_immediate_deadline(struct kunit *test)
+{
+ int ret = tlob_start_task(current, 1);
+
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ udelay(100);
+ /* timer fired -> -EOVERFLOW; if we won the race, 0 is also valid */
+ ret = tlob_stop_task(current);
+ KUNIT_EXPECT_TRUE(test, ret == 0 || ret == -EOVERFLOW);
+ KUNIT_EXPECT_EQ(test, tlob_num_monitored_read(), 0);
+}
+
+/*
+ * kthreads provide distinct task_structs; fill to TLOB_MAX_MONITORED,
+ * then verify -ENOSPC.
+ */
+struct tlob_waiter_ctx {
+ struct completion start;
+ struct completion done;
+};
+
+static int tlob_waiter_fn(void *arg)
+{
+ struct tlob_waiter_ctx *ctx = arg;
+
+ wait_for_completion(&ctx->start);
+ complete(&ctx->done);
+ return 0;
+}
+
+static void tlob_enospc(struct kunit *test)
+{
+ struct tlob_waiter_ctx *ctxs;
+ struct task_struct **threads;
+ int i, ret;
+
+ ctxs = kunit_kcalloc(test, TLOB_MAX_MONITORED,
+ sizeof(*ctxs), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctxs);
+
+ threads = kunit_kcalloc(test, TLOB_MAX_MONITORED,
+ sizeof(*threads), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, threads);
+
+ KUNIT_ASSERT_EQ(test, tlob_num_monitored_read(), 0);
+
+ for (i = 0; i < TLOB_MAX_MONITORED; i++) {
+ init_completion(&ctxs[i].start);
+ init_completion(&ctxs[i].done);
+
+ threads[i] = kthread_run(tlob_waiter_fn, &ctxs[i],
+ "tlob_waiter_%d", i);
+ if (IS_ERR(threads[i])) {
+ KUNIT_FAIL(test, "kthread_run failed at i=%d", i);
+ threads[i] = NULL;
+ goto cleanup;
+ }
+ get_task_struct(threads[i]);
+
+ ret = tlob_start_task(threads[i], 10000000ULL);
+ if (ret != 0) {
+ KUNIT_FAIL(test, "tlob_start_task failed at i=%d: %d",
+ i, ret);
+ put_task_struct(threads[i]);
+ complete(&ctxs[i].start);
+ threads[i] = NULL;
+ goto cleanup;
+ }
+ }
+
+ ret = tlob_start_task(current, 10000000ULL);
+ KUNIT_EXPECT_EQ(test, ret, -ENOSPC);
+
+cleanup:
+ /* cancel monitoring and unblock first, then wait for full exit */
+ for (i = 0; i < TLOB_MAX_MONITORED; i++) {
+ if (!threads[i])
+ break;
+ tlob_stop_task(threads[i]);
+ complete(&ctxs[i].start);
+ }
+ for (i = 0; i < TLOB_MAX_MONITORED; i++) {
+ if (!threads[i])
+ break;
+ kthread_stop(threads[i]);
+ put_task_struct(threads[i]);
+ }
+}
+
+/*
+ * Holder kthread holds a mutex for 80 ms; arm a 10 ms budget, burn ~1 ms
+ * on-CPU, then block on the mutex; timer fires while sleeping -> -EOVERFLOW.
+ */
+struct tlob_holder_ctx {
+ struct mutex lock;
+ struct completion ready;
+ unsigned int hold_ms;
+};
+
+static int tlob_holder_fn(void *arg)
+{
+ struct tlob_holder_ctx *ctx = arg;
+
+ mutex_lock(&ctx->lock);
+ complete(&ctx->ready);
+ msleep(ctx->hold_ms);
+ mutex_unlock(&ctx->lock);
+ return 0;
+}
+
+static void tlob_deadline_fires_sleeping(struct kunit *test)
+{
+ struct tlob_holder_ctx *ctx;
+ struct tlob_kthread_guard *guard;
+ struct task_struct *holder;
+ ktime_t t0;
+ int ret;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ ctx->hold_ms = 80;
+ mutex_init(&ctx->lock);
+ init_completion(&ctx->ready);
+
+ holder = kthread_run(tlob_holder_fn, ctx, "tlob_holder_kunit");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, holder);
+ get_task_struct(holder);
+
+ guard = tlob_guard_kthread(test, holder, NULL);
+ KUNIT_ASSERT_NOT_NULL(test, guard);
+
+ wait_for_completion(&ctx->ready);
+
+ ret = tlob_start_task(current, 10000);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ t0 = ktime_get();
+ while (ktime_us_delta(ktime_get(), t0) < 1000)
+ cpu_relax();
+
+ /* block on mutex: running->sleeping; timer fires while sleeping */
+ mutex_lock(&ctx->lock);
+ mutex_unlock(&ctx->lock);
+
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(current), -EOVERFLOW);
+
+ guard->task = NULL;
+ kthread_stop(holder);
+ put_task_struct(holder);
+}
+
+/*
+ * yield() triggers a preempt sched_switch (prev_state==0): running->waiting.
+ * Busy-spin 50 ms so the 2 ms budget fires regardless of scheduler timing.
+ */
+static void tlob_deadline_fires_waiting(struct kunit *test)
+{
+ ktime_t t0;
+ int ret;
+
+ ret = tlob_start_task(current, 2000);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ yield();
+
+ t0 = ktime_get();
+ while (ktime_us_delta(ktime_get(), t0) < 50000)
+ cpu_relax();
+
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(current), -EOVERFLOW);
+}
+
+/* Arm a 1 ms budget and busy-spin for 50 ms; timer fires in running state. */
+static void tlob_deadline_fires_running(struct kunit *test)
+{
+ ktime_t t0;
+ int ret;
+
+ ret = tlob_start_task(current, 1000);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ t0 = ktime_get();
+ while (ktime_us_delta(ktime_get(), t0) < 50000)
+ cpu_relax();
+
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(current), -EOVERFLOW);
+}
+
+/* Start three tasks, reinit monitor, verify all entries are gone. */
+static int tlob_dummy_fn(void *arg)
+{
+ wait_for_completion((struct completion *)arg);
+ return 0;
+}
+
+static void tlob_reinit_clears_all(struct kunit *test)
+{
+ struct completion *done1, *done2;
+ struct tlob_kthread_guard *guard1, *guard2;
+ struct task_struct *t1, *t2;
+ int ret;
+
+ done1 = kunit_kzalloc(test, sizeof(*done1), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, done1);
+ done2 = kunit_kzalloc(test, sizeof(*done2), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, done2);
+
+ init_completion(done1);
+ init_completion(done2);
+
+ t1 = kthread_run(tlob_dummy_fn, done1, "tlob_dummy1");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, t1);
+ get_task_struct(t1);
+ guard1 = tlob_guard_kthread(test, t1, done1);
+ KUNIT_ASSERT_NOT_NULL(test, guard1);
+
+ t2 = kthread_run(tlob_dummy_fn, done2, "tlob_dummy2");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, t2);
+ get_task_struct(t2);
+ guard2 = tlob_guard_kthread(test, t2, done2);
+ KUNIT_ASSERT_NOT_NULL(test, guard2);
+
+ KUNIT_ASSERT_EQ(test, tlob_start_task(current, 10000000ULL), 0);
+ KUNIT_ASSERT_EQ(test, tlob_start_task(t1, 10000000ULL), 0);
+ KUNIT_ASSERT_EQ(test, tlob_start_task(t2, 10000000ULL), 0);
+
+ tlob_destroy_monitor();
+ ret = tlob_init_monitor();
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(current), -ESRCH);
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(t1), -ESRCH);
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(t2), -ESRCH);
+
+ /* null guards before teardown to prevent double-stop */
+ guard1->task = NULL;
+ guard2->task = NULL;
+ complete(done1);
+ complete(done2);
+ kthread_stop(t1);
+ kthread_stop(t2);
+ put_task_struct(t1);
+ put_task_struct(t2);
+}
+
+static int tlob_task_api_suite_init(struct kunit_suite *suite)
+{
+ rv_kunit_monitoring_on();
+ return tlob_init_monitor();
+}
+
+static void tlob_task_api_suite_exit(struct kunit_suite *suite)
+{
+ tlob_destroy_monitor();
+ rv_kunit_monitoring_off();
+}
+
+static void tlob_task_api_exit(struct kunit *test)
+{
+ /*
+ * tlob_stop_task() returns pool slots via call_rcu (da_pool_return_cb).
+ * Wait for all pending callbacks so each test starts with a full pool.
+ */
+ rcu_barrier();
+}
+
+static struct kunit_case tlob_task_api_cases[] = {
+ KUNIT_CASE(tlob_start_stop_ok),
+ KUNIT_CASE(tlob_double_start),
+ KUNIT_CASE(tlob_stop_without_start),
+ KUNIT_CASE(tlob_zero_threshold),
+ KUNIT_CASE(tlob_immediate_deadline),
+ KUNIT_CASE(tlob_enospc),
+ KUNIT_CASE(tlob_deadline_fires_sleeping),
+ KUNIT_CASE(tlob_deadline_fires_waiting),
+ KUNIT_CASE(tlob_deadline_fires_running),
+ KUNIT_CASE(tlob_reinit_clears_all),
+ {}
+};
+
+static struct kunit_suite tlob_task_api_suite = {
+ .name = "tlob_task_api",
+ .suite_init = tlob_task_api_suite_init,
+ .suite_exit = tlob_task_api_suite_exit,
+ .exit = tlob_task_api_exit,
+ .test_cases = tlob_task_api_cases,
+};
+
+/* Suite 2: sched integration - per-state ns accounting. */
+
+struct tlob_ping_ctx {
+ struct completion ping;
+ struct completion pong;
+};
+
+static int tlob_ping_fn(void *arg)
+{
+ struct tlob_ping_ctx *ctx = arg;
+
+ wait_for_completion(&ctx->ping);
+ complete(&ctx->pong);
+ return 0;
+}
+
+/* Force two context switches and verify stop returns 0 (within budget). */
+static void tlob_sched_switch_accounting(struct kunit *test)
+{
+ struct tlob_ping_ctx *ctx;
+ struct tlob_kthread_guard *guard;
+ struct task_struct *peer;
+ int ret;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ init_completion(&ctx->ping);
+ init_completion(&ctx->pong);
+
+ peer = kthread_run(tlob_ping_fn, ctx, "tlob_ping_kunit");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, peer);
+ get_task_struct(peer);
+
+ guard = tlob_guard_kthread(test, peer, &ctx->ping);
+ KUNIT_ASSERT_NOT_NULL(test, guard);
+
+ ret = tlob_start_task(current, 5000000ULL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /* complete(ping) -> peer runs, forcing a context switch out and back */
+ complete(&ctx->ping);
+ wait_for_completion(&ctx->pong);
+
+ ret = tlob_stop_task(current);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ guard->task = NULL;
+ kthread_stop(peer);
+ put_task_struct(peer);
+}
+
+/* start/stop monitoring a kthread other than current */
+static int tlob_block_fn(void *arg)
+{
+ struct completion *done = arg;
+
+ msleep(20);
+ complete(done);
+ return 0;
+}
+
+static void tlob_monitor_other_task(struct kunit *test)
+{
+ struct completion *done;
+ struct tlob_kthread_guard *guard;
+ struct task_struct *target;
+ int ret;
+
+ done = kunit_kzalloc(test, sizeof(*done), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, done);
+ init_completion(done);
+
+ target = kthread_run(tlob_block_fn, done, "tlob_target_kunit");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, target);
+ get_task_struct(target);
+
+ guard = tlob_guard_kthread(test, target, NULL);
+ KUNIT_ASSERT_NOT_NULL(test, guard);
+
+ ret = tlob_start_task(target, 5000000ULL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ wait_for_completion(done);
+
+ /* 5 s budget won't fire in 20 ms; 0 or -EOVERFLOW are both valid */
+ ret = tlob_stop_task(target);
+ KUNIT_EXPECT_TRUE(test, ret == 0 || ret == -EOVERFLOW);
+
+ guard->task = NULL;
+ kthread_stop(target);
+ put_task_struct(target);
+}
+
+static int tlob_sched_suite_init(struct kunit_suite *suite)
+{
+ rv_kunit_monitoring_on();
+ return tlob_init_monitor();
+}
+
+static void tlob_sched_suite_exit(struct kunit_suite *suite)
+{
+ tlob_destroy_monitor();
+ rv_kunit_monitoring_off();
+}
+
+static struct kunit_case tlob_sched_integration_cases[] = {
+ KUNIT_CASE(tlob_sched_switch_accounting),
+ KUNIT_CASE(tlob_monitor_other_task),
+ {}
+};
+
+static struct kunit_suite tlob_sched_integration_suite = {
+ .name = "tlob_sched_integration",
+ .suite_init = tlob_sched_suite_init,
+ .suite_exit = tlob_sched_suite_exit,
+ .test_cases = tlob_sched_integration_cases,
+};
+
+/* Suite 3: uprobe binding format - add/remove acceptance and rejection. */
+
+static const char * const tlob_format_valid[] = {
+ "p /usr/bin/myapp:4768 4848 threshold=5000",
+ "p /usr/bin/myapp:0x12a0 0x12f0 threshold=10000",
+ "p /opt/my:app/bin:0x100 0x200 threshold=1000",
+};
+
+static const char * const tlob_format_invalid[] = {
+ /* add: malformed */
+ "p /usr/bin/myapp:0x100 0x200 threshold=0",
+ "p :0x100 0x200 threshold=5000",
+ "p /usr/bin/myapp:0x100 threshold=5000",
+ "p /usr/bin/myapp:-1 0x200 threshold=5000",
+ "p /usr/bin/myapp:0x100 0x200",
+ "p /usr/bin/myapp:0x100 0x100 threshold=5000",
+ /* remove: malformed */
+ "-usr/bin/myapp:0x100",
+ "-/usr/bin/myapp",
+ "-/:0x100",
+ "-/usr/bin/myapp:abc",
+};
+
+/*
+ * Valid add lines return -ENOENT (path does not exist in the test environment)
+ * rather than 0; a non-(-EINVAL) return confirms the format was accepted.
+ */
+static void tlob_format_accepted(struct kunit *test)
+{
+ char buf[128];
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tlob_format_valid); i++) {
+ strscpy(buf, tlob_format_valid[i], sizeof(buf));
+ KUNIT_EXPECT_NE(test, tlob_create_or_delete_uprobe(buf), -EINVAL);
+ }
+}
+
+static void tlob_format_rejected(struct kunit *test)
+{
+ char buf[128];
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tlob_format_invalid); i++) {
+ strscpy(buf, tlob_format_invalid[i], sizeof(buf));
+ KUNIT_EXPECT_EQ(test, tlob_create_or_delete_uprobe(buf), -EINVAL);
+ }
+}
+
+static struct kunit_case tlob_uprobe_format_cases[] = {
+ KUNIT_CASE(tlob_format_accepted),
+ KUNIT_CASE(tlob_format_rejected),
+ {}
+};
+
+static struct kunit_suite tlob_uprobe_format_suite = {
+ .name = "tlob_uprobe_format",
+ .test_cases = tlob_uprobe_format_cases,
+};
+
+/* Suite 4: trace output - verify event_tlob and error_env_tlob field values. */
+
+static void tlob_trace_event_format(struct kunit *test)
+{
+ const struct tlob_captured_event *ev;
+ int pid = current->pid;
+ int ret;
+
+ tlob_event_count_reset();
+ ret = tlob_start_task(current, 5000000ULL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /* sleep/wakeup/switch_in: running->sleeping->waiting->running */
+ msleep(20);
+
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(current), 0);
+
+ KUNIT_EXPECT_GE(test, tlob_event_count_read(), 3);
+
+ ev = tlob_last_event_read();
+ KUNIT_EXPECT_EQ(test, ev->id, pid);
+ KUNIT_EXPECT_STREQ(test, ev->state, "waiting");
+ KUNIT_EXPECT_STREQ(test, ev->event, "switch_in");
+ KUNIT_EXPECT_STREQ(test, ev->next_state, "running");
+ KUNIT_EXPECT_TRUE(test, ev->final_state);
+}
+
+static void tlob_trace_error_env_format(struct kunit *test)
+{
+ const struct tlob_captured_error_env *err;
+ ktime_t t0;
+ int pid = current->pid;
+ int ret;
+
+ tlob_error_env_count_reset();
+ ret = tlob_start_task(current, 1000);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ t0 = ktime_get();
+ while (ktime_us_delta(ktime_get(), t0) < 50000)
+ cpu_relax();
+
+ tlob_stop_task(current);
+
+ KUNIT_ASSERT_GE(test, tlob_error_env_count_read(), 1);
+
+ err = tlob_last_error_env_read();
+ KUNIT_EXPECT_EQ(test, err->id, pid);
+ KUNIT_EXPECT_STREQ(test, err->state, "running");
+ KUNIT_EXPECT_STREQ(test, err->event, "budget_exceeded");
+ KUNIT_EXPECT_TRUE(test, strncmp(err->env, "clk_elapsed=", 12) == 0);
+}
+
+static int tlob_trace_suite_init(struct kunit_suite *suite)
+{
+ int ret;
+
+ rv_kunit_monitoring_on();
+ ret = tlob_init_monitor();
+ if (ret)
+ goto err_mon_off;
+ ret = tlob_register_kunit_probes();
+ if (ret)
+ goto err_destroy;
+ ret = tlob_enable_hooks();
+ if (ret)
+ goto err_probes;
+ return 0;
+
+err_probes:
+ tlob_unregister_kunit_probes();
+err_destroy:
+ tlob_destroy_monitor();
+err_mon_off:
+ rv_kunit_monitoring_off();
+ return ret;
+}
+
+static void tlob_trace_suite_exit(struct kunit_suite *suite)
+{
+ tlob_disable_hooks();
+ tlob_unregister_kunit_probes();
+ tlob_destroy_monitor();
+ rv_kunit_monitoring_off();
+}
+
+static struct kunit_case tlob_trace_output_cases[] = {
+ KUNIT_CASE(tlob_trace_event_format),
+ KUNIT_CASE(tlob_trace_error_env_format),
+ {}
+};
+
+static struct kunit_suite tlob_trace_output_suite = {
+ .name = "tlob_trace_output",
+ .suite_init = tlob_trace_suite_init,
+ .suite_exit = tlob_trace_suite_exit,
+ .test_cases = tlob_trace_output_cases,
+};
+
+/*
+ * Suite 5: violation reaction - complement to Suite 4.
+ * Suite 4 checks trace field values; Suite 5 checks semantics:
+ * error count per budget expiry and per-state ns breakdown.
+ */
+
+/* generous budget; usleep forces state transitions; no error must fire */
+static void tlob_no_error_within_budget(struct kunit *test)
+{
+ tlob_error_env_count_reset();
+ tlob_event_count_reset();
+
+ KUNIT_ASSERT_EQ(test, tlob_start_task(current, 10000000ULL), 0);
+ usleep_range(5000, 10000);
+ KUNIT_EXPECT_EQ(test, tlob_stop_task(current), 0);
+ KUNIT_EXPECT_EQ(test, tlob_error_env_count_read(), 0);
+ KUNIT_EXPECT_GE(test, tlob_event_count_read(), 2);
+}
+
+/* busy-spin 50 ms >> 1 ms budget; running_ns must dominate */
+static void tlob_detail_running_dominates(struct kunit *test)
+{
+ const struct tlob_captured_detail *d;
+ u64 total_ns;
+ ktime_t t0;
+ int ret;
+
+ tlob_error_env_count_reset();
+
+ ret = tlob_start_task(current, 1000);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ t0 = ktime_get();
+ while (ktime_us_delta(ktime_get(), t0) < 50000)
+ cpu_relax();
+
+ tlob_stop_task(current);
+
+ KUNIT_EXPECT_EQ(test, tlob_error_env_count_read(), 1);
+ d = tlob_last_detail_read();
+ KUNIT_EXPECT_EQ(test, d->pid, current->pid);
+ KUNIT_EXPECT_EQ(test, d->threshold_us, 1000ULL);
+ total_ns = d->running_ns + d->waiting_ns + d->sleeping_ns;
+ KUNIT_EXPECT_GE(test, total_ns, 1000ULL * 1000);
+ KUNIT_EXPECT_GT(test, d->running_ns, d->sleeping_ns + d->waiting_ns);
+}
+
+struct tlob_hog_ctx {
+ int spin_ms;
+};
+
+static int tlob_hog_fn(void *arg)
+{
+ struct tlob_hog_ctx *ctx = arg;
+ ktime_t t0 = ktime_get();
+
+ while (!kthread_should_stop() &&
+ ktime_ms_delta(ktime_get(), t0) < ctx->spin_ms)
+ cpu_relax();
+ return 0;
+}
+
+/*
+ * SCHED_FIFO kthread bound to the same CPU preempts the monitored task
+ * (sched_switch prev_state == 0: running->waiting) and holds the CPU for
+ * 80 ms >> 10 ms budget, guaranteeing the timer fires in waiting state.
+ */
+static void tlob_detail_waiting_dominates(struct kunit *test)
+{
+ struct tlob_hog_ctx *ctx;
+ struct task_struct *hog;
+ struct tlob_kthread_guard *guard;
+ const struct tlob_captured_detail *d;
+ struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
+ int ret;
+
+ tlob_error_env_count_reset();
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ ctx->spin_ms = 80;
+
+ hog = kthread_create(tlob_hog_fn, ctx, "tlob_s5_hog");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hog);
+ get_task_struct(hog);
+
+ kthread_bind(hog, smp_processor_id());
+ sched_setscheduler_nocheck(hog, SCHED_FIFO, ¶m);
+
+ guard = tlob_guard_kthread(test, hog, NULL);
+ KUNIT_ASSERT_NOT_NULL(test, guard);
+
+ ret = tlob_start_task(current, 10000); /* 10 ms budget */
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ wake_up_process(hog);
+ yield(); /* sched_switch prev_state == 0: running->waiting */
+
+ tlob_stop_task(current);
+
+ KUNIT_EXPECT_EQ(test, tlob_error_env_count_read(), 1);
+ d = tlob_last_detail_read();
+ KUNIT_EXPECT_EQ(test, d->sleeping_ns, 0ULL);
+ KUNIT_EXPECT_GT(test, d->waiting_ns, d->running_ns + d->sleeping_ns);
+
+ guard->task = NULL;
+ kthread_stop(hog);
+ put_task_struct(hog);
+}
+
+/* block on mutex for 80 ms >> 10 ms budget; sleeping_ns must dominate */
+static void tlob_detail_sleeping_dominates(struct kunit *test)
+{
+ struct tlob_holder_ctx *ctx;
+ struct tlob_kthread_guard *guard;
+ struct task_struct *holder;
+ const struct tlob_captured_detail *d;
+ int ret;
+
+ tlob_error_env_count_reset();
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ ctx->hold_ms = 80;
+ mutex_init(&ctx->lock);
+ init_completion(&ctx->ready);
+
+ holder = kthread_run(tlob_holder_fn, ctx, "tlob_s5_detail");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, holder);
+ get_task_struct(holder);
+
+ guard = tlob_guard_kthread(test, holder, NULL);
+ KUNIT_ASSERT_NOT_NULL(test, guard);
+
+ wait_for_completion(&ctx->ready);
+
+ ret = tlob_start_task(current, 10000);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ mutex_lock(&ctx->lock);
+ mutex_unlock(&ctx->lock);
+
+ tlob_stop_task(current);
+
+ KUNIT_EXPECT_EQ(test, tlob_error_env_count_read(), 1);
+ d = tlob_last_detail_read();
+ KUNIT_EXPECT_GT(test, d->sleeping_ns, d->running_ns + d->waiting_ns);
+
+ guard->task = NULL;
+ kthread_stop(holder);
+ put_task_struct(holder);
+}
+
+static int tlob_violation_suite_init(struct kunit_suite *suite)
+{
+ int ret;
+
+ rv_kunit_monitoring_on();
+ ret = tlob_init_monitor();
+ if (ret)
+ goto err_mon_off;
+ ret = tlob_register_kunit_probes();
+ if (ret)
+ goto err_destroy;
+ ret = tlob_enable_hooks();
+ if (ret)
+ goto err_probes;
+ return 0;
+
+err_probes:
+ tlob_unregister_kunit_probes();
+err_destroy:
+ tlob_destroy_monitor();
+err_mon_off:
+ rv_kunit_monitoring_off();
+ return ret;
+}
+
+static void tlob_violation_suite_exit(struct kunit_suite *suite)
+{
+ tlob_disable_hooks();
+ tlob_unregister_kunit_probes();
+ tlob_destroy_monitor();
+ rv_kunit_monitoring_off();
+}
+
+static struct kunit_case tlob_violation_react_cases[] = {
+ KUNIT_CASE(tlob_no_error_within_budget),
+ KUNIT_CASE(tlob_detail_running_dominates),
+ KUNIT_CASE(tlob_detail_sleeping_dominates),
+ KUNIT_CASE(tlob_detail_waiting_dominates),
+ {}
+};
+
+static struct kunit_suite tlob_violation_react_suite = {
+ .name = "tlob_violation_react",
+ .suite_init = tlob_violation_suite_init,
+ .suite_exit = tlob_violation_suite_exit,
+ .test_cases = tlob_violation_react_cases,
+};
+
+kunit_test_suites(&tlob_task_api_suite,
+ &tlob_sched_integration_suite,
+ &tlob_uprobe_format_suite,
+ &tlob_trace_output_suite,
+ &tlob_violation_react_suite);
+
+MODULE_DESCRIPTION("KUnit tests for the tlob RV monitor");
+MODULE_LICENSE("GPL");
--
2.25.1
next prev parent reply other threads:[~2026-05-11 18:25 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-11 18:24 [RFC PATCH v2 00/10] rv/tlob: Add task latency over budget RV monitor wen.yang
2026-05-11 18:24 ` [RFC PATCH v2 01/10] rv/da: fix monitor start ordering and memory ordering for monitoring flag wen.yang
2026-05-11 18:24 ` [RFC PATCH v2 02/10] rv/da: fix per-task da_monitor_destroy() ordering and sync wen.yang
2026-05-11 18:24 ` [RFC PATCH v2 03/10] selftests/verification: fix verificationtest-ktap for out-of-tree execution wen.yang
2026-05-11 18:24 ` [RFC PATCH v2 04/10] rv/da: add pre-allocated storage pool for per-object monitors wen.yang
2026-05-11 18:24 ` [RFC PATCH v2 05/10] rv: add generic uprobe infrastructure for RV monitors wen.yang
2026-05-11 18:24 ` [RFC PATCH v2 06/10] rvgen: support reset() on the __init arrow for global-window HA clocks wen.yang
2026-05-11 18:24 ` [RFC PATCH v2 07/10] rv/tlob: add tlob model DOT file wen.yang
2026-05-11 18:24 ` [RFC PATCH v2 08/10] rv/tlob: add tlob hybrid automaton monitor wen.yang
2026-05-11 18:24 ` wen.yang [this message]
2026-05-11 18:24 ` [RFC PATCH v2 10/10] selftests/verification: add tlob selftests wen.yang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=a12d14297b33b9b8d425bc1b813a8aecbd54bcc6.1778522945.git.wen.yang@linux.dev \
--to=wen.yang@linux.dev \
--cc=gmonaco@redhat.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=rostedt@goodmis.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox