/*
 * Copyright 2015-2018, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *
 *     * Neither the name of the copyright holder nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * obj_persist_count.c -- counting number of persists
 */
#define _GNU_SOURCE

#include "obj.h"
#include "pmalloc.h"
#include "unittest.h"

struct ops_counter {
	unsigned n_cl_stores;
	unsigned n_drain;
	unsigned n_pmem_persist;
	unsigned n_pmem_msync;
	unsigned n_pmem_flush;
	unsigned n_pmem_drain;
	unsigned n_flush_from_pmem_memcpy;
	unsigned n_flush_from_pmem_memset;
	unsigned n_drain_from_pmem_memcpy;
	unsigned n_drain_from_pmem_memset;
	unsigned n_pot_cache_misses;
};

static struct ops_counter ops_counter;
static struct ops_counter tx_counter;

#define FLUSH_ALIGN ((uintptr_t)64)
#define MOVNT_THRESHOLD	256

static unsigned
cl_flushed(const void *addr, size_t len, uintptr_t alignment)
{
	uintptr_t start = (uintptr_t)addr & ~(alignment - 1);
	uintptr_t end = ((uintptr_t)addr + len + alignment - 1) &
			~(alignment - 1);

	return (unsigned)(end - start) / FLUSH_ALIGN;
}

#define PMEM_F_MEM_MOVNT (PMEM_F_MEM_WC | PMEM_F_MEM_NONTEMPORAL)
#define PMEM_F_MEM_MOV   (PMEM_F_MEM_WB | PMEM_F_MEM_TEMPORAL)

static unsigned
bulk_cl_changed(const void *addr, size_t len, unsigned flags)
{
	uintptr_t start = (uintptr_t)addr & ~(FLUSH_ALIGN - 1);
	uintptr_t end = ((uintptr_t)addr + len + FLUSH_ALIGN - 1) &
			~(FLUSH_ALIGN - 1);

	unsigned cl_changed = (unsigned)(end - start) / FLUSH_ALIGN;

	int wc; /* write combining */
	if (flags & PMEM_F_MEM_NOFLUSH)
		wc = 0; /* NOFLUSH always uses temporal instructions */
	else if (flags & PMEM_F_MEM_MOVNT)
		wc = 1;
	else if (flags & PMEM_F_MEM_MOV)
		wc = 0;
	else if (len < MOVNT_THRESHOLD)
		wc = 0;
	else
		wc = 1;

	/* count number of potential cache misses */
	if (!wc) {
		/*
		 * When we don't use write combining, it means all
		 * cache lines may be missing.
		 */
		ops_counter.n_pot_cache_misses += cl_changed;
	} else {
		/*
		 * When we use write combining there won't be any cache misses,
		 * with an exception of unaligned beginning or end.
		 */
		if (start != (uintptr_t)addr)
			ops_counter.n_pot_cache_misses++;
		if (end != ((uintptr_t)addr + len) &&
				start + FLUSH_ALIGN != end)
			ops_counter.n_pot_cache_misses++;
	}

	return cl_changed;
}

static void
flush_cl(const void *addr, size_t len)
{
	unsigned flushed = cl_flushed(addr, len, FLUSH_ALIGN);
	ops_counter.n_cl_stores += flushed;
	ops_counter.n_pot_cache_misses += flushed;
}

static void
flush_msync(const void *addr, size_t len)
{
	unsigned flushed = cl_flushed(addr, len, Pagesize);
	ops_counter.n_cl_stores += flushed;
	ops_counter.n_pot_cache_misses += flushed;
}

FUNC_MOCK(pmem_persist, void, const void *addr, size_t len)
	FUNC_MOCK_RUN_DEFAULT {
		ops_counter.n_pmem_persist++;
		flush_cl(addr, len);
		ops_counter.n_drain++;

		_FUNC_REAL(pmem_persist)(addr, len);
	}
FUNC_MOCK_END

