Logo Search packages:      
Sourcecode: jmp version File versions  Download package

jmp.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <jni.h>
#include <jvmpi.h>
#include <jmp-config.h>
/*#include <assert.h>*/

#include <jmp.h>
#include <time.h>
#include <hash.h>
#include <arena.h>
#include <cls.h>
#include <obj.h>
#include <method.h>
#include <jthread.h>
#include <timerstack.h>
#include <objectstore.h>
#include <ui.h>
#include <object_dump.h>
#include <heap_dump.h>
#include <instance_owners.h>
#include <filter.h>
#include <jmptime.h>
#include <deadlock_detector.h>
#include <string_dumper.h>

#ifdef linux
/* back trace handling only works in linux for what I know. */
#include <execinfo.h>
#endif /* linux */

/* Compile with -DJMPDEBUG to get lots of traces, can be helpful sometimes. */
#ifndef JMPDEBUG
#define trace(a) 
#define trace2(a,b) 
#define trace3(a,b,c)
#define trace4(a,b,c,d)
#else
#define trace(a) fprintf (stderr, a); fflush (stderr)
#define trace2(a,b) fprintf (stderr, a, b); fflush (stderr)
#define trace3(a,b,c) fprintf (stderr, a, b, c); fflush (stderr)
#define trace4(a,b,c,d) fprintf (stderr, a, b, c, d); fflush (stderr)
#endif /* JMPDEBUG */

/** This is the interface into the jvm */
static JVMPI_Interface* jvmpi;

/** are we showing any graphical user interface? */
static int doUI = 1;
/** are we profiling classes. */
static int class_profiling = 0;
/** are we profiling method calls? */
static int method_profiling = 1;
/** are we tracing objects? */
static int object_profiling = 1;
/** Do object allocations follow the filter? */
static int alloc_follow_filter = 0;
/** are we tracing monitors. */
static int monitor_profiling = 1;
/** do we allow dump on signal? */
static int dump_enabled = 0;
/** assume jmp is running in a simulator and don't do jni calls. */
static int simulator = 0;

/** Information about the vm we are running in */
static char* vm_version;
static char* vm_vendor;
static char* vm_name;

/** Use absolute times instead of thread times in method profiling? */
static int absolute_times;

/** These are the hashes where we store our different objects. */
static hashtab* arenas;
static hashtab* classes;
static hashtab* objects;
static hashtab* methods;
static hashtab* threads;

/** The current reset level. */
static int current_reset_level = 0;
/** The minimum level needed when getting objects. */
static int minimum_reset_level = 0;
/** The current gc level. */
static int current_gc_level = 0; 

/** This is our objectstore. */
static objectstore* osp;

/** The names of the primitive array types. */
static char* L = "[L";
static cls* LP = 0;
static char* Z = "[Z";
static cls* ZP = 0;
static char* B = "[B";
static cls* BP = 0;
static char* C = "[C";
static cls* CP = 0;
static char* S = "[S";
static cls* SP = 0;
static char* I = "[I";
static cls* IP = 0;
static char* J = "[J";
static cls* JP = 0;
static char* F = "[F";
static cls* FP = 0;
static char* D = "[D";
static cls* DP = 0;

/** Counters for methods. */
static long c_class_load = 0;
static long c_class_unload = 0;
static long c_object_alloc = 0;
static long c_object_move = 0;
static long c_object_free = 0;
static long c_thread_start = 0;
static long c_thread_end = 0;
static long c_method_entry = 0;
static long c_method_exit = 0;

/** Some counter.. */
static long this_gc_object_move = 0;
static long this_gc_object_free = 0;
static jlong this_gc_time = 0;
static jlong heap_size = 0;

/** Where should we dump data files? */
static char* dumpdir = NULL;

static int down = 0;   /** since we get multiple shutdowns... */
JVMPI_RawMonitor ui_shutdown_lock;

static hashtab* last_monitor_dump;

/** we need to make sure that we call the right set_status during gc.*/
static int gc_requested = 0;

#ifdef linux
void get_backtrace () {
    int i;
    void *array[10];
    size_t size;
    char **strings;
    size = backtrace (array, 10);
    strings = backtrace_symbols (array, size);
    printf ("Obtained %d stack frames.\n", size);
    
    for (i = 0; i < size; i++)
      printf ("%s\n", strings[i]);  
    free (strings);
}
#endif /* linux */

JVMPI_RawMonitor jmp_create_monitor(char *name) {
    return jvmpi->RawMonitorCreate (name);
}

void jmp_delete_monitor(JVMPI_RawMonitor m) {
    jvmpi->RawMonitorDestroy (m);
}

void jmp_lock_monitor (JVMPI_RawMonitor m) {
    jvmpi->RawMonitorEnter (m);
}

void jmp_unlock_monitor (JVMPI_RawMonitor m) {
    jvmpi->RawMonitorExit (m);
}

void jmp_monitor_wait (JVMPI_RawMonitor m, jlong milis) {
    jvmpi->RawMonitorWait (m, milis);
}

void jmp_monitor_notify_all (JVMPI_RawMonitor m) {
    jvmpi->RawMonitorNotifyAll (m);
}

static void lock_all () {
    jmphash_lock (threads);
    jmphash_lock (arenas);
    jmphash_lock (classes);
    jmphash_lock (methods);
    jmphash_lock (objects);
}

static void unlock_all () {
    jmphash_unlock (objects);
    jmphash_unlock (methods);
    jmphash_unlock (classes);
    jmphash_unlock (arenas);
    jmphash_unlock (threads);
}

/** Test if jmp is currently tracing objects. */
inline int tracing_objects () {
    return object_profiling;
}

/** Test if object allocations follows filter. */
inline int allocs_follow_filter () {
    return alloc_follow_filter;
}

/** Test if jmp is currently tracing method calls. */
inline int tracing_methods () {
    return method_profiling;
}

inline int tracing_monitors () {
    return monitor_profiling;
}

/** Test if jmp is using its GUI */
inline int usingUI () {
    return doUI;
}


/** Reset the counter on all classes and method. */
void reset_counters () {
    jmphash_lock (classes);
    jmphash_for_each ((jmphash_iter_f)cls_reset_count, classes);
    current_reset_level++;
    minimum_reset_level = current_reset_level;
    jmphash_unlock (classes);
    jmphash_lock (methods);
    jmphash_for_each ((jmphash_iter_f)method_reset_count, methods);
    jmphash_unlock (methods);
}


/** Restore the counter to theire full values. */
void restore_counters () {
    jmphash_lock (classes);    
    minimum_reset_level = 0;
    jmphash_for_each ((jmphash_iter_f)cls_restore_count, classes);
    jmphash_unlock (classes);
    jmphash_lock (methods);
    jmphash_for_each ((jmphash_iter_f)method_restore_count, methods);
    jmphash_unlock (methods);
}

static void object_free (jobjectID);

/** Run the garbage collector. */
void run_GC () {
    gc_requested = 1;
    jvmpi->RunGC ();    
    gc_requested = 0;
}

/** Get the size of the heap. */
jlong current_heap_size () {
    return heap_size;
}

static void free_the_object (void* v) {
    /* it is not safe to call object_free here, it will mess up the 
     * hashs internal structures. /robo
     */
    obj* op = (obj*)v;
    cls* cp = get_class (obj_get_class_id (op));
    if (cp != NULL) {
      jint os = obj_get_size (op);
      cls_object_free_not_gc (cp, os);
    }
    objectstore_obj_free (osp, op);
}

static void release_object (obj* o) {
    objectstore_obj_free (osp, o);
}

/** Load a primitive array class. */
static cls* load_array_class (char* cls_name) {
    cls* c;
    c = cls_new (cls_name, cls_name, (jobjectID)cls_name, 0, 0, NULL, 0, NULL);
    if (classes) {
      if (c)
          jmphash_insert (c, classes);
      else 
          fprintf (stderr, "failed to allocate array class: '%s'\n", cls_name);
    }
    return c;
}

/** Load all of the primitive array classes. */
static void load_array_classes () {
    LP = load_array_class (L);
    ZP = load_array_class (Z);
    BP = load_array_class (B);
    CP = load_array_class (C);
    SP = load_array_class (S);
    IP = load_array_class (I);
    JP = load_array_class (J);
    FP = load_array_class (F);
    DP = load_array_class (D);
}

/** Perform a heao dump with the specified level.
 */
