/*
 * Copyright (c) 2013, Intel Corporation
 *
 * 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.
 */

/* This file contains code to retrieve performance data */

#include <inttypes.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include "include/types.h"
#include "include/perf.h"
#include "include/proc.h"
#include "include/lwp.h"
#include "include/util.h"
#include "include/disp.h"
#include "include/ui_perf_map.h"
#include "include/os/plat.h"
#include "include/os/node.h"
#include "include/os/os_perf.h"

static perf_ctl_t s_perf_ctl;

uint64_t g_sample_period[PERF_COUNT_NUM][PRECISE_NUM] = {
	{ SMPL_PERIOD_CORECLK_DEFAULT,
	  SMPL_PERIOD_CORECLK_MIN,
	  SMPL_PERIOD_CORECLK_MAX
	},
	{ SMPL_PERIOD_RMA_DEFAULT,
	  SMPL_PERIOD_RMA_MIN,
	  SMPL_PERIOD_RMA_MAX
	},
	{ SMPL_PERIOD_CLK_DEFAULT,
	  SMPL_PERIOD_CLK_MIN,
	  SMPL_PERIOD_CLK_MAX
	},
	{ SMPL_PERIOD_IR_DEFAULT,
	  SMPL_PERIOD_IR_MIN,
	  SMPL_PERIOD_IR_MAX
	},
	{ SMPL_PERIOD_LMA_DEFAULT,
	  SMPL_PERIOD_LMA_MIN,
	  SMPL_PERIOD_LMA_MAX
	},
#ifdef __powerpc64__
	{ SMPL_PERIOD_RMA_1_DEFAULT,
	  SMPL_PERIOD_RMA_1_MIN,
	  SMPL_PERIOD_RMA_1_MAX
	}
#endif
};

typedef struct _perf_pqos_arg {
	pid_t pid;
	int lwpid;
} perf_pqos_arg_t;

static perf_pqos_arg_t s_pqos_arg[PERF_PQOS_CMT_MAX];

static boolean_t
task_valid(perf_task_t *task)
{
	switch (TASKID(task)) {
	case PERF_PROFILING_START_ID:
		/* fall through */
	case PERF_PROFILING_SMPL_ID:
		/* fall through */
	case PERF_PROFILING_PARTPAUSE_ID:
		/* fall through */
	case PERF_PROFILING_MULTIPAUSE_ID:
		/* fall through */
	case PERF_PROFILING_RESTORE_ID:
		/* fall through */
	case PERF_PROFILING_MULTI_RESTORE_ID:
		/* fall through */
	case PERF_LL_START_ID:
		/* fall through */
	case PERF_LL_SMPL_ID:
		/* fall through */
	case PERF_CALLCHAIN_START_ID:
		/* fall through */
	case PERF_CALLCHAIN_SMPL_ID:
		/* fall through */
	case PERF_STOP_ID:
		/* fall through */
	case PERF_QUIT_ID:
		/* fall through */
	case PERF_PQOS_CMT_START_ID:
	case PERF_PQOS_CMT_SMPL_ID:
	case PERF_PQOS_CMT_STOP_ID:
	case PERF_UNCORE_START_ID:
	case PERF_UNCORE_SMPL_ID:
	case PERF_UNCORE_STOP_ID:
		return (B_TRUE);
	default:
		break;
	}

	return (B_FALSE);
}

void
perf_task_set(perf_task_t *task)
{
	(void) pthread_mutex_lock(&s_perf_ctl.mutex);
	(void) memcpy(&s_perf_ctl.task, task, sizeof (perf_task_t));
	(void) pthread_cond_signal(&s_perf_ctl.cond);
	(void) pthread_mutex_unlock(&s_perf_ctl.mutex);
}

void
perf_status_set(perf_status_t status)
{
	(void) pthread_mutex_lock(&s_perf_ctl.status_mutex);
	s_perf_ctl.status = status;
	(void) pthread_cond_signal(&s_perf_ctl.status_cond);
	(void) pthread_mutex_unlock(&s_perf_ctl.status_mutex);
}

void
perf_status_set_no_signal(perf_status_t status)
{
	(void) pthread_mutex_lock(&s_perf_ctl.status_mutex);
	s_perf_ctl.status = status;
	(void) pthread_mutex_unlock(&s_perf_ctl.status_mutex);
}

