/*
 * 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 the helper routines. */

#include <inttypes.h>
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/time.h>
#include <signal.h>
#include <stdarg.h>
#include <errno.h>
#include "include/types.h"
#include "include/util.h"
#include "include/perf.h"
#include "include/os/node.h"

int g_pagesize;
struct timeval g_tvbase;
boolean_t g_cmt_enabled;

static int s_debuglevel;
static FILE *s_logfile;
static debug_ctl_t s_debug_ctl;
static dump_ctl_t s_dump_ctl;
static char s_exit_msg[EXIT_MSG_SIZE];

static unsigned int msdiff(struct timeval *, struct timeval *);

/*
 * Allocate a buffer and reset it to zero.
 */
void *
zalloc(size_t n)
{
	void *p;

	if (n == 0) {
		return (NULL);
	}

	if ((p = malloc(n)) != NULL) {
		(void) memset(p, 0, n);
	}

	return (p);
}

/*
 * Initialization for debug_print component.
 */
int
debug_init(int debug_level, FILE *log)
{
	(void) memset(s_exit_msg, 0, EXIT_MSG_SIZE);
	(void) memset(&s_debug_ctl, 0, sizeof (s_debug_ctl));
	if (pthread_mutex_init(&s_debug_ctl.mutex, NULL) != 0) {
		return (-1);
	}

	s_debug_ctl.inited = B_TRUE;
	s_logfile = log;
	s_debuglevel = debug_level;
	return (0);
}

/*
 * Clean up the resources for debug_print component.
 */
void
debug_fini(void)
{
	if (s_logfile != NULL) {
		(void) fclose(s_logfile);
	}

	if (s_debug_ctl.inited) {
		(void) pthread_mutex_destroy(&s_debug_ctl.mutex);
	}
}

/*
 * Write the message into log file according to different level.
 */
void
debug_print(FILE *out, int level, const char *fmt, ...)
{
	va_list ap;

	if (level <= s_debuglevel) {
		if (out == NULL) {
			if (s_logfile != NULL) {
				(void) pthread_mutex_lock(&s_debug_ctl.mutex);
				(void) fprintf(s_logfile,
				    "%"PRIu64": ", current_ms(&g_tvbase) / 1000);
				va_start(ap, fmt);
				(void) vfprintf(s_logfile, fmt, ap);
				va_end(ap);
				(void) fflush(s_logfile);
				(void) pthread_mutex_unlock(
				    &s_debug_ctl.mutex);
			}
		} else {
			(void) pthread_mutex_lock(&s_debug_ctl.mutex);
			(void) fprintf(out,
			    "%"PRIu64": ", current_ms(&g_tvbase) / 1000);
			va_start(ap, fmt);
			(void) vfprintf(out, fmt, ap);
			va_end(ap);
			(void) fflush(out);
			(void) pthread_mutex_unlock(&s_debug_ctl.mutex);
		}
	}
}

/*
 * Get the current timestamp and convert it to milliseconds
 * (timing from numatop startup).
 */
uint64_t
current_ms(struct timeval *tvbase)
{
	struct timeval tvnow;

	(void) gettimeofday(&tvnow, 0);
	return (msdiff(&tvnow, tvbase));
}

double
ratio(uint64_t value1, uint64_t value2)
{
	double r;

	if (value2 > 0) {
		r = (double)((double)value1 / (double)value2);
	} else {
		r = 0.0;
	}

	return (r);
}

static int
procfs_walk(char *path, int **id_arr, int *num)
{
	static DIR *dirp;
	struct dirent *dentp;
	int i = 0, size = *num, id;
	int *arr1 = *id_arr, *arr2;

	if ((dirp = opendir(path)) == NULL) {
		return (-1);
	}

	while ((dentp = readdir(dirp)) != NULL) {
		if (dentp->d_name[0] == '.') {
			/* skip "." and ".." */
			continue;
		}

		if ((id = atoi(dentp->d_name)) == 0) {
			/* Not a valid pid or lwpid. */
			continue;
		}

		if (i >= size) {
			size = size << 1;
			if ((arr2 = realloc(arr1, size * sizeof (int))) == NULL) {
				free(arr1);
				*id_arr = NULL;
				*num = 0;
				return (-1);
			}

			arr1 = arr2;
		}

		arr1[i] = id;
		i++;
	}

	*id_arr = arr1;
	*num = i;

	(void) closedir(dirp);
	return (0);
}

int
procfs_enum_id(char *path, int **id_arr, int *nids)
{
	int *ids, num = PROCFS_ID_NUM;

	if ((ids = zalloc(PROCFS_ID_NUM * sizeof (int))) == NULL) {
		return (-1);
	}

	if (procfs_walk(path, &ids, &num) != 0) {
		if (ids != NULL) {
			free(ids);
		}

		return (-1);
	}

	*id_arr = ids;
	*nids = num;
	return (0);
}