FUNC_MOCK(pmem_msync, int, const void *addr, size_t len)
	FUNC_MOCK_RUN_DEFAULT {
		ops_counter.n_pmem_msync++;
		flush_msync(addr, len);
		ops_counter.n_drain++;

		return _FUNC_REAL(pmem_msync)(addr, len);
	}
FUNC_MOCK_END

FUNC_MOCK(pmem_flush, void, const void *addr, size_t len)
	FUNC_MOCK_RUN_DEFAULT {
		ops_counter.n_pmem_flush++;
		flush_cl(addr, len);
		_FUNC_REAL(pmem_flush)(addr, len);
	}
FUNC_MOCK_END

FUNC_MOCK(pmem_drain, void, void)
	FUNC_MOCK_RUN_DEFAULT {
		ops_counter.n_pmem_drain++;
		ops_counter.n_drain++;
		_FUNC_REAL(pmem_drain)();
	}
FUNC_MOCK_END

static void
memcpy_nodrain_count(void *dest, const void *src, size_t len, unsigned flags)
{
	unsigned cl_stores = bulk_cl_changed(dest, len, flags);
	if (!(flags & PMEM_F_MEM_NOFLUSH))
		ops_counter.n_flush_from_pmem_memcpy += cl_stores;
	ops_counter.n_cl_stores += cl_stores;
}

static void
memcpy_persist_count(void *dest, const void *src, size_t len, unsigned flags)
{
	memcpy_nodrain_count(dest, src, len, flags);

	ops_counter.n_drain_from_pmem_memcpy++;
	ops_counter.n_drain++;
}

FUNC_MOCK(pmem_memcpy_persist, void *, void *dest, const void *src, size_t len)
	FUNC_MOCK_RUN_DEFAULT {
		memcpy_persist_count(dest, src, len, 0);

		return _FUNC_REAL(pmem_memcpy_persist)(dest, src, len);
	}
FUNC_MOCK_END

FUNC_MOCK(pmem_memcpy_nodrain, void *, void *dest, const void *src, size_t len)
	FUNC_MOCK_RUN_DEFAULT {
		memcpy_nodrain_count(dest, src, len, 0);

		return _FUNC_REAL(pmem_memcpy_nodrain)(dest, src, len);
	}
FUNC_MOCK_END

static unsigned
sanitize_flags(unsigned flags)
{
	if (flags & PMEM_F_MEM_NOFLUSH) {
		/* NOFLUSH implies NODRAIN */
		flags |= PMEM_F_MEM_NODRAIN;
	}

	return flags;
}

FUNC_MOCK(pmem_memcpy, void *, void *dest, const void *src, size_t len,
		unsigned flags)
	FUNC_MOCK_RUN_DEFAULT {
		flags = sanitize_flags(flags);

		if (flags & PMEM_F_MEM_NODRAIN)
			memcpy_nodrain_count(dest, src, len, flags);
		else
			memcpy_persist_count(dest, src, len, flags);

		return _FUNC_REAL(pmem_memcpy)(dest, src, len, flags);
	}
FUNC_MOCK_END

FUNC_MOCK(pmem_memmove_persist, void *, void *dest, const void *src, size_t len)
	FUNC_MOCK_RUN_DEFAULT {
		memcpy_persist_count(dest, src, len, 0);

		return _FUNC_REAL(pmem_memmove_persist)(dest, src, len);
	}
FUNC_MOCK_END

FUNC_MOCK(pmem_memmove_nodrain, void *, void *dest, const void *src, size_t len)
	FUNC_MOCK_RUN_DEFAULT {
		memcpy_nodrain_count(dest, src, len, 0);

		return _FUNC_REAL(pmem_memmove_nodrain)(dest, src, len);
	}
FUNC_MOCK_END

FUNC_MOCK(pmem_memmove, void *, void *dest, const void *src, size_t len,
		unsigned flags)
	FUNC_MOCK_RUN_DEFAULT {
		flags = sanitize_flags(flags);

		if (flags & PMEM_F_MEM_NODRAIN)
			memcpy_nodrain_count(dest, src, len, flags);
		else
			memcpy_persist_count(dest, src, len, flags);

		return _FUNC_REAL(pmem_memmove)(dest, src, len, flags);
	}