static boolean_t
status_failed(perf_status_t status)
{
	switch (status) {
	case PERF_STATUS_PROFILING_FAILED:
		/* fall through */
	case PERF_STATUS_LL_FAILED:
		/* fall through */
	case PERF_STATUS_CALLCHAIN_FAILED:
		return (B_TRUE);

	default:
		break;
	}

	return (B_FALSE);
}

int
perf_status_wait(perf_status_t status)
{
	struct timespec timeout;
	struct timeval tv;
	int s, ret = -1;

	(void) gettimeofday(&tv, NULL);
	timeout.tv_sec = tv.tv_sec + PERF_WAIT_NSEC;
	timeout.tv_nsec = tv.tv_usec * 1000;

	(void) pthread_mutex_lock(&s_perf_ctl.status_mutex);
	for (;;) {
		s = pthread_cond_timedwait(&s_perf_ctl.status_cond,
		    &s_perf_ctl.status_mutex, &timeout);

		if (s_perf_ctl.status == status) {
			ret = 0;
			break;
		}

		if (status_failed(s_perf_ctl.status)) {
			break;
		}

		if (s == ETIMEDOUT) {
			break;
		}
	}

	(void) pthread_mutex_unlock(&s_perf_ctl.status_mutex);
	return (ret);
}

/*
 * The thread handler of 'perf thread'.
 */
/* ARGSUSED */
static void *
perf_handler(void *arg __attribute__((unused)))
{
	perf_task_t task;
	int intval_ms;

	for (;;) {
		(void) pthread_mutex_lock(&s_perf_ctl.mutex);
		task = s_perf_ctl.task;
		while (!task_valid(&task)) {
			(void) pthread_cond_wait(&s_perf_ctl.cond,
			    &s_perf_ctl.mutex);
			task = s_perf_ctl.task;
		}

		TASKID_SET(&s_perf_ctl.task, PERF_INVALID_ID);
		(void) pthread_mutex_unlock(&s_perf_ctl.mutex);

		switch (TASKID(&task)) {
		case PERF_QUIT_ID:
			debug_print(NULL, 2, "perf_handler: received QUIT\n");
			os_allstop();
			goto L_EXIT;

		case PERF_STOP_ID:
			os_allstop();
			perf_status_set(PERF_STATUS_IDLE);
			break;

		case PERF_PROFILING_START_ID:
			if (os_profiling_start(&s_perf_ctl, &task) != 0) {
				goto L_EXIT;
			}
			break;

		case PERF_PROFILING_SMPL_ID:
			(void) os_profiling_smpl(&s_perf_ctl, &task, &intval_ms);
			break;

		case PERF_PROFILING_PARTPAUSE_ID:
			(void) os_profiling_partpause(&s_perf_ctl, &task);
			break;

		case PERF_PROFILING_MULTIPAUSE_ID:
			(void) os_profiling_multipause(&s_perf_ctl, &task);
			break;

		case PERF_PROFILING_RESTORE_ID:
			(void) os_profiling_restore(&s_perf_ctl, &task);
			break;

		case PERF_PROFILING_MULTI_RESTORE_ID:
			(void) os_profiling_multi_restore(&s_perf_ctl, &task);
			break;

		case PERF_CALLCHAIN_START_ID:
			(void) os_callchain_start(&s_perf_ctl, &task);
			break;

		case PERF_CALLCHAIN_SMPL_ID:
			(void) os_callchain_smpl(&s_perf_ctl, &task, &intval_ms);
			break;

		case PERF_LL_START_ID:
			os_ll_start(&s_perf_ctl, &task);
			break;

		case PERF_LL_SMPL_ID:
			os_ll_smpl(&s_perf_ctl, &task, &intval_ms);
			break;

		case PERF_PQOS_CMT_START_ID:
			os_pqos_cmt_start(&s_perf_ctl, &task);
			break;

		case PERF_PQOS_CMT_SMPL_ID:
			os_pqos_cmt_smpl(&s_perf_ctl, &task, &intval_ms);
			break;

		case PERF_PQOS_CMT_STOP_ID:
			os_pqos_proc_stop(&s_perf_ctl, &task);
			perf_status_set(PERF_STATUS_PROFILING_STARTED);
			break;

		case PERF_UNCORE_STOP_ID:
			os_uncore_stop(&s_perf_ctl, &task);
			perf_status_set(PERF_STATUS_PROFILING_STARTED);
			break;

		case PERF_UNCORE_START_ID:
			os_uncore_start(&s_perf_ctl, &task);
			break;

		case PERF_UNCORE_SMPL_ID:
			os_uncore_smpl(&s_perf_ctl, &task, &intval_ms);
			break;

		default:
			break;
		}
	}

L_EXIT:
	debug_print(NULL, 2, "perf thread is exiting.\n");
	return (NULL);
}

