root/usr/src/cmd/sgs/link_audit/common/perfcnt.c
/*
 * 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);
}