FUNC_MOCK_END

static void
memset_nodrain_count(void *dest, size_t len, unsigned flags)
{
	unsigned cl_set = bulk_cl_changed(dest, len, flags);
	if (!(flags & PMEM_F_MEM_NOFLUSH))
		ops_counter.n_flush_from_pmem_memset += cl_set;
	ops_counter.n_cl_stores += cl_set;
}

static void
memset_persist_count(void *dest, size_t len, unsigned flags)
{
	memset_nodrain_count(dest, len, flags);

	ops_counter.n_drain_from_pmem_memset++;
	ops_counter.n_drain++;
}

FUNC_MOCK(pmem_memset_persist, void *, void *dest, int c, size_t len)
	FUNC_MOCK_RUN_DEFAULT {
		memset_persist_count(dest, len, 0);

		return _FUNC_REAL(pmem_memset_persist)(dest, c, len);
	}
FUNC_MOCK_END

FUNC_MOCK(pmem_memset_nodrain, void *, void *dest, int c, size_t len)
	FUNC_MOCK_RUN_DEFAULT {
		memset_nodrain_count(dest, len, 0);

		return _FUNC_REAL(pmem_memset_nodrain)(dest, c, len);
	}
FUNC_MOCK_END

FUNC_MOCK(pmem_memset, void *, void *dest, int c, size_t len, unsigned flags)
	FUNC_MOCK_RUN_DEFAULT {
		flags = sanitize_flags(flags);

		if (flags & PMEM_F_MEM_NODRAIN)
			memset_nodrain_count(dest, len, flags);
		else
			memset_persist_count(dest, len, flags);

		return _FUNC_REAL(pmem_memset)(dest, c, len, flags);
	}
FUNC_MOCK_END

/*
 * reset_counters -- zero all counters
 */
static void
reset_counters(void)
{
	memset(&ops_counter, 0, sizeof(ops_counter));
}

/*
 * print_reset_counters -- print and then zero all counters
 */
static void
print_reset_counters(const char *task, unsigned tx)
{
#define CNT(name) (ops_counter.name - tx * tx_counter.name)
	UT_OUT(
		"%-14s %-7d %-10d %-12d %-10d %-10d %-10d %-15d %-17d %-15d %-17d %-23d",
		task,
		CNT(n_cl_stores),
		CNT(n_drain),
		CNT(n_pmem_persist),
		CNT(n_pmem_msync),
		CNT(n_pmem_flush),
		CNT(n_pmem_drain),
		CNT(n_flush_from_pmem_memcpy),
		CNT(n_drain_from_pmem_memcpy),
		CNT(n_flush_from_pmem_memset),
		CNT(n_drain_from_pmem_memset),
		CNT(n_pot_cache_misses));
#undef CNT
	reset_counters();
}

#define LARGE_SNAPSHOT ((1 << 10) * 10)

struct foo_large {
	uint8_t snapshot[LARGE_SNAPSHOT];
};

struct foo {
	int val;
	uint64_t dest;

	PMEMoid bar;
	PMEMoid bar2;
};

