/*
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2005-2009 Oracle.  All rights reserved.
 *
 * $Id$
 */

/*
 * Macros to help with initializing/assigning key dbts
 */

#define KBUF_LEN 12
#define	INIT_KEY(key, config) do {				\
	memset(&key, 0, sizeof(key));				\
	if (config->orderedkeys) {				\
		key.size = sizeof (u_int32_t);			\
	} else if (config->ksize != 0) {			\
		DB_BENCH_ASSERT(				\
		    (key.data = malloc(key.size = config->ksize)) != NULL); \
	} else {						\
		key.data = kbuf;				\
		key.size = 10;					\
	}							\
	} while (0)

#define	GET_KEY_NEXT(key, config, kbuf, i) do {			\
	size_t tmp_int;						\
	if (config->orderedkeys) {				\
		/* Will be sorted on little-endian system. */	\
		tmp_int = i;					\
		M_32_SWAP(tmp_int);				\
		key.data = &tmp_int;				\
	} else if (config->ksize == 0) {			\
		/*						\
		 * This will produce duplicate keys.		\
		 * That is not such a big deal, since we are	\
		 * using the same seed to srand each time,	\
		 * the scenario is reproducible.		\
		 */						\
		(void)snprintf(kbuf, sizeof(kbuf), "%10d", rand()); \
	} else {						\
		/* TODO: Not sure of the best approach here. */	\
		(void)snprintf(key.data, config->ksize, "%10lu", (u_long)i); \
	}							\
	} while (0)

/* Taken from dbinc/db_swap.h */
#undef	M_32_SWAP
#define	M_32_SWAP(a) {							\
	u_int32_t _tmp;							\
	_tmp = (u_int32_t)a;						\
	((u_int8_t *)&a)[0] = ((u_int8_t *)&_tmp)[3];			\
	((u_int8_t *)&a)[1] = ((u_int8_t *)&_tmp)[2];			\
	((u_int8_t *)&a)[2] = ((u_int8_t *)&_tmp)[1];			\
	((u_int8_t *)&a)[3] = ((u_int8_t *)&_tmp)[0];			\
}

/*
 * A singly linked list, that maintains a pointer
 * to the start and the end of the queue.
 * Should be possible to use a STAILQ, but this seemed easier
 */
typedef struct bench_qentry {
	char data[KBUF_LEN];
	struct bench_qentry *next;
}bench_qentry;
typedef struct bench_q {
	struct bench_qentry *head;
	struct bench_qentry *tail;
} bench_q;
#define	BENCH_Q_TAIL_INSERT(queue, buf) do {		\
	struct bench_qentry *entry;				\
	DB_BENCH_ASSERT(				\
	    (entry = malloc(sizeof(struct bench_qentry))) != NULL); \
	memcpy(entry->data, buf, sizeof(entry->data));	\
	if (queue.head == NULL)				\
		queue.head = queue.tail = entry;	\
	else {						\
		queue.tail->next = entry;		\
		queue.tail = entry;			\
	}						\
} while (0)

#define	BENCH_Q_POP(queue, buf) do {			\
	struct bench_qentry *popped = queue.head;	\
	if (popped == NULL)				\
		break;					\
	if (queue.head->next == NULL)			\
		queue.head = queue.tail = NULL;		\
	else						\
		queue.head = queue.head->next;		\
	memcpy(buf, popped->data, sizeof(buf));	\
	free(popped);					\
} while (0)

/*
 * Retrieve the head of the queue, save the data into user
 * buffer, and push the item back onto the end of the list.
 * Same functionality as pop/insert, but saves a malloc/free
 */
#define	BENCH_Q_POP_PUSH(queue, buf) do {		\
	struct bench_qentry *popped = queue.head;	\
	if (popped == NULL)				\
		break;					\
	if (queue.head->next == NULL)			\
		queue.head = queue.tail = NULL;		\
	else						\
		queue.head = queue.head->next;		\
	memcpy(buf, popped->data, sizeof(buf));	\
	if (queue.head == NULL)				\
		queue.head = queue.tail = popped;	\
	else {						\
		queue.tail->next = popped;		\
		queue.tail = popped;			\
	}						\
} while (0)

typedef enum {
	T_PUT,
	T_GET,
	T_DELETE,
	T_PUT_GET,
	T_PUT_DELETE,
	T_PUT_GET_DELETE,
	T_GET_DELETE,
	T_MIXED
} test_type;

typedef struct
{
	u_int32_t ksize;
	u_int32_t dsize;
	size_t orderedkeys;
	size_t num_dups;
	u_int32_t pagesz;
	u_int32_t cachesz;
	u_int32_t pcount;
	size_t gcount;
	size_t cursor_del;
	size_t verbose;
	test_type workload;
	u_int32_t seed;
	size_t presize;
	DBTYPE type;
	char   *ts;
	char   *message;
	/* Fields used to store timing information */
	db_timespec put_time;
	db_timespec get_time;
	db_timespec del_time;
	db_timespec tot_time;
} CONFIG;
