changeset 9:688cf6a4a7fa

Add a simple jvmti profiling agent
author Omair Majid <omajid@redhat.com>
date Wed, 17 Sep 2014 09:55:23 -0400
parents 300a027f1322
children 87ebe109ad98
files agent/agent.cc attacher/Attacher.java make.sh
diffstat 3 files changed, 175 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/agent.cc	Wed Sep 17 09:55:23 2014 -0400
@@ -0,0 +1,144 @@
+
+#include <stdio.h>
+#include <time.h>
+#include <string>
+#include <map>
+#include <stack>
+#include <atomic>
+
+#include <jvmti.h>
+
+static jvmtiEnv* env = NULL;
+
+// method name -> time
+std::map <jmethodID,std::atomic_ullong> prof_data;
+std::map <jmethodID,std::string> method_names;
+
+// for each thread, maintain a last timer
+std::map <jthread, unsigned long long> current_timer;
+// the current method stack
+std::map <jthread, std::stack<jmethodID>> methods;
+
+static long long get_nanos()
+{
+    struct timespec time;
+    clock_gettime(CLOCK_MONOTONIC, &time);
+    long long nanos = time.tv_nsec + (1000000000 * time.tv_sec);
+    return nanos;
+}
+
+static std::string get_method_name(jvmtiEnv* env, jmethodID method)
+{
+    jclass klass;
+    char* name;
+    char* signature;
+    char* generic;
+
+    std::string result;
+    env->GetMethodDeclaringClass(method, &klass);
+    env->GetClassSignature(klass, &signature, &generic);
+    result += signature;
+    env->Deallocate((unsigned char*)signature);
+    env->Deallocate((unsigned char*)generic);
+    env->GetMethodName(method, &name, &signature, &generic);
+    result += name;
+    result += signature;
+    if (generic != NULL) {
+        result += generic;
+    }
+    env->Deallocate((unsigned char*)name);
+    env->Deallocate((unsigned char*)signature);
+    env->Deallocate((unsigned char*)generic);
+    return result;
+}
+
+static void JNICALL method_entry(jvmtiEnv* jvmti_env, JNIEnv* jni_env, jthread thread, jmethodID method)
+{
+    if (method_names.count(method) == 0) {
+        method_names[method] = get_method_name(jvmti_env, method);
+    }
+
+    long long nanos = get_nanos();
+
+    if (methods[thread].size() != 0) {
+        long long prev = current_timer[thread];
+        prof_data[methods[thread].top()] += (nanos - prev);
+    }
+
+    methods[thread].push(method);
+    current_timer[thread] = nanos;
+
+}
+
+static void JNICALL method_exit(jvmtiEnv* jvmti_env,
+                         JNIEnv* jni_env,
+                         jthread thread,
+                         jmethodID method,
+                         jboolean was_popped_by_exception,
+                         jvalue return_value)
+{
+    long long nanos = get_nanos();
+    long long prev = current_timer[thread];
+
+    prof_data[method] += (nanos - prev);
+    methods[thread].pop();
+    current_timer[thread] = nanos;
+}
+
+static jint initialize(JavaVM* vm)
+{
+
+    jint result;
+
+    result = vm->GetEnv((void**)&env, JVMTI_VERSION_1_1);
+    if (result == JNI_EVERSION) {
+        // this agent is not going to work
+        return 1;
+    }
+
+    jvmtiCapabilities caps = {};
+    caps.can_generate_method_entry_events = 1;
+    caps.can_generate_method_exit_events = 1;
+
+    result = env->AddCapabilities(&caps);
+    if (result != JVMTI_ERROR_NONE) {
+        printf("Error adding method entry/exit capabilities\n");
+    }
+
+    jvmtiEventCallbacks callbacks = {};
+    callbacks.MethodEntry = method_entry;
+    callbacks.MethodExit = method_exit;
+
+    result = env->SetEventCallbacks(&callbacks, sizeof(jvmtiEventCallbacks));
+
+    result = env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, NULL);
+    result = env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, NULL);
+
+    return JNI_OK;
+}
+
+JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved)
+{
+    printf("Agent_OnLoad\n");
+    return initialize(vm);
+}
+
+JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved)
+{
+    printf("Agent_OnAttach\n");
+    return initialize(vm);
+}
+
+JNIEXPORT void JNICALL Agent_OnUnload(JavaVM* vm)
+{
+    printf("Agent_OnUnload\n");
+
+    for (auto iter = prof_data.begin(); iter != prof_data.end(); ++iter) {
+        jmethodID method = iter->first;
+        unsigned long long time = iter->second;
+        printf("%llu %s\n", time, method_names[method].c_str());
+    }
+
+    env->DisposeEnvironment();
+    env = NULL;
+}
--- a/attacher/Attacher.java	Tue Sep 09 13:01:29 2014 -0400
+++ b/attacher/Attacher.java	Wed Sep 17 09:55:23 2014 -0400
@@ -8,12 +8,20 @@
 public class Attacher {
     public static void main(String[] args) {
         String pid = args[0];
-        String agentJar = args[1];
-        String tool = args[2];
+        String agent = args[1];
+        String tool = null;
+        if (args.length > 2) {
+            tool = args[2];
+        }
         try {
             VirtualMachine vm = VirtualMachine.attach(pid);
-            vm.loadAgent(agentJar, tool);
-        } catch (AttachNotSupportedException | IOException | AgentLoadException | AgentInitializationException e) {
+            if (agent.endsWith(".jar")) {
+                vm.loadAgent(agent, tool);
+            } else if (agent.endsWith(".so")) {
+                vm.loadAgentPath(agent, tool);
+            }
+        } catch (AttachNotSupportedException | IOException
+                 | AgentLoadException | AgentInitializationException e) {
             System.err.println("Unable to attach to pid " + pid);
             e.printStackTrace(System.err);
             System.exit(1);
--- a/make.sh	Tue Sep 09 13:01:29 2014 -0400
+++ b/make.sh	Wed Sep 17 09:55:23 2014 -0400
@@ -10,6 +10,8 @@
 
 BUILD=build
 
+AGENT_LIB_NAME=agent.so
+
 AGENT_SRC=agent
 AGENT_BUILD=build/agent
 AGENT_JAR_NAME=agent.jar
@@ -27,7 +29,11 @@
 # Remove everything
 rm -rf ${BUILD}
 
-# Build agent
+# Build native agent
+mkdir -p ${BUILD}
+g++ -Wall -g -std=c++11 -shared -fPIC -o ${BUILD}/${AGENT_LIB_NAME} -I/usr/lib/jvm/java/include -I/usr/lib/jvm/java/include/linux -L${JAVA_HOME}/jre/lib/amd64/server/ ${AGENT_SRC}/*.cc -ljvm
+
+# Build java agent
 mkdir -p ${AGENT_BUILD}
 find ${AGENT_SRC} -iname '*.java' > ${BUILD}/agent-java-files
 ${JAVAC} @${BUILD}/agent-java-files -cp ${JAVASSIST_JAR}:${ASM_JAR} -d ${AGENT_BUILD}
@@ -48,6 +54,8 @@
 find ${TEST_SRC} -iname '*.java' > ${BUILD}/test-java-files
 ${JAVAC} @${BUILD}/test-java-files -d ${TEST_BUILD}
 
+echo "Running Test Programs"
+
 TOOL=javassist
 
 # Run load-agent-on-startup
@@ -67,3 +75,13 @@
 ${JAVA} -cp ${TEST_BUILD} ContinuousObjectAllocator & PID="$!"
 ${JAVA} -cp ${TOOLS_JAR}:${BUILD}/${ATTACHER_JAR_NAME} Attacher ${PID} ${BUILD}/${AGENT_JAR_NAME} ${TOOL}
 wait %1
+
+TOOL=""
+
+# Run load-native-agent-on-startup
+${JAVA} -agentpath:${BUILD}/${AGENT_LIB_NAME} -cp ${TEST_BUILD} PrintDateAndTime
+
+# Run load-agent-after-startup
+${JAVA} -cp ${TEST_BUILD} ContinuousObjectAllocator & PID="$!"
+${JAVA} -cp ${TOOLS_JAR}:${BUILD}/${ATTACHER_JAR_NAME} Attacher ${PID} ${BUILD}/${AGENT_LIB_NAME} ${TOOL}
+wait %1