| /* |
| * Copyright (c) 2003, 2004, 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: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions 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 nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /* 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); |
| } |