/*
 * Initialization for perf control structure.
 */
int
perf_init(void)
{
	boolean_t mutex_inited = B_FALSE;
	boolean_t cond_inited = B_FALSE;
	boolean_t status_mutex_inited = B_FALSE;
	boolean_t status_cond_inited = B_FALSE;

	if (os_perf_init() != 0) {
		return (-1);
	}

	(void) memset(&s_perf_ctl, 0, sizeof (s_perf_ctl));

	if (pthread_mutex_init(&s_perf_ctl.mutex, NULL) != 0) {
		goto L_EXIT;
	}
	mutex_inited = B_TRUE;

	if (pthread_cond_init(&s_perf_ctl.cond, NULL) != 0) {
		goto L_EXIT;
	}
	cond_inited = B_TRUE;

	if (pthread_mutex_init(&s_perf_ctl.status_mutex, NULL) != 0) {
		goto L_EXIT;
	}
	status_mutex_inited = B_TRUE;

	if (pthread_cond_init(&s_perf_ctl.status_cond, NULL) != 0) {
		goto L_EXIT;
	}
	status_cond_inited = B_TRUE;

	if (pthread_create(&s_perf_ctl.thr, NULL, perf_handler, NULL) != 0) {
		goto L_EXIT;
	}

	s_perf_ctl.last_ms = current_ms(&g_tvbase);
	if (perf_profiling_start() != 0) {
		debug_print(NULL, 2, "perf_init: "
		    "perf_profiling_start() failed\n");
		goto L_EXIT;
	}

	s_perf_ctl.inited = B_TRUE;

L_EXIT:
	if (!s_perf_ctl.inited) {
		if (mutex_inited) {
			(void) pthread_mutex_destroy(&s_perf_ctl.mutex);
		}

		if (cond_inited) {
			(void) pthread_cond_destroy(&s_perf_ctl.cond);
		}

		if (status_mutex_inited) {
			(void) pthread_mutex_destroy(&s_perf_ctl.status_mutex);
		}

		if (status_cond_inited) {
			(void) pthread_cond_destroy(&s_perf_ctl.status_cond);
		}

		os_perf_fini();
		return (-1);
	}

	return (0);
}

static void
perfthr_quit_wait(void)
{
	perf_task_t task;
	task_quit_t *t;

	os_perfthr_quit_wait();

	debug_print(NULL, 2, "Send PERF_QUIT_ID to perf thread\n");
	(void) memset(&task, 0, sizeof (perf_task_t));
	t = (task_quit_t *)&task;
	t->task_id = PERF_QUIT_ID;
	perf_task_set(&task);
	(void) pthread_join(s_perf_ctl.thr, NULL);
	debug_print(NULL, 2, "perf thread exit yet\n");
}

/*
 * Release the resources of perf control structure.
 */
void
perf_fini(void)
{
	if (s_perf_ctl.inited) {
		perfthr_quit_wait();
		(void) pthread_mutex_destroy(&s_perf_ctl.mutex);
		(void) pthread_cond_destroy(&s_perf_ctl.cond);
		(void) pthread_mutex_destroy(&s_perf_ctl.status_mutex);
		(void) pthread_cond_destroy(&s_perf_ctl.status_cond);
		s_perf_ctl.inited = B_FALSE;
	}

	os_perf_fini();
}

int
perf_allstop(void)
{
	return (os_perf_allstop());
}

boolean_t
perf_profiling_started(void)
{
	return (os_profiling_started(&s_perf_ctl));
}

int
perf_profiling_start(void)
{
	perf_task_t task;
	task_profiling_t *t;

	(void) memset(&task, 0, sizeof (perf_task_t));
	t = (task_profiling_t *)&task;
	t->task_id = PERF_PROFILING_START_ID;
	perf_task_set(&task);
	return (perf_status_wait(PERF_STATUS_PROFILING_STARTED));
}

/*
 * The user may refresh the current window frequently.
 * One refresh operation would invoke one time perf data
 * sampling. If the sampling interval is too small, the
 * counting of an event with predefined threshold probably
 * doesn't get chance to overflow. Then the sampling data
 * is not very accurate.
 *
 * For example:
 * Suppose the user refreshes the window in each 100ms. The
 * overflow threshold for RMA is 100,000. Suppose for a
 * workload, it's overflowed in each 200ms. Then the user
 * can only see the RMA is 0 after he refreshes the window.
 */
