/* * @(#)hprof_init.c 1.92 05/12/06 * * Copyright (c) 2006 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. */ /* Main source file, the basic JVMTI connection/startup code. */ #include "hprof.h" #include "java_crw_demo.h" /* * This file contains all the startup logic (Agent_Onload) and * connection to the JVMTI interface. * All JVMTI Event callbacks are in this file. * All setting of global data (gdata) is done here. * Options are parsed here. * Option help messages are here. * Termination handled here (VM_DEATH) and shutdown (Agent_OnUnload). * Spawning of the cpu sample loop thread and listener thread is done here. * * Use of private 'static' data has been limited, most shared static data * should be found in the GlobalData structure pointed to by gdata * (see hprof.h). * */ /* The default output filenames. */ #define DEFAULT_TXT_SUFFIX ".txt" #define DEFAULT_OUTPUTFILE "java.hprof" #define DEFAULT_OUTPUTTEMP "java.hprof.temp" /* The only global variable, defined by this library */ GlobalData *gdata; /* Experimental options */ #define EXPERIMENT_NO_EARLY_HOOK 0x1 /* Default trace depth */ #define DEFAULT_TRACE_DEPTH 4 /* Default sample interval */ #define DEFAULT_SAMPLE_INTERVAL 10 /* Default cutoff */ #define DEFAULT_CUTOFF_POINT 0.0001 /* Stringize macros for help. */ #define _TO_STR(a) #a #define TO_STR(a) _TO_STR(a) /* Macros to surround callback code (non-VM_DEATH callbacks). * Note that this just keeps a count of the non-VM_DEATH callbacks that * are currently active, it does not prevent these callbacks from * operating in parallel. It's the VM_DEATH callback that will wait * for all these callbacks to either complete and block, or just block. * We need to hold back these threads so they don't die during the final * VM_DEATH processing. * If the VM_DEATH callback is active in the beginning, then this callback * just blocks to prevent further execution of the thread. * If the VM_DEATH callback is active at the end, then this callback * will notify the VM_DEATH callback if it's the last one. * In all cases, the last thing they do is Enter/Exit the monitor * gdata->callbackBlock, which will block this callback if VM_DEATH * is running. * * WARNING: No not 'return' or 'goto' out of the BEGIN_CALLBACK/END_CALLBACK * block, this will mess up the count. */ #define BEGIN_CALLBACK() \ { /* BEGIN OF CALLBACK */ \ jboolean bypass; \ rawMonitorEnter(gdata->callbackLock); \ if (gdata->vm_death_callback_active) { \ /* VM_DEATH is active, we will bypass the CALLBACK CODE */ \ bypass = JNI_TRUE; \ rawMonitorExit(gdata->callbackLock); \ /* Bypassed CALLBACKS block here until VM_DEATH done */ \ rawMonitorEnter(gdata->callbackBlock); \ rawMonitorExit(gdata->callbackBlock); \ } else { \ /* We will be executing the CALLBACK CODE in this case */ \ gdata->active_callbacks++; \ bypass = JNI_FALSE; \ rawMonitorExit(gdata->callbackLock); \ } \ if ( !bypass ) { \ /* BODY OF CALLBACK CODE (with no callback locks held) */ #define END_CALLBACK() /* Part of bypass if body */ \ rawMonitorEnter(gdata->callbackLock); \ gdata->active_callbacks--; \ /* If VM_DEATH is active, and last one, send notify. */ \ if (gdata->vm_death_callback_active) { \ if (gdata->active_callbacks == 0) { \ rawMonitorNotifyAll(gdata->callbackLock); \ } \ } \ rawMonitorExit(gdata->callbackLock); \ /* Non-Bypassed CALLBACKS block here until VM_DEATH done */ \ rawMonitorEnter(gdata->callbackBlock); \ rawMonitorExit(gdata->callbackBlock); \ } \ } /* END OF CALLBACK */ /* Forward declarations */ static void set_callbacks(jboolean on); /* ------------------------------------------------------------------- */ /* Global data initialization */ /* Get initialized global data area */ static GlobalData * get_gdata(void) { static GlobalData data; /* Create initial default values */ (void)memset(&data, 0, sizeof(GlobalData)); data.fd = -1; /* Non-zero file or socket. */ data.heap_fd = -1; /* For heap=dump, see hprof_io */ data.check_fd = -1; /* For heap=dump, see hprof_io */ data.max_trace_depth = DEFAULT_TRACE_DEPTH; data.prof_trace_depth = DEFAULT_TRACE_DEPTH; data.sample_interval = DEFAULT_SAMPLE_INTERVAL; data.lineno_in_traces = JNI_TRUE; data.output_format = 'a'; /* 'b' for binary */ data.cutoff_point = DEFAULT_CUTOFF_POINT; data.dump_on_exit = JNI_TRUE; data.gc_start_time = -1L; #ifdef DEBUG data.debug = JNI_TRUE; data.coredump = JNI_TRUE; #endif data.micro_state_accounting = JNI_FALSE; data.force_output = JNI_TRUE; data.verbose = JNI_TRUE; data.primfields = JNI_TRUE; data.primarrays = JNI_TRUE; data.table_serial_number_start = 1; data.class_serial_number_start = 100000; data.thread_serial_number_start = 200000; data.trace_serial_number_start = 300000; data.object_serial_number_start = 400000; data.frame_serial_number_start = 500000; data.gref_serial_number_start = 1; data.table_serial_number_counter = data.table_serial_number_start; data.class_serial_number_counter = data.class_serial_number_start; data.thread_serial_number_counter = data.thread_serial_number_start; data.trace_serial_number_counter = data.trace_serial_number_start; data.object_serial_number_counter = data.object_serial_number_start; data.frame_serial_number_counter = data.frame_serial_number_start; data.gref_serial_number_counter = data.gref_serial_number_start; data.unknown_thread_serial_num = data.thread_serial_number_counter++; return &data; } /* ------------------------------------------------------------------- */ /* Error handler callback for the java_crw_demo (classfile read write) functions. */ static void my_crw_fatal_error_handler(const char * msg, const char *file, int line) { char errmsg[256]; (void)md_snprintf(errmsg, sizeof(errmsg), "%s [%s:%d]", msg, file, line); errmsg[sizeof(errmsg)-1] = 0; HPROF_ERROR(JNI_TRUE, errmsg); } static void list_all_tables(void) { string_list(); class_list(); frame_list(); site_list(); object_list(); trace_list(); monitor_list(); tls_list(); loader_list(); } /* ------------------------------------------------------------------- */ /* Option Parsing support */ /** * Socket connection */ /* * Return a socket connect()ed to a "hostname" that is * accept()ing heap profile data on "port." Return a value <= 0 if * such a connection can't be made. */ static int connect_to_socket(char *hostname, unsigned short port) { int fd; if (port == 0 || port > 65535) { HPROF_ERROR(JNI_FALSE, "invalid port number"); return -1; } if (hostname == NULL) { HPROF_ERROR(JNI_FALSE, "hostname is NULL"); return -1; } /* create a socket */ fd = md_connect(hostname, port); return fd; } /* Accept a filename, and adjust the name so that it is unique for this PID */ static void make_unique_filename(char **filename) { int fd; /* Find a file that doesn't exist */ fd = md_open(*filename); if ( fd >= 0 ) { int pid; char *new_name; char *old_name; char *prefix; char suffix[5]; int new_len; /* Close the file. */ md_close(fd); /* Make filename name.PID[.txt] */ pid = md_getpid(); old_name = *filename; new_len = (int)strlen(old_name)+64; new_name = HPROF_MALLOC(new_len); prefix = old_name; suffix[0] = 0; /* Look for .txt suffix if not binary output */ if (gdata->output_format != 'b') { char *dot; char *format_suffix; format_suffix = DEFAULT_TXT_SUFFIX; (void)strcpy(suffix, format_suffix); dot = strrchr(old_name, '.'); if ( dot != NULL ) { int i; int slen; int match; slen = (int)strlen(format_suffix); match = 1; for ( i = 0; i < slen; i++ ) { if ( dot[i]==0 || tolower(format_suffix[i]) != tolower(dot[i]) ) { match = 0; break; } } if ( match ) { (void)strcpy(suffix, dot); *dot = 0; /* truncates prefix and old_name */ } } } /* Construct the name */ (void)md_snprintf(new_name, new_len, "%s.%d%s", prefix, pid, suffix); *filename = new_name; HPROF_FREE(old_name); /* Odds are with Windows, this file may not be so unique. */ (void)remove(gdata->output_filename); } } static int get_tok(char **src, char *buf, int buflen, int sep) { int len; char *p; buf[0] = 0; if ( **src == 0 ) { return 0; } p = strchr(*src, sep); if ( p==NULL ) { len = (int)strlen(*src); p = (*src) + len; } else { /*LINTED*/ len = (int)(p - (*src)); } if ( (len+1) > buflen ) { return 0; } (void)memcpy(buf, *src, len); buf[len] = 0; if ( *p != 0 && *p == sep ) { (*src) = p+1; } else { (*src) = p; } return len; } static jboolean setBinarySwitch(char **src, jboolean *ptr) { char buf[80]; if (!get_tok(src, buf, (int)sizeof(buf), ',')) { return JNI_FALSE; } if (strcmp(buf, "y") == 0) { *ptr = JNI_TRUE; } else if (strcmp(buf, "n") == 0) { *ptr = JNI_FALSE; } else { return JNI_FALSE; } return JNI_TRUE; } static void print_usage(void) { (void)fprintf(stdout, "\n" " HPROF: Heap and CPU Profiling Agent (JVMTI Demonstration Code)\n" "\n" AGENTNAME " usage: java " AGENTLIB "=[help]|[<option>=<value>, ...]\n" "\n" "Option Name and Value Description Default\n" "--------------------- ----------- -------\n" "heap=dump|sites|all heap profiling all\n" "cpu=samples|times|old CPU usage off\n" "monitor=y|n monitor contention n\n" "format=a|b text(txt) or binary output a\n" "file=<file> write data to file " DEFAULT_OUTPUTFILE "[{" DEFAULT_TXT_SUFFIX "}]\n" "net=<host>:<port> send data over a socket off\n" "depth=<size> stack trace depth " TO_STR(DEFAULT_TRACE_DEPTH) "\n" "interval=<ms> sample interval in ms " TO_STR(DEFAULT_SAMPLE_INTERVAL) "\n" "cutoff=<value> output cutoff point " TO_STR(DEFAULT_CUTOFF_POINT) "\n" "lineno=y|n line number in traces? y\n" "thread=y|n thread in traces? n\n" "doe=y|n dump on exit? y\n" "msa=y|n Solaris micro state accounting n\n" "force=y|n force output to <file> y\n" "verbose=y|n print messages about dumps y\n" "\n" "Obsolete Options\n" "----------------\n" "gc_okay=y|n\n" #ifdef DEBUG "\n" "DEBUG Option Description Default\n" "------------ ----------- -------\n" "primfields=y|n include primitive field values y\n" "primarrays=y|n include primitive array values y\n" "debugflags=MASK Various debug flags 0\n" " 0x01 Report refs in and of unprepared classes\n" "logflags=MASK Logging to stderr 0\n" " " TO_STR(LOG_DUMP_MISC) " Misc logging\n" " " TO_STR(LOG_DUMP_LISTS) " Dump out the tables\n" " " TO_STR(LOG_CHECK_BINARY) " Verify & dump format=b\n" "coredump=y|n Core dump on fatal n\n" "errorexit=y|n Exit on any error n\n" "pause=y|n Pause on onload & echo PID n\n" "debug=y|n Turn on all debug checking n\n" "X=MASK Internal use only 0\n" "\n" "Environment Variables\n" "---------------------\n" "_JAVA_HPROF_OPTIONS\n" " Options can be added externally via this environment variable.\n" " Anything contained in it will get a comma prepended to it (if needed),\n" " then it will be added to the end of the options supplied via the\n" " " XRUN " or " AGENTLIB " command line option.\n" #endif "\n" "Examples\n" "--------\n" " - Get sample cpu information every 20 millisec, with a stack depth of 3:\n" " java " AGENTLIB "=cpu=samples,interval=20,depth=3 classname\n" " - Get heap usage information based on the allocation sites:\n" " java " AGENTLIB "=heap=sites classname\n" #ifdef DEBUG " - Using the external option addition with csh, log details on all runs:\n" " setenv _JAVA_HPROF_OPTIONS \"logflags=0xC\"\n" " java " AGENTLIB "=cpu=samples classname\n" " is the same as:\n" " java " AGENTLIB "=cpu=samples,logflags=0xC classname\n" #endif "\n" "Notes\n" "-----\n" " - The option format=b cannot be used with monitor=y.\n" " - The option format=b cannot be used with cpu=old|times.\n" " - Use of the " XRUN " interface can still be used, e.g.\n" " java " XRUN ":[help]|[<option>=<value>, ...]\n" " will behave exactly the same as:\n" " java " AGENTLIB "=[help]|[<option>=<value>, ...]\n" #ifdef DEBUG " - The debug options and environment variables are available with both java\n" " and java_g versions.\n" #endif "\n" "Warnings\n" "--------\n" " - This is demonstration code for the JVMTI interface and use of BCI,\n" " it is not an official product or formal part of the JDK.\n" " - The " XRUN " interface will be removed in a future release.\n" " - The option format=b is considered experimental, this format may change\n" " in a future release.\n" #ifdef DEBUG " - The obsolete options may be completely removed in a future release.\n" " - The debug options and environment variables are not considered public\n" " interfaces and can change or be removed with any type of update of\n" " " AGENTNAME ", including patches.\n" #endif ); } static void option_error(char *description) { char errmsg[FILENAME_MAX+80]; (void)md_snprintf(errmsg, sizeof(errmsg), "%s option error: %s (%s)", AGENTNAME, description, gdata->options); errmsg[sizeof(errmsg)-1] = 0; HPROF_ERROR(JNI_FALSE, errmsg); error_exit_process(1); } static void parse_options(char *command_line_options) { int file_or_net_option_seen = JNI_FALSE; char *all_options; char *extra_options; char *options; char *default_filename; int ulen; if (command_line_options == 0) command_line_options = ""; if ((strcmp(command_line_options, "help")) == 0) { print_usage(); error_exit_process(0); } extra_options = getenv("_JAVA_HPROF_OPTIONS"); if ( extra_options == NULL ) { extra_options = ""; } all_options = HPROF_MALLOC((int)strlen(command_line_options) + (int)strlen(extra_options) + 2); gdata->options = all_options; (void)strcpy(all_options, command_line_options); if ( extra_options[0] != 0 ) { if ( all_options[0] != 0 ) { (void)strcat(all_options, ","); } (void)strcat(all_options, extra_options); } options = all_options; LOG2("parse_options()", all_options); while (*options) { char option[16]; char suboption[FILENAME_MAX+1]; char *endptr; if (!get_tok(&options, option, (int)sizeof(option), '=')) { option_error("general syntax error parsing options"); } if (strcmp(option, "file") == 0) { if ( file_or_net_option_seen ) { option_error("file or net options should only appear once"); } if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) { option_error("syntax error parsing file=filename"); } gdata->utf8_output_filename = HPROF_MALLOC((int)strlen(suboption)+1); (void)strcpy(gdata->utf8_output_filename, suboption); file_or_net_option_seen = JNI_TRUE; } else if (strcmp(option, "net") == 0) { char port_number[16]; if (file_or_net_option_seen ) { option_error("file or net options should only appear once"); } if (!get_tok(&options, suboption, (int)sizeof(suboption), ':')) { option_error("net option missing ':'"); } if (!get_tok(&options, port_number, (int)sizeof(port_number), ',')) { option_error("net option missing port"); } gdata->net_hostname = HPROF_MALLOC((int)strlen(suboption)+1); (void)strcpy(gdata->net_hostname, suboption); gdata->net_port = (int)strtol(port_number, NULL, 10); file_or_net_option_seen = JNI_TRUE; } else if (strcmp(option, "format") == 0) { if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) { option_error("syntax error parsing format=a|b"); } if (strcmp(suboption, "a") == 0) { gdata->output_format = 'a'; } else if (strcmp(suboption, "b") == 0) { gdata->output_format = 'b'; } else { option_error("format option value must be a|b"); } } else if (strcmp(option, "depth") == 0) { if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) { option_error("syntax error parsing depth=DECIMAL"); } gdata->max_trace_depth = (int)strtol(suboption, &endptr, 10); if ((endptr != NULL && *endptr != 0) || gdata->max_trace_depth < 0) { option_error("depth option value must be decimal and >= 0"); } gdata->prof_trace_depth = gdata->max_trace_depth; } else if (strcmp(option, "interval") == 0) { if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) { option_error("syntax error parsing interval=DECIMAL"); } gdata->sample_interval = (int)strtol(suboption, &endptr, 10); if ((endptr != NULL && *endptr != 0) || gdata->sample_interval <= 0) { option_error("interval option value must be decimal and > 0"); } } else if (strcmp(option, "cutoff") == 0) { if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) { option_error("syntax error parsing cutoff=DOUBLE"); } gdata->cutoff_point = strtod(suboption, &endptr); if ((endptr != NULL && *endptr != 0) || gdata->cutoff_point < 0) { option_error("cutoff option value must be floating point and >= 0"); } } else if (strcmp(option, "cpu") == 0) { if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) { option_error("syntax error parsing cpu=y|samples|times|old"); } if ((strcmp(suboption, "samples") == 0) || (strcmp(suboption, "y") == 0)) { gdata->cpu_sampling = JNI_TRUE; } else if (strcmp(suboption, "times") == 0) { gdata->cpu_timing = JNI_TRUE; gdata->old_timing_format = JNI_FALSE; } else if (strcmp(suboption, "old") == 0) { gdata->cpu_timing = JNI_TRUE; gdata->old_timing_format = JNI_TRUE; } else { option_error("cpu option value must be y|samples|times|old"); } } else if (strcmp(option, "heap") == 0) { if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) { option_error("syntax error parsing heap=dump|sites|all"); } if (strcmp(suboption, "dump") == 0) { gdata->heap_dump = JNI_TRUE; } else if (strcmp(suboption, "sites") == 0) { gdata->alloc_sites = JNI_TRUE; } else if (strcmp(suboption, "all") == 0) { gdata->heap_dump = JNI_TRUE; gdata->alloc_sites = JNI_TRUE; } else { option_error("heap option value must be dump|sites|all"); } } else if( strcmp(option,"lineno") == 0) { if ( !setBinarySwitch(&options, &(gdata->lineno_in_traces)) ) { option_error("lineno option value must be y|n"); } } else if( strcmp(option,"thread") == 0) { if ( !setBinarySwitch(&options, &(gdata->thread_in_traces)) ) { option_error("thread option value must be y|n"); } } else if( strcmp(option,"doe") == 0) { if ( !setBinarySwitch(&options, &(gdata->dump_on_exit)) ) { option_error("doe option value must be y|n"); } } else if( strcmp(option,"msa") == 0) { if ( !setBinarySwitch(&options, &(gdata->micro_state_accounting)) ) { option_error("msa option value must be y|n"); } } else if( strcmp(option,"force") == 0) { if ( !setBinarySwitch(&options, &(gdata->force_output)) ) { option_error("force option value must be y|n"); } } else if( strcmp(option,"verbose") == 0) { if ( !setBinarySwitch(&options, &(gdata->verbose)) ) { option_error("verbose option value must be y|n"); } } else if( strcmp(option,"primfields") == 0) { if ( !setBinarySwitch(&options, &(gdata->primfields)) ) { option_error("primfields option value must be y|n"); } } else if( strcmp(option,"primarrays") == 0) { if ( !setBinarySwitch(&options, &(gdata->primarrays)) ) { option_error("primarrays option value must be y|n"); } } else if( strcmp(option,"monitor") == 0) { if ( !setBinarySwitch(&options, &(gdata->monitor_tracing)) ) { option_error("monitor option value must be y|n"); } } else if( strcmp(option,"gc_okay") == 0) { if ( !setBinarySwitch(&options, &(gdata->gc_okay)) ) { option_error("gc_okay option value must be y|n"); } } else if (strcmp(option, "logflags") == 0) { if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) { option_error("logflags option value must be numeric"); } gdata->logflags = (int)strtol(suboption, NULL, 0); } else if (strcmp(option, "debugflags") == 0) { if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) { option_error("debugflags option value must be numeric"); } gdata->debugflags = (int)strtol(suboption, NULL, 0); } else if (strcmp(option, "coredump") == 0) { if ( !setBinarySwitch(&options, &(gdata->coredump)) ) { option_error("coredump option value must be y|n"); } } else if (strcmp(option, "exitpause") == 0) { option_error("The exitpause option was removed, use -XX:OnError='cmd %%p'"); } else if (strcmp(option, "errorexit") == 0) { if ( !setBinarySwitch(&options, &(gdata->errorexit)) ) { option_error("errorexit option value must be y|n"); } } else if (strcmp(option, "pause") == 0) { if ( !setBinarySwitch(&options, &(gdata->pause)) ) { option_error("pause option value must be y|n"); } } else if (strcmp(option, "debug") == 0) { if ( !setBinarySwitch(&options, &(gdata->debug)) ) { option_error("debug option value must be y|n"); } } else if (strcmp(option, "precrash") == 0) { option_error("The precrash option was removed, use -XX:OnError='precrash -p %%p'"); } else if (strcmp(option, "X") == 0) { if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) { option_error("X option value must be numeric"); } gdata->experiment = (int)strtol(suboption, NULL, 0); } else { char errmsg[80]; (void)strcpy(errmsg, "Unknown option: "); (void)strcat(errmsg, option); option_error(errmsg); } } if (gdata->output_format == 'b') { if (gdata->cpu_timing) { option_error("cpu=times|old is not supported with format=b"); } if (gdata->monitor_tracing) { option_error("monitor=y is not supported with format=b"); } } if (gdata->old_timing_format) { gdata->prof_trace_depth = 2; } if (gdata->output_format == 'b') { default_filename = DEFAULT_OUTPUTFILE; } else { default_filename = DEFAULT_OUTPUTFILE DEFAULT_TXT_SUFFIX; } if (!file_or_net_option_seen) { gdata->utf8_output_filename = HPROF_MALLOC((int)strlen(default_filename)+1); (void)strcpy(gdata->utf8_output_filename, default_filename); } if ( gdata->utf8_output_filename != NULL ) { /* UTF-8 to platform encoding (fill in gdata->output_filename) */ ulen = (int)strlen(gdata->utf8_output_filename); gdata->output_filename = (char*)HPROF_MALLOC(ulen*3+3); #ifdef SKIP_NPT (void)strcpy(gdata->output_filename, gdata->utf8_output_filename); #else (void)(gdata->npt->utf8ToPlatform) (gdata->npt->utf, (jbyte*)gdata->utf8_output_filename, ulen, gdata->output_filename, ulen*3+3); #endif } /* By default we turn on gdata->alloc_sites and gdata->heap_dump */ if ( !gdata->cpu_timing && !gdata->cpu_sampling && !gdata->monitor_tracing && !gdata->alloc_sites && !gdata->heap_dump) { gdata->heap_dump = JNI_TRUE; gdata->alloc_sites = JNI_TRUE; } if ( gdata->alloc_sites || gdata->heap_dump ) { gdata->obj_watch = JNI_TRUE; } if ( gdata->obj_watch || gdata->cpu_timing ) { gdata->bci = JNI_TRUE; } /* Create files & sockets needed */ if (gdata->heap_dump) { char *base; int len; /* Get a fast tempfile for the heap information */ base = gdata->output_filename; if ( base==NULL ) { base = default_filename; } len = (int)strlen(base); gdata->heapfilename = HPROF_MALLOC(len + 5); (void)strcpy(gdata->heapfilename, base); (void)strcat(gdata->heapfilename, ".TMP"); make_unique_filename(&(gdata->heapfilename)); (void)remove(gdata->heapfilename); if (gdata->output_format == 'b') { if ( gdata->logflags & LOG_CHECK_BINARY ) { char * check_suffix; check_suffix = ".check" DEFAULT_TXT_SUFFIX; gdata->checkfilename = HPROF_MALLOC((int)strlen(default_filename)+ (int)strlen(check_suffix)+1); (void)strcpy(gdata->checkfilename, default_filename); (void)strcat(gdata->checkfilename, check_suffix); (void)remove(gdata->checkfilename); gdata->check_fd = md_creat(gdata->checkfilename); } if ( gdata->debug ) { gdata->logflags |= LOG_CHECK_BINARY; } gdata->heap_fd = md_creat_binary(gdata->heapfilename); } else { gdata->heap_fd = md_creat(gdata->heapfilename); } if ( gdata->heap_fd < 0 ) { char errmsg[FILENAME_MAX+80]; (void)md_snprintf(errmsg, sizeof(errmsg), "can't create temp heap file: %s", gdata->heapfilename); errmsg[sizeof(errmsg)-1] = 0; HPROF_ERROR(JNI_TRUE, errmsg); } } if ( gdata->net_port > 0 ) { LOG2("Agent_OnLoad", "Connecting to socket"); gdata->fd = connect_to_socket(gdata->net_hostname, (unsigned short)gdata->net_port); if (gdata->fd <= 0) { char errmsg[120]; (void)md_snprintf(errmsg, sizeof(errmsg), "can't connect to %s:%u", gdata->net_hostname, gdata->net_port); errmsg[sizeof(errmsg)-1] = 0; HPROF_ERROR(JNI_FALSE, errmsg); error_exit_process(1); } gdata->socket = JNI_TRUE; } else { /* If going out to a file, obey the force=y|n option */ if ( !gdata->force_output ) { make_unique_filename(&(gdata->output_filename)); } /* Make doubly sure this file does NOT exist */ (void)remove(gdata->output_filename); /* Create the file */ if (gdata->output_format == 'b') { gdata->fd = md_creat_binary(gdata->output_filename); } else { gdata->fd = md_creat(gdata->output_filename); } if (gdata->fd < 0) { char errmsg[FILENAME_MAX+80]; (void)md_snprintf(errmsg, sizeof(errmsg), "can't create profile file: %s", gdata->output_filename); errmsg[sizeof(errmsg)-1] = 0; HPROF_ERROR(JNI_FALSE, errmsg); error_exit_process(1); } } } /* ------------------------------------------------------------------- */ /* Data reset and dump functions */ static void reset_all_data(void) { if (gdata->cpu_sampling || gdata->cpu_timing || gdata->monitor_tracing) { rawMonitorEnter(gdata->data_access_lock); } if (gdata->cpu_sampling || gdata->cpu_timing) { trace_clear_cost(); } if (gdata->monitor_tracing) { monitor_clear(); } if (gdata->cpu_sampling || gdata->cpu_timing || gdata->monitor_tracing) { rawMonitorExit(gdata->data_access_lock); } } static void reset_class_load_status(JNIEnv *env, jthread thread); static void dump_all_data(JNIEnv *env) { verbose_message("Dumping"); if (gdata->monitor_tracing) { verbose_message(" contended monitor usage ..."); tls_dump_monitor_state(env); monitor_write_contended_time(env, gdata->cutoff_point); } if (gdata->heap_dump) { verbose_message(" Java heap ..."); /* Update the class table */ reset_class_load_status(env, NULL); site_heapdump(env); } if (gdata->alloc_sites) { verbose_message(" allocation sites ..."); site_write(env, 0, gdata->cutoff_point); } if (gdata->cpu_sampling) { verbose_message(" CPU usage by sampling running threads ..."); trace_output_cost(env, gdata->cutoff_point); } if (gdata->cpu_timing) { if (!gdata->old_timing_format) { verbose_message(" CPU usage by timing methods ..."); trace_output_cost(env, gdata->cutoff_point); } else { verbose_message(" CPU usage in old prof format ..."); trace_output_cost_in_prof_format(env); } } reset_all_data(); io_flush(); verbose_message(" done.\n"); } /* ------------------------------------------------------------------- */ /* Dealing with class load and unload status */ static void reset_class_load_status(JNIEnv *env, jthread thread) { WITH_LOCAL_REFS(env, 1) { jint class_count; jclass *classes; jint i; /* Get all classes from JVMTI, make sure they are in the class table. */ getLoadedClasses(&classes, &class_count); /* We don't know if the class list has changed really, so we * guess by the class count changing. Don't want to do * a bunch of work on classes when it's unnecessary. * I assume that even though we have global references on the * jclass object that the class is still considered unloaded. * (e.g. GC of jclass isn't required for it to be included * in the unloaded list, or not in the load list) * [Note: Use of Weak references was a performance problem.] */ if ( class_count != gdata->class_count ) { rawMonitorEnter(gdata->data_access_lock); { /* Unmark the classes in the load list */ class_all_status_remove(CLASS_IN_LOAD_LIST); /* Pretend like it was a class load event */ for ( i = 0 ; i < class_count ; i++ ) { jobject loader; loader = getClassLoader(classes[i]); event_class_load(env, thread, classes[i], loader); } /* Process the classes that have been unloaded */ class_do_unloads(env); } rawMonitorExit(gdata->data_access_lock); } /* Free the space and save the count. */ jvmtiDeallocate(classes); gdata->class_count = class_count; } END_WITH_LOCAL_REFS; } /* A GC or Death event has happened, so do some cleanup */ static void object_free_cleanup(JNIEnv *env, jboolean force_class_table_reset) { Stack *stack; /* Then we process the ObjectFreeStack */ rawMonitorEnter(gdata->object_free_lock); { stack = gdata->object_free_stack; gdata->object_free_stack = NULL; /* Will trigger new stack */ } rawMonitorExit(gdata->object_free_lock); /* Notice we just grabbed the stack of freed objects so * any object free events will create a new stack. */ if ( stack != NULL ) { int count; int i; count = stack_depth(stack); /* If we saw something freed in this GC */ if ( count > 0 ) { for ( i = 0 ; i < count ; i++ ) { ObjectIndex object_index; jlong tag; tag = *(jlong*)stack_element(stack,i); object_index = tag_extract(tag); (void)object_free(object_index); } /* We reset the class load status (only do this once) */ reset_class_load_status(env, NULL); force_class_table_reset = JNI_FALSE; } /* Just terminate this stack object */ stack_term(stack); } /* We reset the class load status if we haven't and need to */ if ( force_class_table_reset ) { reset_class_load_status(env, NULL); } } /* Main function for thread that watches for GC finish events */ static void JNICALL gc_finish_watcher(jvmtiEnv *jvmti, JNIEnv *env, void *p) { jboolean active; active = JNI_TRUE; /* Indicate the watcher thread is active */ rawMonitorEnter(gdata->gc_finish_lock); { gdata->gc_finish_active = JNI_TRUE; } rawMonitorExit(gdata->gc_finish_lock); /* Loop while active */ while ( active ) { jboolean do_cleanup; do_cleanup = JNI_FALSE; rawMonitorEnter(gdata->gc_finish_lock); { /* Don't wait if VM_DEATH wants us to quit */ if ( gdata->gc_finish_stop_request ) { /* Time to terminate */ active = JNI_FALSE; } else { /* Wait for notification to do cleanup, or terminate */ rawMonitorWait(gdata->gc_finish_lock, 0); /* After wait, check to see if VM_DEATH wants us to quit */ if ( gdata->gc_finish_stop_request ) { /* Time to terminate */ active = JNI_FALSE; } } if ( active && gdata->gc_finish > 0 ) { /* Time to cleanup, reset count and prepare for cleanup */ gdata->gc_finish = 0; do_cleanup = JNI_TRUE; } } rawMonitorExit(gdata->gc_finish_lock); /* Do the cleanup if requested outside gc_finish_lock */ if ( do_cleanup ) { /* Free up all freed objects, don't force class table reset * We cannot let the VM_DEATH complete while we are doing * this cleanup. So if during this, VM_DEATH happens, * the VM_DEATH callback should block waiting for this * loop to terminate, and send a notification to the * VM_DEATH thread. */ object_free_cleanup(env, JNI_FALSE); /* Cleanup the tls table where the Thread objects were GC'd */ tls_garbage_collect(env); } } /* Falling out means VM_DEATH is happening, we need to notify VM_DEATH * that we are done doing the cleanup. VM_DEATH is waiting on this * notify. */ rawMonitorEnter(gdata->gc_finish_lock); { gdata->gc_finish_active = JNI_FALSE; rawMonitorNotifyAll(gdata->gc_finish_lock); } rawMonitorExit(gdata->gc_finish_lock); } /* ------------------------------------------------------------------- */ /* JVMTI Event callback functions */ static void setup_event_mode(jboolean onload_set_only, jvmtiEventMode state) { if ( onload_set_only ) { setEventNotificationMode(state, JVMTI_EVENT_VM_INIT, NULL); setEventNotificationMode(state, JVMTI_EVENT_VM_DEATH, NULL); if (gdata->bci) { setEventNotificationMode(state, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL); } } else { /* Enable all other JVMTI events of interest now. */ setEventNotificationMode(state, JVMTI_EVENT_THREAD_START, NULL); setEventNotificationMode(state, JVMTI_EVENT_THREAD_END, NULL); setEventNotificationMode(state, JVMTI_EVENT_CLASS_LOAD, NULL); setEventNotificationMode(state, JVMTI_EVENT_CLASS_PREPARE, NULL); setEventNotificationMode(state, JVMTI_EVENT_DATA_DUMP_REQUEST, NULL); if (gdata->cpu_timing) { setEventNotificationMode(state, JVMTI_EVENT_EXCEPTION_CATCH, NULL); } if (gdata->monitor_tracing) { setEventNotificationMode(state, JVMTI_EVENT_MONITOR_WAIT, NULL); setEventNotificationMode(state, JVMTI_EVENT_MONITOR_WAITED, NULL); setEventNotificationMode(state, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, NULL); setEventNotificationMode(state, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL); } if (gdata->obj_watch) { setEventNotificationMode(state, JVMTI_EVENT_OBJECT_FREE, NULL); } setEventNotificationMode(state, JVMTI_EVENT_GARBAGE_COLLECTION_START, NULL); setEventNotificationMode(state, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, NULL); } } /* JVMTI_EVENT_VM_INIT */ static void JNICALL cbVMInit(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) { rawMonitorEnter(gdata->data_access_lock); { LoaderIndex loader_index; ClassIndex cnum; TlsIndex tls_index; gdata->jvm_initializing = JNI_TRUE; /* Header to use in heap dumps */ gdata->header = "JAVA PROFILE 1.0.1"; gdata->segmented = JNI_FALSE; if (gdata->output_format == 'b') { /* We need JNI here to call in and get the current maximum memory */ gdata->maxMemory = getMaxMemory(env); gdata->maxHeapSegment = (jlong)2000000000; /* More than 2Gig triggers segments and 1.0.2 */ if ( gdata->maxMemory >= gdata->maxHeapSegment ) { gdata->header = "JAVA PROFILE 1.0.2"; gdata->segmented = JNI_TRUE; /* 1.0.2 */ } } /* We write the initial header after the VM initializes now * because we needed to use JNI to get maxMemory and determine if * a 1.0.1 or a 1.0.2 header will be used. * This used to be done in Agent_OnLoad. */ io_write_file_header(); LOG("cbVMInit begin"); /* Create a system loader entry first */ loader_index = loader_find_or_create(NULL,NULL); /* Find the thread jclass (does JNI calls) */ gdata->thread_cnum = class_find_or_create("Ljava/lang/Thread;", loader_index); class_add_status(gdata->thread_cnum, CLASS_SYSTEM); /* Issue fake system thread start */ tls_index = tls_find_or_create(env, thread); /* Setup the Tracker class (should be first class in table) */ tracker_setup_class(); /* Find selected system classes to keep track of */ gdata->system_class_size = 0; cnum = class_find_or_create("Ljava/lang/Object;", loader_index); gdata->system_trace_index = tls_get_trace(tls_index, env, gdata->max_trace_depth, JNI_FALSE); gdata->system_object_site_index = site_find_or_create( cnum, gdata->system_trace_index); /* Used to ID HPROF generated items */ gdata->hprof_trace_index = tls_get_trace(tls_index, env, gdata->max_trace_depth, JNI_FALSE); gdata->hprof_site_index = site_find_or_create( cnum, gdata->hprof_trace_index); if ( gdata->logflags & LOG_DUMP_LISTS ) { list_all_tables(); } /* Prime the class table */ reset_class_load_status(env, thread); /* Find the tracker jclass and jmethodID's (does JNI calls) */ if ( gdata->bci ) { tracker_setup_methods(env); } /* Start any agent threads (does JNI, JVMTI, and Java calls) */ /* Thread to watch for gc_finish events */ rawMonitorEnter(gdata->gc_finish_lock); { createAgentThread(env, "HPROF gc_finish watcher", &gc_finish_watcher); } rawMonitorExit(gdata->gc_finish_lock); /* Start up listener thread if we need it */ if ( gdata->socket ) { listener_init(env); } /* Start up cpu sampling thread if we need it */ if ( gdata->cpu_sampling ) { /* Note: this could also get started later (see cpu) */ cpu_sample_init(env); } /* Setup event modes */ setup_event_mode(JNI_FALSE, JVMTI_ENABLE); /* Engage tracking (sets Java Tracker field so injections call into * agent library). */ if ( gdata->bci ) { tracker_engage(env); } /* Indicate the VM is initialized now */ gdata->jvm_initialized = JNI_TRUE; gdata->jvm_initializing = JNI_FALSE; LOG("cbVMInit end"); } rawMonitorExit(gdata->data_access_lock); } /* JVMTI_EVENT_VM_DEATH */ static void JNICALL cbVMDeath(jvmtiEnv *jvmti, JNIEnv *env) { /* * Use local flag to minimize gdata->dump_lock hold time. */ jboolean need_to_dump = JNI_FALSE; LOG("cbVMDeath"); /* Shutdown thread watching gc_finish, outside CALLBACK locks. * We need to make sure the watcher thread is done doing any cleanup * work before we continue here. */ rawMonitorEnter(gdata->gc_finish_lock); { /* Notify watcher thread to finish up, it will send * another notify when done. If the watcher thread is busy * cleaning up, it will detect gc_finish_stop_request when it's done. * Then it sets gc_finish_active to JNI_FALSE and will notify us. * If the watcher thread is waiting to be notified, then the * notification wakes it up. * We do not want to do the VM_DEATH while the gc_finish * watcher thread is in the middle of a cleanup. */ gdata->gc_finish_stop_request = JNI_TRUE; rawMonitorNotifyAll(gdata->gc_finish_lock); /* Wait for the gc_finish watcher thread to notify us it's done */ while ( gdata->gc_finish_active ) { rawMonitorWait(gdata->gc_finish_lock,0); } } rawMonitorExit(gdata->gc_finish_lock); /* The gc_finish watcher thread should be done now, or done shortly. */ /* BEGIN_CALLBACK/END_CALLBACK handling. */ /* The callbackBlock prevents any active callbacks from returning * back to the VM, and also blocks all new callbacks. * We want to prevent any threads from premature death, so * that we don't have worry about that during thread queries * in this final dump process. */ rawMonitorEnter(gdata->callbackBlock); { /* We need to wait for all callbacks actively executing to block * on exit, and new ones will block on entry. * The BEGIN_CALLBACK/END_CALLBACK macros keep track of callbacks * that are active. * Once the last active callback is done, it will notify this * thread and block. */ rawMonitorEnter(gdata->callbackLock); { /* Turn off native calls */ if ( gdata->bci ) { tracker_disengage(env); } gdata->vm_death_callback_active = JNI_TRUE; while (gdata->active_callbacks > 0) { rawMonitorWait(gdata->callbackLock, 0); } } rawMonitorExit(gdata->callbackLock); /* Now we know that no threads will die on us, being blocked * on some event callback, at a minimum ThreadEnd. */ /* Make some basic checks. */ rawMonitorEnter(gdata->data_access_lock); { if ( gdata->jvm_initializing ) { HPROF_ERROR(JNI_TRUE, "VM Death during VM Init"); return; } if ( !gdata->jvm_initialized ) { HPROF_ERROR(JNI_TRUE, "VM Death before VM Init"); return; } if (gdata->jvm_shut_down) { HPROF_ERROR(JNI_TRUE, "VM Death more than once?"); return; } } rawMonitorExit(gdata->data_access_lock); /* Shutdown the cpu loop thread */ if ( gdata->cpu_sampling ) { cpu_sample_term(env); } /* Time to dump the final data */ rawMonitorEnter(gdata->dump_lock); { gdata->jvm_shut_down = JNI_TRUE; if (!gdata->dump_in_process) { need_to_dump = JNI_TRUE; gdata->dump_in_process = JNI_TRUE; /* * Setting gdata->dump_in_process will cause cpu sampling to pause * (if we are sampling). We don't resume sampling after the * dump_all_data() call below because the VM is shutting * down. */ } } rawMonitorExit(gdata->dump_lock); /* Dump everything if we need to */ if (gdata->dump_on_exit && need_to_dump) { dump_all_data(env); } /* Disable all events and callbacks now, all of them. * NOTE: It's important that this be done after the dump * it prevents other threads from messing up the data * because they will block on ThreadStart and ThreadEnd * events due to the CALLBACK block. */ set_callbacks(JNI_FALSE); setup_event_mode(JNI_FALSE, JVMTI_DISABLE); setup_event_mode(JNI_TRUE, JVMTI_DISABLE); /* Write tail of file */ io_write_file_footer(); } rawMonitorExit(gdata->callbackBlock); /* Shutdown the listener thread and socket, or flush I/O buffers */ if (gdata->socket) { listener_term(env); } else { io_flush(); } /* Close the file descriptors down */ if ( gdata->fd >= 0 ) { (void)md_close(gdata->fd); gdata->fd = -1; if ( gdata->logflags & LOG_CHECK_BINARY ) { if (gdata->output_format == 'b' && gdata->output_filename != NULL) { check_binary_file(gdata->output_filename); } } } if ( gdata->heap_fd >= 0 ) { (void)md_close(gdata->heap_fd); gdata->heap_fd = -1; } if ( gdata->check_fd >= 0 ) { (void)md_close(gdata->check_fd); gdata->check_fd = -1; } /* Remove the temporary heap file */ if (gdata->heap_dump) { (void)remove(gdata->heapfilename); } /* If logging, dump the tables */ if ( gdata->logflags & LOG_DUMP_LISTS ) { list_all_tables(); } /* Make sure all global references are deleted */ class_delete_global_references(env); loader_delete_global_references(env); tls_delete_global_references(env); } /* JVMTI_EVENT_THREAD_START */ static void JNICALL cbThreadStart(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) { LOG3("cbThreadStart", "thread is", (int)(long)(ptrdiff_t)thread); BEGIN_CALLBACK() { event_thread_start(env, thread); } END_CALLBACK(); } /* JVMTI_EVENT_THREAD_END */ static void JNICALL cbThreadEnd(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) { LOG3("cbThreadEnd", "thread is", (int)(long)(ptrdiff_t)thread); BEGIN_CALLBACK() { event_thread_end(env, thread); } END_CALLBACK(); } /* JVMTI_EVENT_CLASS_FILE_LOAD_HOOK */ static void JNICALL cbClassFileLoadHook(jvmtiEnv *jvmti_env, JNIEnv* env, jclass class_being_redefined, jobject loader, const char* name, jobject protection_domain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) { /* WARNING: This will be called before VM_INIT. */ LOG2("cbClassFileLoadHook:",(name==NULL?"Unknown":name)); if (!gdata->bci) { return; } BEGIN_CALLBACK() { rawMonitorEnter(gdata->data_access_lock); { const char *classname; if ( gdata->bci_counter == 0 ) { /* Prime the system classes */ class_prime_system_classes(); } gdata->bci_counter++; *new_class_data_len = 0; *new_class_data = NULL; /* Name could be NULL */ if ( name == NULL ) { classname = ((JavaCrwDemoClassname) (gdata->java_crw_demo_classname_function)) (class_data, class_data_len, &my_crw_fatal_error_handler); if ( classname == NULL ) { HPROF_ERROR(JNI_TRUE, "No classname in classfile"); } } else { classname = strdup(name); if ( classname == NULL ) { HPROF_ERROR(JNI_TRUE, "Ran out of malloc() space"); } } /* The tracker class itself? */ if ( strcmp(classname, TRACKER_CLASS_NAME) != 0 ) { ClassIndex cnum; int system_class; unsigned char * new_image; long new_length; int len; char *signature; LoaderIndex loader_index; LOG2("cbClassFileLoadHook injecting class" , classname); /* Define a unique class number for this class */ len = (int)strlen(classname); signature = HPROF_MALLOC(len+3); signature[0] = JVM_SIGNATURE_CLASS; (void)memcpy(signature+1, classname, len); signature[len+1] = JVM_SIGNATURE_ENDCLASS; signature[len+2] = 0; loader_index = loader_find_or_create(env,loader); if ( class_being_redefined != NULL ) { cnum = class_find_or_create(signature, loader_index); } else { cnum = class_create(signature, loader_index); } HPROF_FREE(signature); signature = NULL; /* Make sure class doesn't get unloaded by accident */ class_add_status(cnum, CLASS_IN_LOAD_LIST); /* Is it a system class? */ system_class = 0; if ( (!gdata->jvm_initialized) && (!gdata->jvm_initializing) && ( ( class_get_status(cnum) & CLASS_SYSTEM) != 0 || gdata->bci_counter < 8 ) ) { system_class = 1; LOG2(classname, " is a system class"); } new_image = NULL; new_length = 0; /* Call the class file reader/write demo code */ ((JavaCrwDemo)(gdata->java_crw_demo_function))( cnum, classname, class_data, class_data_len, system_class, TRACKER_CLASS_NAME, TRACKER_CLASS_SIG, (gdata->cpu_timing)?TRACKER_CALL_NAME:NULL, (gdata->cpu_timing)?TRACKER_CALL_SIG:NULL, (gdata->cpu_timing)?TRACKER_RETURN_NAME:NULL, (gdata->cpu_timing)?TRACKER_RETURN_SIG:NULL, (gdata->obj_watch)?TRACKER_OBJECT_INIT_NAME:NULL, (gdata->obj_watch)?TRACKER_OBJECT_INIT_SIG:NULL, (gdata->obj_watch)?TRACKER_NEWARRAY_NAME:NULL, (gdata->obj_watch)?TRACKER_NEWARRAY_SIG:NULL, &new_image, &new_length, &my_crw_fatal_error_handler, &class_set_methods); if ( new_length > 0 ) { unsigned char *jvmti_space; LOG2("cbClassFileLoadHook DID inject this class", classname); jvmti_space = (unsigned char *)jvmtiAllocate((jint)new_length); (void)memcpy((void*)jvmti_space, (void*)new_image, (int)new_length); *new_class_data_len = (jint)new_length; *new_class_data = jvmti_space; /* VM will deallocate */ } else { LOG2("cbClassFileLoadHook DID NOT inject this class", classname); *new_class_data_len = 0; *new_class_data = NULL; } if ( new_image != NULL ) { (void)free((void*)new_image); /* Free malloc() space with free() */ } } (void)free((void*)classname); } rawMonitorExit(gdata->data_access_lock); } END_CALLBACK(); } /* JVMTI_EVENT_CLASS_LOAD */ static void JNICALL cbClassLoad(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, jclass klass) { /* WARNING: This MAY be called before VM_INIT. */ LOG("cbClassLoad"); BEGIN_CALLBACK() { rawMonitorEnter(gdata->data_access_lock); { WITH_LOCAL_REFS(env, 1) { jobject loader; loader = getClassLoader(klass); event_class_load(env, thread, klass, loader); } END_WITH_LOCAL_REFS; } rawMonitorExit(gdata->data_access_lock); } END_CALLBACK(); } /* JVMTI_EVENT_CLASS_PREPARE */ static void JNICALL cbClassPrepare(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, jclass klass) { /* WARNING: This will be called before VM_INIT. */ LOG("cbClassPrepare"); BEGIN_CALLBACK() { rawMonitorEnter(gdata->data_access_lock); { WITH_LOCAL_REFS(env, 1) { jobject loader; loader = NULL; loader = getClassLoader(klass); event_class_prepare(env, thread, klass, loader); } END_WITH_LOCAL_REFS; } rawMonitorExit(gdata->data_access_lock); } END_CALLBACK(); } /* JVMTI_EVENT_DATA_DUMP_REQUEST */ static void JNICALL cbDataDumpRequest(jvmtiEnv *jvmti) { jboolean need_to_dump; LOG("cbDataDumpRequest"); BEGIN_CALLBACK() { need_to_dump = JNI_FALSE; rawMonitorEnter(gdata->dump_lock); { if (!gdata->dump_in_process) { need_to_dump = JNI_TRUE; gdata->dump_in_process = JNI_TRUE; } } rawMonitorExit(gdata->dump_lock); if (need_to_dump) { dump_all_data(getEnv()); rawMonitorEnter(gdata->dump_lock); { gdata->dump_in_process = JNI_FALSE; } rawMonitorExit(gdata->dump_lock); if (gdata->cpu_sampling && !gdata->jvm_shut_down) { cpu_sample_on(NULL, 0); /* resume sampling */ } } } END_CALLBACK(); } /* JVMTI_EVENT_EXCEPTION_CATCH */ static void JNICALL cbExceptionCatch(jvmtiEnv *jvmti, JNIEnv* env, jthread thread, jmethodID method, jlocation location, jobject exception) { LOG("cbExceptionCatch"); BEGIN_CALLBACK() { event_exception_catch(env, thread, method, location, exception); } END_CALLBACK(); } /* JVMTI_EVENT_MONITOR_WAIT */ static void JNICALL cbMonitorWait(jvmtiEnv *jvmti, JNIEnv* env, jthread thread, jobject object, jlong timeout) { LOG("cbMonitorWait"); BEGIN_CALLBACK() { monitor_wait_event(env, thread, object, timeout); } END_CALLBACK(); } /* JVMTI_EVENT_MONITOR_WAITED */ static void JNICALL cbMonitorWaited(jvmtiEnv *jvmti, JNIEnv* env, jthread thread, jobject object, jboolean timed_out) { LOG("cbMonitorWaited"); BEGIN_CALLBACK() { monitor_waited_event(env, thread, object, timed_out); } END_CALLBACK(); } /* JVMTI_EVENT_MONITOR_CONTENDED_ENTER */ static void JNICALL cbMonitorContendedEnter(jvmtiEnv *jvmti, JNIEnv* env, jthread thread, jobject object) { LOG("cbMonitorContendedEnter"); BEGIN_CALLBACK() { monitor_contended_enter_event(env, thread, object); } END_CALLBACK(); } /* JVMTI_EVENT_MONITOR_CONTENDED_ENTERED */ static void JNICALL cbMonitorContendedEntered(jvmtiEnv *jvmti, JNIEnv* env, jthread thread, jobject object) { LOG("cbMonitorContendedEntered"); BEGIN_CALLBACK() { monitor_contended_entered_event(env, thread, object); } END_CALLBACK(); } /* JVMTI_EVENT_GARBAGE_COLLECTION_START */ static void JNICALL cbGarbageCollectionStart(jvmtiEnv *jvmti) { LOG("cbGarbageCollectionStart"); /* Only calls to Allocate, Deallocate, RawMonitorEnter & RawMonitorExit * are allowed here (see the JVMTI Spec). */ gdata->gc_start_time = md_get_timemillis(); } /* JVMTI_EVENT_GARBAGE_COLLECTION_FINISH */ static void JNICALL cbGarbageCollectionFinish(jvmtiEnv *jvmti) { LOG("cbGarbageCollectionFinish"); /* Only calls to Allocate, Deallocate, RawMonitorEnter & RawMonitorExit * are allowed here (see the JVMTI Spec). */ if ( gdata->gc_start_time != -1L ) { gdata->time_in_gc += (md_get_timemillis() - gdata->gc_start_time); gdata->gc_start_time = -1L; } /* Increment gc_finish counter, notify watcher thread */ rawMonitorEnter(gdata->gc_finish_lock); { /* If VM_DEATH is trying to shut it down, don't do anything at all. * Never send notify if VM_DEATH wants the watcher thread to quit. */ if ( gdata->gc_finish_active ) { gdata->gc_finish++; rawMonitorNotifyAll(gdata->gc_finish_lock); } } rawMonitorExit(gdata->gc_finish_lock); } /* JVMTI_EVENT_OBJECT_FREE */ static void JNICALL cbObjectFree(jvmtiEnv *jvmti, jlong tag) { LOG3("cbObjectFree", "tag", (int)tag); /* Only calls to Allocate, Deallocate, RawMonitorEnter & RawMonitorExit * are allowed here (see the JVMTI Spec). */ HPROF_ASSERT(tag!=(jlong)0); rawMonitorEnter(gdata->object_free_lock); { if ( !gdata->jvm_shut_down ) { Stack *stack; stack = gdata->object_free_stack; if ( stack == NULL ) { gdata->object_free_stack = stack_init(512, 512, sizeof(jlong)); stack = gdata->object_free_stack; } stack_push(stack, (void*)&tag); } } rawMonitorExit(gdata->object_free_lock); } static void set_callbacks(jboolean on) { jvmtiEventCallbacks callbacks; (void)memset(&callbacks,0,sizeof(callbacks)); if ( ! on ) { setEventCallbacks(&callbacks); return; } /* JVMTI_EVENT_VM_INIT */ callbacks.VMInit = &cbVMInit; /* JVMTI_EVENT_VM_DEATH */ callbacks.VMDeath = &cbVMDeath; /* JVMTI_EVENT_THREAD_START */ callbacks.ThreadStart = &cbThreadStart; /* JVMTI_EVENT_THREAD_END */ callbacks.ThreadEnd = &cbThreadEnd; /* JVMTI_EVENT_CLASS_FILE_LOAD_HOOK */ callbacks.ClassFileLoadHook = &cbClassFileLoadHook; /* JVMTI_EVENT_CLASS_LOAD */ callbacks.ClassLoad = &cbClassLoad; /* JVMTI_EVENT_CLASS_PREPARE */ callbacks.ClassPrepare = &cbClassPrepare; /* JVMTI_EVENT_DATA_DUMP_REQUEST */ callbacks.DataDumpRequest = &cbDataDumpRequest; /* JVMTI_EVENT_EXCEPTION_CATCH */ callbacks.ExceptionCatch = &cbExceptionCatch; /* JVMTI_EVENT_MONITOR_WAIT */ callbacks.MonitorWait = &cbMonitorWait; /* JVMTI_EVENT_MONITOR_WAITED */ callbacks.MonitorWaited = &cbMonitorWaited; /* JVMTI_EVENT_MONITOR_CONTENDED_ENTER */ callbacks.MonitorContendedEnter = &cbMonitorContendedEnter; /* JVMTI_EVENT_MONITOR_CONTENDED_ENTERED */ callbacks.MonitorContendedEntered = &cbMonitorContendedEntered; /* JVMTI_EVENT_GARBAGE_COLLECTION_START */ callbacks.GarbageCollectionStart = &cbGarbageCollectionStart; /* JVMTI_EVENT_GARBAGE_COLLECTION_FINISH */ callbacks.GarbageCollectionFinish = &cbGarbageCollectionFinish; /* JVMTI_EVENT_OBJECT_FREE */ callbacks.ObjectFree = &cbObjectFree; setEventCallbacks(&callbacks); } static void getCapabilities(void) { jvmtiCapabilities needed_capabilities; jvmtiCapabilities potential_capabilities; /* Fill in ones that we must have */ (void)memset(&needed_capabilities,0,sizeof(needed_capabilities)); needed_capabilities.can_generate_garbage_collection_events = 1; needed_capabilities.can_tag_objects = 1; if (gdata->bci) { needed_capabilities.can_generate_all_class_hook_events = 1; } if (gdata->obj_watch) { needed_capabilities.can_generate_object_free_events = 1; } if (gdata->cpu_timing || gdata->cpu_sampling) { #if 0 /* Not needed until we call JVMTI for CpuTime */ needed_capabilities.can_get_thread_cpu_time = 1; needed_capabilities.can_get_current_thread_cpu_time = 1; #endif needed_capabilities.can_generate_exception_events = 1; } if (gdata->monitor_tracing) { #if 0 /* Not needed until we call JVMTI for CpuTime */ needed_capabilities.can_get_thread_cpu_time = 1; needed_capabilities.can_get_current_thread_cpu_time = 1; #endif needed_capabilities.can_get_owned_monitor_info = 1; needed_capabilities.can_get_current_contended_monitor = 1; needed_capabilities.can_get_monitor_info = 1; needed_capabilities.can_generate_monitor_events = 1; } /* Get potential capabilities */ getPotentialCapabilities(&potential_capabilities); /* Some capabilities would be nicer to have */ needed_capabilities.can_get_source_file_name = potential_capabilities.can_get_source_file_name; needed_capabilities.can_get_line_numbers = potential_capabilities.can_get_line_numbers; /* Add the capabilities */ addCapabilities(&needed_capabilities); } /* Dynamic library loading */ static void * load_library(char *name) { char lname[FILENAME_MAX+1]; char err_buf[256+FILENAME_MAX+1]; char *boot_path; void *handle; handle = NULL; /* The library may be located in different ways, try both, but * if it comes from outside the SDK/jre it isn't ours. */ getSystemProperty("sun.boot.library.path", &boot_path); md_build_library_name(lname, FILENAME_MAX, boot_path, name); handle = md_load_library(lname, err_buf, (int)sizeof(err_buf)); if ( handle == NULL ) { /* This may be necessary on Windows. */ md_build_library_name(lname, FILENAME_MAX, "", name); handle = md_load_library(lname, err_buf, (int)sizeof(err_buf)); if ( handle == NULL ) { HPROF_ERROR(JNI_TRUE, err_buf); } } return handle; } /* Lookup dynamic function pointer in shared library */ static void * lookup_library_symbol(void *library, char **symbols, int nsymbols) { void *addr; int i; addr = NULL; for( i = 0 ; i < nsymbols; i++ ) { addr = md_find_library_entry(library, symbols[i]); if ( addr != NULL ) { break; } } if ( addr == NULL ) { char errmsg[256]; (void)md_snprintf(errmsg, sizeof(errmsg), "Cannot find library symbol '%s'", symbols[0]); HPROF_ERROR(JNI_TRUE, errmsg); } return addr; } /* ------------------------------------------------------------------- */ /* The OnLoad interface */ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { /* See if it's already loaded */ if ( gdata!=NULL && gdata->isLoaded==JNI_TRUE ) { HPROF_ERROR(JNI_TRUE, "Cannot load this JVM TI agent twice, check your java command line for duplicate hprof options."); return JNI_ERR; } gdata = get_gdata(); gdata->isLoaded = JNI_TRUE; error_setup(); LOG2("Agent_OnLoad", "gdata setup"); gdata->jvm = vm; #ifndef SKIP_NPT /* Load in NPT library for character conversions */ NPT_INITIALIZE(&(gdata->npt), NPT_VERSION, NULL); if ( gdata->npt == NULL ) { HPROF_ERROR(JNI_TRUE, "Cannot load npt library"); } gdata->npt->utf = (gdata->npt->utfInitialize)(NULL); if ( gdata->npt->utf == NULL ) { HPROF_ERROR(JNI_TRUE, "Cannot initialize npt utf functions"); } #endif /* Get the JVMTI environment */ getJvmti(); /* Lock needed to protect debug_malloc() code, which is not MT safe */ #ifdef DEBUG gdata->debug_malloc_lock = createRawMonitor("HPROF debug_malloc lock"); #endif parse_options(options); LOG2("Agent_OnLoad", "Has jvmtiEnv and options parsed"); /* Initialize machine dependent code (micro state accounting) */ md_init(); string_init(); /* Table index values look like: 0x10000000 */ class_init(); /* Table index values look like: 0x20000000 */ tls_init(); /* Table index values look like: 0x30000000 */ trace_init(); /* Table index values look like: 0x40000000 */ object_init(); /* Table index values look like: 0x50000000 */ site_init(); /* Table index values look like: 0x60000000 */ frame_init(); /* Table index values look like: 0x70000000 */ monitor_init(); /* Table index values look like: 0x80000000 */ loader_init(); /* Table index values look like: 0x90000000 */ LOG2("Agent_OnLoad", "Tables initialized"); if ( gdata->pause ) { error_do_pause(); } getCapabilities(); /* Set the JVMTI callback functions (do this only once)*/ set_callbacks(JNI_TRUE); /* Create basic locks */ gdata->dump_lock = createRawMonitor("HPROF dump lock"); gdata->data_access_lock = createRawMonitor("HPROF data access lock"); gdata->callbackLock = createRawMonitor("HPROF callback lock"); gdata->callbackBlock = createRawMonitor("HPROF callback block"); gdata->object_free_lock = createRawMonitor("HPROF object free lock"); gdata->gc_finish_lock = createRawMonitor("HPROF gc_finish lock"); /* Set Onload events mode. */ setup_event_mode(JNI_TRUE, JVMTI_ENABLE); LOG2("Agent_OnLoad", "JVMTI capabilities, callbacks and initial notifications setup"); /* Used in VM_DEATH to wait for callbacks to complete */ gdata->jvm_initializing = JNI_FALSE; gdata->jvm_initialized = JNI_FALSE; gdata->vm_death_callback_active = JNI_FALSE; gdata->active_callbacks = 0; /* Write the header information */ io_setup(); /* We sample the start time now so that the time increments can be * placed in the various heap dump segments in micro seconds. */ gdata->micro_sec_ticks = md_get_microsecs(); /* Load java_crw_demo library and find function "java_crw_demo" */ if ( gdata->bci ) { /* Load the library or get the handle to it */ gdata->java_crw_demo_library = load_library("java_crw_demo"); { /* "java_crw_demo" */ static char *symbols[] = JAVA_CRW_DEMO_SYMBOLS; gdata->java_crw_demo_function = lookup_library_symbol(gdata->java_crw_demo_library, symbols, (int)(sizeof(symbols)/sizeof(char*))); } { /* "java_crw_demo_classname" */ static char *symbols[] = JAVA_CRW_DEMO_CLASSNAME_SYMBOLS; gdata->java_crw_demo_classname_function = lookup_library_symbol(gdata->java_crw_demo_library, symbols, (int)(sizeof(symbols)/sizeof(char*))); } } return JNI_OK; } JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { Stack *stack; LOG("Agent_OnUnload"); gdata->isLoaded = JNI_FALSE; stack = gdata->object_free_stack; gdata->object_free_stack = NULL; if ( stack != NULL ) { stack_term(stack); } io_cleanup(); loader_cleanup(); tls_cleanup(); monitor_cleanup(); trace_cleanup(); site_cleanup(); object_cleanup(); frame_cleanup(); class_cleanup(); string_cleanup(); /* Deallocate any memory in gdata */ if ( gdata->net_hostname != NULL ) { HPROF_FREE(gdata->net_hostname); } if ( gdata->utf8_output_filename != NULL ) { HPROF_FREE(gdata->utf8_output_filename); } if ( gdata->output_filename != NULL ) { HPROF_FREE(gdata->output_filename); } if ( gdata->heapfilename != NULL ) { HPROF_FREE(gdata->heapfilename); } if ( gdata->checkfilename != NULL ) { HPROF_FREE(gdata->checkfilename); } if ( gdata->options != NULL ) { HPROF_FREE(gdata->options); } /* Verify all allocated memory has been taken care of. */ malloc_police(); /* Cleanup is hard to do when other threads might still be running * so we skip destroying some raw monitors which still might be in use * and we skip disposal of the jvmtiEnv* which might still be needed. * Only raw monitors that could be held by other threads are left * alone. So we explicitly do NOT do this: * destroyRawMonitor(gdata->callbackLock); * destroyRawMonitor(gdata->callbackBlock); * destroyRawMonitor(gdata->gc_finish_lock); * destroyRawMonitor(gdata->object_free_lock); * destroyRawMonitor(gdata->listener_loop_lock); * destroyRawMonitor(gdata->cpu_loop_lock); * disposeEnvironment(); * gdata->jvmti = NULL; */ /* Destroy basic locks */ destroyRawMonitor(gdata->dump_lock); gdata->dump_lock = NULL; destroyRawMonitor(gdata->data_access_lock); gdata->data_access_lock = NULL; if ( gdata->cpu_sample_lock != NULL ) { destroyRawMonitor(gdata->cpu_sample_lock); gdata->cpu_sample_lock = NULL; } #ifdef DEBUG destroyRawMonitor(gdata->debug_malloc_lock); gdata->debug_malloc_lock = NULL; #endif /* Unload java_crw_demo library */ if ( gdata->bci && gdata->java_crw_demo_library != NULL ) { md_unload_library(gdata->java_crw_demo_library); gdata->java_crw_demo_library = NULL; } /* You would think you could clear out gdata and set it to NULL, but * turns out that isn't a good idea. Some of the threads could be * blocked inside the CALLBACK*() macros, where they got blocked up * waiting for the VM_DEATH callback to complete. They only have * some raw monitor actions to do, but they need access to gdata to do it. * So do not do this: * (void)memset(gdata, 0, sizeof(GlobalData)); * gdata = NULL; */ }