/* * @(#)hprof_listener.c 1.21 10/03/23 * * Copyright (c) 2006, Oracle and/or its affiliates. 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 Oracle 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 MICROSYSTEMS, 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. */ /* The hprof listener loop thread. net=hostname:port option */ /* * The option net=hostname:port causes all hprof output to be sent down * a socket connection, and also allows for commands to come in over the * socket. The commands are documented below. * * This thread can cause havoc when started prematurely or not terminated * properly, see listener_init() and listener_term(), and their calls * in hprof_init.c. * * The listener loop (hprof_listener.c) can dynamically turn on or off the * sampling of all or selected threads. * * The specification of this command protocol is only here, in the comments * below. The HAT tools uses this interface. * It is also unknown how well these options work given the limited * testing of this interface. * */ #include "hprof.h" /* When the hprof Agent in the VM is connected via a socket to the * profiling client, the client may send the hprof Agent a set of commands. * The commands have the following format: * * u1 a TAG denoting the type of the record * u4 a serial number * u4 number of bytes *remaining* in the record. Note that * this number excludes the tag and the length field itself. * [u1]* BODY of the record (a sequence of bytes) */ /* The following commands are presently supported: * * TAG BODY notes * ---------------------------------------------------------- * HPROF_CMD_GC force a GC. * * HPROF_CMD_DUMP_HEAP obtain a heap dump * * HPROF_CMD_ALLOC_SITES obtain allocation sites * * u2 flags 0x0001: incremental vs. complete * 0x0002: sorted by allocation vs. live * 0x0004: whether to force a GC * u4 cutoff ratio (0.0 ~ 1.0) * * HPROF_CMD_HEAP_SUMMARY obtain heap summary * * HPROF_CMD_DUMP_TRACES obtain all newly created traces * * HPROF_CMD_CPU_SAMPLES obtain a HPROF_CPU_SAMPLES record * * u2 ignored for now * u4 cutoff ratio (0.0 ~ 1.0) * * HPROF_CMD_CONTROL changing settings * * u2 0x0001: alloc traces on * 0x0002: alloc traces off * * 0x0003: CPU sampling on * * id: thread object id (NULL for all) * * 0x0004: CPU sampling off * * id: thread object id (NULL for all) * * 0x0005: CPU sampling clear * * 0x0006: clear alloc sites info * * 0x0007: set max stack depth in CPU samples * and alloc traces * * u2: new depth */ typedef enum HprofCmd { HPROF_CMD_GC = 0x01, HPROF_CMD_DUMP_HEAP = 0x02, HPROF_CMD_ALLOC_SITES = 0x03, HPROF_CMD_HEAP_SUMMARY = 0x04, HPROF_CMD_EXIT = 0x05, HPROF_CMD_DUMP_TRACES = 0x06, HPROF_CMD_CPU_SAMPLES = 0x07, HPROF_CMD_CONTROL = 0x08, HPROF_CMD_EOF = 0xFF } HprofCmd; static jint recv_fully(int f, char *buf, int len) { jint nbytes; nbytes = 0; if ( f < 0 ) { return nbytes; } while (nbytes < len) { int res; res = md_recv(f, buf + nbytes, (len - nbytes), 0); if (res < 0) { /* * hprof was disabled before we returned from recv() above. * This means the command socket is closed so we let that * trickle back up the command processing stack. */ LOG("recv() returned < 0"); break; } nbytes += res; } return nbytes; } static unsigned char recv_u1(void) { unsigned char c; jint nbytes; nbytes = recv_fully(gdata->fd, (char *)&c, (int)sizeof(unsigned char)); if (nbytes == 0) { c = HPROF_CMD_EOF; } return c; } static unsigned short recv_u2(void) { unsigned short s; jint nbytes; nbytes = recv_fully(gdata->fd, (char *)&s, (int)sizeof(unsigned short)); if (nbytes == 0) { s = (unsigned short)-1; } return md_ntohs(s); } static unsigned recv_u4(void) { unsigned i; jint nbytes; nbytes = recv_fully(gdata->fd, (char *)&i, (int)sizeof(unsigned)); if (nbytes == 0) { i = (unsigned)-1; } return md_ntohl(i); } static ObjectIndex recv_id(void) { ObjectIndex result; jint nbytes; nbytes = recv_fully(gdata->fd, (char *)&result, (int)sizeof(ObjectIndex)); if (nbytes == 0) { result = (ObjectIndex)0; } return result; } static void JNICALL listener_loop_function(jvmtiEnv *jvmti, JNIEnv *env, void *p) { jboolean keep_processing; unsigned char tag; jboolean kill_the_whole_process; kill_the_whole_process = JNI_FALSE; tag = 0; rawMonitorEnter(gdata->listener_loop_lock); { gdata->listener_loop_running = JNI_TRUE; keep_processing = gdata->listener_loop_running; /* Tell listener_init() that we have started */ rawMonitorNotifyAll(gdata->listener_loop_lock); } rawMonitorExit(gdata->listener_loop_lock); while ( keep_processing ) { LOG("listener loop iteration"); tag = recv_u1(); /* This blocks here on the socket read, a close() * on this fd will wake this up. And if recv_u1() * can't read anything, it returns HPROF_CMD_EOF. */ LOG3("listener_loop", "command = ", tag); if (tag == HPROF_CMD_EOF) { /* The cmd socket has closed so the listener thread is done * just fall out of loop and let the thread die. */ keep_processing = JNI_FALSE; break; } /* seq_num not used */ (void)recv_u4(); /* length not used */ (void)recv_u4(); switch (tag) { case HPROF_CMD_GC: runGC(); break; case HPROF_CMD_DUMP_HEAP: { site_heapdump(env); break; } case HPROF_CMD_ALLOC_SITES: { unsigned short flags; unsigned i_tmp; float ratio; flags = recv_u2(); i_tmp = recv_u4(); ratio = *(float *)(&i_tmp); site_write(env, flags, ratio); break; } case HPROF_CMD_HEAP_SUMMARY: { rawMonitorEnter(gdata->data_access_lock); { io_write_heap_summary( gdata->total_live_bytes, gdata->total_live_instances, gdata->total_alloced_bytes, gdata->total_alloced_instances); } rawMonitorExit(gdata->data_access_lock); break; } case HPROF_CMD_EXIT: keep_processing = JNI_FALSE; kill_the_whole_process = JNI_TRUE; verbose_message("HPROF: received exit event, exiting ...\n"); break; case HPROF_CMD_DUMP_TRACES: rawMonitorEnter(gdata->data_access_lock); { trace_output_unmarked(env); } rawMonitorExit(gdata->data_access_lock); break; case HPROF_CMD_CPU_SAMPLES: { unsigned i_tmp; float ratio; /* flags not used */ (void)recv_u2(); i_tmp = recv_u4(); ratio = *(float *)(&i_tmp); trace_output_cost(env, ratio); break; } case HPROF_CMD_CONTROL: { unsigned short cmd = recv_u2(); if (cmd == 0x0001) { setEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_OBJECT_FREE, NULL); tracker_engage(env); } else if (cmd == 0x0002) { setEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_OBJECT_FREE, NULL); tracker_disengage(env); } else if (cmd == 0x0003) { ObjectIndex thread_object_index; thread_object_index = recv_id(); cpu_sample_on(env, thread_object_index); } else if (cmd == 0x0004) { ObjectIndex thread_object_index; thread_object_index = recv_id(); cpu_sample_off(env, thread_object_index); } else if (cmd == 0x0005) { rawMonitorEnter(gdata->data_access_lock); { trace_clear_cost(); } rawMonitorExit(gdata->data_access_lock); } else if (cmd == 0x0006) { rawMonitorEnter(gdata->data_access_lock); { site_cleanup(); site_init(); } rawMonitorExit(gdata->data_access_lock); } else if (cmd == 0x0007) { gdata->max_trace_depth = recv_u2(); } break; } default:{ char buf[80]; keep_processing = JNI_FALSE; kill_the_whole_process = JNI_TRUE; (void)md_snprintf(buf, sizeof(buf), "failed to recognize cmd %d, exiting..", (int)tag); buf[sizeof(buf)-1] = 0; HPROF_ERROR(JNI_FALSE, buf); break; } } rawMonitorEnter(gdata->data_access_lock); { io_flush(); } rawMonitorExit(gdata->data_access_lock); rawMonitorEnter(gdata->listener_loop_lock); { if ( !gdata->listener_loop_running ) { keep_processing = JNI_FALSE; } } rawMonitorExit(gdata->listener_loop_lock); } /* If listener_term() is causing this loop to terminate, then * you will block here until listener_term wants you to proceed. */ rawMonitorEnter(gdata->listener_loop_lock); { if ( gdata->listener_loop_running ) { /* We are terminating for our own reasons, maybe because of * EOF (socket closed?), or EXIT request, or invalid command. * Not from listener_term(). * We set gdata->listener_loop_running=FALSE so that any * future call to listener_term() will do nothing. */ gdata->listener_loop_running = JNI_FALSE; } else { /* We assume that listener_term() is stopping us, * now we need to tell it we understood. */ rawMonitorNotifyAll(gdata->listener_loop_lock); } } rawMonitorExit(gdata->listener_loop_lock); LOG3("listener_loop", "finished command = ", tag); /* If we got an explicit command request to die, die here */ if ( kill_the_whole_process ) { error_exit_process(0); } } /* External functions */ void listener_init(JNIEnv *env) { /* Create the raw monitor */ gdata->listener_loop_lock = createRawMonitor("HPROF listener lock"); rawMonitorEnter(gdata->listener_loop_lock); { createAgentThread(env, "HPROF listener thread", &listener_loop_function); /* Wait for listener_loop_function() to tell us it started. */ rawMonitorWait(gdata->listener_loop_lock, 0); } rawMonitorExit(gdata->listener_loop_lock); } void listener_term(JNIEnv *env) { rawMonitorEnter(gdata->listener_loop_lock); { /* If we are in the middle of sending bytes down the socket, this * at least keeps us blocked until that processing is done. */ rawMonitorEnter(gdata->data_access_lock); { /* Make sure the socket gets everything */ io_flush(); /* * Graceful shutdown of the socket will assure that all data * sent is received before the socket close completes. */ (void)md_shutdown(gdata->fd, 2 /* disallow sends and receives */); /* This close will cause the listener loop to possibly wake up * from the recv_u1(), this is critical to get thread running again. */ md_close(gdata->fd); } rawMonitorExit(gdata->data_access_lock); /* It could have shut itself down, so we check the global flag */ if ( gdata->listener_loop_running ) { /* It stopped because of something listener_term() did. */ gdata->listener_loop_running = JNI_FALSE; /* Wait for listener_loop_function() to tell us it finished. */ rawMonitorWait(gdata->listener_loop_lock, 0); } } rawMonitorExit(gdata->listener_loop_lock); }