/*
 * @(#)hprof_site.c	1.33 05/01/04
 * 
 * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 * 
 * -Redistribution 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 Sun Microsystems, Inc. or the names of contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST 
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY 
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */

/* Allocation site table. */

/*
 * Every object allocation will have a place where it was allocated,
 *  this is the purpose of the SiteIndex.
 *
 * The allocation site or SiteIndex is unique via a (class,trace) pair.
 *
 * The allocation statistics are accumulated in the SiteInfo for each
 *   site.
 *
 * This file also contains the heap iterate logic, which is closely
 *   associated with the site table, the object table, and the
 *   reference table.
 *
 */

#include "hprof.h"

typedef struct SiteKey {
    ClassIndex cnum;         /* Unique class number */
    TraceIndex trace_index;  /* Trace number */
} SiteKey;

typedef struct SiteInfo {
    int         changed;               /* Objects at this site changed? */
    unsigned    n_alloced_instances;   /* Total allocated instances */
    unsigned    n_alloced_bytes;       /* Total bytes allocated from here */
    unsigned    n_live_instances;      /* Live instances for this site. */
    unsigned    n_live_bytes;          /* Live byte count for this site. */
} SiteInfo;

typedef struct IterateInfo {
    SiteIndex * site_nums;
    int         count;
    int         changed_only;
} IterateInfo;

/* Private internal functions. */

static SiteKey*
get_pkey(SiteIndex index)
{
    void *key_ptr;
    int   key_len;

    table_get_key(gdata->site_table, index, &key_ptr, &key_len);
    HPROF_ASSERT(key_len==sizeof(SiteKey));
    HPROF_ASSERT(key_ptr!=NULL);
    return (SiteKey*)key_ptr;
}

ClassIndex
site_get_class_index(SiteIndex index) 
{
    SiteKey *pkey;

    pkey = get_pkey(index);
    return pkey->cnum;
}

TraceIndex
site_get_trace_index(SiteIndex index) 
{
    SiteKey *pkey;

    pkey = get_pkey(index);
    return pkey->trace_index;
}

static SiteInfo *
get_info(SiteIndex index)
{
    SiteInfo *info;

    info = (SiteInfo*)table_get_info(gdata->site_table, index);
    return info;
}

static void
list_item(TableIndex i, void *key_ptr, int key_len, void *info_ptr, void *arg)
{
    SiteKey         *pkey;
    jlong            n_alloced_instances;
    jlong            n_alloced_bytes;
    jlong            n_live_instances;
    jlong            n_live_bytes;

    HPROF_ASSERT(key_ptr!=NULL);
    HPROF_ASSERT(key_len==sizeof(SiteKey));
    pkey = (SiteKey*)key_ptr;
    
    if ( info_ptr != NULL ) {
        SiteInfo *info;
    
        info = (SiteInfo *)info_ptr;
        n_alloced_instances    = info->n_alloced_instances;
        n_alloced_bytes        = info->n_alloced_bytes;
        n_live_instances       = info->n_live_instances;
        n_live_bytes           = info->n_live_bytes;
    } else {
        n_alloced_instances    = 0;
        n_alloced_bytes        = 0;
        n_live_instances       = 0;
        n_live_bytes           = 0;
    }
    
    debug_message( "Site 0x%08x: class=0x%08x, trace=0x%08x, "
                          "Ninst=(%d,%d), Nbytes=(%d,%d), "
                          "Nlive=(%d,%d), NliveBytes=(%d,%d)\n",
             i, 
             pkey->cnum, 
             pkey->trace_index,
             jlong_high(n_alloced_instances), jlong_low(n_alloced_instances),
             jlong_high(n_alloced_bytes),     jlong_low(n_alloced_bytes),
             jlong_high(n_live_instances),    jlong_low(n_live_instances),
             jlong_high(n_live_bytes),        jlong_low(n_live_bytes));
}

