/*-
 *   BSD LICENSE
 *
 *   Copyright(c) 2015 Intel Corporation. All rights reserved.
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Intel Corporation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <rte_log.h>
#include <rte_common.h>

#include "lthread_diag.h"
#include "lthread_queue.h"
#include "lthread_pool.h"
#include "lthread_objcache.h"
#include "lthread_sched.h"
#include "lthread_diag_api.h"


/* dummy ref value of default diagnostic callback */
static uint64_t dummy_ref;

#define DIAG_SCHED_STATS_FORMAT \
"core %d\n%33s %12s %12s %12s %12s\n"

#define DIAG_CACHE_STATS_FORMAT \
"%20s %12lu %12lu %12lu %12lu %12lu\n"

#define DIAG_QUEUE_STATS_FORMAT \
"%20s %12lu %12lu %12lu\n"


/*
 * texts used in diagnostic events,
 * corresponding diagnostic mask bit positions are given as comment
 */
const char *diag_event_text[] = {
	"LTHREAD_CREATE     ",	/* 00 */
	"LTHREAD_EXIT       ",	/* 01 */
	"LTHREAD_JOIN       ",	/* 02 */
	"LTHREAD_CANCEL     ",	/* 03 */
	"LTHREAD_DETACH     ",	/* 04 */
	"LTHREAD_FREE       ",	/* 05 */
	"LTHREAD_SUSPENDED  ",	/* 06 */
	"LTHREAD_YIELD      ",	/* 07 */
	"LTHREAD_RESCHEDULED",	/* 08 */
	"LTHREAD_SLEEP      ",	/* 09 */
	"LTHREAD_RESUMED    ",	/* 10 */
	"LTHREAD_AFFINITY   ",	/* 11 */
	"LTHREAD_TMR_START  ",	/* 12 */
	"LTHREAD_TMR_DELETE ",	/* 13 */
	"LTHREAD_TMR_EXPIRED",	/* 14 */
	"COND_CREATE        ",	/* 15 */
	"COND_DESTROY       ",	/* 16 */
	"COND_WAIT          ",	/* 17 */
	"COND_SIGNAL        ",	/* 18 */
	"COND_BROADCAST     ",	/* 19 */
	"MUTEX_CREATE       ",	/* 20 */
	"MUTEX_DESTROY      ",	/* 21 */
	"MUTEX_LOCK         ",	/* 22 */
	"MUTEX_TRYLOCK      ",	/* 23 */
	"MUTEX_BLOCKED      ",	/* 24 */
	"MUTEX_UNLOCKED     ",	/* 25 */
	"SCHED_CREATE       ",	/* 26 */
	"SCHED_SHUTDOWN     "	/* 27 */
};


/*
 * set diagnostic ,ask
 */
void lthread_diagnostic_set_mask(DIAG_USED uint64_t mask)
{
#if LTHREAD_DIAG
	diag_mask = mask;
#else
	RTE_LOG(INFO, LTHREAD,
		"LTHREAD_DIAG is not set, see lthread_diag_api.h\n");
#endif
}


/*
 * Check consistency of the scheduler stats
 * Only sensible run after the schedulers are stopped
 * Count the number of objects lying in caches and queues
 * and available in the qnode pool.
 * This should be equal to the total capacity of all
 * qnode pools.
 */
void
_sched_stats_consistency_check(void);
void
_sched_stats_consistency_check(void)
{
#if LTHREAD_DIAG
	int i;
	struct lthread_sched *sched;
	uint64_t count = 0;
	uint64_t capacity = 0;

	for (i = 0; i < LTHREAD_MAX_LCORES; i++) {
		sched = schedcore[i];
		if (sched == NULL)
			continue;

		/* each of these queues consumes a stub node */
		count += 8;
		count += DIAG_COUNT(sched->ready, size);
		count += DIAG_COUNT(sched->pready, size);
		count += DIAG_COUNT(sched->lthread_cache, available);
		count += DIAG_COUNT(sched->stack_cache, available);
		count += DIAG_COUNT(sched->tls_cache, available);
		count += DIAG_COUNT(sched->per_lthread_cache, available);
		count += DIAG_COUNT(sched->cond_cache, available);
		count += DIAG_COUNT(sched->mutex_cache, available);

		/* the node pool does not consume a stub node */
		if (sched->qnode_pool->fast_alloc != NULL)
			count++;
		count += DIAG_COUNT(sched->qnode_pool, available);

		capacity += DIAG_COUNT(sched->qnode_pool, capacity);
	}
	if (count != capacity) {
		RTE_LOG(CRIT, LTHREAD,
			"Scheduler caches are inconsistent\n");
	} else {
		RTE_LOG(INFO, LTHREAD,
			"Scheduler caches are ok\n");
	}
#endif
}


