/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1996, 2013 Oracle and/or its affiliates.  All rights reserved.
 *
 * $Id$
 */

#include "db_config.h"

#include "db_int.h"
#include "dbinc/db_page.h"
#include "dbinc/btree.h"
#include "dbinc/hash.h"
#include "dbinc/heap.h"
#include "dbinc/qam.h"
#include "dbinc/lock.h"
#include "dbinc/mp.h"
#include "dbinc/partition.h"

#ifdef HAVE_STATISTICS
static int __db_print_all __P((DB *, u_int32_t));
static int __db_print_citem __P((DBC *));
static int __db_print_cursor __P((DB *));
static int __db_print_stats __P((DB *, DB_THREAD_INFO *, u_int32_t));
static int __db_stat __P((DB *, DB_THREAD_INFO *, DB_TXN *, void *, u_int32_t));
static int __db_stat_arg __P((DB *, u_int32_t));

/*
 * __db_stat_pp --
 *	DB->stat pre/post processing.
 *
 * PUBLIC: int __db_stat_pp __P((DB *, DB_TXN *, void *, u_int32_t));
 */
int
__db_stat_pp(dbp, txn, spp, flags)
	DB *dbp;
	DB_TXN *txn;
	void *spp;
	u_int32_t flags;
{
	DB_THREAD_INFO *ip;
	ENV *env;
	int handle_check, ret, t_ret;

	env = dbp->env;

	DB_ILLEGAL_BEFORE_OPEN(dbp, "DB->stat");

	if ((ret = __db_stat_arg(dbp, flags)) != 0)
		return (ret);

	ENV_ENTER(env, ip);

	/* Check for replication block. */
	handle_check = IS_ENV_REPLICATED(env);
	if (handle_check && (ret = __db_rep_enter(dbp, 1, 0,
	    IS_REAL_TXN(txn))) != 0) {
		handle_check = 0;
		goto err;
	}

	ret = __db_stat(dbp, ip, txn, spp, flags);

	/* Release replication block. */
	if (handle_check && (t_ret = __env_db_rep_exit(env)) != 0 && ret == 0)
		ret = t_ret;

err:	ENV_LEAVE(env, ip);
	return (ret);
}

/*
 * __db_stat --
 *	DB->stat.
 *
 */
static int
__db_stat(dbp, ip, txn, spp, flags)
	DB *dbp;
	DB_THREAD_INFO *ip;
	DB_TXN *txn;
	void *spp;
	u_int32_t flags;
{
	DBC *dbc;
	ENV *env;
	int ret, t_ret;

	env = dbp->env;

	/* Acquire a cursor. */
	if ((ret = __db_cursor(dbp, ip, txn,
	     &dbc, LF_ISSET(DB_READ_COMMITTED | DB_READ_UNCOMMITTED))) != 0)
		return (ret);

	DEBUG_LWRITE(dbc, NULL, "DB->stat", NULL, NULL, flags);
	LF_CLR(DB_READ_COMMITTED | DB_READ_UNCOMMITTED);
#ifdef HAVE_PARTITION
	if (DB_IS_PARTITIONED(dbp))
		ret = __partition_stat(dbc, spp, flags);
	else
#endif
	switch (dbp->type) {
	case DB_BTREE:
	case DB_RECNO:
		ret = __bam_stat(dbc, spp, flags);
		break;
	case DB_HASH:
		ret = __ham_stat(dbc, spp, flags);
		break;
	case DB_HEAP:
		ret = __heap_stat(dbc, spp, flags);
		break;
	case DB_QUEUE:
		ret = __qam_stat(dbc, spp, flags);
		break;
	case DB_UNKNOWN:
	default:
		ret = (__db_unknown_type(env, "DB->stat", dbp->type));
		break;
	}

	if ((t_ret = __dbc_close(dbc)) != 0 && ret == 0)
		ret = t_ret;

	return (ret);
}

/*
 * __db_stat_arg --
 *	Check DB->stat arguments.
 */
static int
__db_stat_arg(dbp, flags)
	DB *dbp;
	u_int32_t flags;
{
	ENV *env;

	env = dbp->env;

	/* Check for invalid function flags. */
	LF_CLR(DB_READ_COMMITTED | DB_READ_UNCOMMITTED);
	switch (flags) {
	case 0:
	case DB_FAST_STAT:
		break;
	default:
		return (__db_ferr(env, "DB->stat", 0));
	}

	return (0);
}