static void
collect_iterator(TableIndex i, void *key_ptr, int key_len, void *info_ptr, void *arg)
{
    IterateInfo     *iterate;

    HPROF_ASSERT(key_ptr!=NULL);
    HPROF_ASSERT(key_len==sizeof(SiteKey));
    HPROF_ASSERT(arg!=NULL);
    iterate = (IterateInfo *)arg;

    if ( iterate->changed_only ) {
        SiteInfo *info;
        
        info = (SiteInfo *)info_ptr;
        if ( info==NULL || !info->changed ) {
            return;
        }
    }
    iterate->site_nums[iterate->count++] = i;
}

static void
mark_unchanged_iterator(TableIndex i, void *key_ptr, int key_len, void *info_ptr, void *arg)
{
    SiteInfo *info;

    HPROF_ASSERT(key_ptr!=NULL);
    HPROF_ASSERT(key_len==sizeof(SiteKey));
    
    info = (SiteInfo *)info_ptr;
    if ( info != NULL ) {
        info->changed = 0;
    }
}

static int 
qsort_compare_allocated_bytes(const void *p_site1, const void *p_site2)
{
    SiteIndex  site1;
    SiteIndex  site2;
    SiteInfo  *info1;
    SiteInfo  *info2;
    
    HPROF_ASSERT(p_site1!=NULL);
    HPROF_ASSERT(p_site2!=NULL);
    site1 = *(SiteIndex *)p_site1;
    site2 = *(SiteIndex *)p_site2;
    info1 = get_info(site1);
    info2 = get_info(site2);
    return info2->n_alloced_bytes - info1->n_alloced_bytes;
}

static int 
qsort_compare_live_bytes(const void *p_site1, const void *p_site2)
{
    SiteIndex  site1;
    SiteIndex  site2;
    SiteInfo  *info1;
    SiteInfo  *info2;
    
    HPROF_ASSERT(p_site1!=NULL);
    HPROF_ASSERT(p_site2!=NULL);
    site1 = *(SiteIndex *)p_site1;
    site2 = *(SiteIndex *)p_site2;
    info1 = get_info(site1);
    info2 = get_info(site2);
    return info2->n_live_bytes - info1->n_live_bytes;
}

static ClassIndex
find_cnum(jlong class_tag)
{
    ClassIndex cnum;

    if ( class_tag != (jlong)0 ) {
        ObjectIndex class_object_index;
	SiteIndex   site_index;
	SiteKey    *pkey;
	
	class_object_index = tag_extract(class_tag);
	site_index = object_get_site(class_object_index);
	pkey = get_pkey(site_index);
	cnum = pkey->cnum;
    } else {
	LoaderIndex loader_index;

	loader_index = loader_find_or_create(NULL,NULL);
	cnum = class_find_or_create("Ljava/lang/Object;", loader_index);
    }
    return cnum;
}

/* JVMTI callback function. */
static jvmtiIterationControl JNICALL 
root_object(jvmtiHeapRootKind root_kind, jlong class_tag, jlong size, 
		jlong* tag_ptr, void *user_data)
{

   /* Only calls to Allocate, Deallocate, RawMonitorEnter & RawMonitorExit
    *   are allowed here (see the JVMTI Spec).
    */

    ObjectIndex   object_index;
    SiteIndex     object_site_index;
    
    HPROF_ASSERT(tag_ptr!=NULL);
    if ( (*tag_ptr) != (jlong)0 ) {
	object_index      = tag_extract(*tag_ptr);
	object_site_index = object_get_site(object_index);
    } else {
        object_site_index = site_find_or_create(find_cnum(class_tag), 
				gdata->system_trace_index);
	object_index      = object_new(object_site_index, (jint)size, 
			    OBJECT_SYSTEM, gdata->system_thread_serial_num);
	/* Create and set the tag. */
	*tag_ptr = tag_create(object_index);
    }

    switch ( root_kind ) {
        case JVMTI_HEAP_ROOT_JNI_GLOBAL: {
		SerialNumber trace_serial_num;
		SerialNumber gref_serial_num;
		
		if ( object_site_index != 0 ) {
		    SiteKey     *pkey;
		    TraceIndex  trace_index;
		    
		    pkey = get_pkey(object_site_index);
		    trace_index = pkey->trace_index;
		    trace_serial_num = trace_get_serial_number(trace_index);
		} else {
		    trace_serial_num = 
			trace_get_serial_number(gdata->system_trace_index);
		}
		gref_serial_num = gdata->gref_serial_number_counter++;
		io_heap_root_jni_global(object_index, gref_serial_num, 
					trace_serial_num);
		break;
	    }
        case JVMTI_HEAP_ROOT_SYSTEM_CLASS: {
		char *sig;

		sig = "Unknown";
		if ( object_site_index != 0 ) {
		    SiteKey *pkey;
		    
		    pkey = get_pkey(object_site_index);
		    sig = string_get(class_get_signature(pkey->cnum));
		}
		io_heap_root_system_class(object_index, sig);
		break;
	    }
        case JVMTI_HEAP_ROOT_MONITOR: {
		io_heap_root_monitor(object_index);
		break;
	    }
        case JVMTI_HEAP_ROOT_THREAD: {
		SerialNumber thread_serial_num;

		if ( object_index != 0 ) {
		    thread_serial_num = 
			  object_get_thread_serial_number(object_index);
		} else {
		    thread_serial_num = gdata->system_thread_serial_num;
		}
		io_heap_root_thread(object_index, thread_serial_num);
		break;
	    }
        case JVMTI_HEAP_ROOT_OTHER: {
		io_heap_root_unknown(object_index);
		break;
	    }
	default:
	    break;
    }
    return JVMTI_ITERATION_CONTINUE;
}

