/*
 * 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 handle the 'tracked thread'. */

#include <inttypes.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <string.h>
#include "include/types.h"
#include "include/util.h"
#include "include/lwp.h"
#include "include/proc.h"
#include "include/perf.h"
#include "include/os/node.h"
#include "include/os/os_util.h"

/*
 * Allocation and initialization for a 'track_lwp_t' struture.
 * A 'track_lwp_t' structure is allocated for each tracked thread.
 */
static track_lwp_t *
lwp_alloc(void)
{
	int cpuid_max;
	track_lwp_t *lwp;
	count_value_t *countval_arr;
	boolean_t supported;

	if ((cpuid_max = node_cpuid_max()) <= 0) {
		return (NULL);
	}

	if ((countval_arr = zalloc(cpuid_max *
	    sizeof (count_value_t))) == NULL) {
		return (NULL);
	}

	if ((lwp = zalloc(sizeof (track_lwp_t))) == NULL) {
		free(countval_arr);
		return (NULL);
	}

	if (((lwp->perf_priv = perf_priv_alloc(&supported)) == NULL) &&
		(supported)) {
		free(countval_arr);
		free(lwp);
		return (NULL);
	}

	if (pthread_mutex_init(&lwp->mutex, NULL) != 0) {
		free(countval_arr);
		perf_priv_free(lwp->perf_priv);
		free(lwp);
		return (NULL);
	}

	lwp->countval_arr = countval_arr;
	lwp->cpuid_max = cpuid_max;
	os_pqos_cmt_init(&lwp->pqos);
	lwp->inited = B_TRUE;
	return (lwp);
}

/*
 * Clean up the resource of 'track_lwp_t' struture. Before calling
 * lwp_free(), 'track_lwp_t' must be removed from 'lwp_list' in
 * 'track_proc_t' structure.
 */
int
lwp_free(track_lwp_t *lwp)
{
	(void) pthread_mutex_lock(&lwp->mutex);
	if (lwp->ref_count > 0) {
		lwp->removing = B_TRUE;
		(void) pthread_mutex_unlock(&lwp->mutex);
		return (-1);
	}

	if (lwp->countval_arr != NULL) {
		free(lwp->countval_arr);
	}

	perf_priv_free(lwp->perf_priv);
	perf_countchain_reset(&lwp->count_chain);
	perf_llrecgrp_reset(&lwp->llrec_grp);

	os_perf_pqos_free(&lwp->pqos);

	(void) pthread_mutex_unlock(&lwp->mutex);
	(void) pthread_mutex_destroy(&lwp->mutex);

	free(lwp);
	return (0);
}

/*
 * Move the 'sort_idx' to next lwp node and return current one.
 */
track_lwp_t *
lwp_sort_next(track_proc_t *proc)
{
	proc_lwplist_t *list = &proc->lwp_list;
	int idx = list->sort_idx;

	if (list->sort_arr == NULL) {
		return (NULL);
	}

	if (idx < list->nlwps) {
		list->sort_idx++;
		return (list->sort_arr[idx]);
	}

	return (NULL);
}

static int
id_cmp(const void *a, const void *b)
{
	const int *id1 = (const int *)a;
	const int *id2 = (const int *)b;

	if (*id1 > *id2) {
		return (1);
	}

	if (*id1 < *id2) {
		return (-1);
	}

	return (0);
}

/*
 * Enumerate valid threads from '/proc', remove the obsolete threads.
 */
void
lwp_enum_update(track_proc_t *proc)
{
	proc_lwplist_t *list = &proc->lwp_list;
	track_lwp_t *lwp;
	track_lwp_t **arr_new, **arr_old;
	int *lwps_new, nlwp_new;
	int i = 0, j = 0, k;

	if (os_procfs_lwp_enum(proc->pid, &lwps_new, &nlwp_new) != 0) {
		return;
	}

	qsort(lwps_new, nlwp_new, sizeof (int), id_cmp);

	if ((arr_new = zalloc(sizeof (track_lwp_t *) * nlwp_new)) == NULL) {
		goto L_EXIT;
	}

	(void) pthread_mutex_lock(&proc->mutex);

	if ((arr_old = list->id_arr) != NULL) {
		while ((i < nlwp_new) && (j < list->nlwps)) {
			if (lwps_new[i] == arr_old[j]->id) {
				arr_new[i] = arr_old[j];
				i++;
				j++;
				continue;
			}

			if (lwps_new[i] < arr_old[j]->id) {
				if ((lwp = lwp_alloc()) != NULL) {
					lwp->id = lwps_new[i];
					lwp->proc = proc;
					arr_new[i] = lwp;
				}

				i++;
				continue;
			}

			/* The lwpid (arr_old[j]->id) is obsolete */
			(void) lwp_free(arr_old[j]);
			j++;
		}
	}

	for (k = i; k < nlwp_new; k++) {
		if ((lwp = lwp_alloc()) != NULL) {
			lwp->id = lwps_new[k];
			lwp->proc = proc;
			arr_new[k] = lwp;
		}
	}

	if (arr_old != NULL) {
		for (k = j; k < list->nlwps; k++) {
			(void) lwp_free(arr_old[k]);
		}

		free(arr_old);
	}

	list->id_arr = arr_new;
	list->nlwps = nlwp_new;
	(void) pthread_mutex_unlock(&proc->mutex);

L_EXIT:
	free(lwps_new);
}