/*
 * __db_stat_print_pp --
 *	DB->stat_print pre/post processing.
 *
 * PUBLIC: int __db_stat_print_pp __P((DB *, u_int32_t));
 */
int
__db_stat_print_pp(dbp, flags)
	DB *dbp;
	u_int32_t flags;
{
	DB_THREAD_INFO *ip;
	ENV *env;
	int handle_check, ret, t_ret;

	env = dbp->env;

	DB_ILLEGAL_BEFORE_OPEN(dbp, "DB->stat_print");

	/*
	 * !!!
	 * The actual argument checking is simple, do it inline.
	 */
	if ((ret = __db_fchk(env,
	    "DB->stat_print", flags, DB_FAST_STAT | DB_STAT_ALL)) != 0)
		return (ret);

	ENV_ENTER(env, ip);

	/* Check for replication block. */
	handle_check = IS_ENV_REPLICATED(env);
	if (handle_check && (ret = __db_rep_enter(dbp, 1, 0, 0)) != 0) {
		handle_check = 0;
		goto err;
	}

	ret = __db_stat_print(dbp, ip, flags);

	/* Release replication block. */
	if (handle_check && (t_ret = __env_db_rep_exit(env)) != 0 && ret == 0)
		ret = t_ret;

err:	ENV_LEAVE(env, ip);
	return (ret);
}

/*
 * __db_stat_print --
 *	DB->stat_print.
 *
 * PUBLIC: int __db_stat_print __P((DB *, DB_THREAD_INFO *, u_int32_t));
 */
int
__db_stat_print(dbp, ip, flags)
	DB *dbp;
	DB_THREAD_INFO *ip;
	u_int32_t flags;
{
	time_t now;
	int ret;
	char time_buf[CTIME_BUFLEN];

	(void)time(&now);
	__db_msg(dbp->env, "%.24s\tLocal time", __os_ctime(&now, time_buf));

	if (LF_ISSET(DB_STAT_ALL) && (ret = __db_print_all(dbp, flags)) != 0)
		return (ret);

	if ((ret = __db_print_stats(dbp, ip, flags)) != 0)
		return (ret);

	return (0);
}

/*
 * __db_print_stats --
 *	Display default DB handle statistics.
 */
static int
__db_print_stats(dbp, ip, flags)
	DB *dbp;
	DB_THREAD_INFO *ip;
	u_int32_t flags;
{
	DBC *dbc;
	ENV *env;
	int ret, t_ret;

	env = dbp->env;

	/* Acquire a cursor. */
	if ((ret = __db_cursor(dbp, ip, NULL, &dbc, 0)) != 0)
		return (ret);

	DEBUG_LWRITE(dbc, NULL, "DB->stat_print", NULL, NULL, 0);

	switch (dbp->type) {
	case DB_BTREE:
	case DB_RECNO:
		ret = __bam_stat_print(dbc, flags);
		break;
	case DB_HASH:
		ret = __ham_stat_print(dbc, flags);
		break;
	case DB_HEAP:
		ret = __heap_stat_print(dbc, flags);
		break;
	case DB_QUEUE:
		ret = __qam_stat_print(dbc, flags);
		break;
	case DB_UNKNOWN:
	default:
		ret = (__db_unknown_type(env, "DB->stat_print", dbp->type));
		break;
	}

	if ((t_ret = __dbc_close(dbc)) != 0 && ret == 0)
		ret = t_ret;

	return (ret);
}

/*
 * __db_print_all --
 *	Display debugging DB handle statistics.
 */
