Skip to content

Commit

Permalink
runtime profile gathering & export simple API. this is enough to get
Browse files Browse the repository at this point in the history
it to work with Ostrich.
  • Loading branch information
mariusae committed Nov 13, 2010
1 parent 6075e0c commit 6bdaed9
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 117 deletions.
37 changes: 37 additions & 0 deletions Heapster.java
@@ -0,0 +1,37 @@
// The HeapsterHelper class is loaded together with the heapster agent
// in order to proxy bytecode-injected calls back into the agent
// library.
//
// TODO: it seems like this should be entirely unnecessary. fix?

public class Heapster {
public static int isReady = 0;
public static volatile boolean isProfiling = false;

public static void start() {
isProfiling = true;
}

public static void stop() {
isProfiling = false;
}

private static native byte[] _dumpProfile();
private static native void _newObject(Object thread, Object o);

public static void newObject(Object obj) {
if (!isProfiling)
return;

// TODO: can we get a hold of the sizes here? if so, we could do
// the sampling here & never leave bytecode execution.

Thread thread = Thread.currentThread();
if (isReady == 1 && thread != null)
_newObject(thread, obj);
}

public static byte[] dumpProfile() {
return _dumpProfile();
}
}
16 changes: 0 additions & 16 deletions HeapsterHelper.java

This file was deleted.

4 changes: 2 additions & 2 deletions Makefile
Expand Up @@ -10,9 +10,9 @@ LDFLAGS=-fno-strict-aliasing -fPIC -fno-omit-frame-pointer \
-static-libgcc -mimpure-text -shared
DEBUG=-g

all: libheapster.jnilib HeapsterHelper.class
all: libheapster.jnilib Heapster.class

libheapster.jnilib: heapster.o sampler.o java_crw_demo/java_crw_demo.o
libheapster.jnilib: heapster.o sampler.o util.o java_crw_demo/java_crw_demo.o
g++ $(DEBUG) $(LDFLAGS) -o $@ $^ -lc

%.o: %.cc
Expand Down
222 changes: 123 additions & 99 deletions heapster.cc
Expand Up @@ -19,9 +19,14 @@
#include <map>

#include "sampler.h"
#include "util.h"

// NB: only works on little-endian machines
// NB: this is basically a giant race condition right now

// TODO
// > modify sample rate at runtime
// > fix memory leaks
// > ability to clear profile

using namespace std;

Expand Down Expand Up @@ -133,7 +138,7 @@ class Lock {
jrawMonitorID monitor_;
};

#define HELPER_CLASS "HeapsterHelper"
#define HELPER_CLASS "Heapster"
#define HELPER_FIELD_ISREADY "isReady"

