Commit 0561bd56 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'ratelimit.2025.07.23a' of...

Merge tag 'ratelimit.2025.07.23a' of git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu

Pull ratelimit test updates from Paul McKenney:
 "Add functional and stress tests:

   - Add trivial kunit test for ratelimit

   - Make the ratelimit test more reliable (Petr Mladek)

   - Add stress test for ratelimit"

* tag 'ratelimit.2025.07.23a' of git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu:
  lib: Add stress test for ratelimit
  lib: Make the ratelimit test more reliable
  lib: Add trivial kunit test for ratelimit
parents 93942645 5c23ce0c
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -3214,6 +3214,17 @@ config TEST_OBJPOOL

	  If unsure, say N.

config RATELIMIT_KUNIT_TEST
	tristate "KUnit Test for correctness and stress of ratelimit" if !KUNIT_ALL_TESTS
	depends on KUNIT
	default KUNIT_ALL_TESTS
	help
	  This builds the "test_ratelimit" module that should be used
	  for correctness verification and concurrent testings of rate
	  limiting.

	  If unsure, say N.

config INT_POW_KUNIT_TEST
	tristate "Integer exponentiation (int_pow) test" if !KUNIT_ALL_TESTS
	depends on KUNIT
+1 −0
Original line number Diff line number Diff line
@@ -46,5 +46,6 @@ obj-$(CONFIG_STRING_KUNIT_TEST) += string_kunit.o
obj-$(CONFIG_STRING_HELPERS_KUNIT_TEST) += string_helpers_kunit.o
obj-$(CONFIG_USERCOPY_KUNIT_TEST) += usercopy_kunit.o
obj-$(CONFIG_UTIL_MACROS_KUNIT) += util_macros_kunit.o
obj-$(CONFIG_RATELIMIT_KUNIT_TEST) += test_ratelimit.o

obj-$(CONFIG_TEST_RUNTIME_MODULE)		+= module/
+144 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only

#include <kunit/test.h>

#include <linux/ratelimit.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/cpumask.h>

/* a simple boot-time regression test */

#define TESTRL_INTERVAL (5 * HZ)
static DEFINE_RATELIMIT_STATE(testrl, TESTRL_INTERVAL, 3);

#define test_ratelimited(test, expected) \
	KUNIT_ASSERT_EQ(test, ___ratelimit(&testrl, "test_ratelimit_smoke"), (expected))

static void test_ratelimit_smoke(struct kunit *test)
{
	// Check settings.
	KUNIT_ASSERT_GE(test, TESTRL_INTERVAL, 100);

	// Test normal operation.
	test_ratelimited(test, true);
	test_ratelimited(test, true);
	test_ratelimited(test, true);
	test_ratelimited(test, false);

	schedule_timeout_idle(TESTRL_INTERVAL / 2);
	test_ratelimited(test, false);

	schedule_timeout_idle(TESTRL_INTERVAL * 3 / 4);
	test_ratelimited(test, true);

	schedule_timeout_idle(2 * TESTRL_INTERVAL);
	test_ratelimited(test, true);
	test_ratelimited(test, true);

	schedule_timeout_idle(TESTRL_INTERVAL / 2 );
	test_ratelimited(test, true);
	schedule_timeout_idle(TESTRL_INTERVAL * 3 / 4);
	test_ratelimited(test, true);
	test_ratelimited(test, true);
	test_ratelimited(test, true);
	test_ratelimited(test, false);

	// Test disabling.
	testrl.burst = 0;
	test_ratelimited(test, false);
	testrl.burst = 2;
	testrl.interval = 0;
	test_ratelimited(test, true);
	test_ratelimited(test, true);
	test_ratelimited(test, true);
	test_ratelimited(test, true);
	test_ratelimited(test, true);
	test_ratelimited(test, true);
	test_ratelimited(test, true);

	// Testing re-enabling.
	testrl.interval = TESTRL_INTERVAL;
	test_ratelimited(test, true);
	test_ratelimited(test, true);
	test_ratelimited(test, false);
	test_ratelimited(test, false);
}

static struct ratelimit_state stressrl = RATELIMIT_STATE_INIT_FLAGS("stressrl", HZ / 10, 3,
								    RATELIMIT_MSG_ON_RELEASE);

static int doneflag;
static const int stress_duration = 2 * HZ;

struct stress_kthread {
	unsigned long nattempts;
	unsigned long nunlimited;
	unsigned long nlimited;
	unsigned long nmissed;
	struct task_struct *tp;
};

static int test_ratelimit_stress_child(void *arg)
{
	struct stress_kthread *sktp = arg;

	set_user_nice(current, MAX_NICE);
	WARN_ON_ONCE(!sktp->tp);

	while (!READ_ONCE(doneflag)) {
		sktp->nattempts++;
		if (___ratelimit(&stressrl, __func__))
			sktp->nunlimited++;
		else
			sktp->nlimited++;
		cond_resched();
	}

	sktp->nmissed = ratelimit_state_reset_miss(&stressrl);
	return 0;
}

static void test_ratelimit_stress(struct kunit *test)
{
	int i;
	const int n_stress_kthread = cpumask_weight(cpu_online_mask);
	struct stress_kthread skt = { 0 };
	struct stress_kthread *sktp = kcalloc(n_stress_kthread, sizeof(*sktp), GFP_KERNEL);

	KUNIT_EXPECT_NOT_NULL_MSG(test, sktp, "Memory allocation failure");
	for (i = 0; i < n_stress_kthread; i++) {
		sktp[i].tp = kthread_run(test_ratelimit_stress_child, &sktp[i], "%s/%i",
					 "test_ratelimit_stress_child", i);
		KUNIT_EXPECT_NOT_NULL_MSG(test, sktp, "kthread creation failure");
		pr_alert("Spawned test_ratelimit_stress_child %d\n", i);
	}
	schedule_timeout_idle(stress_duration);
	WRITE_ONCE(doneflag, 1);
	for (i = 0; i < n_stress_kthread; i++) {
		kthread_stop(sktp[i].tp);
		skt.nattempts += sktp[i].nattempts;
		skt.nunlimited += sktp[i].nunlimited;
		skt.nlimited += sktp[i].nlimited;
		skt.nmissed += sktp[i].nmissed;
	}
	KUNIT_ASSERT_EQ_MSG(test, skt.nunlimited + skt.nlimited, skt.nattempts,
			    "Outcomes not equal to attempts");
	KUNIT_ASSERT_EQ_MSG(test, skt.nlimited, skt.nmissed, "Misses not equal to limits");
}

static struct kunit_case ratelimit_test_cases[] = {
	KUNIT_CASE_SLOW(test_ratelimit_smoke),
	KUNIT_CASE_SLOW(test_ratelimit_stress),
	{}
};

static struct kunit_suite ratelimit_test_suite = {
	.name = "lib_ratelimit",
	.test_cases = ratelimit_test_cases,
};

kunit_test_suites(&ratelimit_test_suite);

MODULE_DESCRIPTION("___ratelimit() KUnit test suite");
MODULE_LICENSE("GPL");