/* JVMTI callback function. */
static jvmtiIterationControl JNICALL
stack_object(jvmtiHeapRootKind root_kind, 
		jlong class_tag, jlong size, jlong* tag_ptr, 
		jlong thread_tag, jint depth, jmethodID method, jint slot,
		void *user_data)
{

   /* Only calls to Allocate, Deallocate, RawMonitorEnter & RawMonitorExit
    *   are allowed here (see the JVMTI Spec).
    */

    ObjectIndex  object_index;
    SerialNumber thread_serial_num;
    
    HPROF_ASSERT(tag_ptr!=NULL);
    if ( (*tag_ptr) != (jlong)0 ) {
	object_index = tag_extract(*tag_ptr);
	thread_serial_num = object_get_thread_serial_number(object_index);
    } else {
	SiteIndex site_index;
	
        site_index = site_find_or_create(find_cnum(class_tag), 
				gdata->system_trace_index);
	if ( thread_tag != (jlong)0 ) {
	    ObjectIndex thread_object_index;

	    thread_object_index = tag_extract(thread_tag);
	    thread_serial_num = 
	           object_get_thread_serial_number(thread_object_index);
	} else {
	    thread_serial_num = gdata->system_thread_serial_num;
	}
	object_index = object_new(site_index, (jint)size, OBJECT_SYSTEM,
			    thread_serial_num);
	/* Create and set the tag. */
	*tag_ptr = tag_create(object_index);
    }

    HPROF_ASSERT(thread_serial_num!=0);
    HPROF_ASSERT(object_index!=0);
    switch ( root_kind ) {
        case JVMTI_HEAP_ROOT_STACK_LOCAL:
	    io_heap_root_java_frame(object_index, thread_serial_num, depth);
	    break;
        case JVMTI_HEAP_ROOT_JNI_LOCAL:
	    io_heap_root_jni_local(object_index, thread_serial_num, depth);
	    break;
	default:
	    break;
    }
    return JVMTI_ITERATION_CONTINUE;
}

