Re: [PATCH printk v1] printk: ringbuffer: Add KUnit test

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu 2024-11-21 15:56:34, John Ogness wrote:
> From: Thomas Weißschuh <thomas.weissschuh@xxxxxxxxxxxxx>
> 
> The KUnit test validates the correct operation of the ringbuffer.
> A separate dedicated ringbuffer is used so that the global printk
> ringbuffer is not touched.

Thanks a lot for upstreaming this test!

> --- a/init/Kconfig
> +++ b/init/Kconfig
> @@ -1576,6 +1576,18 @@ config PRINTK
>  	  very difficult to diagnose system problems, saying N here is
>  	  strongly discouraged.
>  
> +config PRINTK_RINGBUFFER_TEST

I would call it PRINTK_RINGBUFFER_KUNIT_TEST to make it clear
that it is a KUNIT test. Also it helps to find KUNIT tests
in .config.

Anyway, Documentation/dev-tools/kunit/style.rst suggests the
FOO_KUNIT_TEST name.

> +	tristate "Test for the printk ringbuffer" if !KUNIT_ALL_TESTS
> +	depends on PRINTK && KUNIT
> +	default KUNIT_ALL_TESTS
> +	help
> +	  This builds the printk ringbuffer KUnit test suite.
> +
> +	  For more information on KUnit and unit tests in general, please refer
> +	  to the KUnit documentation.
> +
> +	  If unsure, say N.
> +
>  config BUG
>  	bool "BUG() support" if EXPERT
>  	default y
> --- /dev/null
> +++ b/kernel/printk/printk_ringbuffer_test.c
> @@ -0,0 +1,350 @@
[...]
> +/* test data structure */
> +struct rbdata {
> +	unsigned int len;
> +	char text[] __counted_by(len);
> +};
> +
> +#define MAX_RBDATA_LEN (0x7f + 1)
> +#define MAX_RECORD_SIZE (sizeof(struct rbdata) + MAX_RBDATA_LEN + 1)

It is far from clear what the two +1 are for here.
Also 0x7f is hardcoded below once again.

I would personally do:

/* A mask used to generate random text len. */
#define RBDATA_TEXT_LEN_RND_MASK 0x7f
/* +2 to ensure at least 1 character + terminator. */
#define MAX_RBDATA_TEXT_SIZE (RBDATA_TEXT_LEN_RND_MASK + 2)
#define MAX_PRB_RECORD_SIZE (sizeof(struct rbdata) + MAX_RBDATA_TEXT_SIZE)

Note that I used the suffix _SIZE because I counted the terminator
in both definitions, see below.