void
perf_smpl_wait(void)
{
	int intval_diff;

	intval_diff = current_ms(&g_tvbase) - s_perf_ctl.last_ms;

	if (PERF_INTVAL_MIN_MS > intval_diff) {
		intval_diff = PERF_INTVAL_MIN_MS - intval_diff;
		(void) usleep(intval_diff * USEC_MS);
	}
}

int
perf_profiling_smpl(boolean_t use_dispflag1)
{
	perf_task_t task;
	task_profiling_t *t;

	perf_smpl_wait();
	(void) memset(&task, 0, sizeof (perf_task_t));
	t = (task_profiling_t *)&task;
	t->task_id = PERF_PROFILING_SMPL_ID;
	t->use_dispflag1 = use_dispflag1;
	perf_task_set(&task);
	return (0);
}

int
perf_profiling_partpause(ui_count_id_t ui_count_id)
{
	perf_count_id_t *perf_count_ids = NULL;
	int n_perf_count;

	n_perf_count = get_ui_perf_count_map(ui_count_id, &perf_count_ids);

	if (n_perf_count == PERF_COUNT_INVALID)
		return (-1);

	if (n_perf_count > 1) {
		return (os_perf_profiling_multipause(perf_count_ids));
	} else {
		return (os_perf_profiling_partpause(perf_count_ids[0]));
	}
}

int
perf_profiling_restore(ui_count_id_t ui_count_id)
{
	perf_count_id_t *perf_count_ids = NULL;
	int n_perf_count;

	n_perf_count = get_ui_perf_count_map(ui_count_id, &perf_count_ids);

	if (n_perf_count == PERF_COUNT_INVALID)
		return (-1);

	if (n_perf_count > 1) {
		return (os_perf_profiling_multi_restore(perf_count_ids));
	} else  {
		return (os_perf_profiling_restore(perf_count_ids[0]));
	}
}

boolean_t
perf_callchain_started(void)
{
	if (s_perf_ctl.status == PERF_STATUS_CALLCHAIN_STARTED) {
		return (B_TRUE);
	}

	return (B_FALSE);
}

int
perf_callchain_start(pid_t pid, int lwpid)
{
	return (os_perf_callchain_start(pid, lwpid));
}

int
perf_callchain_smpl(void)
{
	return (os_perf_callchain_smpl());
}

boolean_t
perf_ll_started(void)
{
	if (s_perf_ctl.status == PERF_STATUS_LL_STARTED) {
		return (B_TRUE);
	}

	return (B_FALSE);
}

int
perf_ll_start(pid_t pid)
{
	perf_task_t task;
	task_ll_t *t;

	(void) memset(&task, 0, sizeof (perf_task_t));
	t = (task_ll_t *)&task;
	t->task_id = PERF_LL_START_ID;
	t->pid = pid;
	perf_task_set(&task);
	return (perf_status_wait(PERF_STATUS_LL_STARTED));
}

int
perf_ll_smpl(pid_t pid, int lwpid)
{
	return (os_perf_ll_smpl(&s_perf_ctl, pid, lwpid));
}

void
perf_llrecgrp_reset(perf_llrecgrp_t *grp)
{
	if (grp->rec_arr != NULL) {
		free(grp->rec_arr);
	}

	(void) memset(grp, 0, sizeof (perf_llrecgrp_t));
}

void
perf_countchain_reset(perf_countchain_t *count_chain)
{
	os_perf_countchain_reset(count_chain);
}

void *
perf_priv_alloc(boolean_t *supported)
{
	return (os_perf_priv_alloc(supported));
}

void
perf_priv_free(void *priv)
{
	os_perf_priv_free(priv);	
}

void
perf_ll_started_set(void)
{
	perf_status_set(PERF_STATUS_LL_STARTED);
}

int
perf_pqos_cmt_start(int pid, int lwpid, int flags)
{
	perf_task_t task;
	task_pqos_cmt_t *t;

	(void) memset(&task, 0, sizeof (perf_task_t));
	t = (task_pqos_cmt_t *)&task;
	t->task_id = PERF_PQOS_CMT_START_ID;
	t->pid = pid;
	t->lwpid = lwpid;
	t->flags = flags;
	perf_task_set(&task);
	return (perf_status_wait(PERF_STATUS_PQOS_CMT_STARTED));
}

int
perf_pqos_cmt_smpl(pid_t pid, int lwpid)
{
	return (os_perf_pqos_cmt_smpl(&s_perf_ctl, pid, lwpid));
}

