#include "test/jemalloc_test.h"

#define N_PTRS 3

static void
test_combinations(
    szind_t ind, size_t sizes_array[N_PTRS], int flags_array[N_PTRS]) {
#define MALLCTL_STR_LEN 64
	assert(opt_prof && opt_prof_stats);

	char mallctl_live_str[MALLCTL_STR_LEN];
	char mallctl_accum_str[MALLCTL_STR_LEN];
	if (ind < SC_NBINS) {
		malloc_snprintf(mallctl_live_str, MALLCTL_STR_LEN,
		    "prof.stats.bins.%u.live", (unsigned)ind);
		malloc_snprintf(mallctl_accum_str, MALLCTL_STR_LEN,
		    "prof.stats.bins.%u.accum", (unsigned)ind);
	} else {
		malloc_snprintf(mallctl_live_str, MALLCTL_STR_LEN,
		    "prof.stats.lextents.%u.live", (unsigned)(ind - SC_NBINS));
		malloc_snprintf(mallctl_accum_str, MALLCTL_STR_LEN,
		    "prof.stats.lextents.%u.accum", (unsigned)(ind - SC_NBINS));
	}

	size_t stats_len = 2 * sizeof(uint64_t);

	uint64_t live_stats_orig[2];
	assert_d_eq(
	    mallctl(mallctl_live_str, &live_stats_orig, &stats_len, NULL, 0), 0,
	    "");
	uint64_t accum_stats_orig[2];
	assert_d_eq(
	    mallctl(mallctl_accum_str, &accum_stats_orig, &stats_len, NULL, 0),
	    0, "");

	void *ptrs[N_PTRS];

	uint64_t live_req_sum = 0;
	uint64_t live_count = 0;
	uint64_t accum_req_sum = 0;
	uint64_t accum_count = 0;

	for (size_t i = 0; i < N_PTRS; ++i) {
		size_t sz = sizes_array[i];
		int    flags = flags_array[i];
		void  *p = mallocx(sz, flags);
		assert_ptr_not_null(p, "malloc() failed");
		assert(TEST_MALLOC_SIZE(p) == sz_index2size(ind));
		ptrs[i] = p;
		live_req_sum += sz;
		live_count++;
		accum_req_sum += sz;
		accum_count++;
		uint64_t live_stats[2];
		assert_d_eq(
		    mallctl(mallctl_live_str, &live_stats, &stats_len, NULL, 0),
		    0, "");
		expect_u64_eq(
		    live_stats[0] - live_stats_orig[0], live_req_sum, "");
		expect_u64_eq(
		    live_stats[1] - live_stats_orig[1], live_count, "");
		uint64_t accum_stats[2];
		assert_d_eq(mallctl(mallctl_accum_str, &accum_stats, &stats_len,
		                NULL, 0),
		    0, "");
		expect_u64_eq(
		    accum_stats[0] - accum_stats_orig[0], accum_req_sum, "");
		expect_u64_eq(
		    accum_stats[1] - accum_stats_orig[1], accum_count, "");
	}

	for (size_t i = 0; i < N_PTRS; ++i) {
		size_t sz = sizes_array[i];
		int    flags = flags_array[i];
		sdallocx(ptrs[i], sz, flags);
		live_req_sum -= sz;
		live_count--;
		uint64_t live_stats[2];
		assert_d_eq(
		    mallctl(mallctl_live_str, &live_stats, &stats_len, NULL, 0),
		    0, "");
		expect_u64_eq(
		    live_stats[0] - live_stats_orig[0], live_req_sum, "");
		expect_u64_eq(
		    live_stats[1] - live_stats_orig[1], live_count, "");
		uint64_t accum_stats[2];
		assert_d_eq(mallctl(mallctl_accum_str, &accum_stats, &stats_len,
		                NULL, 0),
		    0, "");
		expect_u64_eq(
		    accum_stats[0] - accum_stats_orig[0], accum_req_sum, "");
		expect_u64_eq(
		    accum_stats[1] - accum_stats_orig[1], accum_count, "");
	}
#undef MALLCTL_STR_LEN
}

static void
test_szind_wrapper(szind_t ind) {
	size_t sizes_array[N_PTRS];
	int    flags_array[N_PTRS];
	for (size_t i = 0, sz = sz_index2size(ind) - N_PTRS; i < N_PTRS;
	     ++i, ++sz) {
		sizes_array[i] = sz;
		flags_array[i] = 0;
	}
	test_combinations(ind, sizes_array, flags_array);
}

TEST_BEGIN(test_prof_stats) {
	test_skip_if(!config_prof);
	test_szind_wrapper(0);
	test_szind_wrapper(1);
	test_szind_wrapper(2);
	test_szind_wrapper(SC_NBINS);
	test_szind_wrapper(SC_NBINS + 1);
	test_szind_wrapper(SC_NBINS + 2);
}
TEST_END

static void
test_szind_aligned_wrapper(szind_t ind, unsigned lg_align) {
	size_t sizes_array[N_PTRS];
	int    flags_array[N_PTRS];
	int    flags = MALLOCX_LG_ALIGN(lg_align);
	for (size_t i = 0, sz = sz_index2size(ind) - N_PTRS; i < N_PTRS;
	     ++i, ++sz) {
		sizes_array[i] = sz;
		flags_array[i] = flags;
	}
	test_combinations(
	    sz_size2index(sz_sa2u(sz_index2size(ind), 1 << lg_align)),
	    sizes_array, flags_array);
}

TEST_BEGIN(test_prof_stats_aligned) {
	test_skip_if(!config_prof);
	for (szind_t ind = 0; ind < 10; ++ind) {
		for (unsigned lg_align = 0; lg_align < 10; ++lg_align) {
			test_szind_aligned_wrapper(ind, lg_align);
		}
	}
	for (szind_t ind = SC_NBINS - 5; ind < SC_NBINS + 5; ++ind) {
		for (unsigned lg_align = SC_LG_LARGE_MINCLASS - 5;
		     lg_align < SC_LG_LARGE_MINCLASS + 5; ++lg_align) {
			test_szind_aligned_wrapper(ind, lg_align);
		}
	}
}
TEST_END

int
main(void) {
	return test(test_prof_stats, test_prof_stats_aligned);
}