class Heapster {
Expand Down Expand Up @@ -168,12 +173,23 @@ class Heapster {
static Heapster* instance;

// * Static JNI hooks.
static void JNI_NewObject(
static JNIEXPORT void JNICALL JNI_NewObject(
JNIEnv* env, jclass klass,
jthread thread, jobject o) {
instance->NewObject(env, klass, thread, o);
}

static JNIEXPORT jbyteArray JNICALL JNI_DumpProfile(
JNIEnv* env, jclass klass) {
const string profile = instance->DumpProfile(env, klass);
jbyteArray buf = env->NewByteArray(profile.size());
// TODO: check error here?
env->SetByteArrayRegion(
buf, 0, profile.size(), (jbyte*)profile.data());

return buf;
}

// * Static JVMTI hooks
static void JNICALL JVMTI_VMStart(jvmtiEnv* jvmti, JNIEnv* env) {
instance->VMStart(env);
Expand All @@ -193,6 +209,11 @@ class Heapster {
const char* name, jobject protection_domain,
jint class_data_len, const unsigned char* class_data,
jint* new_class_data_len, unsigned char** new_class_data) {
// Currently, we always instrument classes (but profiling is
// optional, and won't leave bytecode unecessarily), but in the
// future we may consider dynamic BCI, as per:
//
// http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#bci
instance->ClassFileLoadHook(
jvmti, env, class_being_redefined, loader, name,
protection_domain, class_data_len, class_data,
Expand All @@ -218,7 +239,10 @@ class Heapster {
static JNINativeMethod registry[] = {
{ (char*)"_newObject",
(char*)"(Ljava/lang/Object;Ljava/lang/Object;)V",
(void*)&Heapster::JNI_NewObject }
(void*)&Heapster::JNI_NewObject },
{ (char*)"_dumpProfile",
(char*)"()[B",
(void*)&Heapster::JNI_DumpProfile }
};

klass = env->FindClass(HELPER_CLASS);
Expand All @@ -243,101 +267,7 @@ class Heapster {
}

void JNICALL VMDeath(JNIEnv* env) {
char* profile_path = getenv("HEAPPROFILE");
if (profile_path == NULL)
return;

jvmtiError error = jvmti_->ForceGarbageCollection();
if (error != JVMTI_ERROR_NONE)
warnx("Failed to force garbage collection.\n");

int fd = open(
profile_path, O_WRONLY | O_TRUNC | O_CREAT,
S_IRUSR | S_IWUSR);
if (fd < 0) {
perror("open");
return;
}
FILE* file = fdopen(fd, "w");

// TODO: change "binary" to main class name?
fprintf(file, "--- symbol\nbinary=heapster\n");

// Write out symbol information (traverse the sites & resolve
// method names.)
set<jmethodID> seen_methods;
for (uint32_t i = 0; i < kHashTableSize; ++i) {
for (Site* s = sites_[i]; s != NULL; s = s->next) {
for (int i = 0; i < s->nframes; ++i) {
const jmethodID method = s->stack[i];

if (seen_methods.find(method) != seen_methods.end())
continue;

uintptr_t frame = reinterpret_cast<uintptr_t>(method);
char* method_name;
jvmtiError error =
jvmti_->GetMethodName(method, &method_name, NULL, NULL);
if (error != JVMTI_ERROR_NONE)
continue;

jclass declaring_class;
error = jvmti_->GetMethodDeclaringClass(method, &declaring_class);
if (error != JVMTI_ERROR_NONE)
continue;

char* class_name;
error = jvmti_->GetClassSignature(
declaring_class, &class_name, NULL);
if (error != JVMTI_ERROR_NONE)
continue;

#ifdef __x86_64
fprintf(file, "0x%016lx %s%s\n", frame, class_name, method_name);
#else
fprintf(file, "0x%08lx %s%s\n", frame, class_name, method_name);
#endif

seen_methods.insert(method);
}
}
}

fprintf(file, "---\n");
fprintf(file, "--- profile\n");
fflush(file);

int count = 0;
int total_num_bytes = 0, total_num_allocs = 0;

uintptr_t buf[2 + kMaxStackFrames];

// Write out the header.
buf[0] = 0;
buf[1] = 3;
buf[2] = 0;
buf[3] = 1;
buf[4] = 0;

AtomicWrite(fd, reinterpret_cast<char*>(buf), sizeof(buf[0]) * 5);

for (uint32_t i = 0; i < kHashTableSize; ++i) {
for (Site* s = sites_[i]; s != NULL; s = s->next) {
buf[0] = s->num_bytes; // nsamples
buf[1] = s->nframes; // depth
memcpy(&buf[2], s->stack, s->nframes * sizeof(s->stack[0]));

AtomicWrite(
fd, reinterpret_cast<char*>(buf),
sizeof(buf[0]) * (2 + s->nframes));

++count;
total_num_bytes += s->num_bytes;
total_num_allocs += s->num_allocs;
}
}

close(fd);
/* nothing here */
}

void JNICALL ObjectFree(jlong tag) {
Expand Down Expand Up @@ -484,6 +414,100 @@ class Heapster {
jvmti_->SetTag(o, reinterpret_cast<jlong>(alloc));
}

const string DumpProfile(JNIEnv* env, jclass klass) {
string prof = "";

jvmtiError error = jvmti_->ForceGarbageCollection();
if (error != JVMTI_ERROR_NONE)
warnx("Failed to force garbage collection.\n");

// TODO: change "binary" to main class name?
prof += "--- symbol\nbinary=heapster\n";

// Write out symbol information (traverse the sites & resolve
// method names.)
set<jmethodID> seen_methods;
for (uint32_t i = 0; i < kHashTableSize; ++i) {
for (Site* s = sites_[i]; s != NULL; s = s->next) {
for (int i = 0; i < s->nframes; ++i) {
const jmethodID method = s->stack[i];

if (seen_methods.find(method) != seen_methods.end())
continue;

uintptr_t frame = reinterpret_cast<uintptr_t>(method);
char* method_name;
jvmtiError error =
jvmti_->GetMethodName(method, &method_name, NULL, NULL);
if (error != JVMTI_ERROR_NONE)
continue;

jclass declaring_class;
error = jvmti_->GetMethodDeclaringClass(method, &declaring_class);
if (error != JVMTI_ERROR_NONE)
continue;

char* class_name;
error = jvmti_->GetClassSignature(
declaring_class, &class_name, NULL);
if (error != JVMTI_ERROR_NONE)
continue;

#ifdef __x86_64
prof += StringPrintf(
"0x%016lx %s%s\n", frame, class_name, method_name);
#else
prof += StringPrintf(
"0x%08lx %s%s\n", frame, class_name, method_name);
#endif

seen_methods.insert(method);
}
}
}

prof += "---\n";
prof += "--- profile\n";

int count = 0;
int total_num_bytes = 0, total_num_allocs = 0;

uintptr_t buf[2 + kMaxStackFrames];

// Write out the header.
buf[0] = 0;
buf[1] = 3;
buf[2] = 0;
buf[3] = 1;
buf[4] = 0;

prof.append(reinterpret_cast<char*>(buf), sizeof(buf[0]) * 5);

// AtomicWrite(fd, reinterpret_cast<char*>(buf), sizeof(buf[0]) * 5);

for (uint32_t i = 0; i < kHashTableSize; ++i) {
for (Site* s = sites_[i]; s != NULL; s = s->next) {
buf[0] = s->num_bytes; // nsamples
buf[1] = s->nframes; // depth
memcpy(&buf[2], s->stack, s->nframes * sizeof(s->stack[0]));

// prof += ...

prof.append(reinterpret_cast<char*>(buf),
sizeof(buf[0]) * (2 + s->nframes));
// AtomicWrite(
// fd, reinterpret_cast<char*>(buf),
// sizeof(buf[0]) * (2 + s->nframes));

++count;
total_num_bytes += s->num_bytes;
total_num_allocs += s->num_allocs;
}
}

return prof;
}

jvmtiEnv* jvmti() { return jvmti_; }

private:
Expand Down
14 changes: 14 additions & 0 deletions util.cc
@@ -0,0 +1,14 @@
// Lifted from google-perftools.

#include <string>

using namespace std;

string StringPrintf(const char* format, ...) {
char buf[10240];
va_list ap;
va_start(ap, format);
vsnprintf(buf, sizeof(buf), format, ap);
va_end(ap);
return buf; // implicit conversion
}
6 changes: 6 additions & 0 deletions util.h
@@ -0,0 +1,6 @@
#ifndef HEAPSTER_UTIL_H_
#define HEAPSTER_UTIL_H_

std::string StringPrintf(const char* format, ...);

#endif // HEAPSTER_UTIL_H_

0 comments on commit 6bdaed9

Please sign in to comment.