Merge "memory_prof: add mmap memcpy throughput test"
diff --git a/memory_prof/memory_prof.c b/memory_prof/memory_prof.c
index 0255f03..2e487b4 100644
--- a/memory_prof/memory_prof.c
+++ b/memory_prof/memory_prof.c
@@ -986,6 +986,79 @@
 	}
 }
 
+/**
+ * Memory throughput test. Print some stats.
+ */
+static void mmap_memcpy_test(void)
+{
+	char *chunk, *src, *dst;
+	struct timeval tv;
+	double *data_rates;
+	double sum = 0, sum_of_squares = 0, average, std_dev, dmin, dmax;
+	int iters = 1000, memcpy_iters = 100;
+	int i, j;
+	const size_t chunk_len = SZ_1M;
+
+	MALLOC(double *, data_rates, iters * sizeof(double));
+
+	chunk = mmap(NULL, chunk_len,
+		PROT_READ | PROT_WRITE,
+		MAP_PRIVATE | MAP_ANONYMOUS,
+		-1, 0);
+	if (chunk == MAP_FAILED) {
+		perror("Couldn't allocate 1MB buffer with mmap\n");
+		goto free_data_rates;
+	}
+
+	/*
+	 * Some systems appear to do this trick where they mprotect
+	 * the first page of a large allocation with
+	 * PROT_NONE. Possibly to protect against someone else
+	 * overflowing into their buffer? We do it here just to
+	 * emulate the "real-world" a little closer.
+	 */
+	if (mprotect(chunk, SZ_4K, PROT_NONE)) {
+		perror("Couldn't mprotect the first page of the 1MB buffer\n");
+		goto munmap_chunk;
+	}
+
+	src = chunk + SZ_4K;
+	dst = chunk + SZ_512K;
+	memset(src, 0x5a, SZ_64K);
+	memset(dst, 0xa5, SZ_64K);
+
+	for (i = 0; i < iters; ++i) {
+		float elapsed_ms;
+		mprof_tick(&tv);
+		for (j = 0; j < memcpy_iters; ++j)
+			memcpy(dst, src, SZ_64K);
+		elapsed_ms = mprof_tock(&tv);
+		/* units: MB/s */
+		data_rates[i] = ((SZ_64K * memcpy_iters) / SZ_1M)
+			/ (elapsed_ms / 1000);
+	}
+
+	dmin = dmax = data_rates[0];
+	for (i = 0; i < iters; ++i) {
+		sum += data_rates[i];
+		dmin = MIN(dmin, data_rates[i]);
+		dmax = MAX(dmax, data_rates[i]);
+	}
+	average = sum / iters;
+
+	for (i = 0; i < iters; ++i)
+		sum_of_squares += pow(data_rates[i] - average, 2);
+	std_dev = sqrt(sum_of_squares / iters);
+
+	printf("average: %.3f MB/s, min: %.3f MB/s, max: %.3f MB/s, std_dev: %.3f MB/s\n",
+		average, dmin, dmax, std_dev);
+
+munmap_chunk:
+	munmap(chunk, chunk_len);
+free_data_rates:
+	free(data_rates);
+}
+
 static int file_exists(const char const *fname)
 {
 	struct stat tmp;
@@ -1017,7 +1090,10 @@
 	"  -s         Do the stress test (same as -e)\n"		\
 	"  -t MB      Size (in MB) of temp buffer pre-allocated before Ion allocations (default 0 MB)\n" \
 	"  --ion-memcpy-test\n" \
-	"             Does some memcpy's between various types of Ion buffers\n"
+	"             Does some memcpy's between various types of Ion buffers\n" \
+	"  --mmap-memcpy-test\n" \
+	"             Does some memcpy's between some large buffers allocated\n" \
+	"             by mmap\n"
 
 static void usage(char *progname)
 {
@@ -1026,6 +1102,7 @@
 
 static struct option memory_prof_options[] = {
 	{"ion-memcpy-test", no_argument, 0, 0},
+	{"mmap-memcpy-test", no_argument, 0, 0},
 	{0, 0, 0, 0}
 };
 
@@ -1040,6 +1117,7 @@
 	bool do_oom_test = false;
 	bool do_leak_test = false;
 	bool do_ion_memcpy_test = false;
+	bool do_mmap_memcpy_test = false;
 	const char *alloc_profile = NULL;
 	int num_reps = 1;
 	int num_heap_prof_reps = -1;
@@ -1060,6 +1138,12 @@
 				do_ion_memcpy_test = true;
 				break;
 			}
+			if (strcmp("mmap-memcpy-test",
+					memory_prof_options[option_index].name)
+				== 0) {
+				do_mmap_memcpy_test = true;
+				break;
+			}
 			printf("Unhandled longopt: %s\n",
 				memory_prof_options[option_index].name);
 			break;
@@ -1139,5 +1223,8 @@
 	if (do_ion_memcpy_test)
 		ion_memcpy_test();
 
+	if (do_mmap_memcpy_test)
+		mmap_memcpy_test();
+
 	return rc;
 }