> +static struct test_running {
> +	int runstate;
> +	unsigned long num;
> +	struct kunit *test;
> +} *test_running;
> +static int halt_test;
> +
> +static void fail_record(struct kunit *test, struct rbdata *dat, u64 seq)
> +{
> +	char buf[MAX_RBDATA_LEN + 1];

	char buf[MAX_RBDATA_TEXT_SIZE];

> +	snprintf(buf, sizeof(buf), "%s", dat->text);
> +	buf[sizeof(buf) - 1] = 0;
> +
> +	KUNIT_FAIL(test, "BAD RECORD: seq=%llu len=%u text=%s\n",
> +		   seq, dat->len, dat->len < sizeof(buf) ? buf : "<invalid>");
> +}
> +
> +static bool check_data(struct rbdata *dat)
> +{
> +	unsigned int len;
> +
> +	len = strnlen(dat->text, MAX_RBDATA_LEN + 1);

	len = strnlen(dat->text, MAX_RBDATA_TEXT_SIZE);

> +	/* Sane length? */
> +	if (len != dat->len || !len || len > MAX_RBDATA_LEN)

	if (len != dat->len || !len || len >= MAX_RBDATA_TEXT_SIZE)

Note that I have used >= instead of > because it is the _SIZE.

> +		return false;
> +
> +	/* String repeats with the same character? */
> +	while (len) {
> +		len--;
> +		if (dat->text[len] != dat->text[0])
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +/* Equivalent to CONFIG_LOG_BUF_SHIFT=13 */
> +DEFINE_PRINTKRB(test_rb, 8, 5);
> +
> +static int prbtest_writer(void *data)
> +{
> +	struct test_running *tr = data;
> +	char text_id = 'A' + tr->num;
> +	struct prb_reserved_entry e;
> +	unsigned long count = 0;
> +	struct printk_record r;
> +	u64 min_ns = (u64)-1;
> +	struct rbdata *dat;
> +	u64 total_ns = 0;
> +	u64 max_ns = 0;
> +	u64 post_ns;
> +	u64 pre_ns;
> +	int len;
> +
> +	set_cpus_allowed_ptr(current, cpumask_of(tr->num));
> +
> +	kunit_info(tr->test, "start thread %03lu (writer)\n", tr->num);
> +
> +	tr->runstate = 1;
> +
> +	for (;;) {
> +		/* +2 to ensure at least 1 character + terminator. */
> +		len = sizeof(struct rbdata) + (get_random_u32() & 0x7f) + 2;

		len = sizeof(struct rbdata) +
		      (get_random_u32() & RBDATA_TEXT_LEN_RND_MASK) + 2;

I would add

		WARN_ON_ONCE(len <= MAX_PRB_RECORD_SIZE);


> +		/* specify the text sizes for reservation */
> +		prb_rec_init_wr(&r, len);
> +
> +		pre_ns = local_clock();
> +
> +		if (prb_reserve(&e, &test_rb, &r)) {
> +			r.info->text_len = len;
> +
> +			len -= sizeof(struct rbdata) + 1;

I would add brackets to make it more clear that we actually substract
the "1":

			len -= (sizeof(struct rbdata) + 1);

or even better might be to create two variables:

		/* +1 to ensure at least 1 character*/
		len = (get_random_u32() & 0x7f) + 1;
		size = sizeof(struct rbdata) + len + 1;

> +
> +			dat = (struct rbdata *)&r.text_buf[0];
> +			dat->len = len;
> +			memset(&dat->text[0], text_id, len);
> +			dat->text[len] = 0;
> +
> +			prb_commit(&e);
> +
> +			post_ns = local_clock();

I think that the counting of the time will be misleading in CONFIG_PREEMPT=y.
We should disable preemption or even interrupts to get right numbers.

> +			wake_up_interruptible(&test_wait);
> +
> +			post_ns -= pre_ns;
> +			if (post_ns < min_ns)
> +				min_ns = post_ns;
> +			if (post_ns > max_ns)
> +				max_ns = post_ns;
> +			total_ns += post_ns;
> +		}
> +
> +		if ((count++ & 0x3fff) == 0)
> +			schedule();

This won't work as expected with CONFIG_PREEMPT=y.
IMHO, we should use simply use cond_resched() here.
a
> +		if (READ_ONCE(halt_test) == 1)
> +			break;
> +	}
> +
> +	kunit_info(tr->test, "end thread %03lu: wrote=%lu min_ns=%llu avg_ns=%llu max_ns=%llu\n",
> +		   tr->num, count, min_ns, total_ns / (u64)count, max_ns);
> +
> +	tr->runstate = 2;
> +
> +	return 0;
> +}
> +
> +static int prbtest_reader(void *data)
> +{
> +	struct test_running *tr = data;
> +	char text_buf[MAX_RECORD_SIZE];
> +	unsigned long total_lost = 0;
> +	unsigned long max_lost = 0;
> +	unsigned long count = 0;
> +	struct printk_info info;
> +	struct printk_record r;
> +	int did_sched = 1;
> +	u64 seq = 0;
> +
> +	set_cpus_allowed_ptr(current, cpumask_of(tr->num));
> +
> +	prb_rec_init_rd(&r, &info, &text_buf[0], sizeof(text_buf));
> +
> +	kunit_info(tr->test, "start thread %03lu (reader)\n", tr->num);
> +
> +	tr->runstate = 1;
> +
> +	while (!wait_event_interruptible(test_wait,
> +				kthread_should_stop() ||
> +				prb_read_valid(&test_rb, seq, &r))) {
> +		bool error = false;
> +
> +		if (kthread_should_stop())
> +			break;
> +		/* check/track the sequence */
> +		if (info.seq < seq) {
> +			KUNIT_FAIL(tr->test, "BAD SEQ READ: request=%llu read=%llu\n",
> +				   seq, info.seq);
> +			error = true;
> +		} else if (info.seq != seq && !did_sched) {
> +			total_lost += info.seq - seq;
> +			if (max_lost < info.seq - seq)
> +				max_lost = info.seq - seq;
> +		}
> +
> +		if (!check_data((struct rbdata *)&r.text_buf[0])) {
> +			fail_record(tr->test, (struct rbdata *)&r.text_buf[0], info.seq);
> +			error = true;
> +		}
> +
> +		if (error)
> +			WRITE_ONCE(halt_test, 1);
> +
> +		did_sched = 0;
> +		if ((count++ & 0x3fff) == 0) {
> +			did_sched = 1;
> +			schedule();
> +		}

This won't work with CONFIG_PREEMPT=Y. I would use cond_resched()
and detect scheduling by checking the switch count. I think that
it should be:

	switch_count = READ_ONCE(current->nvcsw + current->nivcsw);

It is inspired by check_hung_task().

> +		if (READ_ONCE(halt_test) == 1)
> +			break;
> +
> +		seq = info.seq + 1;
> +	}
> +
> +	kunit_info(tr->test,
> +		   "end thread %03lu: read=%lu seq=%llu total_lost=%lu max_lost=%lu\n",
> +		   tr->num, count, info.seq, total_lost, max_lost);
> +
> +	while (!kthread_should_stop())
> +		msleep(1000);
> +	tr->runstate = 2;
> +
> +	return 0;
> +}

This is what I have found on top of the review by David Gow.

Best Regards,
Petr




[Index of Archives]     [Linux Wireless]     [Linux Kernel]     [ATH6KL]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [Share Photos]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Device Mapper]

  Powered by Linux