/*
 * Retrieve the process's pid from '/proc'
 */
int
procfs_proc_enum(pid_t **pids, int *num)
{
	/*
	 * It's possible that the id in return buffer is 0,
	 * the caller needs to check again.
	 */
	return (procfs_enum_id("/proc", (int **)pids, num));
}

/*
 * Get the interval in milliseconds between 2 timestamps.
 */
static unsigned int
msdiff(struct timeval *tva, struct timeval *tvb)
{
	time_t sdiff = tva->tv_sec - tvb->tv_sec;
	suseconds_t udiff = tva->tv_usec - tvb->tv_usec;

	if (sdiff < 0) {
		return (0);
	}

	if (udiff < 0) {
		udiff += MICROSEC;
		sdiff--;
	}

	if (sdiff < 0) {
		return (0);
	}

	if (sdiff >= (MAX_VALUE / MS_SEC)) {
		return ((unsigned int)MAX_VALUE);
	}

	return ((unsigned int)(sdiff * MS_SEC + udiff / MS_SEC));
}

void
exit_msg_put(const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        (void) vsnprintf(s_exit_msg, EXIT_MSG_SIZE, fmt, ap);
        va_end(ap);
}

void
exit_msg_print(void)
{
	if (strlen(s_exit_msg) > 0) {
		(void) printf("%s", s_exit_msg);
	}
}

/*
 * Convert the CPU cycles to nanoseconds.
 */
uint64_t
cyc2ns(uint64_t cyc)
{
	uint64_t ns;

	ns = (uint64_t)(cyc * g_nsofclk);
	return (ns);
}

/*
 * Initialization for dump control structure.
 */
int
dump_init(FILE *dump_file)
{
	(void) memset(&s_dump_ctl, 0, sizeof (s_dump_ctl));
	if ((s_dump_ctl.fout = dump_file) != NULL) {
		if ((s_dump_ctl.cache =
		    zalloc(DUMP_CACHE_SIZE)) == NULL) {
			return (-1);
		}

		s_dump_ctl.pcur = s_dump_ctl.cache;
		s_dump_ctl.rest_size = DUMP_CACHE_SIZE;
	}

	return (0);
}

/*
 * Clean up resources of dump control structure.
 */
void
dump_fini(void)
{
	if (s_dump_ctl.fout != NULL) {
		(void) fclose(s_dump_ctl.fout);
	}

	if (s_dump_ctl.cache != NULL) {
		free(s_dump_ctl.cache);
	}
}

/*
 * Write the message into dump file.
 */
void
dump_write(const char *fmt, ...)
{
	va_list ap;
	int n;

	if (s_dump_ctl.fout == NULL) {
		return;
	}

	if (!s_dump_ctl.cache_mode) {
		va_start(ap, fmt);
		(void) vfprintf(s_dump_ctl.fout, fmt, ap);
		va_end(ap);
		(void) fflush(s_dump_ctl.fout);
	} else {
		va_start(ap, fmt);
		n = vsnprintf(s_dump_ctl.pcur,
		    s_dump_ctl.rest_size, fmt, ap);
		va_end(ap);
		s_dump_ctl.pcur += n;
		s_dump_ctl.rest_size -= n;
	}
}

void
dump_cache_enable(void)
{
	s_dump_ctl.cache_mode = B_TRUE;
}

void
dump_cache_disable(void)
{
	s_dump_ctl.cache_mode = B_FALSE;
}

void
dump_cache_flush(void)
{
	if (s_dump_ctl.fout != NULL) {
		(void) fprintf(s_dump_ctl.fout, "%s", s_dump_ctl.cache);
		(void) fflush(s_dump_ctl.fout);

		(void) memset(s_dump_ctl.cache, 0, DUMP_CACHE_SIZE);
		s_dump_ctl.pcur = s_dump_ctl.cache;
		s_dump_ctl.rest_size = DUMP_CACHE_SIZE;
		s_dump_ctl.cache_mode = B_FALSE;
	}
}

/*
 * Print the message to STDERR.
 */
void
stderr_print(char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	/* LINTED E_SEC_PRINTF_VAR_FMT */
	(void) vfprintf(stderr, format, ap);
	(void) fprintf(stderr, "\r");
	va_end(ap);
}

int
array_alloc(void **pp, int *ncur, int *nmax, int size, int num)
{
	int i;
	void *p;

	if (*ncur == *nmax) {
		if (*pp == NULL) {
			if ((*pp = zalloc(num * size)) == NULL) {
				return (-1);
			}

			*nmax = num;
		} else {
			i = (*nmax) << 1;
			if ((p = realloc(*pp, i * size)) == NULL) {
				if (*pp != NULL) {
					free(*pp);
					*pp = NULL;
				}

				return (-1);
			}

			*pp = p;
			*nmax = i;
		}
	}

	return (0);
}

void
pagesize_init(void)
{
	g_pagesize = getpagesize();
}