static int
__db_print_all(dbp, flags)
	DB *dbp;
	u_int32_t flags;
{
	static const FN fn[] = {
		{ DB_AM_CHKSUM,			"DB_AM_CHKSUM" },
		{ DB_AM_COMPENSATE,		"DB_AM_COMPENSATE" },
		{ DB_AM_CREATED,		"DB_AM_CREATED" },
		{ DB_AM_CREATED_MSTR,		"DB_AM_CREATED_MSTR" },
		{ DB_AM_DBM_ERROR,		"DB_AM_DBM_ERROR" },
		{ DB_AM_DELIMITER,		"DB_AM_DELIMITER" },
		{ DB_AM_DISCARD,		"DB_AM_DISCARD" },
		{ DB_AM_DUP,			"DB_AM_DUP" },
		{ DB_AM_DUPSORT,		"DB_AM_DUPSORT" },
		{ DB_AM_ENCRYPT,		"DB_AM_ENCRYPT" },
		{ DB_AM_FIXEDLEN,		"DB_AM_FIXEDLEN" },
		{ DB_AM_INMEM,			"DB_AM_INMEM" },
		{ DB_AM_IN_RENAME,		"DB_AM_IN_RENAME" },
		{ DB_AM_NOT_DURABLE,		"DB_AM_NOT_DURABLE" },
		{ DB_AM_OPEN_CALLED,		"DB_AM_OPEN_CALLED" },
		{ DB_AM_PAD,			"DB_AM_PAD" },
		{ DB_AM_PGDEF,			"DB_AM_PGDEF" },
		{ DB_AM_RDONLY,			"DB_AM_RDONLY" },
		{ DB_AM_READ_UNCOMMITTED,	"DB_AM_READ_UNCOMMITTED" },
		{ DB_AM_RECNUM,			"DB_AM_RECNUM" },
		{ DB_AM_RECOVER,		"DB_AM_RECOVER" },
		{ DB_AM_RENUMBER,		"DB_AM_RENUMBER" },
		{ DB_AM_REVSPLITOFF,		"DB_AM_REVSPLITOFF" },
		{ DB_AM_SECONDARY,		"DB_AM_SECONDARY" },
		{ DB_AM_SNAPSHOT,		"DB_AM_SNAPSHOT" },
		{ DB_AM_SUBDB,			"DB_AM_SUBDB" },
		{ DB_AM_SWAP,			"DB_AM_SWAP" },
		{ DB_AM_TXN,			"DB_AM_TXN" },
		{ DB_AM_VERIFYING,		"DB_AM_VERIFYING" },
		{ 0,				NULL }
	};
	ENV *env;
	char time_buf[CTIME_BUFLEN];

	env = dbp->env;

	__db_msg(env, "%s", DB_GLOBAL(db_line));
	__db_msg(env, "DB handle information:");
	STAT_ULONG("Page size", dbp->pgsize);
	STAT_ISSET("Append recno", dbp->db_append_recno);
	STAT_ISSET("Feedback", dbp->db_feedback);
	STAT_ISSET("Dup compare", dbp->dup_compare);
	STAT_ISSET("App private", dbp->app_private);
	STAT_ISSET("DbEnv", dbp->env);
	STAT_STRING("Type", __db_dbtype_to_string(dbp->type));

	__mutex_print_debug_single(env, "Thread mutex", dbp->mutex, flags);

	STAT_STRING("File", dbp->fname);
	STAT_STRING("Database", dbp->dname);
	STAT_HEX("Open flags", dbp->open_flags);

	__db_print_fileid(env, dbp->fileid, "\tFile ID");

	STAT_ULONG("Cursor adjust ID", dbp->adj_fileid);
	STAT_ULONG("Meta pgno", dbp->meta_pgno);
	if (dbp->locker != NULL)
		STAT_ULONG("Locker ID", dbp->locker->id);
	if (dbp->cur_locker != NULL)
		STAT_ULONG("Handle lock", dbp->cur_locker->id);
	if (dbp->associate_locker != NULL)
		STAT_ULONG("Associate lock", dbp->associate_locker->id);

	__db_msg(env,
	    "%.24s\tReplication handle timestamp",
	    dbp->timestamp == 0 ? "0" : __os_ctime(&dbp->timestamp, time_buf));

	STAT_ISSET("Secondary callback", dbp->s_callback);
	STAT_ISSET("Primary handle", dbp->s_primary);

	STAT_ISSET("api internal", dbp->api_internal);
	STAT_ISSET("Btree/Recno internal", dbp->bt_internal);
	STAT_ISSET("Hash internal", dbp->h_internal);
	STAT_ISSET("Queue internal", dbp->q_internal);

	__db_prflags(env, NULL, dbp->flags, fn, NULL, "\tFlags");

	if (dbp->log_filename == NULL)
		STAT_ISSET("File naming information", dbp->log_filename);
	else
		__dbreg_print_fname(env, dbp->log_filename);

	(void)__db_print_cursor(dbp);

	return (0);
}

/*
 * __db_print_cursor --
 *	Display the cursor active and free queues.
 */