static void run_heap_dump_arg (int level) {
    JVMPI_HeapDumpArg heap_arg;    
    heap_arg.heap_dump_level = level;
    lock_all ();
    if ((level == JVMPI_DUMP_LEVEL_0) || (!tracing_objects ())) {
      /* clear out old incorrect information. */
      jmphash_for_each ((jmphash_iter_f)free_the_object, objects);
      jmphash_clear (objects);
    }
    fprintf (stderr, "requesting heap dump: %d\n", level);
    jvmpi->RequestEvent (JVMPI_EVENT_HEAP_DUMP, &heap_arg);
    fprintf (stderr, "heap dumping done: %d\n", level);
    if (class_profiling == 0) {
      /* clear classes, objects and methods... */
      jmphash_for_each ((jmphash_iter_f)release_object, objects);
      jmphash_clear (objects);
      jmphash_for_each ((jmphash_iter_f)method_free, methods); 
      jmphash_clear (methods);
      jmphash_for_each ((jmphash_iter_f)cls_free, classes); 
      jmphash_clear (classes);
      /* and fill in with standard information... */
      load_array_classes ();    
    }
    unlock_all ();
}

void run_heap_dump () {
    run_heap_dump_arg (JVMPI_DUMP_LEVEL_2);
}

void run_object_dump () {
    run_heap_dump_arg (JVMPI_DUMP_LEVEL_0);
}

void run_string_dump () {
    dump_strings ();
}

hashtab* run_monitor_dump () {
    jvmpi->RequestEvent (JVMPI_EVENT_MONITOR_DUMP, NULL);
    detect_deadlock (last_monitor_dump);
    return last_monitor_dump;
}

void cleanup_monitor_information () {
    if (last_monitor_dump) {
      jmphash_for_each ((jmphash_iter_f)free_monitor_info, last_monitor_dump); 
      jmphash_free (last_monitor_dump);
    }
    last_monitor_dump = NULL;
}

/** Ask the jvm about a specific object allocation.
 */
void get_object_alloc (jobjectID obj_id) {
    jint res = jvmpi->RequestEvent (JVMPI_EVENT_OBJECT_ALLOC, obj_id);
    if (res != JVMPI_SUCCESS)
      fprintf (stderr, "failed to get object (%p), error: %d\n", 
             obj_id, res);
}

/** Ask the jvm about a specified class load.
 */
void get_class_load (jobjectID class_id) {
    jint res;
    if (class_id == 0) {
        return;
    }

    jmphash_lock (classes);
    res = jvmpi->RequestEvent (JVMPI_EVENT_CLASS_LOAD, class_id);
    jmphash_unlock (classes);
    if (res != JVMPI_SUCCESS) 
      fprintf (stderr, "failed to get class (%p), error: %d\n", 
             class_id, res);
}

/** Ask the jvm about a specified arena.
 *  Note: this method is not available in the SUN jvm.
 */
void get_arena_load (jint arena_id) {
    jint res;

    jmphash_lock (arenas);
    res = jvmpi->RequestEvent (JVMPI_EVENT_ARENA_NEW, &arena_id);
    jmphash_unlock (arenas);
    if (res != JVMPI_SUCCESS) 
      fprintf (stderr, "failed to get arena (%d), error: %d\n", 
             arena_id, res);
}

/** Do initial setup. */
static int setup () {
    trace ("JMP setup ()\n");
    arenas = jmphash_new (11, arena_jmphash_func, arena_cmp_func, "arenas");
    classes = jmphash_new (2000, cls_jmphash_func, cls_cmp_func, "classes");
    methods = jmphash_new (10000, method_jmphash_func, method_cmp_func, "methods");
    threads = jmphash_new (50, jthread_jmphash_func, jthread_cmp_func, "threads");
    objects = jmphash_new (100000, obj_jmphash_func, obj_cmp_func, "objects");
    trace ("allocating object store\n");
    osp = objectstore_new ();
    trace ("loading array classes\n");
    load_array_classes ();    
    trace ("setup finished\n");
    return 0;
}

void ui_shutdown_complete () {
    fprintf (stderr, "ui_shutdown_complete enter\n");
    jmp_lock_monitor (ui_shutdown_lock);
    jmp_monitor_notify_all (ui_shutdown_lock);
    jmp_unlock_monitor (ui_shutdown_lock);
    fprintf (stderr, "ui_shutdown_complete exit\n");
}

static void wait_for_ui () {
    if (doUI) {
      /* wait for final updates before destroying data.. */
      ui_shutdown_lock = jmp_create_monitor ("ui_shutdown_lock");
      jmp_lock_monitor (ui_shutdown_lock);
      quit_ui ();
      jmp_monitor_wait (ui_shutdown_lock, 0); 
      jmp_unlock_monitor (ui_shutdown_lock);
      jmp_delete_monitor (ui_shutdown_lock);
    }
}

/** free *p in a safe way, make sure it is unlocked 
 *  after we have set *p to NULL.
 */
static void cleanup_hash (hashtab** p) {
    hashtab* h = *p;
    *p = NULL;
    jmphash_unlock (h);
    jmphash_free (h);
}

/** free all elements in *p and free p. 
 * @param p the hashtab to clean up
 * @param f the function to use to free the elements in p.
 */
static void free_and_cleanup_hash (hashtab** p, jmphash_iter_f f) {
    if (*p) {
      jmphash_for_each (f, *p);
      cleanup_hash (p);
    }
}

/** Tear down all allocations we have made. 
 */
static int teardown () {
    trace ("JMP teardown ()\n");    
    fprintf (stdout, _("teardown called, freeing jmp-data..\n"));
    wait_for_ui ();
    
    down = 1;
    
    free_last_down_link ();
    cleanup_monitor_information ();
    remove_owners_information ();
    if (objects) {
      /*fprintf (stdout, _("freeing objects: %d..\n"), jmphash_cardinal (objects));*/
      /* freeing the objectstore will free all objects in it.. */
      objectstore_free (osp);
      cleanup_hash (&objects);
    }
    free_and_cleanup_hash (&methods, (jmphash_iter_f)method_free);
    free_and_cleanup_hash (&classes, (jmphash_iter_f)cls_free);
    free_and_cleanup_hash (&arenas, (jmphash_iter_f)arena_free);
    free_and_cleanup_hash (&threads, (jmphash_iter_f)jthread_free);
    fprintf (stdout, _("done freeing jmp-data..\n"));    
    free (dumpdir); /* NULL or malloc'ed */
    dumpdir = NULL;
    fprintf (stdout, _("teardown complete.\n"));
    return 0;
}

/** Get a class pointer from its id. */
cls* get_class (const jobjectID class_id) {
    cls c;
    cls* cp = NULL;
    cls_set_class_id (&c, class_id);
    cp = jmphash_search (&c, classes);
    return cp;
}

/** Get the hashtable of all loaded classes. */
hashtab* get_classes () {
    return classes;
}

/** Get an object pointer from its id. */
obj* get_object (const jobjectID obj_id) {
    obj o;
    obj* op = NULL;
    obj_set_object_id (&o, obj_id);
    op = jmphash_search (&o, objects);
    return op;
}

/** Get a method object pointer from its id. */
method* get_method (const jmethodID method_id) {
    method m;
    method* mp = NULL;
    method_set_method_id (&m, method_id);
    mp = jmphash_search (&m, methods);
    return mp;
}

/** Get the hashtable of all methods in the system. */
hashtab* get_methods () {
    return methods;
}

/** Get an jthread pointer from its id. */
jthread* get_jthread (const JNIEnv* env_id) {
    jthread t;
    jthread* tp = NULL;
    jthread_set_env_id (&t, (JNIEnv*)env_id);
    tp = jmphash_search (&t, threads);
    return tp;
}

arena* get_arena (const jint arena_id) {
    arena a;
    arena* ap = NULL;
    arena_set_arena_id (&a, arena_id);
    ap = jmphash_search (&a, arenas);
    return ap;
}

/** get the timerstack for the given thread. */
timerstack* get_timerstack (JNIEnv* env_id) {
    return (timerstack*)jvmpi->GetThreadLocalStorage (env_id);    
}

hashtab* get_threads () {
    return threads;
}

/** Get the given system property str and put the value int dest, 
 */
static void get_string (char** dest, char* str, JNIEnv* env, jclass clz, jmethodID jmethod) {
    jstring j_str;
    const char *utf8;
    j_str = (*env)->NewStringUTF (env, str);
    j_str = (*env)->CallStaticObjectMethod (env, clz, jmethod, j_str);
    utf8 = (*env)->GetStringUTFChars (env, j_str, NULL);
    *dest = strdup (utf8);
    trace3 ("    %s = %s\n", str, utf8);
    (*env)->ReleaseStringUTFChars (env, j_str, utf8);
}

/** Perform additional setup that cant be done in setup () 
 *  This will mean that we get the jvm vendor/version/name to correctly 
 *  select a timing function (maybee remove all time captured up to this point?). 
 */
