/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stropts.h>
#include <link.h>
#include <sys/types.h>
#include <sys/regset.h>
#include <sys/frame.h>
#include <sys/procfs.h>
#include <fcntl.h>
#include <signal.h>
#include "env.h"
#include "hash.h"


typedef struct {
	float		d_time;
	int		d_count;
	const char	*d_symname;
} d_entry;

typedef struct list {
	d_entry		*l_dep;
	struct list	*l_next;
} List;


static Elist	    *bindto_list = NULL;
static Elist	    *bindfrom_list = NULL;

static int  initialized;
extern long long gethrvtime();

static const char *progname;
static long long starts[1000];
static long long accounted[1000];		/* time accounted for */
static int  counter = 0;

static float	total_time = 0.0;
static List	*list_head = NULL;

static hash	*tbl;

static sigset_t		iset;

static void
list_insert(d_entry *dep)
{
	List *new_list;
	List *cur;
	List *prev;

	if ((new_list = malloc(sizeof (List))) == NULL) {
		(void) printf("libperfcnt.so: malloc failed - "
		    "can't print summary\n");
		exit(1);
	}
	new_list->l_dep = dep;

	if (list_head == NULL) {
		list_head = new_list;
		new_list->l_next = NULL;
		return;
	}
	for (cur = list_head, prev = NULL;
	    (cur && (cur->l_dep->d_time < dep->d_time));
	    prev = cur, cur = cur->l_next)
		;
	/*
	 * insert at head of list
	 */
	if (prev == NULL) {
		new_list->l_next = list_head;
		list_head = new_list;
		return;
	}
	prev->l_next = new_list;
	new_list->l_next = cur;
}

uint_t
la_version(uint_t version)
{
	int fd;
	char buffer[100];

	if (version > LAV_CURRENT)
		(void) fprintf(stderr, "perfcnt.so.1: unexpected version: %d\n",
		    version);

	(void) sprintf(buffer, "/proc/%d", (int)getpid());
	if ((fd = open(buffer, O_RDWR)) >= 0) {
		long state = PR_MSACCT;
		if (ioctl(fd, PIOCSET, &state) == -1)
			perror("PIOCSET");
		(void) close(fd);
	}

	initialized++;
	tbl = make_hash(213);

	build_env_list(&bindto_list, (const char *)"PERFCNT_BINDTO");
	build_env_list(&bindto_list, (const char *)"PERFCNT_BINDFROM");

	/*
	 * Initalize iset to the full set of signals to be masked durring
	 * pltenter/pltexit
	 */
	(void) sigfillset(&iset);

	return (LAV_CURRENT);
}


/* ARGSUSED1 */
uint_t
la_objopen(Link_map *lmp, Lmid_t lmid, uintptr_t *cookie)
{
	static int	first = 1;
	uint_t	flags = 0;

	if (first) {
		progname = lmp->l_name;
		first = 0;
	}

	if (bindto_list == NULL)
		flags = LA_FLG_BINDTO;
	else {
		if (check_list(bindto_list, lmp->l_name))
			flags = LA_FLG_BINDTO;
	}
	if (bindfrom_list == NULL)
		flags |= LA_FLG_BINDFROM;
	else {
		if (check_list(bindfrom_list, lmp->l_name))
			flags |= LA_FLG_BINDFROM;
	}

	return (flags);
}

/* ARGSUSED1 */
#if	defined(__sparcv9)
uintptr_t
la_sparcv9_pltenter(Elf64_Sym *symp, uint_t symndx, uintptr_t *refcookie,
	uintptr_t *defcookie, La_sparcv9_regs *regset, uint_t *sb_flags,
	const char *sym_name)
#elif	defined(__sparc)
uintptr_t
la_sparcv8_pltenter(Elf32_Sym *symp, uint_t symndx, uintptr_t *refcookie,
	uintptr_t *defcookie, La_sparcv8_regs *regset, uint_t *sb_flags)
#elif	defined(__amd64)
uintptr_t
la_amd64_pltenter(Elf64_Sym *symp, uint_t symndx, uintptr_t *refcookie,
	uintptr_t *defcookie, La_amd64_regs *regset, uint_t *sb_flags,
	const char *sym_name)
#elif	defined(__i386)
uintptr_t
la_i86_pltenter(Elf32_Sym *symp, uint_t symndx, uintptr_t *refcooke,
	uintptr_t *defcook, La_i86_regs *regset, uint_t *sb_flags)
#endif
{
	accounted[counter] = 0;
	starts[counter] = gethrvtime();
	counter++;
	return (symp->st_value);
}



/* ARGSUSED1 */
#if	defined(_LP64)
/* ARGSUSED */
uintptr_t
la_pltexit64(Elf64_Sym *symp, uint_t symndx, uintptr_t *refcookie,
	uintptr_t *defcookie, uintptr_t retval, const char *sym_name)
#else
uintptr_t
la_pltexit(Elf32_Sym *symp, uint_t symndx, uintptr_t *refcookie,
	uintptr_t *defcookie, uintptr_t retval)
#endif
{
	d_entry		**dep;
	long long	time_used;
	sigset_t	oset;
#if	!defined(_LP64)
	const char	*sym_name = (const char *)symp->st_name;
#endif

	(void) sigprocmask(SIG_BLOCK, &iset, &oset);

	counter--;
	time_used = gethrvtime() - starts[counter];

	dep = (d_entry **)get_hash(tbl, (char *)sym_name);
	if (*dep == NULL) {
		char *ptr = malloc(sizeof (d_entry));
		/* LINTED */
		(*dep) = (d_entry *)ptr;
		(*dep)->d_count = 0;
		(*dep)->d_time = 0.0;
		(*dep)->d_symname = sym_name;
	}

	if (counter)
		accounted[counter - 1] += time_used;

	((*dep)->d_count)++;
	(*dep)->d_time += (double)((time_used - accounted[counter]) / 1.0e9);

	(void) sigprocmask(SIG_SETMASK, &oset, NULL);

	return (retval);
}

/* ARGSUSED1 */
static void
scanlist(d_entry *dep, void *food, char *name)
{
	total_time += dep->d_time;
	list_insert(dep);
}

#pragma fini(cleanup)
static void
cleanup()
{
	List	*cur;
	(void) operate_hash(tbl, scanlist, NULL);
	(void) printf("\n\nPerf Counts for: %s\n\n", progname);
	(void) printf("%20s\tc_count\t    tim\t\tavg. tim\ttot. %%\n",
	    "SYMBOL");
	(void) printf("--------------------------------------------------"
	    "-------------------\n");
	for (cur = list_head; cur; cur = cur->l_next) {
		d_entry		*dep = cur->l_dep;
		float		tim = dep->d_time * 1000000;

		(void) printf("%20s\t%d\t%8.2f\t%8.2f\t%2.2f%%\n",
		    dep->d_symname, dep->d_count, tim, tim / dep->d_count,
		    ((dep->d_time / total_time) * 100.0));
	}
	(void) printf("--------------------------------------------------"
	    "-------------------\n");
	(void) printf("\t\t\t\t\t\tTotal Time: %8.2f\n",
	    total_time * 1000000);
}