static int
__db_print_cursor(dbp)
	DB *dbp;
{
	DBC *dbc;
	ENV *env;
	int ret, t_ret;

	env = dbp->env;

	__db_msg(env, "%s", DB_GLOBAL(db_line));
	__db_msg(env, "DB handle cursors:");

	ret = 0;
	MUTEX_LOCK(dbp->env, dbp->mutex);
	__db_msg(env, "Active queue:");
	TAILQ_FOREACH(dbc, &dbp->active_queue, links)
		if ((t_ret = __db_print_citem(dbc)) != 0 && ret == 0)
			ret = t_ret;
	__db_msg(env, "Join queue:");
	TAILQ_FOREACH(dbc, &dbp->join_queue, links)
		if ((t_ret = __db_print_citem(dbc)) != 0 && ret == 0)
			ret = t_ret;
	__db_msg(env, "Free queue:");
	TAILQ_FOREACH(dbc, &dbp->free_queue, links)
		if ((t_ret = __db_print_citem(dbc)) != 0 && ret == 0)
			ret = t_ret;
	MUTEX_UNLOCK(dbp->env, dbp->mutex);

	return (ret);
}

static int
__db_print_citem(dbc)
	DBC *dbc;
{
	static const FN fn[] = {
		{ DBC_ACTIVE,		"DBC_ACTIVE" },
		{ DBC_DONTLOCK,		"DBC_DONTLOCK" },
		{ DBC_MULTIPLE,		"DBC_MULTIPLE" },
		{ DBC_MULTIPLE_KEY,	"DBC_MULTIPLE_KEY" },
		{ DBC_OPD,		"DBC_OPD" },
		{ DBC_OWN_LID,		"DBC_OWN_LID" },
		{ DBC_READ_COMMITTED,	"DBC_READ_COMMITTED" },
		{ DBC_READ_UNCOMMITTED,	"DBC_READ_UNCOMMITTED" },
		{ DBC_RECOVER,		"DBC_RECOVER" },
		{ DBC_RMW,		"DBC_RMW" },
		{ DBC_TRANSIENT,	"DBC_TRANSIENT" },
		{ DBC_WAS_READ_COMMITTED,"DBC_WAS_READ_COMMITTED" },
		{ DBC_WRITECURSOR,	"DBC_WRITECURSOR" },
		{ DBC_WRITER,		"DBC_WRITER" },
		{ 0,			NULL }
	};
	DB *dbp;
	DBC_INTERNAL *cp;
	ENV *env;

	dbp = dbc->dbp;
	env = dbp->env;
	cp = dbc->internal;

	STAT_POINTER("DBC", dbc);
	STAT_POINTER("Associated dbp", dbc->dbp);
	STAT_POINTER("Associated txn", dbc->txn);
	STAT_POINTER("Internal", cp);
	STAT_HEX("Default locker ID", dbc->lref == NULL ? 0 : dbc->lref->id);
	STAT_HEX("Locker", dbc->locker == NULL ? 0 : dbc->locker->id);
	STAT_STRING("Type", __db_dbtype_to_string(dbc->dbtype));

	STAT_POINTER("Off-page duplicate cursor", cp->opd);
	STAT_POINTER("Referenced page", cp->page);
	STAT_ULONG("Root", cp->root);
	STAT_ULONG("Page number", cp->pgno);
	STAT_ULONG("Page index", cp->indx);
	STAT_STRING("Lock mode", __db_lockmode_to_string(cp->lock_mode));
	__db_prflags(env, NULL, dbc->flags, fn, NULL, "\tFlags");

	switch (dbc->dbtype) {
	case DB_BTREE:
	case DB_RECNO:
		__bam_print_cursor(dbc);
		break;
	case DB_HASH:
		__ham_print_cursor(dbc);
		break;
	case DB_HEAP:
		__heap_print_cursor(dbc);
		break;
	case DB_UNKNOWN:
		DB_ASSERT(env, dbp->type != DB_UNKNOWN);
		/* FALLTHROUGH */
	case DB_QUEUE:
	default:
		break;
	}
	return (0);
}

#else /* !HAVE_STATISTICS */

int
__db_stat_pp(dbp, txn, spp, flags)
	DB *dbp;
	DB_TXN *txn;
	void *spp;
	u_int32_t flags;
{
	COMPQUIET(spp, NULL);
	COMPQUIET(txn, NULL);
	COMPQUIET(flags, 0);

	return (__db_stat_not_built(dbp->env));
}

int
__db_stat_print_pp(dbp, flags)
	DB *dbp;
	u_int32_t flags;
{
	COMPQUIET(flags, 0);

	return (__db_stat_not_built(dbp->env));
}
#endif
