/*-
 * Copyright (c) 2014-2018 MongoDB, Inc.
 * Copyright (c) 2008-2014 WiredTiger, Inc.
 *	All rights reserved.
 *
 * See the file LICENSE for redistribution information.
 */

#include "wt_internal.h"

WT_PROCESS __wt_process;			/* Per-process structure */
static int __wt_pthread_once_failed;		/* If initialization failed */

/*
 * __wt_endian_check --
 *	Check the build matches the machine.
 */
static int
__wt_endian_check(void)
{
	uint64_t v;
	const char *e;
	bool big;

	v = 1;
	big = *((uint8_t *)&v) == 0;

#ifdef WORDS_BIGENDIAN
	if (big)
		return (0);
	e = "big-endian";
#else
	if (!big)
		return (0);
	e = "little-endian";
#endif
	fprintf(stderr,
	    "This is a %s build of the WiredTiger data engine, incompatible "
	    "with this system\n", e);
	return (EINVAL);
}

/*
 * __global_calibrate_ticks --
 *	Calibrate a ratio from rdtsc ticks to nanoseconds.
 */
static void
__global_calibrate_ticks(void)
{
	/*
	 * Default to using __wt_epoch until we have a good value for the ratio.
	 */
	__wt_process.tsc_nsec_ratio = WT_TSC_DEFAULT_RATIO;
	__wt_process.use_epochtime = true;

#if defined (__i386) || defined (__amd64)
	{
	struct timespec start, stop;
	double ratio;
	uint64_t diff_nsec, diff_tsc, min_nsec, min_tsc;
	uint64_t tries, tsc_start, tsc_stop;
	volatile uint64_t i;

	/*
	 * Run this calibration loop a few times to make sure we get a
	 * reading that does not have a potential scheduling shift in it.
	 * The inner loop is CPU intensive but a scheduling change in the
	 * middle could throw off calculations. Take the minimum amount
	 * of time and compute the ratio.
	 */
	min_nsec = min_tsc = UINT64_MAX;
	for (tries = 0; tries < 3; ++tries) {
		/* This needs to be CPU intensive and large enough. */
		__wt_epoch(NULL, &start);
		tsc_start = __wt_rdtsc();
		for (i = 0; i < 100 * WT_MILLION; i++)
			;
		tsc_stop = __wt_rdtsc();
		__wt_epoch(NULL, &stop);
		diff_nsec = WT_TIMEDIFF_NS(stop, start);
		diff_tsc = tsc_stop - tsc_start;

		/* If the clock didn't tick over, we don't have a sample. */
		if (diff_nsec == 0 || diff_tsc == 0)
			continue;
		min_nsec = WT_MIN(min_nsec, diff_nsec);
		min_tsc = WT_MIN(min_tsc, diff_tsc);
	}

	/*
	 * Only use rdtsc if we got a good reading.  One reason this might fail
	 * is that the system's clock granularity is not fine-grained enough.
	 */
	if (min_nsec != UINT64_MAX) {
		ratio = (double)min_tsc / (double)min_nsec;
		if (ratio > DBL_EPSILON) {
			__wt_process.tsc_nsec_ratio = ratio;
			__wt_process.use_epochtime = false;
		}
	}
	}
#endif
}

/*
 * __wt_global_once --
 *	Global initialization, run once.
 */
static void
__wt_global_once(void)
{
	WT_DECL_RET;

	if ((ret =
	    __wt_spin_init(NULL, &__wt_process.spinlock, "global")) != 0) {
		__wt_pthread_once_failed = ret;
		return;
	}

	__wt_checksum_init();
	__global_calibrate_ticks();

	TAILQ_INIT(&__wt_process.connqh);
}

/*
 * __wt_library_init --
 *	Some things to do, before we do anything else.
 */
int
__wt_library_init(void)
{
	static bool first = true;
	WT_DECL_RET;

	/* Check the build matches the machine. */
	WT_RET(__wt_endian_check());

	/*
	 * Do per-process initialization once, before anything else, but only
	 * once.  I don't know how heavy-weight the function (pthread_once, in
	 * the POSIX world), might be, so I'm front-ending it with a local
	 * static and only using that function to avoid a race.
	 */
	if (first) {
		if ((ret = __wt_once(__wt_global_once)) != 0)
			__wt_pthread_once_failed = ret;
		first = false;
	}
	return (__wt_pthread_once_failed);
}