int
main(int argc, char *argv[])
{
	START(argc, argv, "obj_persist_count");

	if (argc != 2)
		UT_FATAL("usage: %s file-name", argv[0]);

	const char *path = argv[1];

	PMEMobjpool *pop;
	if ((pop = pmemobj_create(path, "persist_count",
			PMEMOBJ_MIN_POOL, S_IWUSR | S_IRUSR)) == NULL)
		UT_FATAL("!pmemobj_create: %s", path);

	UT_OUT(
		"%-14s %-7s %-10s %-12s %-10s %-10s %-10s %-15s %-17s %-15s %-17s %-23s",
		"task",
		"cl(all)",
		"drain(all)",
		"pmem_persist",
		"pmem_msync",
		"pmem_flush",
		"pmem_drain",
		"pmem_memcpy_cls",
		"pmem_memcpy_drain",
		"pmem_memset_cls",
		"pmem_memset_drain",
		"potential_cache_misses");


	print_reset_counters("pool_create", 0);

	/* allocate one structure to create a run */
	pmemobj_alloc(pop, NULL, sizeof(struct foo), 0, NULL, NULL);
	reset_counters();

	PMEMoid root = pmemobj_root(pop, sizeof(struct foo));
	UT_ASSERT(!OID_IS_NULL(root));
	print_reset_counters("root_alloc", 0);

	PMEMoid oid;
	int ret = pmemobj_alloc(pop, &oid, sizeof(struct foo), 0, NULL, NULL);
	UT_ASSERTeq(ret, 0);
	print_reset_counters("atomic_alloc", 0);

	pmemobj_free(&oid);
	print_reset_counters("atomic_free", 0);

	struct foo *f = pmemobj_direct(root);

	TX_BEGIN(pop) {
	} TX_END
	memcpy(&tx_counter, &ops_counter, sizeof(ops_counter));
	print_reset_counters("tx_begin_end", 0);

	TX_BEGIN(pop) {
		f->bar = pmemobj_tx_alloc(sizeof(struct foo), 0);
		UT_ASSERT(!OID_IS_NULL(f->bar));
	} TX_END
	print_reset_counters("tx_alloc", 1);

	TX_BEGIN(pop) {
		f->bar2 = pmemobj_tx_alloc(sizeof(struct foo), 0);
		UT_ASSERT(!OID_IS_NULL(f->bar2));
	} TX_END
	print_reset_counters("tx_alloc_next", 1);

	TX_BEGIN(pop) {
		pmemobj_tx_free(f->bar);
	} TX_END
	print_reset_counters("tx_free", 1);

	TX_BEGIN(pop) {
		pmemobj_tx_free(f->bar2);
	} TX_END
	print_reset_counters("tx_free_next", 1);

	TX_BEGIN(pop) {
		pmemobj_tx_xadd_range_direct(&f->val, sizeof(f->val),
			POBJ_XADD_NO_FLUSH);
	} TX_END
	print_reset_counters("tx_add", 1);

	TX_BEGIN(pop) {
		pmemobj_tx_xadd_range_direct(&f->val, sizeof(f->val),
			POBJ_XADD_NO_FLUSH);
	} TX_END
	print_reset_counters("tx_add_next", 1);

	PMEMoid large_foo;
	pmemobj_zalloc(pop, &large_foo, sizeof(struct foo_large), 0);
	UT_ASSERT(!OID_IS_NULL(large_foo));
	reset_counters();

	struct foo_large *flarge = pmemobj_direct(large_foo);

	TX_BEGIN(pop) {
		pmemobj_tx_xadd_range_direct(&flarge->snapshot,
			sizeof(flarge->snapshot),
			POBJ_XADD_NO_FLUSH);
	} TX_END
	print_reset_counters("tx_add_large", 1);

	TX_BEGIN(pop) {
		pmemobj_tx_xadd_range_direct(&flarge->snapshot,
			sizeof(flarge->snapshot),
			POBJ_XADD_NO_FLUSH);
	} TX_END
	print_reset_counters("tx_add_lnext", 1);

	pmalloc(pop, &f->dest, sizeof(f->val), 0, 0);
	print_reset_counters("pmalloc", 0);

	pfree(pop, &f->dest);
	print_reset_counters("pfree", 0);

	uint64_t stack_var;
	pmalloc(pop, &stack_var, sizeof(f->val), 0, 0);
	print_reset_counters("pmalloc_stack", 0);

	pfree(pop, &stack_var);
	print_reset_counters("pfree_stack", 0);

	pmemobj_close(pop);

	DONE(NULL);
}


#ifdef _MSC_VER
/*
 * Since libpmemobj is linked statically, we need to invoke its ctor/dtor.
 */
MSVC_CONSTR(libpmemobj_init)
MSVC_DESTR(libpmemobj_fini)
#endif
