/*
 * Unix SMB/CIFS implementation.
 * Test module for perfcounters
 *
 * Copyright (C) Todd Stecher 2008
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "includes.h"
#include "smbd/smbd.h"

#define PARM_PC_TEST_TYPE		"pc_test"
#define PARM_DUMPON_COUNT		"count"
#define PARM_DUMPON_COUNT_DEFAULT	50

struct perfcount_test_identity {
	uid_t uid;
	char *user;
	char *domain;
};

struct perfcount_test_counter {
	int op;
	int sub_op;
	int ioctl;
	uint64_t bytes_in;
	uint64_t bytes_out;
	int count;

	struct perfcount_test_counter *next;
	struct perfcount_test_counter *prev;
};

struct perfcount_test_context {

	/* wip:  identity */
	struct perfcount_test_identity *id;
	struct perfcount_test_counter *ops;
};

#define MAX_OP 256
struct perfcount_test_counter *g_list[MAX_OP];

int count;

/* determine frequency of dumping results */
int count_mod = 1;

static void perfcount_test_add_counters(struct perfcount_test_context *ctxt)
{
	struct perfcount_test_counter *head;
	struct perfcount_test_counter *ptc;
	struct perfcount_test_counter *tmp;
	bool found;

	for (ptc = ctxt->ops; ptc != NULL; ) {

		found = false;

		if (ptc->op > MAX_OP)
			continue;

		for (head = g_list[ptc->op]; head != NULL; head = head->next) {
			if ((ptc->sub_op == head->sub_op) &&
			    (ptc->ioctl == head->ioctl)) {
				head->bytes_in += ptc->bytes_in;
				head->bytes_out += ptc->bytes_out;
				head->count++;
				tmp = ptc->next;
				DLIST_REMOVE(ctxt->ops, ptc);
				SAFE_FREE(ptc);
				ptc = tmp;
				found = true;
				break;
			}
		}

		/* not in global tracking list - add it */
		if (!found) {
			tmp = ptc->next;
			DLIST_REMOVE(ctxt->ops, ptc);
			ptc->count = 1;
			DLIST_ADD(g_list[ptc->op], ptc);
			ptc = tmp;
		}
	}

}

#if 0

static void perfcount_test_dump_id(struct perfcount_test_identity *id, int lvl)
{
	if (!id)
		return;

	DEBUG(lvl,("uid - %d\n", id->uid));
	DEBUG(lvl,("user - %s\n", id->user));
	DEBUG(lvl,("domain - %s\n", id->domain));
}

#endif

static const char *trans_subop_table[] = {
	"unknown", "trans:create", "trans:ioctl", "trans:set sd",
	"trans:change notify", "trans: rename", "trans:get sd",
	"trans:get quota", "trans:set quota"
};

static const char *trans2_subop_table[] = {
	"trans2:open", "trans2:find first", "trans2:find next",
	"trans2:q fsinfo", "trans2:set fsinfo", "trans2:q path info",
	"trans2:set pathinfo", "trans2:fs ctl", "trans2: io ctl",
	"trans2:find notify first", "trans2:find notify next",
	"trans2:mkdir", "trans2:sess setup", "trans2:get dfs referral",
	"trans2:report dfs inconsistent"
};

static const char *smb_subop_name(int op, int subop)
{
	/* trans */
	if (op == 0x25) {
		if (subop > sizeof(trans_subop_table) /
		    sizeof(trans_subop_table[0])) {
			return "unknown";
		}
		return trans_subop_table[subop];
	} else if (op == 0x32) {
		if (subop > sizeof(trans2_subop_table) /
		    sizeof(trans2_subop_table[0])) {
			return "unknown";
		}
		return trans2_subop_table[subop];
	}

	return "unknown";
}

static void perfcount_test_dump_counter(struct perfcount_test_counter *ptc,
					int lvl)
{
	DEBUG(lvl, ("OP: %s\n", smb_fn_name(ptc->op)));
	if (ptc->sub_op > 0) {
		DEBUG(lvl, ("SUBOP: %s\n",
			smb_subop_name(ptc->op, ptc->sub_op)));
	}

	if (ptc->ioctl > 0) {
		DEBUG(lvl, ("IOCTL: %d\n", ptc->ioctl));
	}

	DEBUG(lvl, ("Count: %d\n\n", ptc->count));
}

static void perfcount_test_dump_counters(void)
{
	int i;
	struct perfcount_test_counter *head;

	count_mod = lp_parm_int(0, PARM_PC_TEST_TYPE, PARM_DUMPON_COUNT,
	    PARM_DUMPON_COUNT_DEFAULT);

	if ((count++ % count_mod) != 0)
		return;

	DEBUG(0,("#####  Dumping Performance Counters #####\n"));

	for (i=0; i < MAX_OP; i++) {
		struct perfcount_test_counter *next;
		for (head = g_list[i]; head != NULL; head = next) {
			next = head->next;
			perfcount_test_dump_counter(head, 0);
			SAFE_FREE(head);
		}
		g_list[i] = NULL;
	}
}

