Christoph Hellwigraid6/kunit: add a benchmark10dab133 months agoHistory
385 lines · 9.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Copyright 2002-2007 H. Peter Anvin - All Rights Reserved * * Test RAID-6 recovery algorithms. */ #include <kunit/test.h>#include <linux/prandom.h>#include <linux/vmalloc.h>#include <linux/raid/pq.h>#include "../algos.h" MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); #define RAID6_KUNIT_SEED		42#define RAID6_KUNIT_NUM_TEST_ITERS	10#define RAID6_KUNIT_MAX_BUFFERS		64 /* Including P and Q */#define RAID6_KUNIT_MAX_FAILURES	2#define RAID6_KUNIT_MAX_BYTES		PAGE_SIZE#define RAID6_KUNIT_ALLOC_BYTES		SZ_16K static struct rnd_state rng;static void *test_buffers[RAID6_KUNIT_MAX_BUFFERS];static void *aligned_buffers[RAID6_KUNIT_MAX_BUFFERS];static void *test_recov_buffers[RAID6_KUNIT_MAX_FAILURES];static size_t test_buflen; struct test_args {	unsigned int recov_idx;	const struct raid6_recov_calls *recov;	unsigned int gen_idx;	const struct raid6_calls *gen;}; static struct test_args args; static u32 rand32(void){	return prandom_u32_state(&rng);} /* Generate a random length that is a multiple of 512. */static unsigned int random_length(unsigned int max_length){	return round_up((rand32() % max_length) + 1, 512);} static unsigned int random_nr_buffers(void){	return (rand32() % (RAID6_KUNIT_MAX_BUFFERS - (RAID6_MIN_DISKS - 1))) +			RAID6_MIN_DISKS;} /* Generate a random alignment that is a multiple of 64. */static unsigned int random_alignment(unsigned int max_alignment){	if (max_alignment == 0)		return 0;	return (rand32() % (max_alignment + 1)) & ~63;} static void makedata(int start, int stop){	int i; 	for (i = start; i <= stop; i++)		prandom_bytes_state(&rng, test_buffers[i], test_buflen);} static char member_type(unsigned int nr_buffers, int d){	if (d == nr_buffers - 2)		return 'P';	if (d == nr_buffers - 1)		return 'Q';	return 'D';} static void test_recover_one(struct kunit *test, unsigned int nr_buffers,		unsigned int len, int faila, int failb){	const struct test_args *ta = test->param_value;	void *dataptrs[RAID6_KUNIT_MAX_BUFFERS];	int i; 	if (faila > failb)		swap(faila, failb); 	for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++)		memset(test_recov_buffers[i], 0xf0, test_buflen); 	memcpy(dataptrs, aligned_buffers, sizeof(dataptrs));	dataptrs[faila] = test_recov_buffers[0];	dataptrs[failb] = test_recov_buffers[1]; 	if (failb == nr_buffers - 1) {		/*		 * We don't implement the data+Q failure scenario, since it		 * is equivalent to a RAID-5 failure (XOR, then recompute Q).		 */		if (WARN_ON_ONCE(faila != nr_buffers - 2))			return; 		/* P+Q failure.  Just rebuild the syndrome. */		ta->gen->gen_syndrome(nr_buffers, len, dataptrs);	} else if (failb == nr_buffers - 2) {		/* data+P failure. */		ta->recov->datap(nr_buffers, len, faila, dataptrs);	} else {		/* data+data failure. */		ta->recov->data2(nr_buffers, len, faila, failb, dataptrs);	} 	KUNIT_EXPECT_MEMEQ_MSG(test, aligned_buffers[faila], dataptrs[faila],			len,			"faila miscompared: %3d[%c] buffers %u len %u (failb=%3d[%c])\n",			faila, member_type(nr_buffers, faila),			nr_buffers, len,			failb, member_type(nr_buffers, failb));	KUNIT_EXPECT_MEMEQ_MSG(test, aligned_buffers[failb], dataptrs[failb],			len,			"failb miscompared: %3d[%c] buffers %u len %u (faila=%3d[%c])\n",			failb, member_type(nr_buffers, failb),			nr_buffers, len,			faila, member_type(nr_buffers, faila));} static void test_recover(struct kunit *test, unsigned int nr_buffers,		unsigned int len){	unsigned int nr_data = nr_buffers - 2;	int iterations, i; 	/* Test P+Q recovery */	test_recover_one(test, nr_buffers, len, nr_data, nr_buffers - 1); 	/* Test data+P recovery */	for (i = 0; i < nr_buffers - 2; i++)		test_recover_one(test, nr_buffers, len, i, nr_data); 	/* Double data failure is impossible with a single data disk */	if (nr_data == 1)		return; 	/* Test data+data recovery using random sampling */	iterations = nr_buffers * 2; /* should provide good enough coverage */	for (i = 0; i < iterations; i++) {		int faila = rand32() % nr_data, failb; 		do {			failb = rand32() % nr_data;		} while (failb == faila); 		test_recover_one(test, nr_buffers, len, faila, failb);	}} /* Simulate rmw run */static void test_rmw_one(struct kunit *test, unsigned int nr_buffers,		unsigned int len, int p1, int p2){	const struct test_args *ta = test->param_value; 	ta->gen->xor_syndrome(nr_buffers, p1, p2, len, aligned_buffers);	makedata(p1, p2);	ta->gen->xor_syndrome(nr_buffers, p1, p2, len, aligned_buffers);	test_recover(test, nr_buffers, len);} static void test_rmw(struct kunit *test, unsigned int nr_buffers,		unsigned int len){	int iterations = nr_buffers / 2, i; 	for (i = 0; i < iterations; i++) {		int p1 = rand32() % (nr_buffers - 2);		int p2 = rand32() % (nr_buffers - 2); 		if (p2 < p1)			swap(p1, p2);		test_rmw_one(test, nr_buffers, len, p1, p2);	}} static void raid6_test_one(struct kunit *test){	const struct test_args *ta = test->param_value;	unsigned int nr_buffers = random_nr_buffers();	unsigned int len = random_length(RAID6_KUNIT_MAX_BYTES);	unsigned int max_alignment;	int i; 	/* Nuke syndromes */	memset(test_buffers[nr_buffers - 2], 0xee, test_buflen);	memset(test_buffers[nr_buffers - 1], 0xee, test_buflen); 	/*	 * If we're not using the entire buffer size, inject randomize alignment	 * into the buffer.	 */	max_alignment = RAID6_KUNIT_MAX_BYTES - len;	if (rand32() % 2 == 0) {		/* Use random alignments mod 64 */		for (i = 0; i < nr_buffers; i++)			aligned_buffers[i] = test_buffers[i] +				random_alignment(max_alignment);	} else {		/* Go up to the guard page, to catch buffer overreads */		unsigned int align = test_buflen - len; 		for (i = 0; i < nr_buffers; i++)			aligned_buffers[i] = test_buffers[i] + align;	} 	/* Generate assumed good syndrome */	ta->gen->gen_syndrome(nr_buffers, len, aligned_buffers); 	test_recover(test, nr_buffers, len); 	if (ta->gen->xor_syndrome)		test_rmw(test, nr_buffers, len);} static void raid6_test(struct kunit *test){	int i; 	for (i = 0; i < RAID6_KUNIT_NUM_TEST_ITERS; i++)		raid6_test_one(test);} static void raid6_benchmark(struct kunit *test){	static const unsigned int nr_to_test[] = {		4, 5, 6, 7, 8, 10, 12, 15, 16, 32,	};	static const unsigned int len_to_test[] = {		SZ_4K, SZ_16K,	};	unsigned int i, j, l;	u64 t; 	if (!IS_ENABLED(CONFIG_RAID6_PQ_KUNIT_BENCHMARK))		kunit_skip(test, "not enabled"); 	/* warm-up */	for (i = 0; i < ARRAY_SIZE(nr_to_test); i++) {		for (j = 0; j < ARRAY_SIZE(len_to_test); j++) {			for (l = 0; l < 10; l++) {				raid6_gen_syndrome(nr_to_test[i],						len_to_test[j], test_buffers);			}		}	} 	/*	 * Preferably this would be a loop over len_to_test, but the kunit	 * logging always adds a newline to each logged format string.	 */	static_assert(ARRAY_SIZE(len_to_test) == 2);	kunit_info(test, "          \t%5u bytes\t%5u bytes\n",			len_to_test[0], len_to_test[1]); 	for (i = 0; i < ARRAY_SIZE(nr_to_test); i++) {		unsigned int nr = nr_to_test[i];		u64 speed[ARRAY_SIZE(len_to_test)]; 		KUNIT_ASSERT_LE(test, nr, RAID6_KUNIT_MAX_BUFFERS); 		for (j = 0; j < ARRAY_SIZE(len_to_test); j++) {			unsigned int len = len_to_test[j];			const unsigned long num_iters = 1000; 			KUNIT_ASSERT_GT(test, len, 0);			KUNIT_ASSERT_LE(test, len, RAID6_KUNIT_ALLOC_BYTES); 			preempt_disable();			t = ktime_get_ns();			for (l = 0; l < num_iters; l++)				raid6_gen_syndrome(nr_to_test[i],						len_to_test[j], test_buffers);			t = max(ktime_get_ns() - t, 1);			preempt_enable(); 			speed[j] = div64_u64((u64)len * num_iters * nr, t);		} 		static_assert(ARRAY_SIZE(len_to_test) == 2);		kunit_info(test, "%3u disks:\t%5llu  GB/s\t%5llu  GB/s\n",				nr, speed[0], speed[1]);	}} static const void *raid6_gen_params(struct kunit *test, const void *prev,		char *desc){	if (!prev) {		memset(&args, 0, sizeof(args));next_algo:		args.recov_idx = 0;		args.gen = raid6_algo_find(args.gen_idx);		if (!args.gen)			return NULL;	} 	if (args.recov)		args.recov_idx++;	args.recov = raid6_recov_algo_find(args.recov_idx);	if (!args.recov) {		args.gen_idx++;		goto next_algo;	} 	snprintf(desc, KUNIT_PARAM_DESC_SIZE, "gen=%s recov=%s",			args.gen->name, args.recov->name);	return &args;} static struct kunit_case raid6_test_cases[] = {	KUNIT_CASE_PARAM(raid6_test, raid6_gen_params),	KUNIT_CASE(raid6_benchmark),	{},}; static int raid6_suite_init(struct kunit_suite *suite){	int i; 	prandom_seed_state(&rng, RAID6_KUNIT_SEED); 	/*	 * Allocate the test buffer using vmalloc() with a page-aligned length	 * so that it is immediately followed by a guard page.  This allows	 * buffer overreads to be detected, even in assembly code.	 */	test_buflen = round_up(RAID6_KUNIT_ALLOC_BYTES, PAGE_SIZE);	for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++) {		test_recov_buffers[i] = vmalloc(test_buflen);		if (!test_recov_buffers[i])			goto out_free_recov_buffers;	}	for (i = 0; i < RAID6_KUNIT_MAX_BUFFERS; i++) {		test_buffers[i] = vmalloc(test_buflen);		if (!test_buffers[i])			goto out_free_buffers;	} 	makedata(0, RAID6_KUNIT_MAX_BUFFERS - 1); 	return 0; out_free_buffers:	for (i = 0; i < RAID6_KUNIT_MAX_BUFFERS; i++)		vfree(test_buffers[i]);	memset(test_buffers, 0, sizeof(test_buffers));out_free_recov_buffers:	for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++)		vfree(test_recov_buffers[i]);	memset(test_recov_buffers, 0, sizeof(test_recov_buffers));	return -ENOMEM;} static void raid6_suite_exit(struct kunit_suite *suite){	int i; 	for (i = 0; i < RAID6_KUNIT_MAX_BUFFERS; i++)		vfree(test_buffers[i]);	memset(test_buffers, 0, sizeof(test_buffers));	for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++)		vfree(test_recov_buffers[i]);	memset(test_recov_buffers, 0, sizeof(test_recov_buffers));} static struct kunit_suite raid6_test_suite = {	.name		= "raid6",	.test_cases	= raid6_test_cases,	.suite_init	= raid6_suite_init,	.suite_exit	= raid6_suite_exit,};kunit_test_suite(raid6_test_suite); MODULE_DESCRIPTION("Unit test for the RAID P/Q library functions");MODULE_LICENSE("GPL");