All of lore.kernel.org
 help / color / mirror / Atom feed
From: Gao Xiang via Linux-erofs <linux-erofs@lists.ozlabs.org>
To: linux-erofs@lists.ozlabs.org
Cc: Miao Xie <miaoxie@huawei.com>
Subject: [PATCH] stress tester for EROFS filesystem
Date: Thu,  6 Feb 2020 21:56:31 +0800	[thread overview]
Message-ID: <20200206135631.1491-1-hsiangkao@aol.com> (raw)
In-Reply-To: 20200206135631.1491-1-hsiangkao.ref@aol.com

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@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@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


           reply	other threads:[~2020-02-06 13:57 UTC|newest]

Thread overview: expand[flat|nested]  mbox.gz  Atom feed
 [parent not found: <20200206135631.1491-1-hsiangkao.ref@aol.com>]

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=20200206135631.1491-1-hsiangkao@aol.com \
    --to=linux-erofs@lists.ozlabs.org \
    --cc=hsiangkao@aol.com \
    --cc=miaoxie@huawei.com \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.