/* JVMTI callback function. */
static jvmtiIterationControl JNICALL
reference_object(jvmtiObjectReferenceKind reference_kind, 
		jlong class_tag, jlong size, jlong* tag_ptr, 
		jlong referrer_tag, jint referrer_index, void *user_data)
{

   /* Only calls to Allocate, Deallocate, RawMonitorEnter & RawMonitorExit
    *   are allowed here (see the JVMTI Spec).
    */

    RefIndex      ref_index;
    RefIndex      prev_ref_index;
    ObjectIndex   referrer_object_index;
    ObjectIndex   object_index;
    jlong         object_tag;

    HPROF_ASSERT(tag_ptr!=NULL);
    HPROF_ASSERT(referrer_tag!=(jlong)0);
    
    if ( referrer_tag != (jlong)0 ) {
	referrer_object_index = tag_extract(referrer_tag);
    } else {
        return JVMTI_ITERATION_CONTINUE;
    }
    
    object_tag = *tag_ptr;
    if ( object_tag != (jlong)0 ) {
        object_index = tag_extract(object_tag);
    } else {
        SiteIndex site_index;
	
        site_index = site_find_or_create(find_cnum(class_tag), 
			gdata->system_trace_index);
	object_index = object_new(site_index, (jint)size, OBJECT_SYSTEM,
				gdata->system_thread_serial_num);
	object_tag = tag_create(object_index);
	*tag_ptr   = object_tag;
    }

    /* Save reference information */
    prev_ref_index = object_get_references(referrer_object_index);
    ref_index = reference_new(prev_ref_index, reference_kind,
		    class_tag, size, object_tag, referrer_index);
    object_set_references(referrer_object_index, ref_index);
    
    return JVMTI_ITERATION_CONTINUE;
}

/* External interfaces */

SiteIndex
site_find_or_create(ClassIndex cnum, TraceIndex trace_index)
{
    SiteIndex index;
    static SiteKey  empty_key;
    SiteKey   key;
    
    key = empty_key;
    HPROF_ASSERT(cnum!=0);
    HPROF_ASSERT(trace_index!=0);
    key.cnum        = cnum;
    key.trace_index = trace_index;
    index = table_find_or_create_entry(gdata->site_table, 
			    &key, (int)sizeof(key), NULL, NULL);
    return index;
}

void
site_init(void)
{
    HPROF_ASSERT(gdata->site_table==NULL);
    gdata->site_table = table_initialize("Site",
                            1024, 1024, 511, (int)sizeof(SiteInfo));
}

void
site_list(void)
{
    debug_message( 
        "--------------------- Site Table ------------------------\n");
    table_walk_items(gdata->site_table, &list_item, NULL);
    debug_message(
        "----------------------------------------------------------\n");
}

void
site_cleanup(void)
{
    table_cleanup(gdata->site_table, NULL, NULL);
    gdata->site_table = NULL;
}

void
site_update_stats(SiteIndex index, jint size, jint hits)
{
    SiteInfo *info;
    
    table_lock_enter(gdata->site_table); {
	info = get_info(index);
	
	info->n_live_instances          += hits;
	info->n_live_bytes              += size;
	info->changed                   = 1;
	
	gdata->total_live_bytes         += size;
	gdata->total_live_instances     += hits;
	 
	if ( size > 0 ) {
	    info->n_alloced_instances   += hits;
	    info->n_alloced_bytes       += size;
	    gdata->total_alloced_bytes = 
		jlong_add(gdata->total_alloced_bytes, jint_to_jlong(size));
	    gdata->total_alloced_instances = 
		jlong_add(gdata->total_alloced_instances, jint_to_jlong(hits));
	}
    } table_lock_exit(gdata->site_table);
}

/* Output allocation sites, up to the given cut-off point, and according
 * to the given flags:
 *
 *      SITE_DUMP_INCREMENTAL only dump what's changed since last dump.
 *      SITE_SORT_BY_ALLOC    sort sites by total allocation rather
 *                                  than live data.
 *      SITE_FORCE_GC         force a GC before the site dump.
 */

