/*-
 * Public Domain 2014-2018 MongoDB, Inc.
 * Public Domain 2008-2014 WiredTiger, Inc.
 *
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "test_util.h"

#define	HOME_SIZE	512
#define	HOME_BASE	"WT_TEST"
static char home[HOME_SIZE];		/* Base home directory */
static char hometmp[HOME_SIZE];		/* Each conn home directory */
static const char * const uri = "table:main";

#define	WTOPEN_CFG_COMMON					\
    "create,log=(file_max=10M,archive=false,enabled),"		\
    "statistics=(fast),statistics_log=(wait=5),"
#define	WT_CONFIG0						\
    WTOPEN_CFG_COMMON						\
    "transaction_sync=(enabled=false)"
#define	WT_CONFIG1						\
    WTOPEN_CFG_COMMON						\
    "transaction_sync=(enabled,method=none)"
#define	WT_CONFIG2						\
    WTOPEN_CFG_COMMON						\
    "transaction_sync=(enabled,method=fsync)"

#define	MAX_DBS		10
#define	MAX_IDLE_TIME	30
#define	IDLE_INCR	5

#define	MAX_KV		100
#define	MAX_VAL		128

static void usage(void)
    WT_GCC_FUNC_DECL_ATTRIBUTE((noreturn));
static void
usage(void)
{
	fprintf(stderr,
	    "usage: %s [-I] [-D maxdbs] [-h dir]\n", progname);
	exit(EXIT_FAILURE);
}

extern int __wt_optind;
extern char *__wt_optarg;

static WT_CONNECTION **connections = NULL;
static WT_CURSOR **cursors = NULL;
static WT_RAND_STATE rnd;
static WT_SESSION **sessions = NULL;

static int
get_stat(WT_SESSION *stat_session, int stat_field, uint64_t *valuep)
{
	WT_CURSOR *statc;
	int ret;
	const char *desc, *pvalue;

	testutil_check(stat_session->open_cursor(stat_session,
	    "statistics:", NULL, NULL, &statc));
	statc->set_key(statc, stat_field);
	if ((ret = statc->search(statc)) != 0)
		return (ret);

	ret = statc->get_value(statc, &desc, &pvalue, valuep);
	testutil_check(statc->close(statc));
	return (ret);
}

static void
run_ops(int dbs)
{
	WT_ITEM data;
	uint32_t db;
	uint8_t buf[MAX_VAL];
	int db_set, i, key;

	memset(buf, 0, sizeof(buf));
	for (i = 0; i < MAX_VAL; ++i)
		buf[i] = (uint8_t)__wt_random(&rnd);
	data.data = buf;
	/*
	 * Write a small amount of data into a random subset of the databases.
	 */
	db_set = dbs / 4;
	for (i = 0; i < db_set; ++i) {
		db = __wt_random(&rnd) % (uint32_t)dbs;
		printf("Write to database %" PRIu32 "\n", db);
		for (key = 0; key < MAX_KV; ++key) {
			data.size = __wt_random(&rnd) % MAX_VAL;
			cursors[db]->set_key(cursors[db], key);
			cursors[db]->set_value(cursors[db], &data);
			testutil_check(cursors[db]->insert(cursors[db]));
		}
	}
}

int
main(int argc, char *argv[])
{
	uint64_t cond_reset, cond_wait;
	uint64_t *cond_reset_orig;
	int cfg, ch, dbs, i;
	char cmd[128];
	const char *working_dir, *wt_cfg;
	bool idle;

	(void)testutil_set_progname(argv);

	dbs = MAX_DBS;
	working_dir = HOME_BASE;
	idle = false;
	while ((ch = __wt_getopt(progname, argc, argv, "D:h:I")) != EOF)
		switch (ch) {
		case 'D':
			dbs = atoi(__wt_optarg);
			break;
		case 'h':
			working_dir = __wt_optarg;
			break;
		case 'I':
			idle = true;
			break;
		default:
			usage();
		}
	argc -= __wt_optind;
	argv += __wt_optind;
	if (argc != 0)
		usage();

	/*
	 * Allocate arrays for connection handles, sessions, statistics
	 * cursors and, if needed, data cursors.
	 */
	connections = dcalloc((size_t)dbs, sizeof(WT_CONNECTION *));
	sessions = dcalloc((size_t)dbs, sizeof(WT_SESSION *));
	cond_reset_orig = dcalloc((size_t)dbs, sizeof(uint64_t));
	cursors = idle ? NULL : dcalloc((size_t)dbs, sizeof(WT_CURSOR *));
	memset(cmd, 0, sizeof(cmd));
	/*
	 * Set up all the directory names.
	 */
	testutil_work_dir_from_path(home, HOME_SIZE, working_dir);
	testutil_make_work_dir(home);
	__wt_random_init(&rnd);
	for (i = 0; i < dbs; ++i) {
		testutil_check(__wt_snprintf(
		    hometmp, HOME_SIZE, "%s/%s.%d", home, HOME_BASE, i));
		testutil_make_work_dir(hometmp);
		/*
		 * Open each database.  Rotate different configurations
		 * among them.  Open a session and statistics cursor.
		 * If writing data, create the table and open a data cursor.
		 */
		cfg = i % 3;
		if (cfg == 0)
			wt_cfg = WT_CONFIG0;
		else if (cfg == 1)
			wt_cfg = WT_CONFIG1;
		else
			wt_cfg = WT_CONFIG2;
		testutil_check(wiredtiger_open(
		    hometmp, NULL, wt_cfg, &connections[i]));
		testutil_check(connections[i]->open_session(connections[i],
		    NULL, NULL, &sessions[i]));
		if (!idle) {
			testutil_check(sessions[i]->create(sessions[i],
			    uri, "key_format=Q,value_format=u"));
			testutil_check(sessions[i]->open_cursor(sessions[i],
			    uri, NULL, NULL, &cursors[i]));
		}
	}

	sleep(10);

	/*
	 * Record original reset setting.  There could have been some
	 * activity during the creation period.
	 */
	for (i = 0; i < dbs; ++i)
		testutil_check(get_stat(sessions[i],
		    WT_STAT_CONN_COND_AUTO_WAIT_RESET, &cond_reset_orig[i]));
	for (i = 0; i < MAX_IDLE_TIME; i += IDLE_INCR) {
		if (!idle)
			run_ops(dbs);
		printf("Sleep %d (%d of %d)\n", IDLE_INCR, i, MAX_IDLE_TIME);
		sleep(IDLE_INCR);
	}
	for (i = 0; i < dbs; ++i) {
		testutil_check(get_stat(sessions[i],
		    WT_STAT_CONN_COND_AUTO_WAIT_RESET, &cond_reset));
		testutil_check(get_stat(sessions[i],
		    WT_STAT_CONN_COND_AUTO_WAIT, &cond_wait));
		/*
		 * On an idle workload there should be no resets of condition
		 * variables during the idle period.  Even with a light
		 * workload, resets should not be very common.  We look for 5%.
		 */
		if (idle && cond_reset != cond_reset_orig[i])
			testutil_die(ERANGE,
			    "condition reset on idle connection %d of %" PRIu64,
			    i, cond_reset);
		if (!idle && cond_reset > cond_wait / 20)
			testutil_die(ERANGE, "connection %d condition reset %"
			    PRIu64 " exceeds 5%% of %" PRIu64,
			    i, cond_reset, cond_wait);
		testutil_check(connections[i]->close(connections[i], NULL));
	}

	/* Cleanup allocated memory. */
	free(connections);
	free(sessions);
	free(cond_reset_orig);
	free(cursors);

	return (EXIT_SUCCESS);
}
