[RFC 15/16] NOVA: Performance measurement

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Steven Swanson <swanson@xxxxxxxxxxx>
---
 fs/nova/perf.c  |  594 ++++++++++++++++++++++++++++++++++++++++++++++++
 fs/nova/perf.h  |   96 ++++++++
 fs/nova/stats.c |  685 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/nova/stats.h |  218 ++++++++++++++++++
 4 files changed, 1593 insertions(+)
 create mode 100644 fs/nova/perf.c
 create mode 100644 fs/nova/perf.h
 create mode 100644 fs/nova/stats.c
 create mode 100644 fs/nova/stats.h

diff --git a/fs/nova/perf.c b/fs/nova/perf.c
new file mode 100644
index 000000000000..35a4c6a490c3
--- /dev/null
+++ b/fs/nova/perf.c
@@ -0,0 +1,594 @@
+/*
+ * BRIEF DESCRIPTION
+ *
+ * Performance test routines
+ *
+ * Copyright 2015-2016 Regents of the University of California,
+ * UCSD Non-Volatile Systems Lab, Andiry Xu <jix024@xxxxxxxxxxx>
+ * Copyright 2012-2013 Intel Corporation
+ * Copyright 2009-2011 Marco Stornelli <marco.stornelli@xxxxxxxxx>
+ * Copyright 2003 Sony Corporation
+ * Copyright 2003 Matsushita Electric Industrial Co., Ltd.
+ * 2003-2004 (c) MontaVista Software, Inc. , Steve Longerbeam
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include "perf.h"
+
+/* normal memcpy functions */
+static int memcpy_read_call(char *dst, char *src, size_t off, size_t size)
+{
+	/* pin dst address to cache most writes, if size fits */
+	memcpy(dst, src + off, size);
+	return 0;
+}
+
+static int memcpy_write_call(char *dst, char *src, size_t off, size_t size)
+{
+	/* pin src address to cache most reads, if size fits */
+	memcpy(dst + off, src, size);
+	return 0;
+}
+
+static int memcpy_bidir_call(char *dst, char *src, size_t off, size_t size)
+{
+	/* minimize caching by forwarding both src and dst */
+	memcpy(dst + off, src + off, size);
+	return 0;
+}
+
+static const memcpy_call_t memcpy_calls[] = {
+	/* order should match enum memcpy_call_id */
+	{ "memcpy (mostly read)",  memcpy_read_call },
+	{ "memcpy (mostly write)", memcpy_write_call },
+	{ "memcpy (read write)",   memcpy_bidir_call }
+};
+
+/* copy from pmem functions */
+static int from_pmem_call(char *dst, char *src, size_t off, size_t size)
+{
+	/* pin dst address to cache most writes, if size fits */
+	/* src address should point to pmem */
+	memcpy_mcsafe(dst, src + off, size);
+	return 0;
+}
+
+static const memcpy_call_t from_pmem_calls[] = {
+	/* order should match enum from_pmem_call_id */
+	{ "memcpy_mcsafe", from_pmem_call }
+};
+
+/* copy to pmem functions */
+static int to_pmem_nocache_call(char *dst, char *src, size_t off, size_t size)
+{
+	/* pin src address to cache most reads, if size fits */
+	/* dst address should point to pmem */
+	memcpy_to_pmem_nocache(dst + off, src, size);
+	return 0;
+}
+
+static int to_flush_call(char *dst, char *src, size_t off, size_t size)
+{
+	/* pin src address to cache most reads, if size fits */
+	/* dst address should point to pmem */
+	nova_flush_buffer(dst + off, size, 0);
+	return 0;
+}
+
+static int to_pmem_flush_call(char *dst, char *src, size_t off, size_t size)
+{
+	/* pin src address to cache most reads, if size fits */
+	/* dst address should point to pmem */
+	memcpy(dst + off, src, size);
+	nova_flush_buffer(dst + off, size, 0);
+	return 0;
+}
+
+static const memcpy_call_t to_pmem_calls[] = {
+	/* order should match enum to_pmem_call_id */
+	{ "memcpy_to_pmem_nocache", to_pmem_nocache_call },
+	{ "flush buffer",	    to_flush_call },
+	{ "memcpy + flush buffer",  to_pmem_flush_call }
+};
+
+/* checksum functions */
+static u64 zlib_adler32_call(u64 init, char *data, size_t size)
+{
+	u64 csum;
+
+	/* include/linux/zutil.h */
+	csum = zlib_adler32(init, data, size);
+	return csum;
+}
+
+static u64 nd_fletcher64_call(u64 init, char *data, size_t size)
+{
+	u64 csum;
+
+	/* drivers/nvdimm/core.c */
+	csum = nd_fletcher64(data, size, 1);
+	return csum;
+}
+
+static u64 libcrc32c_call(u64 init, char *data, size_t size)
+{
+	u32 crc = (u32) init;
+
+	crc = crc32c(crc, data, size);
+	return (u64) crc;
+}
+
+static u64 nova_crc32c_call(u64 init, char *data, size_t size)
+{
+	u32 crc = (u32) init;
+
+	crc = nova_crc32c(crc, data, size);
+	return (u64) crc;
+}
+
+static u64 plain_xor64_call(u64 init, char *data, size_t size)
+{
+	u64 csum = init;
+	u64 *word = (u64 *) data;
+
+	while (size > 8) {
+		csum ^= *word;
+		word += 1;
+		size -= 8;
+	}
+
+	/* for perf testing ignore trailing bytes, if any */
+
+	return csum;
+}
+
+static const checksum_call_t checksum_calls[] = {
+	/* order should match enum checksum_call_id */
+	{ "zlib_adler32",  zlib_adler32_call },
+	{ "nd_fletcher64", nd_fletcher64_call },
+	{ "libcrc32c",     libcrc32c_call },
+	{ "nova_crc32c",   nova_crc32c_call },
+	{ "plain_xor64",   plain_xor64_call }
+};
+
+/* raid5 functions */
+static u64 nova_block_parity_call(char **data, char *parity,
+	size_t size, int disks)
+{
+	int i, j, strp, num_strps = disks;
+	size_t strp_size = size;
+	char *block = *data;
+	u64 xor;
+
+	/* FIXME: using same code as in parity.c; need a way to reuse that */
+
+	if (static_cpu_has(X86_FEATURE_XMM2)) { // sse2 128b
+		for (i = 0; i < strp_size; i += 16) {
+			asm volatile("movdqa %0, %%xmm0" : : "m" (block[i]));
+			for (strp = 1; strp < num_strps; strp++) {
+				j = strp * strp_size + i;
+				asm volatile(
+					"movdqa     %0, %%xmm1\n"
+					"pxor   %%xmm1, %%xmm0\n"
+					: : "m" (block[j])
+				);
+			}
+			asm volatile("movntdq %%xmm0, %0" : "=m" (parity[i]));
+		}
+	} else { // common 64b
+		for (i = 0; i < strp_size; i += 8) {
+			xor = *((u64 *) &block[i]);
+			for (strp = 1; strp < num_strps; strp++) {
+				j = strp * strp_size + i;
+				xor ^= *((u64 *) &block[j]);
+			}
+			*((u64 *) &parity[i]) = xor;
+		}
+	}
+
+	return *((u64 *) parity);
+}
+
+static u64 nova_block_csum_parity_call(char **data, char *parity,
+	size_t size, int disks)
+{
+	int i;
+	size_t strp_size = size;
+	char *block = *data;
+	u32 volatile crc[8]; // avoid results being optimized out
+	u64 qwd[8];
+	u64 acc[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+
+	/* FIXME: using same code as in parity.c; need a way to reuse that */
+
+	for (i = 0; i < strp_size / 8; i++) {
+		qwd[0] = *((u64 *) (block));
+		qwd[1] = *((u64 *) (block + 1 * strp_size));
+		qwd[2] = *((u64 *) (block + 2 * strp_size));
+		qwd[3] = *((u64 *) (block + 3 * strp_size));
+		qwd[4] = *((u64 *) (block + 4 * strp_size));
+		qwd[5] = *((u64 *) (block + 5 * strp_size));
+		qwd[6] = *((u64 *) (block + 6 * strp_size));
+		qwd[7] = *((u64 *) (block + 7 * strp_size));
+
+		// if (data_csum > 0 && unroll_csum) {
+			nova_crc32c_qword(qwd[0], acc[0]);
+			nova_crc32c_qword(qwd[1], acc[1]);
+			nova_crc32c_qword(qwd[2], acc[2]);
+			nova_crc32c_qword(qwd[3], acc[3]);
+			nova_crc32c_qword(qwd[4], acc[4]);
+			nova_crc32c_qword(qwd[5], acc[5]);
+			nova_crc32c_qword(qwd[6], acc[6]);
+			nova_crc32c_qword(qwd[7], acc[7]);
+		// }
+
+		// if (data_parity > 0) {
+			parity[i] = qwd[0] ^ qwd[1] ^ qwd[2] ^ qwd[3] ^
+					qwd[4] ^ qwd[5] ^ qwd[6] ^ qwd[7];
+		// }
+
+		block += 8;
+	}
+	// if (data_csum > 0 && unroll_csum) {
+		crc[0] = cpu_to_le32((u32) acc[0]);
+		crc[1] = cpu_to_le32((u32) acc[1]);
+		crc[2] = cpu_to_le32((u32) acc[2]);
+		crc[3] = cpu_to_le32((u32) acc[3]);
+		crc[4] = cpu_to_le32((u32) acc[4]);
+		crc[5] = cpu_to_le32((u32) acc[5]);
+		crc[6] = cpu_to_le32((u32) acc[6]);
+		crc[7] = cpu_to_le32((u32) acc[7]);
+	// }
+
+	return *((u64 *) parity);
+}
+
+#if 0 // some test machines do not have this function (need CONFIG_MD_RAID456)
+static u64 xor_blocks_call(char **data, char *parity,
+	size_t size, int disks)
+{
+	int xor_cnt, disk_id;
+
+	memcpy(parity, data[0], size); /* init parity with the first disk */
+	disks--;
+	disk_id = 1;
+	while (disks > 0) {
+		/* each xor_blocks call can do at most MAX_XOR_BLOCKS (4) */
+		xor_cnt = min(disks, MAX_XOR_BLOCKS);
+		/* crypto/xor.c, used in lib/raid6 and fs/btrfs */
+		xor_blocks(xor_cnt, size, parity, (void **)(data + disk_id));
+
+		disks -= xor_cnt;
+		disk_id += xor_cnt;
+	}
+
+	return *((u64 *) parity);
+}
+#endif
+
+static const raid5_call_t raid5_calls[] = {
+	/* order should match enum raid5_call_id */
+	{ "nova_block_parity", nova_block_parity_call },
+	{ "nova_block_csum_parity", nova_block_csum_parity_call },
+//	{ "xor_blocks", xor_blocks_call },
+};
+
+/* memory pools for perf testing */
+static void *nova_alloc_vmem_pool(size_t poolsize)
+{
+	void *pool = vmalloc(poolsize);
+
+	if (pool == NULL)
+		return NULL;
+
+	/* init pool to verify some checksum results */
+	// memset(pool, 0xAC, poolsize);
+
+	/* to have a clean start, flush the data cache for the given virtual
+	 * address range in the vmap area
+	 */
+	flush_kernel_vmap_range(pool, poolsize);
+
+	return pool;
+}
+
+static void nova_free_vmem_pool(void *pool)
+{
+	if (pool != NULL)
+		vfree(pool);
+}
+
+static void *nova_alloc_pmem_pool(struct super_block *sb,
+	struct nova_inode_info_header *sih, int cpu, size_t poolsize,
+	unsigned long *blocknr, int *allocated)
+{
+	int num;
+	void *pool;
+	size_t blocksize, blockoff;
+	u8 blocktype = NOVA_BLOCK_TYPE_4K;
+
+	blocksize = blk_type_to_size[blocktype];
+	num = poolsize / blocksize;
+	if (poolsize % blocksize)
+		num++;
+
+	sih->ino = NOVA_TEST_PERF_INO;
+	sih->i_blk_type = blocktype;
+	sih->log_head = 0;
+	sih->log_tail = 0;
+
+	*allocated = nova_new_data_blocks(sb, sih, blocknr, 0, num,
+					  ALLOC_NO_INIT, cpu, ALLOC_FROM_HEAD);
+	if (*allocated < num) {
+		nova_dbg("%s: allocated pmem blocks %d < requested blocks %d\n",
+						__func__, *allocated, num);
+		if (*allocated > 0)
+			nova_free_data_blocks(sb, sih, *blocknr, *allocated);
+
+		return NULL;
+	}
+
+	blockoff = nova_get_block_off(sb, *blocknr, blocktype);
+	pool = nova_get_block(sb, blockoff);
+
+	return pool;
+}
+
+static void nova_free_pmem_pool(struct super_block *sb,
+	struct nova_inode_info_header *sih, char **pmem,
+	unsigned long blocknr, int num)
+{
+	if (num > 0)
+		nova_free_data_blocks(sb, sih, blocknr, num);
+	*pmem = NULL;
+}
+
+static int nova_test_func_perf(struct super_block *sb, unsigned int func_id,
+	size_t poolsize, size_t size, unsigned int disks)
+{
+	u64 csum = 12345, xor = 0;
+
+	u64 volatile result; // avoid results being optimized out
+	const char *fname = NULL;
+	char *src = NULL, *dst = NULL, *pmem = NULL;
+	char **data = NULL, *parity;
+	size_t off = 0;
+	int cpu, i, j, reps, err = 0, allocated = 0;
+	unsigned int call_id = 0, call_gid = 0;
+	unsigned long blocknr = 0, nsec, lat, thru;
+	struct nova_inode_info_header perf_sih;
+	const memcpy_call_t *fmemcpy = NULL;
+	const checksum_call_t *fchecksum = NULL;
+	const raid5_call_t *fraid5 = NULL;
+	timing_t perf_time;
+
+	cpu = get_cpu(); /* get cpu id and disable preemption */
+	reps = poolsize / size; /* raid calls will adjust this number */
+	call_id = func_id - 1; /* individual function id starting from 1 */
+
+	/* normal memcpy */
+	if (call_id < NUM_MEMCPY_CALLS) {
+		src = nova_alloc_vmem_pool(poolsize);
+		dst = nova_alloc_vmem_pool(poolsize);
+		if (src == NULL || dst == NULL) {
+			err = -ENOMEM;
+			goto out;
+		}
+
+		fmemcpy = &memcpy_calls[call_id];
+		fname = fmemcpy->name;
+		call_gid = memcpy_gid;
+
+		goto test;
+	}
+	call_id -= NUM_MEMCPY_CALLS;
+
+	/* memcpy from pmem */
+	if (call_id < NUM_FROM_PMEM_CALLS) {
+		pmem = nova_alloc_pmem_pool(sb, &perf_sih, cpu, poolsize,
+							&blocknr, &allocated);
+		dst = nova_alloc_vmem_pool(poolsize);
+		if (pmem == NULL || dst == NULL) {
+			err = -ENOMEM;
+			goto out;
+		}
+
+		fmemcpy = &from_pmem_calls[call_id];
+		fname = fmemcpy->name;
+		call_gid = from_pmem_gid;
+
+		goto test;
+	}
+	call_id -= NUM_FROM_PMEM_CALLS;
+
+	/* memcpy to pmem */
+	if (call_id < NUM_TO_PMEM_CALLS) {
+		src = nova_alloc_vmem_pool(poolsize);
+		pmem = nova_alloc_pmem_pool(sb, &perf_sih, cpu, poolsize,
+							&blocknr, &allocated);
+		if (src == NULL || pmem == NULL) {
+			err = -ENOMEM;
+			goto out;
+		}
+
+		fmemcpy = &to_pmem_calls[call_id];
+		fname = fmemcpy->name;
+		call_gid = to_pmem_gid;
+
+		goto test;
+	}
+	call_id -= NUM_TO_PMEM_CALLS;
+
+	/* checksum */
+	if (call_id < NUM_CHECKSUM_CALLS) {
+		src = nova_alloc_vmem_pool(poolsize);
+
+		fchecksum = &checksum_calls[call_id];
+		fname = fchecksum->name;
+		call_gid = checksum_gid;
+
+		goto test;
+	}
+	call_id -= NUM_CHECKSUM_CALLS;
+
+	/* raid5 */
+	if (call_id < NUM_RAID5_CALLS) {
+		src = nova_alloc_vmem_pool(poolsize);
+		data = kcalloc(disks, sizeof(char *), GFP_NOFS);
+		if (data == NULL) {
+			err = -ENOMEM;
+			goto out;
+		}
+
+		reps = poolsize / ((disks + 1) * size); /* +1 for parity */
+
+		fraid5 = &raid5_calls[call_id];
+		fname = fraid5->name;
+		call_gid = raid5_gid;
+
+		if (call_id == nova_block_csum_parity_id && disks != 8) {
+			nova_dbg("%s only for 8 disks, skip testing\n", fname);
+			goto out;
+		}
+
+		goto test;
+	}
+	call_id -= NUM_RAID5_CALLS;
+
+	/* continue with the next call group */
+
+test:
+	if (fmemcpy == NULL && fchecksum == NULL && fraid5 == NULL) {
+		nova_dbg("%s: function struct error\n", __func__);
+		err = -EFAULT;
+		goto out;
+	}
+
+	reset_perf_timer();
+	NOVA_START_TIMING(perf_t, perf_time);
+
+	switch (call_gid) {
+	case memcpy_gid:
+		for (i = 0; i < reps; i++, off += size)
+			err = fmemcpy->call(dst, src, off, size);
+		break;
+	case from_pmem_gid:
+		for (i = 0; i < reps; i++, off += size)
+			err = fmemcpy->call(dst, pmem, off, size);
+		break;
+	case to_pmem_gid:
+		nova_memunlock_range(sb, pmem, poolsize);
+		for (i = 0; i < reps; i++, off += size)
+			err = fmemcpy->call(pmem, src, off, size);
+		nova_memlock_range(sb, pmem, poolsize);
+		break;
+	case checksum_gid:
+		for (i = 0; i < reps; i++, off += size)
+			/* checksum calls are memory-read intensive */
+			csum = fchecksum->call(csum, src + off, size);
+		result = csum;
+		break;
+	case raid5_gid:
+		for (i = 0; i < reps; i++, off += (disks + 1) * size) {
+			for (j = 0; j < disks; j++)
+				data[j] = &src[off + j * size];
+			parity = src + off + disks * size;
+			xor = fraid5->call(data, parity, size, disks);
+		}
+		result = xor;
+		break;
+	default:
+		nova_dbg("%s: invalid function group %d\n", __func__, call_gid);
+		break;
+	}
+
+	NOVA_END_TIMING(perf_t, perf_time);
+	nsec = read_perf_timer();
+
+	// nova_info("checksum value: 0x%016llx\n", csum);
+
+	lat  = (err) ? 0 : nsec / reps;
+	if (call_gid == raid5_gid)
+		thru = (err) ? 0 : mb_per_sec(reps * disks * size, nsec);
+	else
+		thru = (err) ? 0 : mb_per_sec(reps * size, nsec);
+
+	if (cpu != smp_processor_id()) /* scheduling shouldn't happen */
+		nova_dbg("cpu was %d, now %d\n", cpu, smp_processor_id());
+
+	nova_info("%4u %25s %4u %8lu %8lu\n", func_id, fname, cpu, lat, thru);
+
+out:
+	nova_free_vmem_pool(src);
+	nova_free_vmem_pool(dst);
+	nova_free_pmem_pool(sb, &perf_sih, &pmem, blocknr, allocated);
+
+	if (data != NULL)
+		kfree(data);
+
+	put_cpu(); /* enable preemption */
+
+	if (err)
+		nova_dbg("%s: performance test aborted\n", __func__);
+	return err;
+}
+
+int nova_test_perf(struct super_block *sb, unsigned int func_id,
+	unsigned int poolmb, size_t size, unsigned int disks)
+{
+	int id, ret = 0;
+	size_t poolsize = poolmb * 1024 * 1024;
+
+	if (!measure_timing) {
+		nova_dbg("%s: measure_timing not set!\n", __func__);
+		ret = -EFAULT;
+		goto out;
+	}
+	if (func_id > NUM_PERF_CALLS) {
+		nova_dbg("%s: invalid function id %d!\n", __func__, func_id);
+		ret = -EFAULT;
+		goto out;
+	}
+	if (poolmb < 1 || 1024 < poolmb) { /* limit pool size to 1GB */
+		nova_dbg("%s: invalid pool size %u MB!\n", __func__, poolmb);
+		ret = -EFAULT;
+		goto out;
+	}
+	if (size < 64 || poolsize < size || (size % 64)) {
+		nova_dbg("%s: invalid data size %zu!\n", __func__, size);
+		ret = -EFAULT;
+		goto out;
+	}
+	if (disks < 1 || 32 < disks) { /* limit number of disks */
+		nova_dbg("%s: invalid disk count %u!\n", __func__, disks);
+		ret = -EFAULT;
+		goto out;
+	}
+
+	nova_info("test function performance\n");
+	nova_info("pool size %u MB, work size %zu, disks %u\n",
+					poolmb, size, disks);
+
+	nova_info("%4s %25s %4s %8s %8s\n", "id", "name", "cpu", "ns", "MB/s");
+	nova_info("-------------------------------------------------------\n");
+	if (func_id == 0) {
+		/* individual function id starting from 1 */
+		for (id = 1; id <= NUM_PERF_CALLS; id++) {
+			ret = nova_test_func_perf(sb, id, poolsize,
+							size, disks);
+			if (ret < 0)
+				goto out;
+		}
+	} else {
+		ret = nova_test_func_perf(sb, func_id, poolsize, size, disks);
+	}
+	nova_info("-------------------------------------------------------\n");
+
+out:
+	return ret;
+}
diff --git a/fs/nova/perf.h b/fs/nova/perf.h
new file mode 100644
index 000000000000..94bee4674f2e
--- /dev/null
+++ b/fs/nova/perf.h
@@ -0,0 +1,96 @@
+/*
+ * BRIEF DESCRIPTION
+ *
+ * Performance test
+ *
+ * Copyright 2015-2016 Regents of the University of California,
+ * UCSD Non-Volatile Systems Lab, Andiry Xu <jix024@xxxxxxxxxxx>
+ * Copyright 2012-2013 Intel Corporation
+ * Copyright 2009-2011 Marco Stornelli <marco.stornelli@xxxxxxxxx>
+ * Copyright 2003 Sony Corporation
+ * Copyright 2003 Matsushita Electric Industrial Co., Ltd.
+ * 2003-2004 (c) MontaVista Software, Inc. , Steve Longerbeam
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/zutil.h>
+#include <linux/libnvdimm.h>
+#include <linux/raid/xor.h>
+#include "nova.h"
+
+#define	reset_perf_timer()	__this_cpu_write(Timingstats_percpu[perf_t], 0)
+#define	read_perf_timer()	__this_cpu_read(Timingstats_percpu[perf_t])
+
+#define	mb_per_sec(size, nsec)	(nsec == 0 ? 0 : \
+				(size * (1000000000 / 1024 / 1024) / nsec))
+
+enum memcpy_call_id {
+	memcpy_read_id = 0,
+	memcpy_write_id,
+	memcpy_bidir_id,
+	NUM_MEMCPY_CALLS
+};
+
+enum from_pmem_call_id {
+	memcpy_mcsafe_id = 0,
+	NUM_FROM_PMEM_CALLS
+};
+
+enum to_pmem_call_id {
+	memcpy_to_pmem_nocache_id = 0,
+	flush_buffer_id,
+	memcpy_to_pmem_flush_id,
+	NUM_TO_PMEM_CALLS
+};
+
+enum checksum_call_id {
+	zlib_adler32_id = 0,
+	nd_fletcher64_id,
+	libcrc32c_id,
+	nova_crc32c_id,
+	plain_xor64_id,
+	NUM_CHECKSUM_CALLS
+};
+
+enum raid5_call_id {
+	nova_block_parity_id = 0,
+	nova_block_csum_parity_id,
+//	xor_blocks_id,
+	NUM_RAID5_CALLS
+};
+
+#define	NUM_PERF_CALLS	\
+	 (NUM_MEMCPY_CALLS + NUM_FROM_PMEM_CALLS + NUM_TO_PMEM_CALLS + \
+	  NUM_CHECKSUM_CALLS + NUM_RAID5_CALLS)
+
+enum call_group_id {
+	memcpy_gid = 0,
+	from_pmem_gid,
+	to_pmem_gid,
+	checksum_gid,
+	raid5_gid
+};
+
+typedef struct {
+	const char *name;                              /* name of this call */
+//	int (*valid)(void);            /* might need for availability check */
+	int (*call)(char *, char *, size_t, size_t); /* dst, src, off, size */
+} memcpy_call_t;
+
+typedef struct {
+	const char *name;                              /* name of this call */
+//	int (*valid)(void);            /* might need for availability check */
+	u64 (*call)(u64, char *, size_t);               /* init, data, size */
+} checksum_call_t;
+
+typedef struct {
+	const char *name;                              /* name of this call */
+//	int (*valid)(void);            /* might need for availability check */
+	u64 (*call)(char **, char *,                        /* data, parity */
+			size_t, int);          /* per-disk-size, data disks */
+} raid5_call_t;
diff --git a/fs/nova/stats.c b/fs/nova/stats.c
new file mode 100644
index 000000000000..cacf76f0d16d
--- /dev/null
+++ b/fs/nova/stats.c
@@ -0,0 +1,685 @@
+/*
+ * NOVA File System statistics
+ *
+ * Copyright 2015-2016 Regents of the University of California,
+ * UCSD Non-Volatile Systems Lab, Andiry Xu <jix024@xxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "nova.h"
+
+const char *Timingstring[TIMING_NUM] = {
+	/* Init */
+	"================ Initialization ================",
+	"init",
+	"mount",
+	"ioremap",
+	"new_init",
+	"recovery",
+
+	/* Namei operations */
+	"============= Directory operations =============",
+	"create",
+	"lookup",
+	"link",
+	"unlink",
+	"symlink",
+	"mkdir",
+	"rmdir",
+	"mknod",
+	"rename",
+	"readdir",
+	"add_dentry",
+	"remove_dentry",
+	"setattr",
+	"setsize",
+
+	/* I/O operations */
+	"================ I/O operations ================",
+	"dax_read",
+	"cow_write",
+	"inplace_write",
+	"copy_to_nvmm",
+	"dax_get_block",
+	"read_iter",
+	"write_iter",
+
+	/* Memory operations */
+	"============== Memory operations ===============",
+	"memcpy_read_nvmm",
+	"memcpy_write_nvmm",
+	"memcpy_write_back_to_nvmm",
+	"handle_partial_block",
+
+	/* Memory management */
+	"============== Memory management ===============",
+	"alloc_blocks",
+	"new_data_blocks",
+	"new_log_blocks",
+	"free_blocks",
+	"free_data_blocks",
+	"free_log_blocks",
+
+	/* Transaction */
+	"================= Transaction ==================",
+	"transaction_new_inode",
+	"transaction_link_change",
+	"update_tail",
+
+	/* Logging */
+	"============= Logging operations ===============",
+	"append_dir_entry",
+	"append_file_entry",
+	"append_mmap_entry",
+	"append_link_change",
+	"append_setattr",
+	"append_snapshot_info",
+	"inplace_update_entry",
+
+	/* Tree */
+	"=============== Tree operations ================",
+	"checking_entry",
+	"assign_blocks",
+
+	/* GC */
+	"============= Garbage collection ===============",
+	"log_fast_gc",
+	"log_thorough_gc",
+	"check_invalid_log",
+
+	/* Integrity */
+	"============ Integrity operations ==============",
+	"block_csum",
+	"block_parity",
+	"block_csum_parity",
+	"protect_memcpy",
+	"protect_file_data",
+	"verify_entry_csum",
+	"verify_data_csum",
+	"calc_entry_csum",
+	"restore_file_data",
+	"reset_mapping",
+	"reset_vma",
+
+	/* Others */
+	"================ Miscellaneous =================",
+	"find_cache_page",
+	"fsync",
+	"write_pages",
+	"fallocate",
+	"direct_IO",
+	"free_old_entry",
+	"delete_file_tree",
+	"delete_dir_tree",
+	"new_vfs_inode",
+	"new_nova_inode",
+	"free_inode",
+	"free_inode_log",
+	"evict_inode",
+	"test_perf",
+	"wprotect",
+
+	/* Mmap */
+	"=============== MMap operations ================",
+	"mmap_page_fault",
+	"mmap_pmd_fault",
+	"mmap_pfn_mkwrite",
+	"insert_vma",
+	"remove_vma",
+	"set_vma_readonly",
+	"mmap_cow",
+	"udpate_mapping",
+	"udpate_pfn",
+	"mmap_handler",
+
+	/* Rebuild */
+	"=================== Rebuild ====================",
+	"rebuild_dir",
+	"rebuild_file",
+	"rebuild_snapshot_table",
+
+	/* Snapshot */
+	"=================== Snapshot ===================",
+	"create_snapshot",
+	"init_snapshot_info",
+	"delete_snapshot",
+	"append_snapshot_filedata",
+	"append_snapshot_inode",
+};
+
+u64 Timingstats[TIMING_NUM];
+DEFINE_PER_CPU(u64[TIMING_NUM], Timingstats_percpu);
+u64 Countstats[TIMING_NUM];
+DEFINE_PER_CPU(u64[TIMING_NUM], Countstats_percpu);
+u64 IOstats[STATS_NUM];
+DEFINE_PER_CPU(u64[STATS_NUM], IOstats_percpu);
+
+static void nova_print_alloc_stats(struct super_block *sb)
+{
+	struct nova_sb_info *sbi = NOVA_SB(sb);
+	struct free_list *free_list;
+	unsigned long alloc_log_count = 0;
+	unsigned long alloc_log_pages = 0;
+	unsigned long alloc_data_count = 0;
+	unsigned long alloc_data_pages = 0;
+	unsigned long free_log_count = 0;
+	unsigned long freed_log_pages = 0;
+	unsigned long free_data_count = 0;
+	unsigned long freed_data_pages = 0;
+	int i;
+
+	nova_info("=========== NOVA allocation stats ===========\n");
+	nova_info("Alloc %llu, alloc steps %llu, average %llu\n",
+		Countstats[new_data_blocks_t], IOstats[alloc_steps],
+		Countstats[new_data_blocks_t] ?
+			IOstats[alloc_steps] / Countstats[new_data_blocks_t]
+			: 0);
+	nova_info("Free %llu\n", Countstats[free_data_t]);
+	nova_info("Fast GC %llu, check pages %llu, free pages %llu, average %llu\n",
+		Countstats[fast_gc_t], IOstats[fast_checked_pages],
+		IOstats[fast_gc_pages], Countstats[fast_gc_t] ?
+			IOstats[fast_gc_pages] / Countstats[fast_gc_t] : 0);
+	nova_info("Thorough GC %llu, checked pages %llu, free pages %llu, average %llu\n",
+		Countstats[thorough_gc_t],
+		IOstats[thorough_checked_pages], IOstats[thorough_gc_pages],
+		Countstats[thorough_gc_t] ?
+			IOstats[thorough_gc_pages] / Countstats[thorough_gc_t]
+			: 0);
+
+	for (i = 0; i < sbi->cpus; i++) {
+		free_list = nova_get_free_list(sb, i);
+
+		alloc_log_count += free_list->alloc_log_count;
+		alloc_log_pages += free_list->alloc_log_pages;
+		alloc_data_count += free_list->alloc_data_count;
+		alloc_data_pages += free_list->alloc_data_pages;
+		free_log_count += free_list->free_log_count;
+		freed_log_pages += free_list->freed_log_pages;
+		free_data_count += free_list->free_data_count;
+		freed_data_pages += free_list->freed_data_pages;
+	}
+
+	nova_info("alloc log count %lu, allocated log pages %lu, alloc data count %lu, allocated data pages %lu, free log count %lu, freed log pages %lu, free data count %lu, freed data pages %lu\n",
+		alloc_log_count, alloc_log_pages,
+		alloc_data_count, alloc_data_pages,
+		free_log_count, freed_log_pages,
+		free_data_count, freed_data_pages);
+}
+
+static void nova_print_IO_stats(struct super_block *sb)
+{
+	nova_info("=========== NOVA I/O stats ===========\n");
+	nova_info("Read %llu, bytes %llu, average %llu\n",
+		Countstats[dax_read_t], IOstats[read_bytes],
+		Countstats[dax_read_t] ?
+			IOstats[read_bytes] / Countstats[dax_read_t] : 0);
+	nova_info("COW write %llu, bytes %llu, average %llu, write breaks %llu, average %llu\n",
+		Countstats[cow_write_t], IOstats[cow_write_bytes],
+		Countstats[cow_write_t] ?
+			IOstats[cow_write_bytes] / Countstats[cow_write_t] : 0,
+		IOstats[cow_write_breaks], Countstats[cow_write_t] ?
+			IOstats[cow_write_breaks] / Countstats[cow_write_t]
+			: 0);
+	nova_info("Inplace write %llu, bytes %llu, average %llu, write breaks %llu, average %llu\n",
+		Countstats[inplace_write_t], IOstats[inplace_write_bytes],
+		Countstats[inplace_write_t] ?
+			IOstats[inplace_write_bytes] /
+			Countstats[inplace_write_t] : 0,
+		IOstats[inplace_write_breaks], Countstats[inplace_write_t] ?
+			IOstats[inplace_write_breaks] /
+			Countstats[inplace_write_t] : 0);
+}
+
+void nova_get_timing_stats(void)
+{
+	int i;
+	int cpu;
+
+	for (i = 0; i < TIMING_NUM; i++) {
+		Timingstats[i] = 0;
+		Countstats[i] = 0;
+		for_each_possible_cpu(cpu) {
+			Timingstats[i] += per_cpu(Timingstats_percpu[i], cpu);
+			Countstats[i] += per_cpu(Countstats_percpu[i], cpu);
+		}
+	}
+}
+
+void nova_get_IO_stats(void)
+{
+	int i;
+	int cpu;
+
+	for (i = 0; i < STATS_NUM; i++) {
+		IOstats[i] = 0;
+		for_each_possible_cpu(cpu)
+			IOstats[i] += per_cpu(IOstats_percpu[i], cpu);
+	}
+}
+
+void nova_print_timing_stats(struct super_block *sb)
+{
+	int i;
+
+	nova_get_timing_stats();
+	nova_get_IO_stats();
+
+	nova_info("=========== NOVA kernel timing stats ============\n");
+	for (i = 0; i < TIMING_NUM; i++) {
+		/* Title */
+		if (Timingstring[i][0] == '=') {
+			nova_info("\n%s\n\n", Timingstring[i]);
+			continue;
+		}
+
+		if (measure_timing || Timingstats[i]) {
+			nova_info("%s: count %llu, timing %llu, average %llu\n",
+				Timingstring[i],
+				Countstats[i],
+				Timingstats[i],
+				Countstats[i] ?
+				Timingstats[i] / Countstats[i] : 0);
+		} else {
+			nova_info("%s: count %llu\n",
+				Timingstring[i],
+				Countstats[i]);
+		}
+	}
+
+	nova_info("\n");
+	nova_print_alloc_stats(sb);
+	nova_print_IO_stats(sb);
+}
+
+static void nova_clear_timing_stats(void)
+{
+	int i;
+	int cpu;
+
+	for (i = 0; i < TIMING_NUM; i++) {
+		Countstats[i] = 0;
+		Timingstats[i] = 0;
+		for_each_possible_cpu(cpu) {
+			per_cpu(Timingstats_percpu[i], cpu) = 0;
+			per_cpu(Countstats_percpu[i], cpu) = 0;
+		}
+	}
+}
+
+static void nova_clear_IO_stats(struct super_block *sb)
+{
+	struct nova_sb_info *sbi = NOVA_SB(sb);
+	struct free_list *free_list;
+	int i;
+	int cpu;
+
+	for (i = 0; i < STATS_NUM; i++) {
+		IOstats[i] = 0;
+		for_each_possible_cpu(cpu)
+			per_cpu(IOstats_percpu[i], cpu) = 0;
+	}
+
+	for (i = 0; i < sbi->cpus; i++) {
+		free_list = nova_get_free_list(sb, i);
+
+		free_list->alloc_log_count = 0;
+		free_list->alloc_log_pages = 0;
+		free_list->alloc_data_count = 0;
+		free_list->alloc_data_pages = 0;
+		free_list->free_log_count = 0;
+		free_list->freed_log_pages = 0;
+		free_list->free_data_count = 0;
+		free_list->freed_data_pages = 0;
+	}
+}
+
+void nova_clear_stats(struct super_block *sb)
+{
+	nova_clear_timing_stats();
+	nova_clear_IO_stats(sb);
+}
+
+void nova_print_inode(struct nova_inode *pi)
+{
+	nova_dbg("%s: NOVA inode %llu\n", __func__, pi->nova_ino);
+	nova_dbg("valid %u, deleted %u, blk type %u, flags %u\n",
+		pi->valid, pi->deleted, pi->i_blk_type, pi->i_flags);
+	nova_dbg("size %llu, ctime %u, mtime %u, atime %u\n",
+		pi->i_size, pi->i_ctime, pi->i_mtime, pi->i_atime);
+	nova_dbg("mode %u, links %u, xattr 0x%llx, csum %u\n",
+		pi->i_mode, pi->i_links_count, pi->i_xattr, pi->csum);
+	nova_dbg("uid %u, gid %u, gen %u, create time %u\n",
+		pi->i_uid, pi->i_gid, pi->i_generation, pi->i_create_time);
+	nova_dbg("head 0x%llx, tail 0x%llx, alter head 0x%llx, tail 0x%llx\n",
+		pi->log_head, pi->log_tail, pi->alter_log_head,
+		pi->alter_log_tail);
+	nova_dbg("create epoch id %llu, delete epoch id %llu\n",
+		pi->create_epoch_id, pi->delete_epoch_id);
+}
+
+static inline void nova_print_file_write_entry(struct super_block *sb,
+	u64 curr, struct nova_file_write_entry *entry)
+{
+	nova_dbg("file write entry @ 0x%llx: epoch %llu, trans %llu, pgoff %llu, pages %u, blocknr %llu, reassigned %u, updating %u, invalid count %u, size %llu, mtime %u\n",
+			curr, entry->epoch_id, entry->trans_id,
+			entry->pgoff, entry->num_pages,
+			entry->block >> PAGE_SHIFT,
+			entry->reassigned, entry->updating,
+			entry->invalid_pages, entry->size, entry->mtime);
+}
+
+static inline void nova_print_set_attr_entry(struct super_block *sb,
+	u64 curr, struct nova_setattr_logentry *entry)
+{
+	nova_dbg("set attr entry @ 0x%llx: epoch %llu, trans %llu, invalid %u, mode %u, size %llu, atime %u, mtime %u, ctime %u\n",
+			curr, entry->epoch_id, entry->trans_id,
+			entry->invalid, entry->mode,
+			entry->size, entry->atime, entry->mtime, entry->ctime);
+}
+
+static inline void nova_print_link_change_entry(struct super_block *sb,
+	u64 curr, struct nova_link_change_entry *entry)
+{
+	nova_dbg("link change entry @ 0x%llx: epoch %llu, trans %llu, invalid %u, links %u, flags %u, ctime %u\n",
+			curr, entry->epoch_id, entry->trans_id,
+			entry->invalid, entry->links,
+			entry->flags, entry->ctime);
+}
+
+static inline void nova_print_mmap_entry(struct super_block *sb,
+	u64 curr, struct nova_mmap_entry *entry)
+{
+	nova_dbg("mmap write entry @ 0x%llx: epoch %llu, invalid %u, pgoff %llu, pages %llu\n",
+			curr, entry->epoch_id, entry->invalid,
+			entry->pgoff, entry->num_pages);
+}
+
+static inline void nova_print_snapshot_info_entry(struct super_block *sb,
+	u64 curr, struct nova_snapshot_info_entry *entry)
+{
+	nova_dbg("snapshot info entry @ 0x%llx: epoch %llu, deleted %u, timestamp %llu\n",
+			curr, entry->epoch_id, entry->deleted,
+			entry->timestamp);
+}
+
+static inline size_t nova_print_dentry(struct super_block *sb,
+	u64 curr, struct nova_dentry *entry)
+{
+	nova_dbg("dir logentry @ 0x%llx: epoch %llu, trans %llu, reassigned %u, invalid %u, inode %llu, links %u, namelen %u, rec len %u, name %s, mtime %u\n",
+			curr, entry->epoch_id, entry->trans_id,
+			entry->reassigned, entry->invalid,
+			le64_to_cpu(entry->ino),
+			entry->links_count, entry->name_len,
+			le16_to_cpu(entry->de_len), entry->name,
+			entry->mtime);
+
+	return le16_to_cpu(entry->de_len);
+}
+
+u64 nova_print_log_entry(struct super_block *sb, u64 curr)
+{
+	void *addr;
+	size_t size;
+	u8 type;
+
+	addr = (void *)nova_get_block(sb, curr);
+	type = nova_get_entry_type(addr);
+	switch (type) {
+	case SET_ATTR:
+		nova_print_set_attr_entry(sb, curr, addr);
+		curr += sizeof(struct nova_setattr_logentry);
+		break;
+	case LINK_CHANGE:
+		nova_print_link_change_entry(sb, curr, addr);
+		curr += sizeof(struct nova_link_change_entry);
+		break;
+	case MMAP_WRITE:
+		nova_print_mmap_entry(sb, curr, addr);
+		curr += sizeof(struct nova_mmap_entry);
+		break;
+	case SNAPSHOT_INFO:
+		nova_print_snapshot_info_entry(sb, curr, addr);
+		curr += sizeof(struct nova_snapshot_info_entry);
+		break;
+	case FILE_WRITE:
+		nova_print_file_write_entry(sb, curr, addr);
+		curr += sizeof(struct nova_file_write_entry);
+		break;
+	case DIR_LOG:
+		size = nova_print_dentry(sb, curr, addr);
+		curr += size;
+		if (size == 0) {
+			nova_dbg("%s: dentry with size 0 @ 0x%llx\n",
+					__func__, curr);
+			curr += sizeof(struct nova_file_write_entry);
+			NOVA_ASSERT(0);
+		}
+		break;
+	case NEXT_PAGE:
+		nova_dbg("%s: next page sign @ 0x%llx\n", __func__, curr);
+		curr = PAGE_TAIL(curr);
+		break;
+	default:
+		nova_dbg("%s: unknown type %d, 0x%llx\n", __func__, type, curr);
+		curr += sizeof(struct nova_file_write_entry);
+		NOVA_ASSERT(0);
+		break;
+	}
+
+	return curr;
+}
+
+void nova_print_curr_log_page(struct super_block *sb, u64 curr)
+{
+	struct nova_inode_page_tail *tail;
+	u64 start, end;
+
+	start = BLOCK_OFF(curr);
+	end = PAGE_TAIL(curr);
+
+	while (start < end)
+		start = nova_print_log_entry(sb, start);
+
+	tail = nova_get_block(sb, end);
+	nova_dbg("Page tail. curr 0x%llx, next page 0x%llx, %u entries, %u invalid\n",
+			start, tail->next_page,
+			tail->num_entries, tail->invalid_entries);
+}
+
+void nova_print_nova_log(struct super_block *sb,
+	struct nova_inode_info_header *sih)
+{
+	u64 curr;
+
+	if (sih->log_tail == 0 || sih->log_head == 0)
+		return;
+
+	curr = sih->log_head;
+	nova_dbg("Pi %lu: log head 0x%llx, tail 0x%llx\n",
+			sih->ino, curr, sih->log_tail);
+	while (curr != sih->log_tail) {
+		if ((curr & (PAGE_SIZE - 1)) == LOG_BLOCK_TAIL) {
+			struct nova_inode_page_tail *tail =
+					nova_get_block(sb, curr);
+			nova_dbg("Log tail, curr 0x%llx, next page 0x%llx, %u entries, %u invalid\n",
+					curr, tail->next_page,
+					tail->num_entries,
+					tail->invalid_entries);
+			curr = tail->next_page;
+		} else {
+			curr = nova_print_log_entry(sb, curr);
+		}
+	}
+}
+
+void nova_print_inode_log(struct super_block *sb, struct inode *inode)
+{
+	struct nova_inode_info *si = NOVA_I(inode);
+	struct nova_inode_info_header *sih = &si->header;
+
+	nova_print_nova_log(sb, sih);
+}
+
+int nova_get_nova_log_pages(struct super_block *sb,
+	struct nova_inode_info_header *sih, struct nova_inode *pi)
+{
+	struct nova_inode_log_page *curr_page;
+	u64 curr, next;
+	int count = 1;
+
+	if (pi->log_head == 0 || pi->log_tail == 0) {
+		nova_dbg("Pi %lu has no log\n", sih->ino);
+		return 0;
+	}
+
+	curr = pi->log_head;
+	curr_page = (struct nova_inode_log_page *)nova_get_block(sb, curr);
+	while ((next = curr_page->page_tail.next_page) != 0) {
+		curr = next;
+		curr_page = (struct nova_inode_log_page *)
+			nova_get_block(sb, curr);
+		count++;
+	}
+
+	return count;
+}
+
+void nova_print_nova_log_pages(struct super_block *sb,
+	struct nova_inode_info_header *sih)
+{
+	struct nova_inode_log_page *curr_page;
+	u64 curr, next;
+	int count = 1;
+	int used = count;
+
+	if (sih->log_head == 0 || sih->log_tail == 0) {
+		nova_dbg("Pi %lu has no log\n", sih->ino);
+		return;
+	}
+
+	curr = sih->log_head;
+	nova_dbg("Pi %lu: log head @ 0x%llx, tail @ 0x%llx\n",
+			sih->ino, curr, sih->log_tail);
+	curr_page = (struct nova_inode_log_page *)nova_get_block(sb, curr);
+	while ((next = curr_page->page_tail.next_page) != 0) {
+		nova_dbg("Current page 0x%llx, next page 0x%llx, %u entries, %u invalid\n",
+			curr >> PAGE_SHIFT, next >> PAGE_SHIFT,
+			curr_page->page_tail.num_entries,
+			curr_page->page_tail.invalid_entries);
+		if (sih->log_tail >> PAGE_SHIFT == curr >> PAGE_SHIFT)
+			used = count;
+		curr = next;
+		curr_page = (struct nova_inode_log_page *)
+			nova_get_block(sb, curr);
+		count++;
+	}
+	if (sih->log_tail >> PAGE_SHIFT == curr >> PAGE_SHIFT)
+		used = count;
+	nova_dbg("Pi %lu: log used %d pages, has %d pages, si reports %lu pages\n",
+		sih->ino, used, count,
+		sih->log_pages);
+}
+
+void nova_print_inode_log_pages(struct super_block *sb, struct inode *inode)
+{
+	struct nova_inode_info *si = NOVA_I(inode);
+	struct nova_inode_info_header *sih = &si->header;
+
+	nova_print_nova_log_pages(sb, sih);
+}
+
+int nova_check_inode_logs(struct super_block *sb, struct nova_inode *pi)
+{
+	int count1 = 0;
+	int count2 = 0;
+	int tail1_at = 0;
+	int tail2_at = 0;
+	u64 curr, alter_curr;
+
+	curr = pi->log_head;
+	alter_curr = pi->alter_log_head;
+
+	while (curr && alter_curr) {
+		if (alter_log_page(sb, curr) != alter_curr ||
+				alter_log_page(sb, alter_curr) != curr)
+			nova_dbg("Inode %llu page %d: curr 0x%llx, alter 0x%llx, alter_curr 0x%llx, alter 0x%llx\n",
+					pi->nova_ino, count1,
+					curr, alter_log_page(sb, curr),
+					alter_curr,
+					alter_log_page(sb, alter_curr));
+
+		count1++;
+		count2++;
+		if ((curr >> PAGE_SHIFT) == (pi->log_tail >> PAGE_SHIFT))
+			tail1_at = count1;
+		if ((alter_curr >> PAGE_SHIFT) ==
+				(pi->alter_log_tail >> PAGE_SHIFT))
+			tail2_at = count2;
+		curr = next_log_page(sb, curr);
+		alter_curr = next_log_page(sb, alter_curr);
+	}
+
+	while (curr) {
+		count1++;
+		if ((curr >> PAGE_SHIFT) == (pi->log_tail >> PAGE_SHIFT))
+			tail1_at = count1;
+		curr = next_log_page(sb, curr);
+	}
+
+	while (alter_curr) {
+		count2++;
+		if ((alter_curr >> PAGE_SHIFT) ==
+				(pi->alter_log_tail >> PAGE_SHIFT))
+			tail2_at = count2;
+		alter_curr = next_log_page(sb, alter_curr);
+	}
+
+	nova_dbg("Log1 %d pages, tail @ page %d\n", count1, tail1_at);
+	nova_dbg("Log2 %d pages, tail @ page %d\n", count2, tail2_at);
+
+	return 0;
+}
+
+void nova_print_free_lists(struct super_block *sb)
+{
+	struct nova_sb_info *sbi = NOVA_SB(sb);
+	struct free_list *free_list;
+	int i;
+
+	nova_dbg("======== NOVA per-CPU free list allocation stats ========\n");
+	for (i = 0; i < sbi->cpus; i++) {
+		free_list = nova_get_free_list(sb, i);
+		nova_dbg("Free list %d: block start %lu, block end %lu, num_blocks %lu, num_free_blocks %lu, blocknode %lu\n",
+			i, free_list->block_start, free_list->block_end,
+			free_list->block_end - free_list->block_start + 1,
+			free_list->num_free_blocks, free_list->num_blocknode);
+
+		nova_dbg("Free list %d: csum start %lu, replica csum start %lu, csum blocks %lu, parity start %lu, parity blocks %lu\n",
+			i, free_list->csum_start, free_list->replica_csum_start,
+			free_list->num_csum_blocks,
+			free_list->parity_start, free_list->num_parity_blocks);
+
+		nova_dbg("Free list %d: alloc log count %lu, allocated log pages %lu, alloc data count %lu, allocated data pages %lu, free log count %lu, freed log pages %lu, free data count %lu, freed data pages %lu\n",
+			 i,
+			 free_list->alloc_log_count,
+			 free_list->alloc_log_pages,
+			 free_list->alloc_data_count,
+			 free_list->alloc_data_pages,
+			 free_list->free_log_count,
+			 free_list->freed_log_pages,
+			 free_list->free_data_count,
+			 free_list->freed_data_pages);
+	}
+}
diff --git a/fs/nova/stats.h b/fs/nova/stats.h
new file mode 100644
index 000000000000..766ba0a77872
--- /dev/null
+++ b/fs/nova/stats.h
@@ -0,0 +1,218 @@
+/*
+ * NOVA File System statistics
+ *
+ * Copyright 2015-2016 Regents of the University of California,
+ * UCSD Non-Volatile Systems Lab, Andiry Xu <jix024@xxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+/* ======================= Timing ========================= */
+enum timing_category {
+	/* Init */
+	init_title_t,
+	init_t,
+	mount_t,
+	ioremap_t,
+	new_init_t,
+	recovery_t,
+
+	/* Namei operations */
+	namei_title_t,
+	create_t,
+	lookup_t,
+	link_t,
+	unlink_t,
+	symlink_t,
+	mkdir_t,
+	rmdir_t,
+	mknod_t,
+	rename_t,
+	readdir_t,
+	add_dentry_t,
+	remove_dentry_t,
+	setattr_t,
+	setsize_t,
+
+	/* I/O operations */
+	io_title_t,
+	dax_read_t,
+	cow_write_t,
+	inplace_write_t,
+	copy_to_nvmm_t,
+	dax_get_block_t,
+	read_iter_t,
+	write_iter_t,
+
+	/* Memory operations */
+	memory_title_t,
+	memcpy_r_nvmm_t,
+	memcpy_w_nvmm_t,
+	memcpy_w_wb_t,
+	partial_block_t,
+
+	/* Memory management */
+	mm_title_t,
+	new_blocks_t,
+	new_data_blocks_t,
+	new_log_blocks_t,
+	free_blocks_t,
+	free_data_t,
+	free_log_t,
+
+	/* Transaction */
+	trans_title_t,
+	create_trans_t,
+	link_trans_t,
+	update_tail_t,
+
+	/* Logging */
+	logging_title_t,
+	append_dir_entry_t,
+	append_file_entry_t,
+	append_mmap_entry_t,
+	append_link_change_t,
+	append_setattr_t,
+	append_snapshot_info_t,
+	update_entry_t,
+
+	/* Tree */
+	tree_title_t,
+	check_entry_t,
+	assign_t,
+
+	/* GC */
+	gc_title_t,
+	fast_gc_t,
+	thorough_gc_t,
+	check_invalid_t,
+
+	/* Integrity */
+	integrity_title_t,
+	block_csum_t,
+	block_parity_t,
+	block_csum_parity_t,
+	protect_memcpy_t,
+	protect_file_data_t,
+	verify_entry_csum_t,
+	verify_data_csum_t,
+	calc_entry_csum_t,
+	restore_data_t,
+	reset_mapping_t,
+	reset_vma_t,
+
+	/* Others */
+	others_title_t,
+	find_cache_t,
+	fsync_t,
+	write_pages_t,
+	fallocate_t,
+	direct_IO_t,
+	free_old_t,
+	delete_file_tree_t,
+	delete_dir_tree_t,
+	new_vfs_inode_t,
+	new_nova_inode_t,
+	free_inode_t,
+	free_inode_log_t,
+	evict_inode_t,
+	perf_t,
+	wprotect_t,
+
+	/* Mmap */
+	mmap_title_t,
+	mmap_fault_t,
+	pmd_fault_t,
+	pfn_mkwrite_t,
+	insert_vma_t,
+	remove_vma_t,
+	set_vma_read_t,
+	mmap_cow_t,
+	update_mapping_t,
+	update_pfn_t,
+	mmap_handler_t,
+
+	/* Rebuild */
+	rebuild_title_t,
+	rebuild_dir_t,
+	rebuild_file_t,
+	rebuild_snapshot_t,
+
+	/* Snapshot */
+	snapshot_title_t,
+	create_snapshot_t,
+	init_snapshot_info_t,
+	delete_snapshot_t,
+	append_snapshot_file_t,
+	append_snapshot_inode_t,
+
+	/* Sentinel */
+	TIMING_NUM,
+};
+
+enum stats_category {
+	alloc_steps,
+	cow_write_breaks,
+	inplace_write_breaks,
+	read_bytes,
+	cow_write_bytes,
+	inplace_write_bytes,
+	fast_checked_pages,
+	thorough_checked_pages,
+	fast_gc_pages,
+	thorough_gc_pages,
+	dirty_pages,
+	protect_head,
+	protect_tail,
+	block_csum_parity,
+	dax_cow_during_snapshot,
+	mapping_updated_pages,
+	cow_overlap_mmap,
+	dax_new_blocks,
+	inplace_new_blocks,
+	fdatasync,
+
+	/* Sentinel */
+	STATS_NUM,
+};
+
+extern const char *Timingstring[TIMING_NUM];
+extern u64 Timingstats[TIMING_NUM];
+DECLARE_PER_CPU(u64[TIMING_NUM], Timingstats_percpu);
+extern u64 Countstats[TIMING_NUM];
+DECLARE_PER_CPU(u64[TIMING_NUM], Countstats_percpu);
+extern u64 IOstats[STATS_NUM];
+DECLARE_PER_CPU(u64[STATS_NUM], IOstats_percpu);
+
+typedef struct timespec timing_t;
+
+#define NOVA_START_TIMING(name, start) \
+	{if (measure_timing) getrawmonotonic(&start); }
+
+#define NOVA_END_TIMING(name, start) \
+	{if (measure_timing) { \
+		timing_t end; \
+		getrawmonotonic(&end); \
+		__this_cpu_add(Timingstats_percpu[name], \
+			(end.tv_sec - start.tv_sec) * 1000000000 + \
+			(end.tv_nsec - start.tv_nsec)); \
+	} \
+	__this_cpu_add(Countstats_percpu[name], 1); \
+	}
+
+#define NOVA_STATS_ADD(name, value) \
+	{__this_cpu_add(IOstats_percpu[name], value); }
+
+





[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux