/**********************************************************************
 * Copyright (c) 2005, 2009 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * $Id: hcthread.c,v 1.5 2009/09/10 21:17:40 jcayne Exp $
 *
 * Contributors:
 * IBM - Initial API and implementation
 **********************************************************************/

#include "hcthread.h"

JavaVM *jvm = NULL;
RA_AGENT_HANDLE handle = NULL;
ra_data_target_hdl_t *targetHandle = NULL;
BOOL attached = FALSE;
BOOL isAuto = FALSE;
BOOL isNative = TRUE;
ra_critsec_t lock;

/*
 * Initialization called from the java layer
 */
JNIEXPORT void JNICALL Java_org_eclipse_hyades_logging_jvm_threadanalysis_ThreadDumpAgentImpl_init0(JNIEnv *env, jobject obj, jstring name, jstring type) {
	char *_name;
	char *_type;

	/* Retrieve the current JVM and store it */
	ENV(env)->GetJavaVM(ENVPARM(env) &jvm);

	/* Translate the java strings to native strings */
	_name = (char*)(ENV(env)->GetStringUTFChars(ENVPARM(env) name, NULL));
	_type = (char*)(ENV(env)->GetStringUTFChars(ENVPARM(env) type, NULL));

	isNative = FALSE;
	init(_name, _type);
}

/*
 * Deregister called from the java layer
 */
JNIEXPORT void JNICALL Java_org_eclipse_hyades_logging_jvm_threadanalysis_ThreadDumpAgentImpl_deregister0(JNIEnv *env, jobject obj) {
	deregister();
}

/*
 * Dump thread called from the java layer
 */
JNIEXPORT void JNICALL Java_org_eclipse_hyades_logging_jvm_threadanalysis_ThreadDumpAgentImpl_dumpThreads0(JNIEnv *env, jobject obj) {
	/* Return the location of the running java program which has just produced a thread dump */
	dump();
}

/*
 * Get process ID called from the java layer
 */
JNIEXPORT jint JNICALL Java_org_eclipse_hyades_logging_jvm_threadanalysis_ThreadDumpAgentImpl_getPid0(JNIEnv *env, jobject obj) {
	return getpid();
}

/*
 * JVMPI interface
 */
JNIEXPORT jint JNICALL JVM_OnLoad(JavaVM *_jvm, char *options, void *reserved) {
	jvm = _jvm;

	init(AGENT_NAME, AGENT_TYPE);

	return JNI_OK;
}

/*
 * Register the agent with the agent controller
 */
void init(char* name, char* type) {
/*
	JDK1_1InitArgs vm_args;
	JNIEnv *env;
*/

	/* Create a lock for the critical section */
	ra_mutexCreate(&lock);

	/* Initialize and register the agent with the agent controller */
	handle = ra_initializeBindings(name, type, messageHandler, FALSE);

	targetHandle = (ra_data_target_hdl_t*)malloc(sizeof(ra_data_target_hdl_t));

	/* Start the message handler */
	ra_startListener(handle, FALSE);
	debugTrace("Registered agent: \"%s\" of type: \"%s\"", name, type);

	/* If this is called by the native code (and not by -Xrun), it won't have a JVM associated */
	if(isNative && (jvm == NULL)) {
		debugTrace("Not supported");
/*
		JNI_GetDefaultJavaVMInitArgs(&vm_args);
		if(JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args) != 0) {
			debugTrace("Error creating Java VM");
			return;
		}
		else {
			debugTrace("Java VM created successfully");
		}
*/
	}

}

/*
 * Deregister the agent with the agent controller
 */
void deregister() {
	/* Deregister with the agent controller */
	ra_finalizeBindings(handle);
	debugTrace("Deregistered agent");

	/* Destroy the lock for the critical section */
	ra_mutexDestroy(&lock);

	if(isNative) {
		/* Destroy the JVM */
#if defined __cplusplus && defined _HPUX
		ENV(jvm)->DestroyJavaVM();
#else
		ENV(jvm)->DestroyJavaVM(jvm);
#endif
		debugTrace("Destroyed from the JVM");
	}

	free(targetHandle);
	targetHandle = NULL;
}

/*
 * Message handler for agent contorller messages
 */
void messageHandler(ra_command_t *command) {
	debugTrace("Command received, tag = 0x%x", command->tag);
	switch(command->tag) {
		case RA_CUSTOM_COMMAND:
			{
				debugTrace("Message received: RA_CUSTOM_COMMAND, data = %s", command->info.custom_command.message.data);

				if(strcmp(command->info.custom_command.message.data, "ANALYSEHEAP")==0) {
					dump();
					debugTrace("Manual thread dump");
				}
				else if(strcmp(command->info.custom_command.message.data, "RESUME")==0) {
					dump();
/*
					isAuto = TRUE;
					autodump(3);
*/
					debugTrace("Manual thread dump");
				}
				break;
			}
		case RA_START_MONITORING_AGENT_REMOTE:
		case RA_START_MONITORING_AGENT_LOCAL:
			{
				debugTrace("Message received: RA_START_MONITORING");
				if(!attached) {
					if(command->tag == RA_START_MONITORING_AGENT_LOCAL) {
						targetHandle->dtarget = RA_SHAREDMEMORY;
						ra_attachToShm(command->info.start_monitor_local.file.data, &targetHandle->dtargetHdl.shmHdl);
						debugTrace("Attached to shared memory");
					}
					else if(command->tag == RA_START_MONITORING_AGENT_REMOTE) {
						targetHandle->dtarget = RA_SOCKET;
						ra_connectToTCPServer(command->info.start_monitor_remote.ip, (unsigned short)command->info.start_monitor_remote.port, &targetHandle->dtargetHdl.socketFD);
						debugTrace("Connected to TCP server");
					}
					attached = TRUE;
				}
				break;
			}
		case RA_STOP_MONITORING_AGENT:
			{
				debugTrace("Message received: RA_STOP_MONITORING");
				isAuto = FALSE;
				break;
			}
		case RA_DETACH_FROM_AGENT:
			{
				debugTrace("Message received: RA_DETACH_FROM_AGENT");
				if(targetHandle->dtarget == RA_SHAREDMEMORY) {
					ra_stopFlushingShm(&targetHandle->dtargetHdl.shmHdl);
				}
				else if(targetHandle->dtarget == RA_SOCKET) {
					ra_closeSocket(targetHandle->dtargetHdl.socketFD);
				}
				isAuto = FALSE;
				attached = FALSE;
				break;
			}
		default:
			break;
	}
}

/*
 * Automatically performing a thread dump
 */
void autodump(int interval) {
	TID tid;
	HANDLE handle;
#if defined(_WIN32)
	handle = CreateThread(NULL, 0, autoDumpProxy, (LPVOID)(&interval), 0, &tid);
	CloseHandle(handle);
#elif defined(_AIX)
	pthread_attr_t thread_attr;

	pthread_attr_init(&thread_attr);
	pthread_attr_setstacksize( &thread_attr, 4194304 );
	pthread_create(&tid, &thread_attr, autoDumpThread, &interval);
#else
	pthread_create(&tid, NULL, autoDumpThread, &interval);
#endif
	debugTrace("Auto dump thread created: ID = %d, interval = %d sec", tid, interval);
}

/*
 * Windows proxy for the thread
 */
#ifdef _WIN32
DWORD WINAPI autoDumpProxy(LPVOID args) {
	DWORD returnVal = 0;
	autoDumpThread(args);
	return returnVal;
}
#endif

/*
 * Thread for automatically dumping the threads at a certain time interval
 * Note: On Windows 2000 this thread will exit after the Ctrl-Break is sent. It will keep on running
 *       on Windows XP.
 */
void *autoDumpThread(void *args) {
	int interval = *((int*)args);
	while(isAuto) {
		dump();
		sleep(interval);
	}
	return NULL;
}

/*
 * Dump thread called from the native layer
 */
void dump() {
	char* currentPath = (char*)malloc(_MAX_PATH * sizeof(char));;

	/* Enter critical section */
	ra_mutexEnter(&lock);

	getRunningDir(currentPath);
	debugTrace("Current working directory: %s", currentPath);

	hc_dumpThreads();
	debugTrace("Finished native dump()");

	/* Pushing the filename into the Java layer to do the processing */
	processDumpData(currentPath);
	debugTrace("Finished native processDumpData()");

	/* Leave critical section */
	ra_mutexExit(&lock);

	free(currentPath);

	return;
}

/*
 * Retrieve the current working directory
 */
void getRunningDir(char* buffer) {
	getcwd(buffer, _MAX_PATH);
}

/*
 * Call the "kill -3" or "Ctrl-Break" to initiate the thread dump
 */
void hc_dumpThreads() {
	BOOL rc = FALSE;
#if defined(_WIN32)
	DWORD dw;

	rc = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0);
/*
	if(raise(SIGBREAK) == 0) {
		debugTrace("Signal raised successfully: %d", SIGBREAK);
	}
	else {
		debugTrace("Problem raising signal: %d", SIGBREAK);
	}
*/
	if(rc == 0) {
		dw = GetLastError();
		debugTrace("Error sending console control event: %d to process: %d", dw, getpid());
	}

	debugTrace("Control-Break sent to process: %d", GetCurrentProcessId());
/* Since Linux threads are sub-processes so we need to send the signal to the main thread */
#elif defined(__linux__)
	int last = getpid();
	int pid = getppid(); /* start searching from the current pid's parent */

	while(isProcessName(pid, "java")) { /* Navigate until main thread is found */
		debugTrace("Parent is still java, going to its parent");
		last = pid;
		pid = getParentProcessId(last);
		debugTrace("Checking parent pid: %d", pid);
	}

	kill(last, SIGQUIT);
	debugTrace("Signal %d raised to process: %d", SIGQUIT, last);

#else
	if(raise(SIGQUIT) == 0) {
		debugTrace("Signal %d raised successfully to process: %d", SIGQUIT, getpid());
	}
	else {
		debugTrace("Problem raising signal %d to process %d", SIGQUIT, getpid());
	}
#endif
}

/*
 * Call the java code to process the javacore file
 */
void processDumpData(char* path) {
	JNIEnv *env;
	jclass cls;
	jmethodID method;
	jstring xmlString;
	char *localXmlString;
	unsigned char *copy;
	int length;

	debugTrace("Inside native processDumpData()");

	/* Attach to the current JVM */
	ENV(jvm)->AttachCurrentThread(ENVPARM(jvm) (void**)&env, NULL);
	debugTrace("Attached current thread to the JVM");

	/* Initialize the java class object */
	cls = ENV(env)->FindClass(ENVPARM(env) THREAD_DUMP_AGENT_CLASS_NAME);

	if(cls != NULL) {
		debugTrace("Found class \"%s\"", THREAD_DUMP_AGENT_CLASS_NAME);
		method = ENV(env)->GetStaticMethodID(ENVPARM(env) cls, THREAD_DUMP_AGENT_PROCESSOR, THREAD_DUMP_AGENT_PROCESSOR_ARGS);

		if(method != NULL) {
			debugTrace("Found method \"%s\"", THREAD_DUMP_AGENT_PROCESSOR);
			xmlString = (jstring)(ENV(env)->CallStaticObjectMethod(ENVPARM(env) cls, method, ENV(env)->NewStringUTF(ENVPARM(env) path)));

			if(xmlString != NULL) {
				debugTrace("Returned successfully from java layer processDumpData()");
				localXmlString = (char*)(ENV(env)->GetStringUTFChars(ENVPARM(env) xmlString, NULL));

				if(localXmlString != NULL) {
					debugTrace("Translated java string to local string: %s", localXmlString);

					length = strlen(localXmlString);
					copy = ra_allocateMessageBlock(length);
					memcpy(copy, localXmlString, length);

					if(targetHandle != NULL) {
						ra_writeMessageBlock(targetHandle, RA_BINARY_DATA, copy, length);
						debugTrace("Wrote XML fragments to target handle");
					}
					else {
						debugTrace("Cannot write to target handle");
					}

					ra_freeMessageBlock(copy);
					ENV(env)->ReleaseStringUTFChars(ENVPARM(env) xmlString, localXmlString);
					debugTrace("Released local XML string");
				}
				else {
					debugTrace("Cannot translate java string to local string");
				}
			}
			else {
				debugTrace("Problem getting XML string from java layer");
			}
		}
		else {
			debugTrace("Cannot find the method \"%s\" in the java layer", THREAD_DUMP_AGENT_PROCESSOR);
		}
	}
	else {
		debugTrace("Cannot find the class \"%s\" in the java layer", THREAD_DUMP_AGENT_CLASS_NAME);
	}
	/* Detach from the JVM */
#if defined __cplusplus && defined _HPUX
	ENV(jvm)->DetachCurrentThread();
#else
	ENV(jvm)->DetachCurrentThread(jvm);
#endif
	debugTrace("Detached from the JVM");
}