static void jvm_init_done (JNIEnv* env) {
    jint tstatus;
    jclass c_system;
    jmethodID m_get_property;
    trace ("jvm_init_done.\nContinuing with JMP setup...\n");
    
    trace ("Going to evaluate vm version\n");
    if (env == NULL) {
      fprintf (stderr, "Warning: jvm_init_done with JNIEnv == NULL, assuming simulator\n");
      simulator = 1;
    }
    if (!simulator) {
      c_system = (*env)->FindClass (env, "java/lang/System");
      m_get_property =
          (*env)->GetStaticMethodID (env, c_system, "getProperty",
                               "(Ljava/lang/String;)Ljava/lang/String;");
      
      get_string (&vm_version, "java.vm.version", env, c_system, m_get_property);
      get_string (&vm_vendor, "java.vm.vendor", env, c_system, m_get_property);
      get_string (&vm_name, "java.vm.name", env, c_system, m_get_property);
    } else {
      vm_version = strdup ("jmp");
      vm_vendor = strdup ("jmp");
      vm_name = strdup ("jmp");
    }
    
    jmptime_init (absolute_times, vm_version, vm_vendor, vm_name);
      
    if (doUI || (get_dump_timer () > 0)) {
      tstatus = jvmpi->CreateSystemThread("jmp-gtk", 
                                  JVMPI_NORMAL_PRIORITY, 
                                  gtkthread);
      if (tstatus == JNI_ERR)
          fprintf (stdout, 
                 "JMP worker thread create status: %d (ok: %d, bad: %d)\n", 
                 tstatus, 
                 JNI_OK, 
                 JNI_ERR);
    } 
    
    trace ("JMP setup complete...\n");    
    
} 

static int run_data_dump_unlocked () {
    int ret = -1;
    if (objects && classes && methods) 
      ret = dump_data (dumpdir, classes, methods, threads);
    return ret;
}

int
run_data_dump () {
    int ret = -1;
    
    trace ("run_data_dump ()\n");
    lock_all ();
    ret = run_data_dump_unlocked ();
    unlock_all ();    
    return ret;
}

void
run_show_objects_alloced_by_method (method* m) {
    trace2 ("run_show_objects_alloced_by_method (%s)\n", m->jmpname);
    if (!down && objects)
      show_objects_alloced_by_method (objects, m, minimum_reset_level);
}


void
run_show_objects_alloced_by_class (cls* c) {
    trace2 ("run_show_objects_alloced_by_class (%s)\n", c->name);
    if (!down && objects)
      show_objects_alloced_by_class (objects, c, minimum_reset_level);
}


void
run_find_owners (cls* c) {
    lock_all ();
    show_instance_owners (objects, c);
    unlock_all ();
}

void run_find_instance_owners (obj* o) {
    lock_all ();
    show_owner_for_object (objects, o);
    unlock_all ();
}

void
run_owners_statistics (cls* c) {
    lock_all ();
    show_owners_statistics (objects, c);
    unlock_all ();
}

typedef struct string_callback_data {
    string_callback sc;
    void* data;
} string_callback_data;

static void filter_strings (obj* o, string_callback_data* scd) {
    if (obj_get_class (o) == CP && minimum_reset_level <= obj_get_reset_level (o))
      scd->sc (o, scd->data);
}

void for_each_string (string_callback sc, void* data) {
    string_callback_data scd; 
    scd.sc = sc;
    scd.data = data;
    jmphash_lock (objects);
    jmphash_for_each_with_arg ((jmphash_iter_fa)filter_strings, objects, &scd);
    jmphash_unlock (objects);
}

/** Get the super class of c
 *  NOTE: call this with gc disabled (heap dump has gc disabled, so 
 *  only needed if you add another path into this method).
 * 
 * @param c the cls we want to get the super class for.
 */
cls* get_super_class (cls* c) {
    if (c->super)
      return c->super;
    /* jvm does not like our fake ids. */
    if (c == LP || c == ZP || c == BP || c == CP || c == SP || 
      c == IP || c == JP || c == FP || c == DP)
      return NULL;
    jvmpi->RequestEvent (JVMPI_EVENT_OBJECT_DUMP, cls_get_class_id (c));
    return c->super;
}

/** Call get_last_down_link after this to get the object links.
 */
void get_instance_info (obj* o) {
    jvmpi->DisableGC ();
    jvmpi->RequestEvent (JVMPI_EVENT_OBJECT_DUMP, obj_get_object_id (o));
    jvmpi->EnableGC ();
}

void check_objects (size_t cnt) {
    int diff = jmphash_cardinal (objects) - cnt;
    fprintf (stderr, "objects has: %u, cnt is %u (%u)\n", jmphash_cardinal (objects), cnt, diff);
}

static void data_reset () {
    trace ("data_reset ()\n");
}

static void disable_gc_events () {
    jvmpi->DisableEvent (JVMPI_EVENT_GC_START, NULL);
    jvmpi->DisableEvent (JVMPI_EVENT_GC_FINISH, NULL);
}

static void disable_arena_events () {
    jvmpi->DisableEvent (JVMPI_EVENT_ARENA_NEW, NULL);
    jvmpi->DisableEvent (JVMPI_EVENT_ARENA_DELETE, NULL);
}

static void disable_class_events () {
    jvmpi->DisableEvent (JVMPI_EVENT_CLASS_LOAD, NULL);
    jvmpi->DisableEvent (JVMPI_EVENT_CLASS_UNLOAD, NULL);
    jvmpi->DisableEvent (JVMPI_EVENT_OBJECT_MOVE, NULL);
}

void disable_object_events () {
    if (object_profiling) {
      /* Disable object tracing events. */
      jvmpi->DisableEvent (JVMPI_EVENT_OBJECT_ALLOC, NULL);
      jvmpi->DisableEvent (JVMPI_EVENT_OBJECT_FREE, NULL);
      object_profiling = 0;
    }
}

static void disable_thread_events (int keep_threads) {
    if (!keep_threads) {
        jvmpi->DisableEvent (JVMPI_EVENT_THREAD_START, NULL);
      jvmpi->DisableEvent (JVMPI_EVENT_THREAD_END, NULL);
    }
}

void disable_method_events () {
    if (method_profiling) {
      /* Disable method profiling events. */
      jvmpi->DisableEvent (JVMPI_EVENT_METHOD_ENTRY, NULL);
      jvmpi->DisableEvent (JVMPI_EVENT_METHOD_EXIT, NULL);
      method_profiling = 0;
    }
}

static void disable_dump_events () {
    if (dump_enabled) {
      jvmpi->DisableEvent (JVMPI_EVENT_DATA_DUMP_REQUEST, NULL);
      jvmpi->DisableEvent (JVMPI_EVENT_DATA_RESET_REQUEST, NULL);
      dump_enabled = 0;
    }
}