void 
site_write(JNIEnv *env, int flags, double cutoff)
{
    HPROF_ASSERT(gdata->site_table!=NULL);
    LOG3("site_write", "flags", flags);
    
    if (flags & SITE_FORCE_GC) {
        runGC();
    }

    HPROF_ASSERT(gdata->total_live_bytes!=0);

    rawMonitorEnter(gdata->data_access_lock); {
        
        IterateInfo     iterate;
        int             site_table_size;
        double          accum_percent;
        void *          comment_str;
        int             i;
        int             cutoff_count;
	int             nbytes;

        accum_percent = 0;
        site_table_size = table_element_count(gdata->site_table);
        
        (void)memset(&iterate, 0, sizeof(iterate));
	nbytes            = site_table_size * (int)sizeof(SiteIndex);
	if ( nbytes > 0 ) {
	    iterate.site_nums = HPROF_MALLOC(nbytes);
	    (void)memset(iterate.site_nums, 0, nbytes);
	}
        iterate.count   = 0;
        iterate.changed_only = flags & SITE_DUMP_INCREMENTAL;
        table_walk_items(gdata->site_table, &collect_iterator, &iterate);

        site_table_size = iterate.count;
        
        if (flags & SITE_SORT_BY_ALLOC) {
            comment_str = "allocated bytes";
            qsort(iterate.site_nums, site_table_size, sizeof(SiteIndex), 
                    &qsort_compare_allocated_bytes);
        } else {
            comment_str = "live bytes";
            qsort(iterate.site_nums, site_table_size, sizeof(SiteIndex), 
                    &qsort_compare_live_bytes); 
        }

        trace_output_unmarked(env);
        
        cutoff_count = 0;
        for (i = 0; i < site_table_size; i++) {
            SiteInfo   *info;
            SiteIndex   index;
            double      ratio;
            
            index= iterate.site_nums[i];
            HPROF_ASSERT(index!=0);
            info        = get_info(index);
            ratio       = (double)info->n_live_bytes / (double)gdata->total_live_bytes;
            if (ratio < cutoff) {
                break;
            }
            cutoff_count++;
        }
        
        io_write_sites_header(  comment_str,
                                flags,
                                cutoff,
                                gdata->total_live_bytes,
                                gdata->total_live_instances,
                                gdata->total_alloced_bytes,
                                gdata->total_alloced_instances,
                                cutoff_count);
        
        for (i = 0; i < cutoff_count; i++) {
            SiteInfo     *info;
            SiteKey      *pkey;
            SiteIndex     index;
            char         *class_signature;
            double        ratio;
            
            index = iterate.site_nums[i];
            pkey         = get_pkey(index);
            info         = get_info(index);
            
            ratio       = (double)info->n_live_bytes / (double)gdata->total_live_bytes;
            accum_percent += ratio;
            
            class_signature  = string_get(class_get_signature(pkey->cnum));
            
            io_write_sites_elem(i + 1,
                                ratio,
                                accum_percent,
                                class_signature,
                                class_get_serial_number(pkey->cnum),
                                trace_get_serial_number(pkey->trace_index),
                                info->n_live_bytes,
                                info->n_live_instances,
                                info->n_alloced_bytes,
                                info->n_alloced_instances);
        }
        
        io_write_sites_footer();

        table_walk_items(gdata->site_table, &mark_unchanged_iterator, NULL);

	if ( iterate.site_nums != NULL ) {
	    HPROF_FREE(iterate.site_nums);
        }

    } rawMonitorExit(gdata->data_access_lock);
}

void
site_heapdump(JNIEnv *env)
{
   
    rawMonitorEnter(gdata->data_access_lock); {
	
	struct { int i; } user_data; /* FIXUP */

	user_data.i = 0;
   
	/* Remove class dumped status, all classes must be dumped */
	class_all_status_remove(CLASS_DUMPED);

	/* Dump the last thread traces and get the lists back we need */
	tls_dump_traces(env);
       
	/* Write header for heap dump */
	io_heap_header(gdata->total_live_instances, gdata->total_live_bytes);

	/* Write out the thread roots first (threads that ended already did) */
	tls_output_heap_threads();

	/* Setup a clean reference table */
	reference_init();
	
	/* Walk over all reachable objects and dump out roots */
	gdata->gref_serial_number_counter = gdata->gref_serial_number_start;
	iterateOverReachableObjects(&root_object, &stack_object, &reference_object, (void*)&user_data);

	/* Process reference information. */
	object_reference_dump(env);
	object_clear_references();
        reference_cleanup();

	/* Dump the last thread traces and get the lists back we need */
	tls_dump_traces(env);

	/* Write out footer for heap dump */
	io_heap_footer();
	
    } rawMonitorExit(gdata->data_access_lock);
}

