[PATCH] stress tester for EROFS filesystem

Gao Xiang hsiangkao at aol.com
Fri Feb 7 00:56:31 AEDT 2020


Introduce an open-source stress tester for EROFS to
do kernel regression test since fsstress cannot be
used in all read-only filesystems.

When it runs, given workers are spawned and read data
in different patterns. At the same time, stress tester
automatically drops page/slab caches and triggers page
compaction as well. Therefore, root permission is required.

Take a regression for example, revert

commit a112152f6f3a ("staging: erofs: fix mis-acted TAIL merging behavior")

will cause corrupted data due to bad tail-merging.

The issue can also be simply caused by killing
the following 2 lines in z_erofs_do_read_page():

if (cur)
	tight &= (clt->mode >= COLLECT_PRIMARY_FOLLOWED);

With enwik9 [1] dataset, it can then be observed by
using stress tester:

$ sudo ./stress testdir/enwik9 enwik9 > /dev/null

and output such messages:

doscan: 118784 bytes mismatch @ 9383936
test failed (17673): Bad message
...

[1] https://cs.fit.edu/~mmahoney/compression/textdata.html
Signed-off-by: Gao Xiang <hsiangkao at aol.com>
---
 stress.c | 385 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 385 insertions(+)
 create mode 100644 stress.c