void disable_monitor_events () {
    if (monitor_profiling) {
      jvmpi->DisableEvent (JVMPI_EVENT_MONITOR_CONTENDED_ENTER, NULL);
      jvmpi->DisableEvent (JVMPI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
      jvmpi->DisableEvent (JVMPI_EVENT_MONITOR_CONTENDED_EXIT , NULL);
      jvmpi->DisableEvent (JVMPI_EVENT_MONITOR_WAIT, NULL);
      jvmpi->DisableEvent (JVMPI_EVENT_MONITOR_WAITED , NULL);
      
      jvmpi->DisableEvent (JVMPI_EVENT_RAW_MONITOR_CONTENDED_ENTER, NULL);
      jvmpi->DisableEvent (JVMPI_EVENT_RAW_MONITOR_CONTENDED_ENTERED, NULL);
      jvmpi->DisableEvent (JVMPI_EVENT_RAW_MONITOR_CONTENDED_EXIT, NULL);

      monitor_profiling = 0;
    }
}

jint get_thread_state (const jthread* jt) {
    return jvmpi->GetThreadStatus (jt->env_id);
}

int get_current_gc_level () {
    return current_gc_level;
}

static void disable_events (int keep_threads) {
    /* Disable standard events */
    jvmpi->DisableEvent (JVMPI_EVENT_JVM_INIT_DONE, NULL);
    jvmpi->DisableEvent (JVMPI_EVENT_JVM_SHUT_DOWN, NULL);
    disable_gc_events ();
    disable_arena_events ();
    disable_class_events ();
    disable_object_events ();
    disable_thread_events (keep_threads);
    disable_method_events ();
    disable_dump_events ();
    disable_monitor_events ();
}

/** The jvm is shutting down so clean up our mess.
 */
static void jvm_shut_down () {
    if (down)
      return;
    trace ("jvm_shut_down ()\n");
    fprintf (stdout, "jvm_shut_down.\n");

    disable_events (1);
    
    if (vm_version)
      free (vm_version);
    if (vm_vendor)
      free (vm_vendor);
    if (vm_name)
      free (vm_name);

    lock_all ();

    /* print statistics. */
    fprintf (stdout, "c_class_load: %ld\n", c_class_load);
    fprintf (stdout, "c_class_unload: %ld\n", c_class_unload);
    fprintf (stdout, "c_object_alloc: %ld\n", c_object_alloc);
    fprintf (stdout, "c_object_move: %ld\n", c_object_move);
    fprintf (stdout, "c_object_free: %ld\n", c_object_free);
    fprintf (stdout, "c_thread_start: %ld\n", c_thread_start);
    fprintf (stdout, "c_thread_end: %ld\n", c_thread_end);
    fprintf (stdout, "c_method_entry: %ld\n", c_method_entry);
    fprintf (stdout, "c_method_exit: %ld\n", c_method_exit);
    
    trace ("running data dump\n");
    run_data_dump_unlocked ();
    
    trace ("tearing down jmp data structures\n");
    teardown ();

    /* Unlocking here should be done, except that 
     * all hashes are gone, so we cant do it... 
     * unlock_all ();
    */
}

/** grab the locks needed during gc.
 */
static void gc_start () {
    trace2 ("gc_start (), %d\n", gc_requested);
    if (down)
      return;
    lock_all ();
    if (gc_requested)
      set_status (_("Running garbage collection..."));
    else 
      set_status_lock (_("Running garbage collection..."));
    this_gc_object_move = 0;
    this_gc_object_free = 0;
    this_gc_time = get_absolute_time (jvmpi);
}


/** Releas the locks needed taken during gc. Show some info.
 */
static void gc_finish (jlong used_objects,
                   jlong used_object_space,
                   jlong total_object_space) {
    char buf[200];
    jlong td = get_absolute_time (jvmpi) - this_gc_time;
    jlong jsec = td / 1000000000ll;
    jlong jnsec = (td % 1000000000ll) / 1000ll;
    if (down)
      return;
    remove_owners_information ();
    trace2 ("gc_finish (), %d\n", gc_requested);
    snprintf (buf, 200, 
            _("Garbage collection completed: %ld objects moved, "
            "%ld objects freed in %lld.%06lld seconds"), 
            this_gc_object_move, this_gc_object_free, jsec, jnsec);
    if (gc_requested)
      set_status (buf); 
    else 
      set_status_lock (buf); 
    heap_size = total_object_space;
    current_gc_level++;
    unlock_all ();
}


/** A new arena has arrived.
 */
static void jvmpi_arena_new (jint arena_id, const char* arena_name) {
    arena* a;
    char buffer[80];
    
    snprintf (buffer, 80, _("Arena %d, %s created."), 
            arena_id, arena_name);
    fprintf (stderr, "arena_new: %d, %s\n", arena_id, arena_name);
    set_status_lock (buffer);
    trace3 ("arena_new: (%d, %s)\n", arena_id, arena_name);
    if (!arenas)
      return;
    
    a = arena_new (arena_id, arena_name);
    if (a == NULL) {
      fprintf (stderr, "arena_new: failed to allocate arena %d, '%s'\n",
             arena_id, arena_name);
    } else {
      jmphash_lock (arenas);
      jmphash_insert (a, arenas);
      jmphash_unlock (arenas);
    }
}

/** Free an object if it is in the arena that is being destroyed.. */
void free_object_if_in_arena (obj* o, jint* arena_id) {
    if (o->arena_id == *arena_id)
      object_free (o->obj_id);
}

/** An arena is destroyed, every object in arena are also deallocated so remove
 *  them (we do not get an explicit object_freed for them).
 */
static void arena_delete(jint arena_id) {
    /* The JVMPI_EVENT_ARENA_DELETE is issued in
       the thread suspended mode.  So we should
       NOT use malloc() here.  That's the reason
       for this arena-initialization by hand
       instead of using arena_new().  Change it
       if somebody has a better idea. */
    /* The locks are aquired in the gc_start()
       already! */
    arena a;
    arena *ah = NULL;
    
    trace2 ("arena_delete (%d)\n", arena_id);
    arena_set_arena_id (&a, arena_id);
    
    ah = jmphash_search (&a, arenas);
    if (!ah) {
      /* I don't think that's good either (may malloc), but
         since that's only an error message, I hope
         it's ok for now. */
      fprintf (stderr, "arena_delete: failed to find freed arena: %d.\n",
             arena_id);
    } else {
      jmphash_remove (ah, arenas);
      arena_free (ah);
    }
    
    jmphash_for_each_with_arg ((jmphash_iter_fa)free_object_if_in_arena, 
                      objects, &arena_id);
}


/** A class has been loaded. 
 *  Locks will only be taken if requested = 0.
 *  Requested class_load are assumed to be locked already..
 */
static void class_load (JNIEnv* env,
                  const char *class_name, 
                  const char *source_name, 
                  jint num_interfaces,
                  jint num_methods, 
                  JVMPI_Method *meths, 
                  jint num_static_fields,
                  JVMPI_Field *statics, 
                  jint num_instance_fields, 
                  JVMPI_Field *instances, 
                  jobjectID class_id,
                  jint requested) {
    cls* c;
    method* m;
    int i;

    trace3 ("class_load: (%s, %p ...)\n", class_name, class_id);

    if (down || !classes)
      return;

    c_class_load++;
    
    /* Store the class. */
    if (!requested)
      jmphash_lock (classes);

    c = get_class (class_id);
    if (c == NULL) {
      c = cls_new (class_name, source_name, class_id, num_interfaces, 
                 num_static_fields, statics, 
                 num_instance_fields, instances);
      if (c == NULL) {
          fprintf (stderr, "class_load: failed to allocate cls: %s, %s, %p.\n", 
                 class_name, source_name, class_id);
      } else {
          jmphash_insert (c, classes);
          if (!strcmp ("java/lang/Object", class_name))
            cls_print (c);
      }
      
      /* and its methods */
      if (!requested)
          jmphash_lock (methods);
      
      for (i = 0; i < num_methods; i++) {
          JVMPI_Method *jmethod;
          jmethod = meths + i;
          m = get_method (jmethod->method_id);
          if (m == NULL) {
            m = method_new (jmethod->method_name, jmethod->method_signature,
                        jmethod->start_lineno, jmethod->end_lineno, 
                        jmethod->method_id, c);
            if (m == NULL) {
                fprintf (stderr, "class_load: failed to allocate method: %s, %p, %s, %s.\n",
                       class_name, class_id, jmethod->method_name, jmethod->method_signature);
            } else {
                jmphash_insert (m, methods);
            }
          }
      }

      if (!requested)
          jmphash_unlock (methods);
    }
    
    if (!requested)
      jmphash_unlock (classes);
}


/** A class has been unloaded.
 * TODO evaluate what should happen to methods for this class, we
 * probably want to keep them. 
 */
static void class_unload (jobjectID class_id) {
    cls* cp = NULL;

    trace2 ("class_unload (%p)\n", class_id);
    if (down || !classes)
      return;
    c_class_unload++;
    cp = get_class (class_id);
    if (!cp) {
      fprintf (stderr, "class_unload: failed to find unloaded class: %p.\n", class_id);
    } else {
      /* It should be marked as unloaded but there
         are still interesting statistics. So it
         should not be removed! */
      /* dont remove it if we have objects that have not been GC:ed... */
      /* jmphash_remove (cp, classes);
         cls_free (cp);*/
    }
}

static cls* get_class_pointer (jint is_array, jobjectID class_id) {
    cls* cp = NULL;
    switch (is_array) {
    case JVMPI_NORMAL_OBJECT: 
      cp = get_class (class_id);
      if (!cp) {
          /* Ok, we dont know about this class yet, request it */
          get_class_load (class_id);
          cp = get_class (class_id);          
      }
      break;
    case JVMPI_CLASS:
      /* The documentation say that class_id is null always for [L... to bad.*/
      cp = LP;
      break;
    case JVMPI_BOOLEAN:
      cp = ZP;
      break;
    case JVMPI_BYTE:
      cp = BP; 
      break;
    case JVMPI_CHAR:
      cp = CP; 
      break;
    case JVMPI_SHORT:
      cp = SP; 
      break;
    case JVMPI_INT:
      cp = IP; 
      break;
    case JVMPI_LONG:
      cp = JP; 
      break;
    case JVMPI_FLOAT:
      cp = FP; 
      break;
    case JVMPI_DOUBLE:
      cp = DP; 
      break;
    } 
    return cp;
}

/** Create a timerstack for the given thread.
 */
static inline timerstack* setup_thread_local_storage (JNIEnv* thread_env_id) {
    timerstack* s;
    s = (timerstack*)jvmpi->GetThreadLocalStorage (thread_env_id);
    if (s)
      return s;

    /* Ok, since we have the GC disabled this will deadlock
     * if we try anything fancy like: 
     * jobjectID thread_id = jvmpi->GetThreadObject (env_id);
     * jvmpi->RequestEvent (JVMPI_EVENT_THREAD_START, thread_id);
     * 
     * We can not get stack frames for a thread that has not started.
     */

    /* we do not need to lock, we are the thread that handle this storage. */
    trace2 ("setup_thread_local_storage %p\n", thread_env_id);
    s = timerstack_new (100);
    if (s == NULL) 
      fprintf (stderr, "thread_start: failed to allocate thread local stoarge.\n");
    jvmpi->SetThreadLocalStorage (thread_env_id, s);
    return s;
}

/** A java object is beeing allocated...
 */
static void object_alloc (jint arena_id,
                    jobjectID class_id,
                    jint is_array,
                    jint size,
                    jobjectID obj_id,
                    jint requested,
                    JNIEnv* env) {
    cls* cp = NULL;
    obj* op = NULL;
    method* mp = NULL;      
    
    if (down || !objects)
      return;

    trace3 ("object_alloc (%p, %p)\n", class_id, obj_id);
    c_object_alloc++;

    /* First some quick manipulation of the class */
    jmphash_lock (classes);
    cp = get_class_pointer (is_array, class_id);
    if (cp) 
      cls_object_alloc (cp, size, current_gc_level);
    jmphash_unlock (classes);

    /* We dont change cp from now on,
     * only use the pointer which never changes. 
     */
    if (cp) {
      if (method_profiling) {
          methodtime* mt;
          timerstack* t = setup_thread_local_storage (env);
          if (t) {
            /* SUN's jvm seems evil. It creates a lot of
               objects before sending thread start.
               Calling getCallTrace on these will give 0
               frames back (to bad). which means that we
               wont get any method here...
            */
            /* object alloc is the owner of this thread, 
             * no possible way to have a method_(exit|entry)
             * so we do not need to lock. /robo
             * 
             * Actually we do, the thread window can be 
             * showing the thread. /robo
             */
            timerstack_lock (t);
            if (t->top > 0) {
                mt = t->times + (t->top - 1);
                if (allocs_follow_filter ())
                  mp = mt->filtered_method;
                else 
                  mp = mt->method;
                mp->allocated_objects++;
                mp->allocated_memory += size;
                mp->modified = 1;
            }
            timerstack_unlock (t);
          }
      }

      jmphash_lock (objects);
      /* we use the lock on objects to ensure that 
       * objectstore_obj_new is thread safe 
       */
      op = objectstore_obj_new (osp, arena_id, cp, is_array, size, 
                          obj_id, mp, current_reset_level, 
                          current_gc_level);
      jmphash_insert (op, objects);
      jmphash_unlock (objects);
    } else {
      fprintf (stderr, "failed to find class(%p) for object_alloc (%p)"
             "(probably java.lang.Class?)\n", class_id, obj_id);
    }
}


static void object_move (jint arena_id,
                   jobjectID obj_id, 
                   jint new_arena_id,
                   jobjectID new_obj_id) {
    /* This is executed in the thread suspended
       mode.  We should not call any malloc() etc
       in here.  The jmphash_insert()'s below are
       fine because the jmphash_remove()'s will free
       up an element in the same hash tables.
       BUT this has to be remembered iff the hash
       implementation ever changes! */
    obj* op = NULL;
    obj* hop = NULL;
    cls* cp = NULL;

    trace3 ("object_move (%p, %p)\n", obj_id, new_obj_id);
    this_gc_object_move++;
    c_object_move++;
    
    if (objects && object_profiling) {
      op = get_object (obj_id);
      if (op != NULL) {
          hop = jmphash_remove (op, objects);
          obj_set_arena_id (op, new_arena_id);
          obj_set_object_id (op, new_obj_id);
          jmphash_insert (op, objects);
      }
    }

    if (classes) {
      /* try to get class */
      cp = get_class (obj_id);
      if (cp) {
          /* have to have a good hash table since new 
           * classes will reuse the old object  id 
           */
          jmphash_remove (cp, classes);
          cls_set_class_id (cp, new_obj_id);
          jmphash_insert (cp, classes);
      }
    }    
}


static void object_free (jobjectID obj_id) {
    /* This is executed in the thread suspended
       mode.  We should not call any malloc() etc
       in here.
    */
    obj* op = NULL;
    obj* hop = NULL;
    cls* cp = NULL;
    
    trace2 ("object_free (%p)\n", obj_id);
    this_gc_object_free++;
    c_object_free++;
    if (!objects)
      return;
    
    op = get_object (obj_id);
    if (op != NULL) { /* we ignore objects that we know nothing about. */
      hop = jmphash_remove (op, objects);
      cp = get_class (obj_get_class_id (hop));
      if (cp != NULL) {
          jint os = obj_get_size (hop);
          jint ol = obj_get_gc_level (hop);
          cls_object_free (cp, os, ol);
      }
      objectstore_obj_free (osp, hop);
    }
}


static void thread_start_nolock (char* thread_name, 
                         char* group_name,
                         char* parent_name,
                         jobjectID thread_id,
                         JNIEnv* thread_env_id, 
                         jint requested) {
    jthread* t;
    c_thread_start++;

    if (!down && threads) {
      t = get_jthread (thread_env_id);
      if (t == NULL) {
          timerstack *s = setup_thread_local_storage (thread_env_id);
          t = jthread_new (thread_name, group_name, parent_name, 
                       thread_id, thread_env_id, s);
          if (t == NULL) {
            fprintf (stderr, 
                   "thread_start: failed to allocate jthread:"
                   "%s, %s, %s, %p, %p\n",
                   thread_name, group_name, parent_name, 
                   thread_id, thread_env_id);
          } else {
            jmphash_insert (t, threads);
          }
      }
    }
}


static void thread_start (char* thread_name, 
                    char* group_name,
                    char* parent_name,
                    jobjectID thread_id,
                    JNIEnv* thread_env_id, 
                    jint requested) {
    char buffer[80];
    
    snprintf (buffer,80, _("Thread %s started"), thread_name);
    set_status_lock (buffer);
    trace4 ("thread_start (%s,%s,%s,", thread_name, group_name, parent_name);
    trace3 ("%p,%p)\n", thread_id, thread_env_id);
    jmphash_lock (threads);
    thread_start_nolock (thread_name, group_name, parent_name, thread_id,
                   thread_env_id, requested);
    jmphash_unlock (threads);
}


static void thread_end (JNIEnv* env) {
    jthread* t;
    timerstack* s;

    trace2 ("thread_end (%p)\n", env);
    if (!threads)
      return;

    if (!down && threads) {
      jmphash_lock (threads);
      c_thread_end++;
      t = get_jthread (env);
      if (!t) {
          fprintf (stderr, "failed to find thread that ended: %p\n", env);
      } else {
          /* Do we really want to remove the dead threads? */
          /* No we probably only want to mark them dead and move them
           * to a dead storage, maybe free the timerstack though /robo 
           */
          jmphash_remove (t, threads);
          jthread_free (t);
      }
      
      s = (timerstack*)jvmpi->GetThreadLocalStorage (env);
      jvmpi->SetThreadLocalStorage (env, NULL);
      timerstack_free (s);
      jmphash_unlock (threads);
    }
}

/** only call this with methods locked. */
static method* get_unknown_method (jmethodID method_id) {
    /* ok, try really hard to get it... */
    /* MW: I don't think this will work! */
    /* This seems to work ok for me, it also seems to 
     * happen on NT/4 frequently (according to some bug
     * reports Ive got. To try this: simply disable 
     * class_load and class_unload events. /robo 
     */
    jobjectID class_id;
    cls* c;
    method* m;
    trace2 ("method_id %p is unknown, trying to find class...\n", 
          method_id);
    jvmpi->DisableGC ();
    class_id = jvmpi->GetMethodClass (method_id);
    get_class_load (class_id);
    c = get_class (class_id);
    m = get_method (method_id);
    trace3 ("method_id %p => %p...\n", method_id, m);
    jvmpi->EnableGC ();
    /* Got this 20040201, after a bit of running with full tracing...
    tried to get unknown method: 0xca4b9a0 => class: 0x90099e8 
        (sun.reflect.GeneratedSerializationConstructorAccessor122) => (nil)
    */
    if (m == NULL) {
      fprintf (stderr, 
             "tried to get unknown method: %p => class: %p (%s) => %p\n",
             method_id, c, (c ? cls_get_name (c) : "?"), m);
    }
    return m;
}

/** A method is beeing called. */
static void method_entry (jmethodID method_id, JNIEnv *env) {
    timerstack* s;
    jlong tval;
    method *m;

    trace3 ("method_entry (%p, %p)\n", method_id, env);
    /* Since we don't move or delete the method
       struct's, it's safe to use the pointer in
       the timerstack */
    if (down)
      return;
    jmphash_lock (methods);
    m = get_method (method_id);
    if (m == NULL)
      m = get_unknown_method (method_id);
    jmphash_unlock(methods);
    c_method_entry++;
    
    if (m) {
      s = setup_thread_local_storage (env);
      tval = get_thread_time (jvmpi);    
      jthread_method_entry (s, m, tval);
    }
    trace ("method_entry (...) done\n");
}

/** A method has exited. 
 */
static void method_exit (jmethodID method_id, JNIEnv* env) {
    timerstack* s;
    jlong tval;
    
    trace3 ("method_exit (%p, %p)\n", method_id, env);
    if (down)
      return;
    c_method_exit++;
    s = setup_thread_local_storage (env);
    tval = get_thread_time (jvmpi);
    
    jthread_method_exit (s, method_id, tval, env);
    trace ("method_exit (...) done\n");
}


static void heap_dump (int dump_level,
                   char* begin,
                   char* end,
                   jint num_traces,
                   JVMPI_CallTrace* traces) {
    trace4 ("heap dump dump_level: %d, begin: %p, end: %p, ", dump_level, begin, end);
    trace3 ("num_traces: %d, traces: %p\n", num_traces, traces);    
    switch (dump_level) {
    case JVMPI_DUMP_LEVEL_0:
      heap_dump_0 (dump_level, begin, end, num_traces, traces);
      break;
    case JVMPI_DUMP_LEVEL_1:
      heap_dump_1 (dumpdir, dump_level, begin, end, num_traces, traces);
      break;
    case JVMPI_DUMP_LEVEL_2:
      heap_dump_2 (dumpdir, dump_level, begin, end, num_traces, traces);
      break;
    }
}

typedef void (*contend_func)(timerstack*, obj*, jlong);

static void monitor_contend_handler (JNIEnv* env_id, jobjectID object, 
                             char* trace, contend_func func) {
    jlong tval;
    obj* o;
    timerstack* s;

    o = get_object (object);
    if (!o) {
      jvmpi->RequestEvent(JVMPI_EVENT_OBJECT_ALLOC, object);
      o = get_object (object);
    }
    trace4 ("%s %p, %s\n", trace, env_id, 
          cls_get_name (obj_get_class (o)));
    s = setup_thread_local_storage (env_id);
    tval = get_absolute_time (jvmpi);
    func (s, o, tval);
}

static obj* get_object_hard (jobjectID obj_id) {
    obj* o = get_object (obj_id);
    if (o == NULL) {
      get_object_alloc (obj_id);
      o = get_object (obj_id);
    }
    return o;
}

static void monitor_contended_enter (JNIEnv* env_id, jobjectID object) {
    timerstack* s = setup_thread_local_storage (env_id);
    /* Since we don't want to update ts->waiting on object_move we use the obj* instead */
    s->waiting = get_object_hard (object);
    s->timeout = 0;
    monitor_contend_handler (env_id, object, "monitor contended enter", 
                       (contend_func)jthread_contenation_enter);
}

static void monitor_contended_entered (JNIEnv* env_id, jobjectID object) {
    timerstack* s = setup_thread_local_storage (env_id);
    s->waiting = NULL;
    s->timeout = 0;
    monitor_contend_handler (env_id, object, "monitor contended entered", 
                       (contend_func)jthread_contenation_entered);
}

static inline void monitor_contended_exit (JNIEnv* env_id, jobjectID object) {
    timerstack* ts;
    trace ("monitor_contended_exit\n");
    ts = setup_thread_local_storage (env_id);
    /*
    jthread* jt = get_jthread (env_id);
    obj* o = get_object (object);
    fprintf (stderr, "JVMPI_EVENT_MONITOR_CONTENDED_EXIT   : env: %p => %s, obj: %p => %s\n", 
           env_id,jthread_get_thread_name (jt),  object, cls_get_name (obj_get_class (o)));
    */
    ts->waiting = NULL;
    ts->timeout = 0;
}

static void monitor_wait (JNIEnv* env_id, jobjectID object, jlong timeout) {
    timerstack* ts;
    trace ("monitor_wait\n");
    ts = setup_thread_local_storage (env_id);
    /*
    jthread* jt = get_jthread (env_id);
    obj* o = get_object (object);
    fprintf (stderr, "JVMPI_EVENT_MONITOR_WAIT  : env: %p => %s, obj: %p => %s, %lld\n", 
           env_id, jthread_get_thread_name (jt), object, cls_get_name (obj_get_class (o)), timeout);
    */
    /* Since we don't want to update ts->waiting on object_move we use the obj* instead */
    ts->waiting = get_object_hard (object);
    ts->timeout = timeout;    
}

static void monitor_waited (JNIEnv* env_id, jobjectID object, jlong timeout) {
    timerstack* ts;
    trace ("monitor_waited\n");
    ts = setup_thread_local_storage (env_id);
    /*
    jthread* jt = get_jthread (env_id);
    obj* o = get_object (object);
    fprintf (stderr, "JVMPI_EVENT_MONITOR_WAITED: env: %p => %s, obj: %p => %s, %lld\n", 
           env_id, jthread_get_thread_name (jt), object, cls_get_name (obj_get_class (o)), timeout);
    */
    ts->waiting = NULL;
    ts->timeout = 0;
}

/** Switch on the event_type and call the right method. 
 */
void notifyEvent (JVMPI_Event *e) {
    trace3 ("e->event_type: %d, e->env: %p: ", e->event_type, e->env_id);
    
    /** Events that are tested with the jvmsimulator are marked with "tested" 
     *  That mark means that valgrind does not find any errors.
     */
    switch(e->event_type) {
    case JVMPI_EVENT_JVM_INIT_DONE:  /* tested */
      jvm_init_done (e->env_id);
      break;
    case JVMPI_EVENT_JVM_SHUT_DOWN:  /* tested */
      jvm_shut_down ();
      break;
    case JVMPI_EVENT_GC_START:       /* tested */
      gc_start ();
      break;
    case JVMPI_EVENT_GC_FINISH:      /* tested */
      gc_finish (e->u.gc_info.used_objects, 
               e->u.gc_info.used_object_space,
               e->u.gc_info.total_object_space);            
      break;
    case JVMPI_EVENT_ARENA_NEW:      /* tested */
      jvmpi_arena_new (e->u.new_arena.arena_id, 
                   e->u.new_arena.arena_name);
      break;
    case JVMPI_EVENT_ARENA_DELETE:   /* tested */
      arena_delete (e->u.delete_arena.arena_id);
      break;
    case JVMPI_EVENT_CLASS_LOAD:     /* tested */
    case JVMPI_EVENT_CLASS_LOAD | JVMPI_REQUESTED_EVENT:
      class_load (e->env_id,
                e->u.class_load.class_name, e->u.class_load.source_name,
                e->u.class_load.num_interfaces, e->u.class_load.num_methods,
                e->u.class_load.methods, e->u.class_load.num_static_fields,
                e->u.class_load.statics, e->u.class_load.num_instance_fields, 
                e->u.class_load.instances, e->u.class_load.class_id, 
                e->event_type & JVMPI_REQUESTED_EVENT);
      break;
    case JVMPI_EVENT_CLASS_UNLOAD:   /* tested */
      class_unload (e->u.class_unload.class_id);
      break;      
    case JVMPI_EVENT_OBJECT_ALLOC:   /* tested */
    case JVMPI_EVENT_OBJECT_ALLOC | JVMPI_REQUESTED_EVENT:
      object_alloc (e->u.obj_alloc.arena_id,
                  e->u.obj_alloc.class_id,
                  e->u.obj_alloc.is_array,
                  e->u.obj_alloc.size,
                  e->u.obj_alloc.obj_id,
                  e->event_type & JVMPI_REQUESTED_EVENT,
                  e->env_id);
      break;
    case JVMPI_EVENT_OBJECT_MOVE:    /* tested */
      object_move (e->u.obj_move.arena_id, e->u.obj_move.obj_id, 
                 e->u.obj_move.new_arena_id, e->u.obj_move.new_obj_id);
      break;
    case JVMPI_EVENT_OBJECT_FREE:    /* tested */
      object_free (e->u.obj_free.obj_id); 
      break;
    case JVMPI_EVENT_THREAD_START:   /* tested */
    case JVMPI_EVENT_THREAD_START | JVMPI_REQUESTED_EVENT:
      thread_start (e->u.thread_start.thread_name, 
                  e->u.thread_start.group_name, 
                  e->u.thread_start.parent_name,
                  e->u.thread_start.thread_id, 
                  e->u.thread_start.thread_env_id,
                  e->event_type & JVMPI_REQUESTED_EVENT);
      break;
    case JVMPI_EVENT_THREAD_END:     /* tested */
      thread_end (e->env_id);
      break;
    case JVMPI_EVENT_METHOD_ENTRY:   /* tested */
      method_entry (e->u.method.method_id, e->env_id);
      break;
    case JVMPI_EVENT_METHOD_EXIT:    /* tested */
      method_exit (e->u.method.method_id, e->env_id);
      break;
    case JVMPI_EVENT_DATA_DUMP_REQUEST:   /* tested */
      run_data_dump ();
      break;
    case JVMPI_EVENT_DATA_RESET_REQUEST:  /* tested */
      data_reset ();
      break;      
    case JVMPI_EVENT_HEAP_DUMP:
    case JVMPI_EVENT_HEAP_DUMP | JVMPI_REQUESTED_EVENT:
      heap_dump (e->u.heap_dump.dump_level,
               e->u.heap_dump.begin,
               e->u.heap_dump.end,
               e->u.heap_dump.num_traces,
               e->u.heap_dump.traces);
      break;
    case JVMPI_EVENT_OBJECT_DUMP:
    case JVMPI_EVENT_OBJECT_DUMP | JVMPI_REQUESTED_EVENT:
      object_dump (e->u.object_dump.data_len,
                 e->u.object_dump.data);
      break;
    case JVMPI_EVENT_MONITOR_CONTENDED_ENTER:   /* tested */
      monitor_contended_enter (e->env_id, e->u.monitor.object);
      break;
    case JVMPI_EVENT_MONITOR_CONTENDED_ENTERED: /* tested */
      monitor_contended_entered (e->env_id, e->u.monitor.object);
      break;
    case JVMPI_EVENT_MONITOR_CONTENDED_EXIT:    /* tested */
      monitor_contended_exit (e->env_id, e->u.monitor.object);
      break;
    case JVMPI_EVENT_MONITOR_DUMP:
    case JVMPI_EVENT_MONITOR_DUMP | JVMPI_REQUESTED_EVENT:
      last_monitor_dump = monitor_dump (e->u.monitor_dump.begin,
                                e->u.monitor_dump.end,
                                e->u.monitor_dump.num_traces,
                                e->u.monitor_dump.traces,
                                e->u.monitor_dump.threads_status);
      break;
    case JVMPI_EVENT_MONITOR_WAIT:    /* tested */
      monitor_wait (e->env_id, e->u.monitor_wait.object, e->u.monitor_wait.timeout);
      break;
    case JVMPI_EVENT_MONITOR_WAITED:  /* tested */
      monitor_waited (e->env_id, e->u.monitor_wait.object, e->u.monitor_wait.timeout);
      break;

    case JVMPI_EVENT_RAW_MONITOR_CONTENDED_ENTER:
      /*
      if (e->u.raw_monitor.id != jmphash_get_monitor (methods)) 
          fprintf (stderr, "raw_monitor_contended_enter  :%p, %p => %s\n", 
                 e->env_id, e->u.raw_monitor.id, e->u.raw_monitor.name);
      */
      break;
    case JVMPI_EVENT_RAW_MONITOR_CONTENDED_ENTERED:
      /*
      if (e->u.raw_monitor.id != jmphash_get_monitor (methods)) 
          fprintf (stderr, "raw_monitor_contended_entered :d%p, %p => %s\n", 
                 e->env_id, e->u.raw_monitor.id, e->u.raw_monitor.name);
      */
      break;
    case JVMPI_EVENT_RAW_MONITOR_CONTENDED_EXIT:
      /*
      if (e->u.raw_monitor.id != jmphash_get_monitor (methods)) 
          fprintf (stderr, "raw_monitor_contended_exit   :%p, %p => %s\n", 
                 e->env_id, e->u.raw_monitor.id, e->u.raw_monitor.name);
      */
      break;
    default:
      trace ("unhandled.\n");
    }
}

static void display_help () {
    fprintf (stdout, "java -Xrunjmp[:[options]] package.Class\n");
    fprintf (stdout, "options is a comma separated list and may include:\n");
    fprintf (stdout, "help      - to show this text.\n");
    fprintf (stdout, "nomethods - to disable method profiling.\n");
    fprintf (stdout, "noobjects - to disable object profiling.\n");
    fprintf (stdout, "nomonitors - to disable monitor profiling.\n");
    fprintf (stdout, "allocfollowsfilter - to group object allocations into filtered methods.\n");    
    fprintf (stdout, "nogui     - to run jmp without the user interface.\n");
    fprintf (stdout, "dodump    - to allow to be called with signals.\n");
    fprintf (stdout, "dumpdir=<directoryr> - to specify where the dump-/heapdumpfiles go.\n");
    fprintf (stdout, "dumptimer=<n> - to specify automatic dump every n:th second.\n");
    fprintf (stdout, "filter=<somefilter> - to specify an initial recursive filter.\n");
    fprintf (stdout, "threadtime - to specify that timing of methods and monitors \n"
           "           should use thread cpu time instead of absolute time.\n"); 
    fprintf (stdout, "simulator - to specify that jmp should not perform any jni tricks.\n"
           "           probably only useful if you debug jmp.\n");
    fprintf (stdout, "\nAn example may look like this:\n"); 
    fprintf (stdout, "java -Xrunjmp:nomethods,dumpdir=/tmp/jmpdump/ rabbit.proxy.Proxy\n"); 
}

static size_t get_size_of_option (char* option) {
    size_t t;
    char* n;
    n = strstr (option, ",");
    if (n != NULL) 
      t = n - option;
    else 
      t = strlen (option);
    return t;
}

static char* setup_filter (char* m) {
    char* filter;
    char* typeEnd;
    char* mem;
    static char *filterTypeStr[] = {"class", "package", 
                            "recursive", "all"};
    int filterMode = FILTER_INCLUDE;
    int filterType = FILTER_MATCH_RECURSIVE;

    size_t t = get_size_of_option (m + 7);
    mem = malloc (t + 1);
    filter = mem;
    strncpy (filter, m + 7, t);
    filter[t] = '\0';

    typeEnd = strchr (filter, ':');
    if (typeEnd != NULL && typeEnd <= filter + t) {
      int i;
      char* typestr = filter;

      *typeEnd = '\0';
      filter = typeEnd + 1;
      if (typestr[0] == '+') {
          filterMode = FILTER_INCLUDE;
          typestr++;
      } else if (typestr[0] == '-') {
          filterMode = FILTER_EXCLUDE;
          typestr++;
      }
      filterType = -1;
      for (i=0; i < sizeof (filterTypeStr) / sizeof (char*); i++) {
          if (strcmp (typestr, filterTypeStr[i]) == 0) {
            filterType = i;
          }
      }

      if (filterType == -1) {
          fprintf (stdout, 
                 "strange filter type (%s) value specified, ignored\n",
                 typestr);
      }
    }

    if (filterType != -1) {
      fprintf (stdout, 
             "    adding %s filter for %s with matching mode %s\n", 
             (filterMode == FILTER_INCLUDE ? 
              "inclusive" : "exclusive"),
             filter, filterTypeStr[filterType]);                              
      filter_add_filter (filterMode, filter, filterType);
    }
    free (mem);
    return m + 7 + t + 1;
}

static void parse_options (char* options) {
    char* m = NULL;
    char* co;

    /** Parse the options we got. */
    if (options != NULL) {
      m = strstr (options, "help");
      if (m != NULL) {
          fprintf (stdout, "help wanted..\n");
          display_help ();
          exit (0);
      }

      m = strstr (options, "nomethods");
      method_profiling = (m == NULL);
      
      m = strstr (options, "noobjects");
      object_profiling = (m == NULL);
      
      m = strstr (options, "nomonitors");
      monitor_profiling = (m == NULL);

      m = strstr (options, "nogui");
      doUI = (m == NULL);
      
      m = strstr (options, "dodump");
      dump_enabled = (m != NULL);

      m = strstr (options, "simulator");
      simulator = (m != NULL);
      
      m = strstr (options, "dumpdir=");
      if (m != NULL) {
          size_t t = get_size_of_option (m + 8);          
          dumpdir = malloc (t + 1);
          strncpy (dumpdir, m + 8, t);
          dumpdir[t] = '\0';
      }
      
      m = strstr (options, "dumptimer=");
      if (m != NULL) {
          char *e;
          size_t t = get_size_of_option (m + 10); 
          long l = strtol (m + 10, &e, 0);
          if (e != m + 10 + t) {
            fprintf (stdout, "strange dumptimer (%ld) value specified, ignored: %p, %p...\n", 
                   l, (m + 10), (m + 10 + t));
          } else {
             set_dump_timer (l);
          }       
      }

      m = strstr (options, "allocfollowsfilter");
      if (m != NULL) {
          alloc_follow_filter = 1;
      }
      
      co = options;
      while (co) {
          m = strstr (co, "filter=");
          if (m != NULL) {
            co = setup_filter (m);
          } else {
            co = NULL;
          }
      }
      
      absolute_times = strstr (options, "threadtime") == NULL;
    } else {
      /* to be consistent with above... */
      absolute_times = 1;
    }
}

static void enable_gc_events () {
    jvmpi->EnableEvent (JVMPI_EVENT_GC_START, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_GC_FINISH, NULL);
}

static void enable_arena_events () {
    jvmpi->EnableEvent (JVMPI_EVENT_ARENA_NEW, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_ARENA_DELETE, NULL);
}

static void enable_class_events () {
    class_profiling = 1;
    jvmpi->EnableEvent (JVMPI_EVENT_CLASS_LOAD, NULL); 
    jvmpi->EnableEvent (JVMPI_EVENT_CLASS_UNLOAD, NULL);
    /* classes are moved so we need this here.. */
    jvmpi->EnableEvent (JVMPI_EVENT_OBJECT_MOVE, NULL);
}

static void ensure_class_profiling () {
    if (class_profiling == 0)
      enable_class_events ();
}

void enable_object_events () {
    ensure_class_profiling ();
    object_profiling = 1;
    /* Enable object tracing events. */
    jvmpi->EnableEvent (JVMPI_EVENT_OBJECT_ALLOC, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_OBJECT_FREE, NULL);
}

static void enable_thread_events () {
    jvmpi->EnableEvent (JVMPI_EVENT_THREAD_START, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_THREAD_END, NULL);
}

void get_call_trace (jthread* t) {
    JNIEnv* tid = jthread_get_env_id (t);
    get_call_trace_env (tid);
}

void get_call_trace_env (JNIEnv* tid) {
    int i;
    timerstack* ts;
    JVMPI_CallTrace ct;
    JVMPI_CallFrame* cf;
    jint depth = 10;
    cf = malloc (sizeof (*cf) * depth);
    /* can only get calltrace for suspended threads.. */

    /* Currently there seems to be a bug(?) in the SUN jdk, it will
     * always return 0 call frames for threads that are in sleep ()
     * This is not a big problem, will give us one stack underflow 
     * later on which will make us get the stack from that thread
     * and that works.
     * reported and got an internal review ID of: 179429, 
     * probably not visible yet /robo
     * External id should now be: 4860655.
     */
    jvmpi->SuspendThread (tid);
    ts = get_timerstack (tid);
    if (ts) {
      jlong tval;
      timerstack_lock (ts);
      tval = get_thread_time (jvmpi);
      ct.frames = cf;
      ct.env_id = tid;
      ts->top = 0;
      ts->last_contentation = -1;
      jvmpi->GetCallTrace (&ct, depth);
      trace3 ("got: %d methods for %p...\n", ct.num_frames, tid); 
      for (i = ct.num_frames - 1; i >= 0; i--) {
          method* m;
          JVMPI_CallFrame* f = ct.frames + i;
          m = get_method (f->method_id);
          if (m == NULL) 
            m = get_unknown_method (f->method_id);
          if (m) {
            /** We should always have m since we always trace classes.
             *  In case we change our mind in the future /robo
             */
            jthread_method_entry (ts, m, tval);
          }
      }
      timerstack_unlock (ts);
    }
    jvmpi->ResumeThread (tid);
    free (cf);
}

static void enable_method_events_and_stacks (int get_stacks) {
    ensure_class_profiling ();

    /* Ok, the thread stacks are bogous, clear them all and try to get 
     * real stacks, timing will be a bit bogous, but not to much...
     */
    
    if (get_stacks) {
      int locks;
      jmphash_lock (threads);
      jmphash_lock (methods);
      locks = timerstacks_get_need_locks ();
      timerstacks_set_need_locks (1);

      /*
        WARNING! due to a bug inside SUN's jvm
        method profiling may cause the jvm to crash see:
        http://developer.java.sun.com/developer/bugParade/bugs/4652208.html
        for more info
      */

      /* Enable method profiling events under lock, so we 
       * don't get odd method entries. 
       */
      jvmpi->EnableEvent (JVMPI_EVENT_METHOD_ENTRY, NULL);
      jvmpi->EnableEvent (JVMPI_EVENT_METHOD_EXIT, NULL);
      
      jvmpi->DisableGC ();
      jmphash_for_each ((jmphash_iter_f)get_call_trace, threads);
      jvmpi->EnableGC ();
      timerstacks_set_need_locks (locks);
      jmphash_unlock (methods);
      jmphash_unlock (threads);
    } else {
      jvmpi->EnableEvent (JVMPI_EVENT_METHOD_ENTRY, NULL);
      jvmpi->EnableEvent (JVMPI_EVENT_METHOD_EXIT, NULL);
    }
    method_profiling = 1;
}

void enable_method_events () {
    enable_method_events_and_stacks (1);
}

static void enable_dump_events () {
    dump_enabled = 1;
    jvmpi->EnableEvent (JVMPI_EVENT_DATA_DUMP_REQUEST, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_DATA_RESET_REQUEST, NULL);
}

void enable_monitor_events () {
    jvmpi->EnableEvent (JVMPI_EVENT_MONITOR_CONTENDED_ENTER, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_MONITOR_CONTENDED_EXIT , NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_MONITOR_WAIT, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_MONITOR_WAITED , NULL);
    
    jvmpi->EnableEvent (JVMPI_EVENT_RAW_MONITOR_CONTENDED_ENTER, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_RAW_MONITOR_CONTENDED_ENTERED, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_RAW_MONITOR_CONTENDED_EXIT, NULL);
}

static void enable_events () {
    /* Enable standard events */
    jvmpi->EnableEvent (JVMPI_EVENT_JVM_INIT_DONE, NULL);
    jvmpi->EnableEvent (JVMPI_EVENT_JVM_SHUT_DOWN, NULL);    
    enable_gc_events ();
    enable_arena_events ();
    /* we dont have to enable class events yet, object 
     * and/or method profiling will do that for us.
     * enable_class_events ();
     */
    if (object_profiling)
      enable_object_events ();
    enable_thread_events ();
    if (method_profiling)     
      enable_method_events_and_stacks (0);
    if (dump_enabled)
      enable_dump_events ();
    if (monitor_profiling)
      enable_monitor_events ();
}

/** This method is the one that is called by the jvm on startup. 
 */
JNIEXPORT jint JNICALL JVM_OnLoad (JavaVM *jvm, char *options, void *reserved) {
#ifndef VERSION
#define VERSION "unknown"
#endif
    fprintf (stdout, "jmp/%s initializing: (%s):...\n", VERSION, (options ? options : ""));
    parse_options (options);
    fprintf (stdout, "    tracing objects: %s\n", (object_profiling ? "true" : "false"));
    fprintf (stdout, "    tracing methods: %s\n", (method_profiling ? "true" : "false"));
    fprintf (stdout, "    tracing monitors: %s\n", (monitor_profiling ? "true" : "false"));
    fprintf (stdout, "    showing gui: %s\n", (doUI ? "true" : "false"));
    fprintf (stdout, "    dump/reset by signal allowed: %s\n", (dump_enabled ? "true" : "false"));

    /* get jvmpi interface pointer */
    if (((*jvm)->GetEnv(jvm, (void **)&jvmpi, JVMPI_VERSION_1)) < 0) {
      fprintf (stderr, "jmp: error in obtaining jvmpi interface pointer\n");
      return JNI_ERR;
    }

    jvmpi->NotifyEvent = notifyEvent;   

    if (setup ())
      return JNI_ERR;
    
    fprintf (stdout, "jmp: Enabling localization.\n");
    /* Enable internationalization */
#ifdef LC_ALL    
    setlocale (LC_ALL, "");
    bindtextdomain (PACKAGE, LOCALEDIR);
    bind_textdomain_codeset (PACKAGE,"UTF-8");
    textdomain (PACKAGE);
#endif
    fprintf (stdout, _("jmp: Loaded and registered correctly.\n"));

    /* initialize jvmpi interface */
    enable_events ();

    /* initialize the ui */
    init_ui ();

    return JNI_OK;    
}

/* Emacs Local Variables: */
/* Emacs mode:C */
/* Emacs c-indentation-style:"gnu" */
/* Emacs c-hanging-braces-alist:((brace-list-open)(brace-entry-open)(defun-open after)(substatement-open after)(block-close . c-snug-do-while)(extern-lang-open after)) */
/* Emacs c-cleanup-list:(brace-else-brace brace-elseif-brace space-before-funcall) */
/* Emacs c-basic-offset:4 */
/* Emacs End: */

Generated by  Doxygen 1.6.0   Back to index