Stress tests to explore the 3.8 sync slowdown issue

A couple of tests that I wrote to help figure out why in some cases
sync is slower in 3.8 than in 3.4

BUG=chromium:240411
TEST=run crdel and usync on client

Change-Id: Ia633552297ad6ca9e68e6db4dd51a8e832f8e8b9
Reviewed-on: https://gerrit.chromium.org/gerrit/59489
Commit-Queue: Paul Taysom <taysom@chromium.org>
Reviewed-by: Paul Taysom <taysom@chromium.org>
Tested-by: Paul Taysom <taysom@chromium.org>
diff --git a/file.m/crdel.c b/file.m/crdel.c
new file mode 100644
index 0000000..99d5f85
--- /dev/null
+++ b/file.m/crdel.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Like hammer, this test stresses the file system but instead of
+ * using writes to one big file, crdel uses lots of little files.
+ * Uses a circular buffer of files that are created, written and
+ * then deleted. The creation and deletion run synchrounous but
+ * keep the circular buffer at least 90% full.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <eprintf.h>
+#include <esys.h>
+#include <puny.h>
+#include <style.h>
+#include <timer.h>
+#include <twister.h>
+#include <util.h>
+
+enum {	MAX_NAME = 12,
+	MAX_FILE_SIZE = 1 << 14,
+	NUM_FILES = 1 << 16 };
+
+struct file {
+	char name[MAX_NAME];
+};
+
+bool Done = FALSE;
+struct file *File;
+u64 Num_files = NUM_FILES;
+int Next;
+int Last;
+unint Commited;
+unint Deleted;
+
+static bool is_empty(void)
+{
+	return Next == Last;
+}
+
+static bool is_full(void)
+{
+	int next = Next + 1;
+	if (next == Num_files) {
+		return Last == 0;
+	} else {
+		return next == Last;
+	}
+}
+
+static void commit(void)
+{
+	if (++Next == Num_files)
+		Next = 0;
+	++Commited;
+}
+
+static void delete(void)
+{
+	if (++Last == Num_files)
+		Last = 0;
+	++Deleted;
+}
+
+static void doze(void)
+{
+	struct timespec sleep = { 0, ONE_THOUSAND /* ns */};
+
+	nanosleep(&sleep, 0);
+}
+
+static void fill(u8 *buf, int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++) {
+		buf[i] = twister_random();
+	}
+}
+
+void *creator(void *unused)
+{
+	u8 *buf = emalloc(Option.file_size);
+	int fd;
+
+	fill(buf, Option.file_size);
+	while (!Done) {
+		while (is_full())
+			doze();
+		gen_name(File[Next].name, MAX_NAME);
+		fd = ecreat(File[Next].name);
+		/* Can't advance Next until after file is created */
+		commit();
+		ewrite(fd, buf, Option.file_size);
+		eclose(fd);
+	}
+	return NULL;
+}
+
+void *unlinker(void *unused)
+{
+	while (!Done) {
+		while (is_empty())
+			doze();
+		if (((Commited - Deleted) * 100 / Num_files) > 90) {
+			eunlink(File[Last].name);
+			delete();
+		} else {
+			doze();
+		}
+	}
+	while (!is_empty()) {
+		eunlink(File[Last].name);
+		delete();
+	}
+	return NULL;
+}
+
+void *syncer(void *unused)
+{
+	struct timespec sleep = { 20 /* s */, 0 /* ns */};
+	u64 start;
+	u64 finish;
+	u64 i;
+
+	for (i = 0; !Done; i++) {
+		start = nsecs();
+		esync();
+		finish = nsecs();
+		printf("sync: %4llu. %3.2g\n", i,
+			(double)(finish - start)/ONE_BILLION);
+		nanosleep(&sleep, NULL);
+	}
+	return NULL;
+}
+
+void *timer(void *arg)
+{
+	struct timespec sleep = { Option.sleep_secs, 0 /* ns */ };
+	unint old_num_created;
+	unint old_num_unlinked;
+	unint delta_created;
+	unint delta_unlinked;
+	u64 i;
+
+	printf("secs created/sec deleted/sec\n");
+	for (i = 0; !Done; i++) {
+		old_num_unlinked = Deleted;
+		old_num_created = Commited;
+		nanosleep(&sleep, NULL);
+		delta_unlinked = Deleted - old_num_unlinked;
+		delta_created = Commited - old_num_created;
+		printf("%4llu. %10ld %10ld\n", i, delta_created, delta_unlinked);
+	}
+	return NULL;
+}
+
+void cleanup(void)
+{
+	static bool cleaning_up = FALSE;
+	char cmd[1024];
+	int rc;
+
+	if (!Option.cleanup || cleaning_up)
+		return;
+	cleaning_up = TRUE;
+	echdir("..");
+	rc = snprintf(cmd, sizeof(cmd), "rm -fr %s", Option.dir);
+	if (rc > sizeof(cmd) - 2) {	/* shouldn't be that big */
+		eprintf("counldn't cleanup %s", cmd);
+	}
+	esystem(cmd);
+}
+
+void setup(void)
+{
+	emkdir(Option.dir);
+	echdir(Option.dir);
+	File = ezalloc(sizeof(*File) * Num_files);
+	set_cleanup(cleanup);
+}
+
+void usage(void)
+{
+	pr_usage("-d<directory> -n<number of files> -z<size in bytes>\n"
+		"  busy continuously creates and deletes files\n"
+		"  It displays the create/delete in seconds\n"
+		"\td - directory to create paths\n"
+		"\tn - max files to create\n"
+		"\ts - seconds to sleep between reports\n"
+		"\tz - size in MiBs of file to hammer system");
+}
+
+bool myopt(int c)
+{
+	switch (c) {
+	case 'n':
+		Num_files = strtoll(optarg, NULL, 0);
+		break;
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+void epthread_create(const char *name, pthread_t *thread,
+	const pthread_attr_t *attr, void *(*start_routine) (void *),
+	void *arg)
+{
+	int rc;
+
+	rc = pthread_create(thread, attr, start_routine, arg);
+	if (rc)
+		fatal("%s thread:");
+}
+
+int main(int argc, char *argv[])
+{
+	pthread_t timer_thread;
+	pthread_t creator_thread;
+	pthread_t unlinker_thread;
+	pthread_t syncer_thread;
+
+	Option.file_size = MAX_FILE_SIZE;
+	Option.sleep_secs = 1;
+
+	punyopt(argc, argv, myopt, "n:");
+	setup();
+
+	epthread_create("timer", &timer_thread, NULL, timer, NULL);
+	epthread_create("creator", &creator_thread, NULL, creator, NULL);
+	epthread_create("unlinker", &unlinker_thread, NULL, unlinker, NULL);
+	epthread_create("syncer", &syncer_thread, NULL, syncer, NULL);
+
+	pthread_join(syncer_thread, NULL);
+	pthread_join(creator_thread, NULL);
+	pthread_join(unlinker_thread, NULL);
+	pthread_join(timer_thread, NULL);
+
+	cleanup();
+	return 0;
+}
diff --git a/file.m/usync.c b/file.m/usync.c
new file mode 100644
index 0000000..58b6055
--- /dev/null
+++ b/file.m/usync.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Does time test of sync for creating files, reading files
+ * and deleting files.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <eprintf.h>
+#include <esys.h>
+#include <puny.h>
+#include <style.h>
+#include <timer.h>
+#include <twister.h>
+#include <util.h>
+
+enum {	MAX_NAME = 12,
+	DEFAULT_FILE_SIZE = 1 << 14,
+	BYTES_TO_READ = 1,
+	NUM_START_FILES = 1 << 9,
+	NUM_FILES = 1 << 18 };
+
+struct file {
+	char name[MAX_NAME];
+};
+
+static void cleanup(void);
+
+struct file *File;
+u64 Num_files = NUM_FILES;
+u64 Num_bytes_to_read = BYTES_TO_READ;
+bool Prompt = FALSE;
+
+static void prompt(const char *label)
+{
+	int c;
+
+	if (Prompt) {
+		printf("%s > ", label);
+		for (;;) {
+			c = getchar();
+			switch (c) {
+			case EOF:
+			case 'q':
+				cleanup();
+				exit(0);
+				break;
+			case 'p':
+				Prompt = FALSE;
+				break;
+			case '\n':
+				return;
+			default:
+				/* ignore;  */
+				break;
+			}
+		}
+	}
+}
+
+static void fill(u8 *buf, int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++) {
+		buf[i] = twister_random();
+	}
+}
+
+static void createfiles(int num_files)
+{
+	u8 *buf = emalloc(Option.file_size);
+	int fd;
+	int i;
+
+	fill(buf, Option.file_size);
+	for (i = 0; i < num_files; i++) {
+		gen_name(File[i].name, MAX_NAME);
+		fd = ecreat(File[i].name);
+		ewrite(fd, buf, Option.file_size);
+		eclose(fd);
+	}
+	free(buf);
+}
+
+static void unlinkfiles(int num_files)
+{
+	int i;
+
+	for (i = 0; i < num_files; i++) {
+		eunlink(File[i].name);
+	}
+}
+
+static void readfiles(int num_files)
+{
+	u64 bytes_to_read = Num_bytes_to_read;
+	u8 *buf;
+	int fd;
+	int i;
+
+	if (bytes_to_read > Option.file_size)
+		bytes_to_read = Option.file_size;
+	buf = emalloc(bytes_to_read);
+	for (i = 0; i < num_files; i++) {
+		fd = eopen(File[i].name, O_RDONLY);
+		eread(fd, buf, bytes_to_read);
+		eclose(fd);
+	}
+	free(buf);
+}
+
+static void time_sync(const char *label, int n)
+{
+	u64 start;
+	u64 finish;
+
+	start = nsecs();
+	esync();
+	finish = nsecs();
+	printf("%10s %8d. %g ms\n",
+		label, n, (double)(finish - start)/1000000.0);
+	prompt("sync done");
+}
+
+void crsyncdel(int n)
+{
+	createfiles(n);
+	prompt("create done");
+	time_sync("create", n);
+	readfiles(n);
+	prompt("read done");
+	time_sync("read", n);
+	unlinkfiles(n);
+	prompt("unlink done");
+	time_sync("unlink", n);
+}
+
+static void cleanup(void)
+{
+	static bool	cleaning_up = FALSE;
+
+	char	cmd[1024];
+	int	rc;
+
+	if (!Option.cleanup || cleaning_up) return;
+	cleaning_up = TRUE;
+	echdir("..");
+	rc = snprintf(cmd, sizeof(cmd), "rm -fr %s", Option.dir);
+	if (rc >= sizeof(cmd)) {
+		eprintf("counldn't cleanup %s", cmd);
+	}
+	esystem(cmd);
+}
+
+static void setup(void)
+{
+	emkdir(Option.dir);
+	echdir(Option.dir);
+	File = ezalloc(sizeof(*File) * Num_files);
+	set_cleanup(cleanup);
+}
+
+void usage(void)
+{
+	pr_usage("-p -d<directory> -n<number of files> -z<size in bytes>\n"
+		"  Times sync after creating files, reading them and then\n"
+		"  deleting them. Uses powers of 2 starting with %d.\n"
+		"\td - directory to create for files\n"
+		"\tn - maximum number files to create\n"
+		"\tp - prompt after each set of operations\n"
+		"\tz - size bytes of each file",
+		NUM_START_FILES);
+}
+
+bool myopt(int c)
+{
+	switch (c) {
+	case 'n':
+		Num_files = strtoll(optarg, NULL, 0);
+		break;
+	case 'p':
+		Prompt = TRUE;
+		break;
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+int main(int argc, char *argv[])
+{
+	int i;
+
+	Option.file_size = DEFAULT_FILE_SIZE;
+
+	punyopt(argc, argv, myopt, "n:p");
+	setup();
+
+	esync();
+	/*
+	 * Number of files grows by powers of two until the
+	 * specified number of files is reached. I start
+	 * at a large enough number so I see more than just
+	 * noise.
+	 */
+	for (i = NUM_START_FILES; i <= Num_files; i <<= 1) {
+		crsyncdel(i);
+	}
+	cleanup();
+	return 0;
+}
diff --git a/include/eprintf.h b/include/eprintf.h
index d70158b..6076c70 100644
--- a/include/eprintf.h
+++ b/include/eprintf.h
@@ -53,6 +53,7 @@
 void *ezalloc  (size_t);
 void *erealloc (void *, size_t);
 void *eallocpages (size_t npages, size_t size);
+void esystem(const char *command);
 char *estrdup  (const char *);
 
 void setprogname (const char *);
diff --git a/include/esys.h b/include/esys.h
index c655e66..99baa64 100644
--- a/include/esys.h
+++ b/include/esys.h
@@ -8,9 +8,11 @@
  */
 
 void eclose(int fd);
+void echdir(const char *pathname);
 int ecreat(const char *pathname);
 void efsync(int fd);
 void efdatasync(int fd);
+void emkdir(const char *pathname);
 int eopen(const char *pathname, int flags);
 int eread(int fd, void *buf, size_t nbyte);
 int epread(int fd, void *buf, size_t nbyte, off_t offset);
diff --git a/libpuny.b/eprintf.c b/libpuny.b/eprintf.c
index b6578b5..42f2f29 100644
--- a/libpuny.b/eprintf.c
+++ b/libpuny.b/eprintf.c
@@ -229,6 +229,19 @@
 	return t;
 }
 
+/* esystem: execute a shell command, exit on error */
+void esystem(const char *command)
+{
+	int rc = system(command);
+
+	if (rc == -1) {
+		fatal("system: %s:", command);
+	}
+	if (rc != 0) {
+		fatal("system: %s exit=%d", command, rc);
+	}
+}
+
 #if __linux__ || __WINDOWS__
 static char *name = NULL;  /* program name for messages */
 
diff --git a/libpuny.b/esys.c b/libpuny.b/esys.c
index d61558a..faf88e8 100644
--- a/libpuny.b/esys.c
+++ b/libpuny.b/esys.c
@@ -26,9 +26,16 @@
 		fatal("close:");
 }
 
+void echdir(const char *pathname)
+{
+	int rc = chdir(pathname);
+	if (rc == -1)
+		fatal("chdir %s:", pathname);
+}
+
 int ecreat(const char *pathname)
 {
-	int fd = creat(pathname, 0666);
+	int fd = creat(pathname, 0600);
 	if (fd == -1)
 		fatal("creat %s:", pathname);
 	return fd;
@@ -48,9 +55,16 @@
 		fatal("fdatasync:");
 }
 
+void emkdir(const char *pathname)
+{
+	int rc = mkdir(pathname, 0700);
+	if (rc == -1)
+		fatal("mkdir %s:", pathname);
+}
+
 int eopen(const char *pathname, int flags)
 {
-	int fd = open(pathname, flags, 0666);
+	int fd = open(pathname, flags, 0600);
 	if (fd == -1)
 		fatal("open %s:", pathname);
 	return fd;