/*
 * lwp refcount increment. The 'track_lwp_t' structure can
 * be only released when the refcount is 0.
 */
int
lwp_refcount_inc(track_lwp_t *lwp)
{
	int ret = -1;

	(void) pthread_mutex_lock(&lwp->mutex);

	if ((!lwp->removing) && (!lwp->quitting)) {
		lwp->ref_count++;
		ret = 0;
	}

	(void) pthread_mutex_unlock(&lwp->mutex);
	return (ret);
}

/*
 * lwp refcount decrement. If the refcount is 0 after decrement
 * and the 'removing' flag is set, release the 'track_lwp_t' structure.
 */
void
lwp_refcount_dec(track_lwp_t *lwp)
{
	boolean_t remove = B_FALSE;

	(void) pthread_mutex_lock(&lwp->mutex);
	lwp->ref_count--;

	if ((lwp->ref_count == 0) && (lwp->removing)) {
		remove = B_TRUE;
	}

	(void) pthread_mutex_unlock(&lwp->mutex);
	if (remove) {
		(void) lwp_free(lwp);
	}
}

static uint64_t
count_value_get(track_lwp_t *lwp, ui_count_id_t ui_count_id)
{
	return (node_countval_sum(lwp->countval_arr,
	    NODE_ALL, ui_count_id));
}

/*
 * Compute the value of key for lwp sorting.
 */
int
lwp_key_compute(track_lwp_t *lwp, void *arg, boolean_t *end)
{
	sort_key_t sortkey = *((sort_key_t *)arg);

	switch (sortkey) {
	case SORT_KEY_CPU:
		lwp->key = count_value_get(lwp, UI_COUNT_CLK);
		break;

	default:
		break;
	}

	*end = B_FALSE;
	return (0);
}

/*
 * Update the lwp's per CPU perf data.
 */
int
lwp_countval_update(track_lwp_t *lwp, int cpu, perf_count_id_t perf_count_id,
    uint64_t value)
{
	count_value_t *countval, *arr_new;
	int cpuid_max = node_cpuid_max();

	/*
	 * Check if new cpu hotadd/online
	 */
	if (cpu >= lwp->cpuid_max) {
		ASSERT(cpuid_max > lwp->cpuid_max);
		if ((arr_new = realloc(lwp->countval_arr,
		    sizeof (count_value_t) * cpuid_max)) == NULL) {
			return (-1);
		}

		(void) memset(&arr_new[lwp->cpuid_max], 0,
		    sizeof (count_value_t) * (cpuid_max - lwp->cpuid_max));

		lwp->countval_arr = arr_new;
		lwp->cpuid_max = cpuid_max;
	}

	countval = &lwp->countval_arr[cpu];
	countval->counts[perf_count_id] += value;
	return (0);
}

int
lwp_intval_get(track_lwp_t *lwp)
{
	return (lwp->intval_ms);
}

static int
intval_update(track_lwp_t *lwp, void *arg, boolean_t *end)
{
	int intval_ms = *((int *)arg);

	*end = B_FALSE;
	lwp->intval_ms = intval_ms;
	return (0);
}

/*
 * Update with the sampling interval for all threads in process.
 */
void
lwp_intval_update(track_proc_t *proc, int intval_ms)
{
	(void) pthread_mutex_lock(&proc->mutex);
	proc_lwp_traverse(proc, intval_update, &intval_ms);
	(void) pthread_mutex_unlock(&proc->mutex);
}

void
lwp_quitting_set(track_lwp_t *lwp)
{
	lwp->quitting = B_TRUE;
}
