OSDN Git Service

Add support for streaming hprof dumps.
authorAndy McFadden <fadden@android.com>
Fri, 29 Jan 2010 01:01:39 +0000 (17:01 -0800)
committerAndy McFadden <fadden@android.com>
Fri, 29 Jan 2010 21:23:39 +0000 (13:23 -0800)
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.

libcore/dalvik/src/main/java/dalvik/system/VMDebug.java
vm/SignalCatcher.c
vm/alloc/Heap.c
vm/alloc/HeapInternal.h
vm/hprof/Hprof.c
vm/hprof/Hprof.h
vm/hprof/HprofOutput.c
vm/native/dalvik_system_VMDebug.c

index cfae706..ce3e95c 100644 (file)
@@ -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
index 90211fd..7edbf38 100644 (file)
@@ -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);
index bec30f3..4774819 100644 (file)
@@ -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;
 
index a2d31fe..9a5071f 100644 (file)
@@ -170,6 +170,7 @@ struct GcHeap {
     const char*     hprofFileName;
     hprof_context_t *hprofContext;
     int             hprofResult;
+    bool            hprofDirectToDdms;
 #endif
 };
 
index 2e6f7c9..8380fd8 100644 (file)
 #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);
+}
+
index 696b0a7..db5049f 100644 (file)
@@ -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
index c6d1cbc..0677c85 100644 (file)
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 #include <sys/time.h>
+#include <cutils/open_memstream.h>
 #include <time.h>
+#include <errno.h>
 #include "Hprof.h"
 
 #define HPROF_MAGIC_STRING  "JAVA PROFILE 1.0.3"
         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;
 
index 3f0b5fc..1d2f024 100644 (file)
@@ -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",