/*
 * Debugging output
 */
void debugTrace(char* msg, ...) {
#if _DEBUG
	va_list argp;
	int len;
    int bufferSize = 8192;
    char *buffer = NULL;

	va_start(argp, msg);

	buffer = (char*)malloc(bufferSize * sizeof(char));
	while((len = vsnprintf(buffer, bufferSize, msg, argp)) < 0) {
		bufferSize += 1024;
		buffer = (char*)malloc(bufferSize * sizeof(char));
	}

	va_end(argp);

	/* Check if local logging is enabled */
	printf("DEBUG(NATIVE): %s\n", buffer);
	free(buffer);
#endif
}

/*
 * Sleep in seconds
 */
#if defined(_WIN32)
void sleep(int sec) {
	Sleep(sec * 1000);
}
#endif

#if defined(__linux__)
/*
 * Get the process status from /proc
 */
void getProcessStat(int pid, char* value) {
	FILE *fp;
	char *proc;
	int rc;

	proc = (char*)malloc(sizeof(char) * _MAX_PATH);

	sprintf(proc, "/proc/%d/stat", pid); /* open the stat file of pid */
	debugTrace("Opening file %s for reading", proc);

	fp = fopen(proc, "r");
	rc = fread(value, sizeof(char), _MAX_PATH, fp);
	fclose(fp);
	debugTrace("%d bytes read from file %s", rc, proc);

	free(proc);

	return;
}

/*
 * Check if the process with this pid has the name specified
 */
BOOL isProcessName(int pid, char* pname) {
	char *stat;
	char *name;
	BOOL exist;

	debugTrace("Inside isProcessName(%d, %s)", pid, pname);
	stat = (char*)malloc(sizeof(char) * _MAX_PATH);
	getProcessStat(pid, stat);

	strtok(stat, " "); /* skip the 1st tok, process id */
	name = strtok(NULL, " "); /* got the 2nd tok, parent name */

	if(strstr(name, pname) != NULL) {
		debugTrace("String \"%s\" found in string \"%s\"", pname, name);
		exist = TRUE;
	}
	else {
		debugTrace("String \"%s\" not found in string \"%s\"", pname, name);
		exist = FALSE;
	}

	free(stat);
/*	free(name); /* is it an error? */

	return exist;
}

/*
 * Get the parent pid of a process
 */
int getParentProcessId(int pid) {
	char *stat;
	char *ppid_str;
	int ppid;

	debugTrace("Inside getParentProcessId(%d)", pid);
	stat = (char*)malloc(sizeof(char) * _MAX_PATH);
	getProcessStat(pid, stat);

	strtok(stat, " "); /* skip the 1st tok, process id */
	strtok(NULL, " "); /* skip the 2nd tok, process name */
	strtok(NULL, " "); /* skip the 3rd tok, process status */
	ppid_str = strtok(NULL, " "); /* got the 4th tok, parent process id */

	sscanf(ppid_str, "%d", &ppid);
	debugTrace("Parent process ID found is %d", ppid);

	free(stat);
/*	free(ppid_str); /* is it an error? */

	return ppid;
}

#endif