diff --git a/stress.c b/stress.c
new file mode 100644
index 0000000..de23580
--- /dev/null
+++ b/stress.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * stress test for EROFS read-only filesystem
+ *
+ * Copyright (C) 2019-2020 Gao Xiang <hsiangkao at aol.com>
+ */
+#define _LARGEFILE64_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#define PAGE_SHIFT	12
+#define PAGE_SIZE	(1 << PAGE_SHIFT)
+#define MAX_CHUNKSIZE	(4 * 1024 * 1024)
+#define MAX_SCAN_CHUNKSIZE	(256 * 1024)
+
+unsigned int nprocs = 512;
+sig_atomic_t should_stop = 0;
+
+enum {
+	RANDSCAN_ALIGNED,
+	RANDSCAN_UNALIGNED,
+	RANDREAD,		/* oneshot randread */
+	DROP_FILE_CACHE_RAND,
+	DROP_FILE_CACHE_ALL,
+	DROP_PAGE_CACHE,
+	DROP_SLAB_CACHE,
+	COMPACT_MEMORY,
+};
+
+const int globalop[] = {
+	RANDSCAN_ALIGNED,
+	RANDSCAN_UNALIGNED,
+	RANDSCAN_UNALIGNED,
+	RANDREAD,
+	RANDREAD,
+	RANDREAD,
+	DROP_FILE_CACHE_ALL,
+	DROP_PAGE_CACHE,
+	DROP_SLAB_CACHE,
+	COMPACT_MEMORY,
+};
+
+#define GLOBALOPS	(sizeof(globalop) / sizeof(globalop[0]))
+
+int drop_caches(int mode)
+{
+	static const char *procfile[] = {
+		[DROP_PAGE_CACHE] = "/proc/sys/vm/drop_caches",
+		[DROP_SLAB_CACHE] = "/proc/sys/vm/drop_caches",
+		[COMPACT_MEMORY] = "/proc/sys/vm/compact_memory",
+	};
+	static const char *val[] = {
+		[DROP_PAGE_CACHE] = "1\n",
+		[DROP_SLAB_CACHE] = "2\n",
+		[COMPACT_MEMORY] = "1\n",
+	};
+	FILE *f;
+	clock_t start;
+
+	if (!procfile[mode])
+		return -EINVAL;
+
+	printf("drop_caches(%u): %s=%s", getpid(), procfile[mode], val[mode]);
+
+	f = fopen(procfile[mode], "w");
+	if (!f)
+		return -errno;
+
+	start = clock();
+	while (clock() < start + CLOCKS_PER_SEC) {
+		fputs(val[mode], f);
+		sleep(0);
+	}
+	fclose(f);
+	return 0;
+}
+
+int drop_file_cache(int fd, int mode)
+{
+	clock_t start;
+
+	printf("drop_file_cache(%u)\n", getpid());
+	start = clock();
+	while (clock() < start + CLOCKS_PER_SEC / 2) {
+		posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+		sleep(0);
+	}
+	return 0;
+}
+
+int tryopen(char *filename)
+{
+	int fd = open(filename, O_RDONLY);
+
+	if (fd < 0)
+		return -errno;
+
+	/* use force_page_cache_readahead for every read request */
+	posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);
+	return fd;
+}
+
+int doscan(int fd, int chkfd, uint64_t filesize, uint64_t chunksize)
+{
+	static char buf[MAX_SCAN_CHUNKSIZE], chkbuf[MAX_SCAN_CHUNKSIZE];
+	uint64_t pos;
+
+	printf("doscan(%u): filesize: %llu, chunksize: %llu\n",
+	       getpid(), (unsigned long long)filesize,
+	       (unsigned long long)chunksize);
+
+	for (pos = 0; pos < filesize; pos += chunksize) {
+		ssize_t nread, nread2;
+
+		nread = pread64(fd, buf, chunksize, pos);
+
+		if (nread <= 0)
+			return -errno;
+
+		if (nread < chunksize && nread != filesize - pos)
+			return -ERANGE;
+
+		if (chkfd < 0)
+			continue;
+
+		nread2 = pread64(chkfd, chkbuf, chunksize, pos);
+		if (nread2 <= 0)
+			return -errno;
+
+		if (nread != nread2)
+			return -EFBIG;
+
+		if (memcmp(buf, chkbuf, nread)) {
+			fprintf(stderr, "doscan: %llu bytes mismatch @ %llu\n",
+				(unsigned long long)chunksize,
+				(unsigned long long)pos);
+			return -EBADMSG;
+		}
+	}
+	return 0;
+}
+
+int randread(int fd, int chkfd, uint64_t filesize)
+{
+	static char buf[MAX_CHUNKSIZE], chkbuf[MAX_CHUNKSIZE];
+
+	uint64_t start = (random() * random()) % filesize;
+	uint64_t length, count;
+	size_t nread, nread2;
+
+	count = 0;
+	do {
+		length = (random() * random()) % MAX_CHUNKSIZE;
+		if (++count > 1000 && length)
+			break;
+	} while (start + length > filesize || !length);
+
+	if (start + length > filesize)
+		length = filesize - start;
+
+	printf("randread(%u): %llu bytes @ %llu\n",
+	       getpid(), (unsigned long long)length,
+	       (unsigned long long)start);
+
+	nread = pread64(fd, buf, length, start);
+
+	if (nread != length)
+		return -errno;
+
+	if (chkfd < 0)
+		return 0;
+
+	nread2 = pread64(chkfd, chkbuf, length, start);
+	if (nread2 <= 0)
+		return -errno;
+
+	if (nread != nread2)
+		return -EFBIG;
+
+	if (memcmp(buf, chkbuf, nread)) {
+		fprintf(stderr, "randread: %llu bytes mismatch @ %llu\n",
+			(unsigned long long)length,
+			(unsigned long long)start);
+		return -EBADMSG;
+	}
+	return 0;
+}
+
+int testfd(int fd, int chkfd, int mode)
+{
+	const off64_t filesize = lseek64(fd, 0, SEEK_END);
+	uint64_t chunksize, maxchunksize;
+	int err;
+
+	if (mode == RANDSCAN_ALIGNED) {
+		maxchunksize = (filesize - PAGE_SIZE > MAX_SCAN_CHUNKSIZE ?
+				MAX_SCAN_CHUNKSIZE : filesize - PAGE_SIZE);
+
+		chunksize = random() * random() % maxchunksize;
+		chunksize = (((chunksize - 1) >> PAGE_SHIFT) + 1)
+			<< PAGE_SHIFT;
+		if (!chunksize)
+			chunksize = PAGE_SIZE;
+		err = doscan(fd, chkfd, filesize, chunksize);
+		if (err)
+			return err;
+	} else if (mode == RANDSCAN_UNALIGNED) {
+		chunksize = (random() * random() % MAX_SCAN_CHUNKSIZE) + 1;
+		err = doscan(fd, chkfd, filesize, chunksize);
+		if (err)
+			return err;
+	} else if (mode == RANDREAD) {
+		err = randread(fd, chkfd, filesize);
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+void randomdelay(void)
+{
+	clock_t start;
+	clock_t length = (random() * random() % CLOCKS_PER_SEC) >> 1;
+
+	start = clock();
+	while (clock() < start + length)
+		sleep(0);
+}
+
+void sg_handler(int signum)
+{
+	switch (signum) {
+	case SIGTERM:
+		should_stop = 1;
+		break;
+	default:
+		break;
+	}
+}
+
+static char *testfile, *comprfile;
+
+static int parse_options(int argc, char *argv[])
+{
+	int opt;
+
+	while ((opt = getopt(argc, argv, "p:")) != -1) {
+		switch (opt) {
+		case 'p':
+			nprocs = atoi(optarg);
+			if (nprocs < 0) {
+				fprintf(stderr, "invalid workers %d\n",
+					nprocs);
+				return -EINVAL;
+			}
+			break;
+		default: /* '?' */
+			return -EINVAL;
+		}
+	}
+
+	if (optind >= argc)
+		return -EINVAL;
+
+	testfile = argv[optind++];
+
+	if (argc > optind)
+		comprfile = argv[optind++];
+	return 0;
+}
+
+void usage(void)
+{
+	fputs("usage: [options] TESTFILE [COMPRFILE]\n\n"
+	      "stress tester for read-only filesystems\n"
+	      " -p#     set workers to #\n", stderr);
+}
+
+int main(int argc, char *argv[])
+{
+	unsigned int i;
+	int err, stat;
+	int fd, chkfd;
+	struct sigaction action;
+
+	err = parse_options(argc, argv);
+	if (err) {
+		if (err == -EINVAL)
+			usage();
+		return 1;
+	}
+
+	if (testfile) {
+		fd = tryopen(testfile);
+		if (fd < 0) {
+			fprintf(stderr, "cannot open testfile %s: %s\n",
+				testfile, strerror(errno));
+			return 1;
+		}
+	}
+
+	chkfd = -1;
+	if (comprfile) {
+		chkfd = tryopen(comprfile);
+		if (chkfd < 0) {
+			fprintf(stderr, "cannot open comprfile %s: %s\n",
+				comprfile, strerror(errno));
+			return 1;
+		}
+	}
+	setpgid(0, 0);
+	action.sa_handler = sg_handler;
+	action.sa_flags = 0;
+
+	if (sigaction(SIGTERM, &action, 0)) {
+		perror("sigaction failed");
+		exit(1);
+	}
+
+	/* spawn nprocs processes */
+	for (i = 0; i < nprocs; ++i) {
+		if (fork() == 0) {
+			sigemptyset(&action.sa_mask);
+			if (sigaction(SIGTERM, &action, 0)) {
+				perror("sigaction failed");
+				exit(1);
+			}
+
+			srandom(clock() * i);
+			while (!should_stop) {
+				int op = globalop[random() % GLOBALOPS];
+
+				if (op == DROP_FILE_CACHE_RAND ||
+				    op == DROP_FILE_CACHE_ALL) {
+					err = drop_file_cache(fd, op);
+				} else if (op <= RANDREAD) {
+					randomdelay();
+					err = testfd(fd, chkfd, op);
+				} else {
+					err = drop_caches(op);
+				}
+
+				if (err) {
+					fprintf(stderr, "test failed (%u): %s\n",
+						getpid(), strerror(-err));
+					exit(1);
+				}
+			}
+			return 0;
+		}
+	}
+
+	err = 0;
+	while (wait(&stat) > 0 && !should_stop) {
+		if (!WIFEXITED(stat)) {
+			err = 1;
+			break;
+		}
+
+		if (WEXITSTATUS(stat)) {
+			err = WEXITSTATUS(stat);
+			break;
+		}
+	}
+	action.sa_flags = SA_RESTART;
+	sigaction(SIGTERM, &action, 0);
+	kill(-getpid(), SIGTERM);
+	/* wait until all children exit */
+	while (wait(&stat) > 0)
+		continue;
+
+	if (chkfd >= 0)
+		close(chkfd);
+	close(fd);
+	return err;
+}
+
-- 
2.20.1



More information about the Linux-erofs mailing list