From 6bf992c9d51f1e12aa37fe4c791c156402a9b79b Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Thu, 28 Jan 2010 17:01:39 -0800 Subject: [PATCH] Add support for streaming hprof dumps. This adds the dumpHprofDataDdms method, which generates the hprof dump in RAM and then spits the whole thing at DDMS. The idea is to avoid touching /sdcard, since not all apps have permission to do that. This rearranges hprofShutdown() a fair bit. It used to re-use a context struct, saving interesting bits to local variables before zapping it; now we just create a second context struct and free both at the end. For bug 2092855. --- .../src/main/java/dalvik/system/VMDebug.java | 10 ++ vm/SignalCatcher.c | 2 +- vm/alloc/Heap.c | 6 +- vm/alloc/HeapInternal.h | 1 + vm/hprof/Hprof.c | 151 +++++++++++++-------- vm/hprof/Hprof.h | 21 ++- vm/hprof/HprofOutput.c | 26 +++- vm/native/dalvik_system_VMDebug.c | 31 ++++- 8 files changed, 183 insertions(+), 65 deletions(-) diff --git a/libcore/dalvik/src/main/java/dalvik/system/VMDebug.java b/libcore/dalvik/src/main/java/dalvik/system/VMDebug.java index cfae7060f..ce3e95c3e 100644 --- a/libcore/dalvik/src/main/java/dalvik/system/VMDebug.java +++ b/libcore/dalvik/src/main/java/dalvik/system/VMDebug.java @@ -311,6 +311,16 @@ public final class VMDebug { public static native void dumpHprofData(String fileName) throws IOException; /** + * Collect "hprof" and send it to DDMS. This will cause a GC. + * + * @throws UnsupportedOperationException if the VM was built without + * HPROF support. + * + * @hide + */ + public static native void dumpHprofDataDdms(); + + /** * Primes the register map cache. * * @hide diff --git a/vm/SignalCatcher.c b/vm/SignalCatcher.c index 90211fd24..7edbf3888 100644 --- a/vm/SignalCatcher.c +++ b/vm/SignalCatcher.c @@ -255,7 +255,7 @@ loop: } else if (rcvd == SIGUSR1) { #if WITH_HPROF LOGI("SIGUSR1 forcing GC and HPROF dump\n"); - hprofDumpHeap(NULL); + hprofDumpHeap(NULL, false); #else LOGI("SIGUSR1 forcing GC (no HPROF)\n"); dvmCollectGarbage(false); diff --git a/vm/alloc/Heap.c b/vm/alloc/Heap.c index bec30f3b7..477481934 100644 --- a/vm/alloc/Heap.c +++ b/vm/alloc/Heap.c @@ -856,7 +856,8 @@ void dvmCollectGarbageInternal(bool collectSoftReferences, enum GcReason reason) (int) time(NULL), (int) getpid()); gcHeap->hprofFileName = nameBuf; } - gcHeap->hprofContext = hprofStartup(gcHeap->hprofFileName); + gcHeap->hprofContext = hprofStartup(gcHeap->hprofFileName, + gcHeap->hprofDirectToDdms); if (gcHeap->hprofContext != NULL) { hprofStartHeapDump(gcHeap->hprofContext); } @@ -1071,7 +1072,7 @@ void dvmCollectGarbageInternal(bool collectSoftReferences, enum GcReason reason) * * Returns 0 on success, or an error code on failure. */ -int hprofDumpHeap(const char* fileName) +int hprofDumpHeap(const char* fileName, bool directToDdms) { int result; @@ -1079,6 +1080,7 @@ int hprofDumpHeap(const char* fileName) gDvm.gcHeap->hprofDumpOnGc = true; gDvm.gcHeap->hprofFileName = fileName; + gDvm.gcHeap->hprofDirectToDdms = directToDdms; dvmCollectGarbageInternal(false, GC_HPROF_DUMP_HEAP); result = gDvm.gcHeap->hprofResult; diff --git a/vm/alloc/HeapInternal.h b/vm/alloc/HeapInternal.h index a2d31fe74..9a5071fae 100644 --- a/vm/alloc/HeapInternal.h +++ b/vm/alloc/HeapInternal.h @@ -170,6 +170,7 @@ struct GcHeap { const char* hprofFileName; hprof_context_t *hprofContext; int hprofResult; + bool hprofDirectToDdms; #endif }; diff --git a/vm/hprof/Hprof.c b/vm/hprof/Hprof.c index 2e6f7c93c..8380fd8cf 100644 --- a/vm/hprof/Hprof.c +++ b/vm/hprof/Hprof.c @@ -33,15 +33,13 @@ #define kHeadSuffix "-hptemp" hprof_context_t * -hprofStartup(const char *outputFileName) +hprofStartup(const char *outputFileName, bool directToDdms) { - hprof_context_t *ctx; + FILE* fp = NULL; - ctx = malloc(sizeof(*ctx)); - if (ctx != NULL) { + if (!directToDdms) { int len = strlen(outputFileName); char fileName[len + sizeof(kHeadSuffix)]; - FILE *fp; /* Construct the temp file name. This wasn't handed to us by the * application, so we need to be careful about stomping on it. @@ -49,14 +47,12 @@ hprofStartup(const char *outputFileName) sprintf(fileName, "%s" kHeadSuffix, outputFileName); if (access(fileName, F_OK) == 0) { LOGE("hprof: temp file %s exists, bailing\n", fileName); - free(ctx); return NULL; } fp = fopen(fileName, "w+"); if (fp == NULL) { LOGE("hprof: can't open %s: %s.\n", fileName, strerror(errno)); - free(ctx); return NULL; } if (unlink(fileName) != 0) { @@ -64,20 +60,28 @@ hprofStartup(const char *outputFileName) /* keep going */ } LOGI("hprof: dumping VM heap to \"%s\".\n", fileName); + } - hprofStartup_String(); - hprofStartup_Class(); + hprofStartup_String(); + hprofStartup_Class(); #if WITH_HPROF_STACK - hprofStartup_StackFrame(); - hprofStartup_Stack(); + hprofStartup_StackFrame(); + hprofStartup_Stack(); #endif - /* pass in "fp" for the temp file, and the name of the output file */ - hprofContextInit(ctx, strdup(outputFileName), fp, false); - } else { + hprof_context_t *ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) { LOGE("hprof: can't allocate context.\n"); + if (fp != NULL) + fclose(fp); + return NULL; } + /* pass in "fp" for the temp file, and the name of the output file */ + hprofContextInit(ctx, strdup(outputFileName), fp, false, directToDdms); + + assert(ctx->fp != NULL); + return ctx; } @@ -115,46 +119,55 @@ copyFileToFile(FILE *dstFp, FILE *srcFp) * Finish up the hprof dump. Returns true on success. */ bool -hprofShutdown(hprof_context_t *ctx) +hprofShutdown(hprof_context_t *tailCtx) { - FILE *tempFp = ctx->fp; - FILE *fp; + FILE *fp = NULL; /* flush output to the temp file, then prepare the output file */ - hprofFlushCurrentRecord(ctx); - free(ctx->curRec.body); - ctx->curRec.body = NULL; - ctx->curRec.allocLen = 0; - ctx->fp = NULL; - - LOGI("hprof: dumping heap strings to \"%s\".\n", ctx->fileName); - fp = fopen(ctx->fileName, "w"); - if (fp == NULL) { - LOGE("can't open %s: %s\n", ctx->fileName, strerror(errno)); - fclose(tempFp); - free(ctx->fileName); - free(ctx); - return false; + hprofFlushCurrentRecord(tailCtx); + + LOGI("hprof: dumping heap strings to \"%s\".\n", tailCtx->fileName); + if (!tailCtx->directToDdms) { + fp = fopen(tailCtx->fileName, "w"); + if (fp == NULL) { + LOGE("can't open %s: %s\n", tailCtx->fileName, strerror(errno)); + hprofFreeContext(tailCtx); + return false; + } + } + + /* + * Create a new context struct for the start of the file. We + * heap-allocate it so we can share the "free" function. + */ + hprof_context_t *headCtx = malloc(sizeof(*headCtx)); + if (headCtx == NULL) { + LOGE("hprof: can't allocate context.\n"); + if (fp != NULL) + fclose(fp); + hprofFreeContext(tailCtx); + return NULL; } - hprofContextInit(ctx, ctx->fileName, fp, true); + hprofContextInit(headCtx, strdup(tailCtx->fileName), fp, true, + tailCtx->directToDdms); - hprofDumpStrings(ctx); - hprofDumpClasses(ctx); + hprofDumpStrings(headCtx); + hprofDumpClasses(headCtx); /* Write a dummy stack trace record so the analysis * tools don't freak out. */ - hprofStartNewRecord(ctx, HPROF_TAG_STACK_TRACE, HPROF_TIME); - hprofAddU4ToRecord(&ctx->curRec, HPROF_NULL_STACK_TRACE); - hprofAddU4ToRecord(&ctx->curRec, HPROF_NULL_THREAD); - hprofAddU4ToRecord(&ctx->curRec, 0); // no frames + hprofStartNewRecord(headCtx, HPROF_TAG_STACK_TRACE, HPROF_TIME); + hprofAddU4ToRecord(&headCtx->curRec, HPROF_NULL_STACK_TRACE); + hprofAddU4ToRecord(&headCtx->curRec, HPROF_NULL_THREAD); + hprofAddU4ToRecord(&headCtx->curRec, 0); // no frames #if WITH_HPROF_STACK - hprofDumpStackFrames(ctx); - hprofDumpStacks(ctx); + hprofDumpStackFrames(headCtx); + hprofDumpStacks(headCtx); #endif - hprofFlushCurrentRecord(ctx); + hprofFlushCurrentRecord(headCtx); hprofShutdown_Class(); hprofShutdown_String(); @@ -163,24 +176,52 @@ hprofShutdown(hprof_context_t *ctx) hprofShutdown_StackFrame(); #endif - /* - * Append the contents of the temp file to the output file. The temp - * file was removed immediately after being opened, so it will vanish - * when we close it. - */ - rewind(tempFp); - if (!copyFileToFile(ctx->fp, tempFp)) { - LOGW("hprof: file copy failed, hprof data may be incomplete\n"); - /* finish up anyway */ + if (tailCtx->directToDdms) { + /* flush to ensure memstream pointer and size are updated */ + fflush(headCtx->fp); + fflush(tailCtx->fp); + + /* send the data off to DDMS */ + struct iovec iov[2]; + iov[0].iov_base = headCtx->fileDataPtr; + iov[0].iov_len = headCtx->fileDataSize; + iov[1].iov_base = tailCtx->fileDataPtr; + iov[1].iov_len = tailCtx->fileDataSize; + dvmDbgDdmSendChunkV(CHUNK_TYPE("HPDS"), iov, 2); + } else { + /* + * Append the contents of the temp file to the output file. The temp + * file was removed immediately after being opened, so it will vanish + * when we close it. + */ + rewind(tailCtx->fp); + if (!copyFileToFile(headCtx->fp, tailCtx->fp)) { + LOGW("hprof: file copy failed, hprof data may be incomplete\n"); + /* finish up anyway */ + } } - fclose(tempFp); - fclose(ctx->fp); - free(ctx->fileName); - free(ctx->curRec.body); - free(ctx); + hprofFreeContext(headCtx); + hprofFreeContext(tailCtx); /* throw out a log message for the benefit of "runhat" */ LOGI("hprof: heap dump completed, temp file removed\n"); return true; } + +/* + * Free any heap-allocated items in "ctx", and then free "ctx" itself. + */ +void +hprofFreeContext(hprof_context_t *ctx) +{ + assert(ctx != NULL); + + if (ctx->fp != NULL) + fclose(ctx->fp); + free(ctx->curRec.body); + free(ctx->fileName); + free(ctx->fileDataPtr); + free(ctx); +} + diff --git a/vm/hprof/Hprof.h b/vm/hprof/Hprof.h index 696b0a7d9..db5049f08 100644 --- a/vm/hprof/Hprof.h +++ b/vm/hprof/Hprof.h @@ -125,13 +125,23 @@ typedef struct hprof_context_t { * can cast from a context to a record. */ hprof_record_t curRec; - char *fileName; - FILE *fp; + u4 gcThreadSerialNumber; u1 gcScanState; HprofHeapId currentHeap; // which heap we're currently emitting u4 stackTraceSerialNumber; size_t objectsInSegment; + + /* + * If "directToDdms" is not set, "fileName" is valid, and "fileDataPtr" + * and "fileDataSize" are not used. If "directToDdms" is not set, + * it's the other way around. + */ + bool directToDdms; + char *fileName; + char *fileDataPtr; // for open_memstream + size_t fileDataSize; // for open_memstream + FILE *fp; } hprof_context_t; @@ -178,7 +188,7 @@ int hprofDumpHeapObject(hprof_context_t *ctx, const Object *obj); */ void hprofContextInit(hprof_context_t *ctx, char *fileName, FILE *fp, - bool writeHeader); + bool writeHeader, bool directToDdms); int hprofFlushRecord(hprof_record_t *rec, FILE *fp); int hprofFlushCurrentRecord(hprof_context_t *ctx); @@ -234,8 +244,9 @@ int hprofShutdown_StackFrame(void); * Hprof.c functions */ -hprof_context_t *hprofStartup(const char *outputFileName); +hprof_context_t* hprofStartup(const char *outputFileName, bool directToDdms); bool hprofShutdown(hprof_context_t *ctx); +void hprofFreeContext(hprof_context_t *ctx); /* * Heap.c functions @@ -244,7 +255,7 @@ bool hprofShutdown(hprof_context_t *ctx); * the heap implementation; these functions require heap knowledge, * so they are implemented in Heap.c. */ -int hprofDumpHeap(const char* fileName); +int hprofDumpHeap(const char* fileName, bool directToDdms); void dvmHeapSetHprofGcScanState(hprof_heap_tag_t state, u4 threadSerialNumber); #endif // _DALVIK_HPROF_HPROF diff --git a/vm/hprof/HprofOutput.c b/vm/hprof/HprofOutput.c index c6d1cbce8..0677c85d2 100644 --- a/vm/hprof/HprofOutput.c +++ b/vm/hprof/HprofOutput.c @@ -14,7 +14,9 @@ * limitations under the License. */ #include +#include #include +#include #include "Hprof.h" #define HPROF_MAGIC_STRING "JAVA PROFILE 1.0.3" @@ -54,11 +56,33 @@ buf_[offset_ + 7] = (unsigned char)(value_ ); \ } while (0) +/* + * Initialize an hprof context struct. + * + * This will take ownership of "fileName" and "fp". + */ void hprofContextInit(hprof_context_t *ctx, char *fileName, FILE *fp, - bool writeHeader) + bool writeHeader, bool directToDdms) { memset(ctx, 0, sizeof (*ctx)); + + if (directToDdms) { + /* + * Have to do this here, because it must happen after we + * memset the struct (want to treat fileDataPtr/fileDataSize + * as read-only while the file is open). + */ + assert(fp == NULL); + fp = open_memstream(&ctx->fileDataPtr, &ctx->fileDataSize); + if (fp == NULL) { + /* not expected */ + LOGE("hprof: open_memstream failed: %s\n", strerror(errno)); + dvmAbort(); + } + } + + ctx->directToDdms = directToDdms; ctx->fileName = fileName; ctx->fp = fp; diff --git a/vm/native/dalvik_system_VMDebug.c b/vm/native/dalvik_system_VMDebug.c index 3f0b5fc48..1d2f024f7 100644 --- a/vm/native/dalvik_system_VMDebug.c +++ b/vm/native/dalvik_system_VMDebug.c @@ -93,6 +93,7 @@ static void Dalvik_dalvik_system_VMDebug_getVmFeatureList(const u4* args, #ifdef WITH_HPROF /* VM responds to DDMS heap dump requests */ features[idx++] = "hprof-heap-dump"; + features[idx++] = "hprof-heap-dump-streaming"; #endif assert(idx <= MAX_FEATURE_COUNT); @@ -664,7 +665,7 @@ static void Dalvik_dalvik_system_VMDebug_dumpHprofData(const u4* args, RETURN_VOID(); } - result = hprofDumpHeap(fileName); + result = hprofDumpHeap(fileName, false); free(fileName); if (result != 0) { @@ -681,6 +682,32 @@ static void Dalvik_dalvik_system_VMDebug_dumpHprofData(const u4* args, } /* + * static void dumpHprofDataDdms() + * + * Cause "hprof" data to be computed and sent directly to DDMS. + */ +static void Dalvik_dalvik_system_VMDebug_dumpHprofDataDdms(const u4* args, + JValue* pResult) +{ +#ifdef WITH_HPROF + int result; + + result = hprofDumpHeap("[DDMS]", true); + + if (result != 0) { + /* ideally we'd throw something more specific based on actual failure */ + dvmThrowException("Ljava/lang/RuntimeException;", + "Failure during heap dump -- check log output for details"); + RETURN_VOID(); + } +#else + dvmThrowException("Ljava/lang/UnsupportedOperationException;", NULL); +#endif + + RETURN_VOID(); +} + +/* * static boolean cacheRegisterMap(String classAndMethodDescr) * * If the specified class is loaded, and the named method exists, ensure @@ -876,6 +903,8 @@ const DalvikNativeMethod dvm_dalvik_system_VMDebug[] = { Dalvik_dalvik_system_VMDebug_threadCpuTimeNanos }, { "dumpHprofData", "(Ljava/lang/String;)V", Dalvik_dalvik_system_VMDebug_dumpHprofData }, + { "dumpHprofDataDdms", "()V", + Dalvik_dalvik_system_VMDebug_dumpHprofDataDdms }, { "cacheRegisterMap", "(Ljava/lang/String;)Z", Dalvik_dalvik_system_VMDebug_cacheRegisterMap }, { "dumpReferenceTables", "()V", -- 2.11.0