/*  operations */
static void perfcount_test_start(struct smb_perfcount_data *pcd)
{
	struct perfcount_test_context *ctxt;
	struct perfcount_test_counter *ctr;
	/*
	 * there shouldn't already be a context here - if so,
	 * there's an unbalanced call to start / end.
	 */
	if (pcd->context) {
		DEBUG(0,("perfcount_test_start - starting "
			 "initialized context - %p\n", pcd));
		return;
	}

	ctxt = SMB_MALLOC_P(struct perfcount_test_context);
	if (!ctxt)
		return;

	ZERO_STRUCTP(ctxt);

	/* create 'default' context */
	ctr = SMB_MALLOC_P(struct perfcount_test_counter);
	if (!ctr) {
		SAFE_FREE(ctxt);
		return;
	}

	ZERO_STRUCTP(ctr);
	ctr->op = ctr->sub_op = ctr->ioctl = -1;
	DLIST_ADD(ctxt->ops, ctr);

	pcd->context = (void*)ctxt;
}

static void perfcount_test_add(struct smb_perfcount_data *pcd)
{
	struct perfcount_test_context *ctxt =
		(struct perfcount_test_context *)pcd->context;
	struct perfcount_test_counter *ctr;

        if (pcd->context == NULL)
                return;

	ctr = SMB_MALLOC_P(struct perfcount_test_counter);
	if (!ctr) {
		return;
	}

	DLIST_ADD(ctxt->ops, ctr);

}

static void perfcount_test_set_op(struct smb_perfcount_data *pcd, int op)
{
	struct perfcount_test_context *ctxt =
		(struct perfcount_test_context *)pcd->context;

        if (pcd->context == NULL)
                return;

	ctxt->ops->op = op;
}

static void perfcount_test_set_subop(struct smb_perfcount_data *pcd, int sub_op)
{
	struct perfcount_test_context *ctxt =
		(struct perfcount_test_context *)pcd->context;

        if (pcd->context == NULL)
                return;

	ctxt->ops->sub_op = sub_op;
}

static void perfcount_test_set_ioctl(struct smb_perfcount_data *pcd, int io_ctl)
{
	struct perfcount_test_context *ctxt =
		(struct perfcount_test_context *)pcd->context;
        if (pcd->context == NULL)
                return;

	ctxt->ops->ioctl = io_ctl;
}

static void perfcount_test_set_msglen_in(struct smb_perfcount_data *pcd,
					 uint64_t bytes_in)
{
	struct perfcount_test_context *ctxt =
		(struct perfcount_test_context *)pcd->context;
        if (pcd->context == NULL)
                return;

	ctxt->ops->bytes_in = bytes_in;
}

static void perfcount_test_set_msglen_out(struct smb_perfcount_data *pcd,
					  uint64_t bytes_out)
{
	struct perfcount_test_context *ctxt =
		(struct perfcount_test_context *)pcd->context;

        if (pcd->context == NULL)
                return;

	ctxt->ops->bytes_out = bytes_out;
}

static void perfcount_test_copy_context(struct smb_perfcount_data *pcd,
				        struct smb_perfcount_data *new_pcd)
{
	struct perfcount_test_context *ctxt =
		(struct perfcount_test_context *)pcd->context;
	struct perfcount_test_context *new_ctxt;

	struct perfcount_test_counter *ctr;
	struct perfcount_test_counter *new_ctr;

        if (pcd->context == NULL)
                return;

	new_ctxt = SMB_MALLOC_P(struct perfcount_test_context);
	if (!new_ctxt) {
		return;
	}

	memcpy(new_ctxt, ctxt, sizeof(struct perfcount_test_context));

	for (ctr = ctxt->ops; ctr != NULL; ctr = ctr->next) {
		new_ctr = SMB_MALLOC_P(struct perfcount_test_counter);
		if (!new_ctr) {
			goto error;
		}

		memcpy(new_ctr, ctr, sizeof(struct perfcount_test_counter));
		new_ctr->next = NULL;
		new_ctr->prev = NULL;
		DLIST_ADD(new_ctxt->ops, new_ctr);
	}

	new_pcd->context = new_ctxt;
	return;

error:

	for (ctr = new_ctxt->ops; ctr != NULL; ) {
		new_ctr = ctr->next;
		SAFE_FREE(ctr);
		ctr = new_ctr;
	}

	SAFE_FREE(new_ctxt);
}

/*
 * For perf reasons, its best to use some global state
 * when an operation is deferred, we need to alloc a copy.
 */
static void perfcount_test_defer_op(struct smb_perfcount_data *pcd,
				    struct smb_perfcount_data *def_pcd)
{
	/* we don't do anything special to deferred ops */
	return;
}

static void perfcount_test_end(struct smb_perfcount_data *pcd)
{
	struct perfcount_test_context *ctxt =
		(struct perfcount_test_context *)pcd->context;
        if (pcd->context == NULL)
                return;

	/* @bug - we don't store outbytes right for chained cmds */
	perfcount_test_add_counters(ctxt);
	perfcount_test_dump_counters();
	pcd->context = NULL;
	SAFE_FREE(ctxt);
}


static struct smb_perfcount_handlers perfcount_test_handlers = {
	perfcount_test_start,
	perfcount_test_add,
	perfcount_test_set_op,
	perfcount_test_set_subop,
	perfcount_test_set_ioctl,
	perfcount_test_set_msglen_in,
	perfcount_test_set_msglen_out,
	perfcount_test_copy_context,
	perfcount_test_defer_op,
	perfcount_test_end
};

NTSTATUS samba_init_module(void)
{
	return smb_register_perfcounter(SMB_PERFCOUNTER_INTERFACE_VERSION,
					"pc_test", &perfcount_test_handlers);
}