#if LTHREAD_DIAG
/*
 * Display node pool stats
 */
static inline void
_qnode_pool_display(DIAG_USED struct qnode_pool *p)
{

	printf(DIAG_CACHE_STATS_FORMAT,
			p->name,
			DIAG_COUNT(p, rd),
			DIAG_COUNT(p, wr),
			DIAG_COUNT(p, available),
			DIAG_COUNT(p, prealloc),
			DIAG_COUNT(p, capacity));
	fflush(stdout);
}
#endif


#if LTHREAD_DIAG
/*
 * Display queue stats
 */
static inline void
_lthread_queue_display(DIAG_USED struct lthread_queue *q)
{
#if DISPLAY_OBJCACHE_QUEUES
	printf(DIAG_QUEUE_STATS_FORMAT,
			q->name,
			DIAG_COUNT(q, rd),
			DIAG_COUNT(q, wr),
			DIAG_COUNT(q, size));
	fflush(stdout);
#else
	printf("%s: queue stats disabled\n",
			q->name);

#endif
}
#endif

#if LTHREAD_DIAG
/*
 * Display objcache stats
 */
static inline void
_objcache_display(DIAG_USED struct lthread_objcache *c)
{

	printf(DIAG_CACHE_STATS_FORMAT,
			c->name,
			DIAG_COUNT(c, rd),
			DIAG_COUNT(c, wr),
			DIAG_COUNT(c, available),
			DIAG_COUNT(c, prealloc),
			DIAG_COUNT(c, capacity));
	_lthread_queue_display(c->q);
	fflush(stdout);
}
#endif

/*
 * Display sched stats
 */
void
lthread_sched_stats_display(void)
{
#if LTHREAD_DIAG
	int i;
	struct lthread_sched *sched;

	for (i = 0; i < LTHREAD_MAX_LCORES; i++) {
		sched = schedcore[i];
		if (sched != NULL) {
			printf(DIAG_SCHED_STATS_FORMAT,
					sched->lcore_id,
					"rd",
					"wr",
					"present",
					"nb preallocs",
					"capacity");
			_lthread_queue_display(sched->ready);
			_lthread_queue_display(sched->pready);
			_qnode_pool_display(sched->qnode_pool);
			_objcache_display(sched->lthread_cache);
			_objcache_display(sched->stack_cache);
			_objcache_display(sched->tls_cache);
			_objcache_display(sched->per_lthread_cache);
			_objcache_display(sched->cond_cache);
			_objcache_display(sched->mutex_cache);
		fflush(stdout);
		}
	}
	_sched_stats_consistency_check();
#else
	RTE_LOG(INFO, LTHREAD,
		"lthread diagnostics disabled\n"
		"hint - set LTHREAD_DIAG in lthread_diag_api.h\n");
#endif
}

/*
 * Defafult diagnostic callback
 */
static uint64_t
_lthread_diag_default_cb(uint64_t time, struct lthread *lt, int diag_event,
		uint64_t diag_ref, const char *text, uint64_t p1, uint64_t p2)
{
	uint64_t _p2;
	int lcore = (int) rte_lcore_id();

	switch (diag_event) {
	case LT_DIAG_LTHREAD_CREATE:
	case LT_DIAG_MUTEX_CREATE:
	case LT_DIAG_COND_CREATE:
		_p2 = dummy_ref;
		break;
	default:
		_p2 = p2;
		break;
	}

	printf("%"PRIu64" %d %8.8lx %8.8lx %s %8.8lx %8.8lx\n",
		time,
		lcore,
		(uint64_t) lt,
		diag_ref,
		text,
		p1,
		_p2);

	return dummy_ref++;
}

/*
 * plug in default diag callback with mask off
 */
void _lthread_diag_ctor(void)__attribute__((constructor));
void _lthread_diag_ctor(void)
{
	diag_cb = _lthread_diag_default_cb;
	diag_mask = 0;
}


/*
 * enable diagnostics
 */
void lthread_diagnostic_enable(DIAG_USED diag_callback cb,
				DIAG_USED uint64_t mask)
{
#if LTHREAD_DIAG
	if (cb == NULL)
		diag_cb = _lthread_diag_default_cb;
	else
		diag_cb = cb;
	diag_mask = mask;
#else
	RTE_LOG(INFO, LTHREAD,
		"LTHREAD_DIAG is not set, see lthread_diag_api.h\n");
#endif
}