int perf_pqos_active_proc_setup(int flags, boolean_t refresh)
{
	int nprocs, nlwps, i, j = 0, pqos_num;
	track_proc_t *proc;

	proc_lwp_count(&nprocs, &nlwps);
	pqos_num = MIN(nprocs, PERF_PQOS_CMT_MAX);

	memset(s_pqos_arg, 0, sizeof(s_pqos_arg));

	proc_group_lock();
	proc_resort(SORT_KEY_CPU);

	for (i = 0; i < pqos_num; i++) {
		if ((proc = proc_sort_next()) == NULL) {
			break;
		}

		if (!proc->pqos.task_id) {
			s_pqos_arg[j].pid = proc->pid;
			j++;
		}
	}

	for (i = pqos_num; i < nprocs; i++) {
		if ((proc = proc_sort_next()) == NULL) {
			break;
		}

		if (!proc->pqos.task_id)
			os_perf_pqos_free(&proc->pqos);
	}

	proc_group_unlock();

	for (i = 0; i < j; i++) {
		if (perf_pqos_cmt_start(s_pqos_arg[i].pid, 0, flags) != 0)
			return -1;
	}

	if ((j > 0) && (!refresh))
		s_perf_ctl.last_ms_pqos = current_ms(&g_tvbase);

	return 0;
}

boolean_t
perf_pqos_cmt_started(void)
{
	return (os_perf_pqos_cmt_started(&s_perf_ctl));
}

int
perf_pqos_cmt_stop(pid_t pid, int lwpid)
{
	perf_task_t task;
	task_pqos_cmt_t *t;

	(void) memset(&task, 0, sizeof (perf_task_t));
	t = (task_pqos_cmt_t *)&task;
	t->task_id = PERF_PQOS_CMT_STOP_ID;
	t->pid = pid;
	t->lwpid = lwpid;
	perf_task_set(&task);
	return (perf_status_wait(PERF_STATUS_PROFILING_STARTED));
}

int perf_pqos_proc_setup(int pid, int lwpid, int flags)
{
	track_proc_t *proc;
	track_lwp_t *lwp = NULL;
	int ret = 0;
	boolean_t end;

	if ((proc = proc_find(pid)) == NULL)
		return -1;

	if (lwpid == 0 && proc->pqos.task_id) {
		s_perf_ctl.last_ms_pqos = current_ms(&g_tvbase);
		os_pqos_cmt_proc_smpl(proc, NULL, &end);
		goto L_EXIT;
	}

	if (lwpid != 0) {
		if ((lwp = proc_lwp_find(proc, lwpid)) == NULL) {
			ret = -1;
			goto L_EXIT;
		}

		if (lwp->pqos.task_id) {
			s_perf_ctl.last_ms_pqos = current_ms(&g_tvbase);
			os_pqos_cmt_lwp_smpl(lwp, NULL, &end);
			goto L_EXIT;
		}
	}

	if (perf_pqos_cmt_start(pid, lwpid, flags) != 0)
		ret = -1;

	s_perf_ctl.last_ms_pqos = current_ms(&g_tvbase);

L_EXIT:
	if (lwp != NULL)
		lwp_refcount_dec(lwp);

	proc_refcount_dec(proc);
	return ret;
}

int perf_uncore_stop(int nid)
{
	perf_task_t task;
	task_uncore_t *t;

	(void) memset(&task, 0, sizeof (perf_task_t));
	t = (task_uncore_t *)&task;
	t->task_id = PERF_UNCORE_STOP_ID;
	t->nid = nid;
	perf_task_set(&task);
	return (perf_status_wait(PERF_STATUS_PROFILING_STARTED));
}

static int
perf_uncore_start(int nid)
{
	perf_task_t task;
	task_uncore_t *t;

	(void) memset(&task, 0, sizeof (perf_task_t));
	t = (task_uncore_t *)&task;
	t->task_id = PERF_UNCORE_START_ID;
	t->nid = nid;
	perf_task_set(&task);
	return (perf_status_wait(PERF_STATUS_UNCORE_STARTED));
}

int perf_uncore_setup(int nid)
{
	if (perf_uncore_start(nid) != 0)
		return -1;
		
	return 0;
}

int perf_uncore_smpl(int nid)
{
	return (os_perf_uncore_smpl(&s_perf_ctl, nid));
}

boolean_t
perf_uncore_started(void)
{
	return (os_perf_uncore_started(&s_perf_ctl));
}
