/*
 * @(#)hprof_listener.c	1.18 04/07/27
 * 
 * Copyright (c) 2004 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.
 */

/* 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);
}

