Add hprof. This code builds but is untested.
Change-Id: Ied6549ef903761447fb3d28cf28b8bbc76e5ef35
diff --git a/build/Android.common.mk b/build/Android.common.mk
index 4fb0187..1aebb65 100644
--- a/build/Android.common.mk
+++ b/build/Android.common.mk
@@ -120,6 +120,11 @@
src/file_linux.cc \
src/heap.cc \
src/heap_bitmap.cc \
+ src/hprof/hprof.cc \
+ src/hprof/hprof_class.cc \
+ src/hprof/hprof_heap.cc \
+ src/hprof/hprof_output.cc \
+ src/hprof/hprof_string.cc \
src/image.cc \
src/image_writer.cc \
src/indirect_reference_table.cc \
diff --git a/src/hprof/hprof.cc b/src/hprof/hprof.cc
new file mode 100644
index 0000000..9415549
--- /dev/null
+++ b/src/hprof/hprof.cc
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Preparation and completion of hprof data generation. The output is
+ * written into two files and then combined. This is necessary because
+ * we generate some of the data (strings and classes) while we dump the
+ * heap, and some analysis tools require that the class and string data
+ * appear first.
+ */
+
+#include "hprof.h"
+#include "heap.h"
+#include "debugger.h"
+#include "stringprintf.h"
+#include "thread_list.h"
+#include "logging.h"
+
+#include <sys/uio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <time.h>
+
+namespace art {
+
+namespace hprof {
+
+#define kHeadSuffix "-hptemp"
+
+hprof_context_t* hprofStartup(const char *outputFileName, int fd,
+ bool directToDdms)
+{
+ hprofStartup_String();
+ hprofStartup_Class();
+
+ hprof_context_t *ctx = (hprof_context_t *)malloc(sizeof(*ctx));
+ if (ctx == NULL) {
+ LOG(ERROR) << "hprof: can't allocate context.";
+ return NULL;
+ }
+
+ /* pass in name or descriptor of the output file */
+ hprofContextInit(ctx, strdup(outputFileName), fd, false, directToDdms);
+
+ CHECK(ctx->memFp != NULL);
+
+ return ctx;
+}
+
+// TODO: use File::WriteFully
+int sysWriteFully(int fd, const void* buf, size_t count, const char* logMsg)
+{
+ while (count != 0) {
+ ssize_t actual = TEMP_FAILURE_RETRY(write(fd, buf, count));
+ if (actual < 0) {
+ int err = errno;
+ LOG(ERROR) << StringPrintf("%s: write failed: %s", logMsg, strerror(err));
+ return err;
+ } else if (actual != (ssize_t) count) {
+ LOG(DEBUG) << StringPrintf("%s: partial write (will retry): (%d of %zd)",
+ logMsg, (int) actual, count);
+ buf = (const void*) (((const uint8_t*) buf) + actual);
+ }
+ count -= actual;
+ }
+
+ return 0;
+}
+
+/*
+ * Finish up the hprof dump. Returns true on success.
+ */
+bool hprofShutdown(hprof_context_t *tailCtx)
+{
+ /* flush the "tail" portion of the output */
+ hprofFlushCurrentRecord(tailCtx);
+
+ /*
+ * 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 = (hprof_context_t *)malloc(sizeof(*headCtx));
+ if (headCtx == NULL) {
+ LOG(ERROR) << "hprof: can't allocate context.";
+ hprofFreeContext(tailCtx);
+ return false;
+ }
+ hprofContextInit(headCtx, strdup(tailCtx->fileName), tailCtx->fd, true,
+ tailCtx->directToDdms);
+
+ LOG(INFO) << StringPrintf("hprof: dumping heap strings to \"%s\".", tailCtx->fileName);
+ hprofDumpStrings(headCtx);
+ hprofDumpClasses(headCtx);
+
+ /* Write a dummy stack trace record so the analysis
+ * tools don't freak out.
+ */
+ 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
+
+ hprofFlushCurrentRecord(headCtx);
+
+ hprofShutdown_Class();
+ hprofShutdown_String();
+
+ /* flush to ensure memstream pointer and size are updated */
+ fflush(headCtx->memFp);
+ fflush(tailCtx->memFp);
+
+ if (tailCtx->directToDdms) {
+ /* 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;
+ Dbg::DdmSendChunkV(CHUNK_TYPE("HPDS"), iov, 2);
+ } else {
+ /*
+ * Open the output file, and copy the head and tail to it.
+ */
+ CHECK(headCtx->fd == tailCtx->fd);
+
+ int outFd;
+ if (headCtx->fd >= 0) {
+ outFd = dup(headCtx->fd);
+ if (outFd < 0) {
+ LOG(ERROR) << StringPrintf("dup(%d) failed: %s", headCtx->fd, strerror(errno));
+ /* continue to fail-handler below */
+ }
+ } else {
+ outFd = open(tailCtx->fileName, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (outFd < 0) {
+ LOG(ERROR) << StringPrintf("can't open %s: %s", headCtx->fileName, strerror(errno));
+ /* continue to fail-handler below */
+ }
+ }
+ if (outFd < 0) {
+ hprofFreeContext(headCtx);
+ hprofFreeContext(tailCtx);
+ return false;
+ }
+
+ int result;
+ result = sysWriteFully(outFd, headCtx->fileDataPtr,
+ headCtx->fileDataSize, "hprof-head");
+ result |= sysWriteFully(outFd, tailCtx->fileDataPtr,
+ tailCtx->fileDataSize, "hprof-tail");
+ close(outFd);
+ if (result != 0) {
+ hprofFreeContext(headCtx);
+ hprofFreeContext(tailCtx);
+ return false;
+ }
+ }
+
+ /* throw out a log message for the benefit of "runhat" */
+ LOG(INFO) << StringPrintf("hprof: heap dump completed (%dKB)",
+ (headCtx->fileDataSize + tailCtx->fileDataSize + 1023) / 1024);
+
+ hprofFreeContext(headCtx);
+ hprofFreeContext(tailCtx);
+
+ return true;
+}
+
+/*
+ * Free any heap-allocated items in "ctx", and then free "ctx" itself.
+ */
+void hprofFreeContext(hprof_context_t *ctx)
+{
+ CHECK(ctx != NULL);
+
+ /* we don't own ctx->fd, do not close */
+
+ if (ctx->memFp != NULL)
+ fclose(ctx->memFp);
+ free(ctx->curRec.body);
+ free(ctx->fileName);
+ free(ctx->fileDataPtr);
+ free(ctx);
+}
+
+/*
+ * Visitor invoked on every root reference.
+ */
+void HprofRootVisitor(const Object* obj, void* arg) {
+ uint32_t threadId = 0; // TODO
+ /*RootType */ size_t type = 0; // TODO
+
+ static const hprof_heap_tag_t xlate[] = {
+ HPROF_ROOT_UNKNOWN,
+ HPROF_ROOT_JNI_GLOBAL,
+ HPROF_ROOT_JNI_LOCAL,
+ HPROF_ROOT_JAVA_FRAME,
+ HPROF_ROOT_NATIVE_STACK,
+ HPROF_ROOT_STICKY_CLASS,
+ HPROF_ROOT_THREAD_BLOCK,
+ HPROF_ROOT_MONITOR_USED,
+ HPROF_ROOT_THREAD_OBJECT,
+ HPROF_ROOT_INTERNED_STRING,
+ HPROF_ROOT_FINALIZING,
+ HPROF_ROOT_DEBUGGER,
+ HPROF_ROOT_REFERENCE_CLEANUP,
+ HPROF_ROOT_VM_INTERNAL,
+ HPROF_ROOT_JNI_MONITOR,
+ };
+ hprof_context_t *ctx;
+
+ CHECK(arg != NULL);
+ CHECK(type < (sizeof(xlate) / sizeof(hprof_heap_tag_t)));
+ if (obj == NULL) {
+ return;
+ }
+ ctx = (hprof_context_t *)arg;
+ ctx->gcScanState = xlate[type];
+ ctx->gcThreadSerialNumber = threadId;
+ hprofMarkRootObject(ctx, obj, 0);
+ ctx->gcScanState = 0;
+ ctx->gcThreadSerialNumber = 0;
+}
+
+/*
+ * Visitor invoked on every heap object.
+ */
+
+static void HprofBitmapCallback(Object *obj, void *arg)
+{
+ CHECK(obj != NULL);
+ CHECK(arg != NULL);
+ hprof_context_t *ctx = (hprof_context_t *)arg;
+ hprofDumpHeapObject(ctx, obj);
+}
+
+/*
+ * Walk the roots and heap writing heap information to the specified
+ * file.
+ *
+ * If "fd" is >= 0, the output will be written to that file descriptor.
+ * Otherwise, "fileName" is used to create an output file.
+ *
+ * If "directToDdms" is set, the other arguments are ignored, and data is
+ * sent directly to DDMS.
+ *
+ * Returns 0 on success, or an error code on failure.
+ */
+int hprofDumpHeap(const char* fileName, int fd, bool directToDdms)
+{
+ hprof_context_t *ctx;
+ int success;
+
+ CHECK(fileName != NULL);
+ ScopedHeapLock lock;
+
+ ThreadList* thread_list = Runtime::Current()->GetThreadList();
+ thread_list->SuspendAll();
+
+ ctx = hprofStartup(fileName, fd, directToDdms);
+ if (ctx == NULL) {
+ return -1;
+ }
+ Runtime::Current()->VisitRoots(HprofRootVisitor, ctx);
+ Heap::GetLiveBits()->Walk(HprofBitmapCallback, ctx);
+ hprofFinishHeapDump(ctx);
+//TODO: write a HEAP_SUMMARY record
+ success = hprofShutdown(ctx) ? 0 : -1;
+ thread_list->ResumeAll();
+ return success;
+}
+
+} // namespace hprof
+
+} // namespace art
diff --git a/src/hprof/hprof.h b/src/hprof/hprof.h
new file mode 100644
index 0000000..092f00d
--- /dev/null
+++ b/src/hprof/hprof.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef HPROF_HPROF_H_
+#define HPROF_HPROF_H_
+
+#include <stdio.h>
+#include "globals.h"
+#include "file.h"
+#include "object.h"
+
+namespace art {
+
+namespace hprof {
+
+#define HPROF_ID_SIZE (sizeof (uint32_t))
+
+#define UNIQUE_ERROR() \
+ -((((uintptr_t)__func__) << 16 | __LINE__) & (0x7fffffff))
+
+#define HPROF_TIME 0
+#define HPROF_NULL_STACK_TRACE 0
+#define HPROF_NULL_THREAD 0
+
+typedef uint32_t hprof_id;
+typedef hprof_id hprof_string_id;
+typedef hprof_id hprof_object_id;
+typedef hprof_id hprof_class_object_id;
+
+enum hprof_basic_type {
+ hprof_basic_object = 2,
+ hprof_basic_boolean = 4,
+ hprof_basic_char = 5,
+ hprof_basic_float = 6,
+ hprof_basic_double = 7,
+ hprof_basic_byte = 8,
+ hprof_basic_short = 9,
+ hprof_basic_int = 10,
+ hprof_basic_long = 11,
+};
+
+enum hprof_tag_t {
+ HPROF_TAG_STRING = 0x01,
+ HPROF_TAG_LOAD_CLASS = 0x02,
+ HPROF_TAG_UNLOAD_CLASS = 0x03,
+ HPROF_TAG_STACK_FRAME = 0x04,
+ HPROF_TAG_STACK_TRACE = 0x05,
+ HPROF_TAG_ALLOC_SITES = 0x06,
+ HPROF_TAG_HEAP_SUMMARY = 0x07,
+ HPROF_TAG_START_THREAD = 0x0A,
+ HPROF_TAG_END_THREAD = 0x0B,
+ HPROF_TAG_HEAP_DUMP = 0x0C,
+ HPROF_TAG_HEAP_DUMP_SEGMENT = 0x1C,
+ HPROF_TAG_HEAP_DUMP_END = 0x2C,
+ HPROF_TAG_CPU_SAMPLES = 0x0D,
+ HPROF_TAG_CONTROL_SETTINGS = 0x0E,
+};
+
+/* Values for the first byte of
+ * HEAP_DUMP and HEAP_DUMP_SEGMENT
+ * records:
+ */
+enum hprof_heap_tag_t {
+ /* standard */
+ HPROF_ROOT_UNKNOWN = 0xFF,
+ HPROF_ROOT_JNI_GLOBAL = 0x01,
+ HPROF_ROOT_JNI_LOCAL = 0x02,
+ HPROF_ROOT_JAVA_FRAME = 0x03,
+ HPROF_ROOT_NATIVE_STACK = 0x04,
+ HPROF_ROOT_STICKY_CLASS = 0x05,
+ HPROF_ROOT_THREAD_BLOCK = 0x06,
+ HPROF_ROOT_MONITOR_USED = 0x07,
+ HPROF_ROOT_THREAD_OBJECT = 0x08,
+ HPROF_CLASS_DUMP = 0x20,
+ HPROF_INSTANCE_DUMP = 0x21,
+ HPROF_OBJECT_ARRAY_DUMP = 0x22,
+ HPROF_PRIMITIVE_ARRAY_DUMP = 0x23,
+
+ /* Android */
+ HPROF_HEAP_DUMP_INFO = 0xfe,
+ HPROF_ROOT_INTERNED_STRING = 0x89,
+ HPROF_ROOT_FINALIZING = 0x8a, /* obsolete */
+ HPROF_ROOT_DEBUGGER = 0x8b,
+ HPROF_ROOT_REFERENCE_CLEANUP = 0x8c, /* obsolete */
+ HPROF_ROOT_VM_INTERNAL = 0x8d,
+ HPROF_ROOT_JNI_MONITOR = 0x8e,
+ HPROF_UNREACHABLE = 0x90, /* obsolete */
+ HPROF_PRIMITIVE_ARRAY_NODATA_DUMP = 0xc3,
+};
+
+/* Represents a top-level hprof record, whose serialized
+ * format is:
+ *
+ * uint8_t TAG: denoting the type of the record
+ * uint32_t TIME: number of microseconds since the time stamp in the header
+ * uint32_t LENGTH: number of bytes that follow this uint32_t field
+ * and belong to this record
+ * [uint8_t]* BODY: as many bytes as specified in the above uint32_t field
+ */
+struct hprof_record_t {
+ unsigned char *body;
+ uint32_t time;
+ uint32_t length;
+ size_t allocLen;
+ uint8_t tag;
+ bool dirty;
+};
+
+enum HprofHeapId {
+ HPROF_HEAP_DEFAULT = 0,
+ HPROF_HEAP_ZYGOTE = 'Z',
+ HPROF_HEAP_APP = 'A'
+};
+
+struct hprof_context_t {
+ /* curRec *must* be first so that we
+ * can cast from a context to a record.
+ */
+ hprof_record_t curRec;
+
+ uint32_t gcThreadSerialNumber;
+ uint8_t gcScanState;
+ HprofHeapId currentHeap; // which heap we're currently emitting
+ uint32_t stackTraceSerialNumber;
+ size_t objectsInSegment;
+
+ /*
+ * If directToDdms is set, "fileName" and "fd" will be ignored.
+ * Otherwise, "fileName" must be valid, though if "fd" >= 0 it will
+ * only be used for debug messages.
+ */
+ bool directToDdms;
+ char *fileName;
+ char *fileDataPtr; // for open_memstream
+ size_t fileDataSize; // for open_memstream
+ FILE *memFp;
+ int fd;
+};
+
+
+/*
+ * hprof_string.cc functions
+ */
+
+hprof_string_id hprofLookupStringId(String* string);
+hprof_string_id hprofLookupStringId(const char* string);
+hprof_string_id hprofLookupStringId(std::string string);
+
+int hprofDumpStrings(hprof_context_t *ctx);
+
+int hprofStartup_String(void);
+int hprofShutdown_String(void);
+
+
+/*
+ * hprof_class.cc functions
+ */
+
+hprof_class_object_id hprofLookupClassId(Class* clazz);
+
+int hprofDumpClasses(hprof_context_t *ctx);
+
+int hprofStartup_Class(void);
+int hprofShutdown_Class(void);
+
+
+/*
+ * hprof_heap.cc functions
+ */
+
+int hprofStartHeapDump(hprof_context_t *ctx);
+int hprofFinishHeapDump(hprof_context_t *ctx);
+
+int hprofSetGcScanState(hprof_context_t *ctx,
+ hprof_heap_tag_t state, uint32_t threadSerialNumber);
+int hprofMarkRootObject(hprof_context_t *ctx,
+ const Object *obj, jobject jniObj);
+
+int hprofDumpHeapObject(hprof_context_t *ctx, const Object *obj);
+
+/*
+ * hprof_output.cc functions
+ */
+
+void hprofContextInit(hprof_context_t *ctx, char *fileName, int fd,
+ bool writeHeader, bool directToDdms);
+
+int hprofFlushRecord(hprof_record_t *rec, FILE *fp);
+int hprofFlushCurrentRecord(hprof_context_t *ctx);
+int hprofStartNewRecord(hprof_context_t *ctx, uint8_t tag, uint32_t time);
+
+int hprofAddU1ToRecord(hprof_record_t *rec, uint8_t value);
+int hprofAddU1ListToRecord(hprof_record_t *rec,
+ const uint8_t *values, size_t numValues);
+
+int hprofAddUtf8StringToRecord(hprof_record_t *rec, const char *str);
+
+int hprofAddU2ToRecord(hprof_record_t *rec, uint16_t value);
+int hprofAddU2ListToRecord(hprof_record_t *rec,
+ const uint16_t *values, size_t numValues);
+
+int hprofAddU4ToRecord(hprof_record_t *rec, uint32_t value);
+int hprofAddU4ListToRecord(hprof_record_t *rec,
+ const uint32_t *values, size_t numValues);
+
+int hprofAddU8ToRecord(hprof_record_t *rec, uint64_t value);
+int hprofAddU8ListToRecord(hprof_record_t *rec,
+ const uint64_t *values, size_t numValues);
+
+#define hprofAddIdToRecord(rec, id) hprofAddU4ToRecord((rec), (uint32_t)(id))
+#define hprofAddIdListToRecord(rec, values, numValues) \
+ hprofAddU4ListToRecord((rec), (const uint32_t *)(values), (numValues))
+
+/*
+ * hprof.cc functions
+ */
+
+hprof_context_t* hprofStartup(const char *outputFileName, int fd,
+ bool directToDdms);
+bool hprofShutdown(hprof_context_t *ctx);
+void hprofFreeContext(hprof_context_t *ctx);
+int hprofDumpHeap(const char* fileName, int fd, bool directToDdms);
+
+} // namespace hprof
+
+} // namespace art
+
+#endif // HPROF_HPROF_H_
diff --git a/src/hprof/hprof_class.cc b/src/hprof/hprof_class.cc
new file mode 100644
index 0000000..d64e553
--- /dev/null
+++ b/src/hprof/hprof_class.cc
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Class object pool
+ */
+
+#include "hprof.h"
+#include "object.h"
+#include "logging.h"
+#include "unordered_set.h"
+
+namespace art {
+
+namespace hprof {
+
+typedef std::tr1::unordered_set<Class*, ObjectIdentityHash> ClassSet;
+typedef std::tr1::unordered_set<Class*, ObjectIdentityHash>::iterator ClassSetIterator;
+static Mutex classes_lock_("hprof classes");
+static ClassSet classes_;
+
+int hprofStartup_Class() {
+ return 0;
+}
+
+int hprofShutdown_Class() {
+ return 0;
+}
+
+static int getPrettyClassNameId(Class* clazz) {
+ std::string name(PrettyClass(clazz));
+ return hprofLookupStringId(name); // TODO: leaks
+}
+
+hprof_class_object_id hprofLookupClassId(Class* clazz) {
+ if (clazz == NULL) {
+ /* Someone's probably looking up the superclass
+ * of java.lang.Object or of a primitive class.
+ */
+ return (hprof_class_object_id)0;
+ }
+
+ MutexLock mu(classes_lock_);
+
+ std::pair<ClassSetIterator, bool> result = classes_.insert(clazz);
+ Class* present = *result.first;
+
+ /* Make sure that the class's name is in the string table.
+ * This is a bunch of extra work that we only have to do
+ * because of the order of tables in the output file
+ * (strings need to be dumped before classes).
+ */
+ getPrettyClassNameId(clazz);
+
+ return (hprof_string_id) present;
+}
+
+int hprofDumpClasses(hprof_context_t *ctx) {
+ MutexLock mu(classes_lock_);
+
+ hprof_record_t *rec = &ctx->curRec;
+
+ uint32_t nextSerialNumber = 1;
+
+ for (ClassSetIterator it = classes_.begin(); it != classes_.end(); ++it) {
+ Class* clazz = *it;
+ CHECK(clazz != NULL);
+
+ int err = hprofStartNewRecord(ctx, HPROF_TAG_LOAD_CLASS, HPROF_TIME);
+ if (err != 0) {
+ return err;
+ }
+
+ /* LOAD CLASS format:
+ *
+ * uint32_t: class serial number (always > 0)
+ * ID: class object ID
+ * uint32_t: stack trace serial number
+ * ID: class name string ID
+ *
+ * We use the address of the class object structure as its ID.
+ */
+ hprofAddU4ToRecord(rec, nextSerialNumber++);
+ hprofAddIdToRecord(rec, (hprof_class_object_id) clazz);
+ hprofAddU4ToRecord(rec, HPROF_NULL_STACK_TRACE);
+ hprofAddIdToRecord(rec, getPrettyClassNameId(clazz));
+ }
+
+ return 0;
+}
+
+} // namespace hprof
+
+} // namespace art
diff --git a/src/hprof/hprof_heap.cc b/src/hprof/hprof_heap.cc
new file mode 100644
index 0000000..9545f7c
--- /dev/null
+++ b/src/hprof/hprof_heap.cc
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Heap object dump
+ */
+#include "hprof.h"
+#include "primitive.h"
+#include "object.h"
+#include "logging.h"
+
+namespace art {
+
+namespace hprof {
+
+/* Set DUMP_PRIM_DATA to 1 if you want to include the contents
+ * of primitive arrays (byte arrays, character arrays, etc.)
+ * in heap dumps. This can be a large amount of data.
+ */
+#define DUMP_PRIM_DATA 1
+
+#define OBJECTS_PER_SEGMENT ((size_t)128)
+#define BYTES_PER_SEGMENT ((size_t)4096)
+
+/* The static field-name for the synthetic object generated to account
+ * for class Static overhead.
+ */
+#define STATIC_OVERHEAD_NAME "$staticOverhead"
+/* The ID for the synthetic object generated to account for class
+ * Static overhead.
+ */
+#define CLASS_STATICS_ID(clazz) ((hprof_object_id)(((uint32_t)(clazz)) | 1))
+
+int hprofStartHeapDump(hprof_context_t *ctx)
+{
+ ctx->objectsInSegment = OBJECTS_PER_SEGMENT;
+ ctx->currentHeap = HPROF_HEAP_DEFAULT;
+ return 0;
+}
+
+int hprofFinishHeapDump(hprof_context_t *ctx)
+{
+ return hprofStartNewRecord(ctx, HPROF_TAG_HEAP_DUMP_END, HPROF_TIME);
+}
+
+int hprofSetGcScanState(hprof_context_t *ctx,
+ hprof_heap_tag_t state,
+ uint32_t threadSerialNumber)
+{
+ /* Used by hprofMarkRootObject()
+ */
+ ctx->gcScanState = state;
+ ctx->gcThreadSerialNumber = threadSerialNumber;
+ return 0;
+}
+
+static hprof_basic_type signatureToBasicTypeAndSize(const char *sig,
+ size_t *sizeOut)
+{
+ char c = sig[0];
+ hprof_basic_type ret;
+ size_t size;
+
+ switch (c) {
+ case '[':
+ case 'L': ret = hprof_basic_object; size = 4; break;
+ case 'Z': ret = hprof_basic_boolean; size = 1; break;
+ case 'C': ret = hprof_basic_char; size = 2; break;
+ case 'F': ret = hprof_basic_float; size = 4; break;
+ case 'D': ret = hprof_basic_double; size = 8; break;
+ case 'B': ret = hprof_basic_byte; size = 1; break;
+ case 'S': ret = hprof_basic_short; size = 2; break;
+ default: CHECK(false);
+ case 'I': ret = hprof_basic_int; size = 4; break;
+ case 'J': ret = hprof_basic_long; size = 8; break;
+ }
+
+ if (sizeOut != NULL) {
+ *sizeOut = size;
+ }
+
+ return ret;
+}
+
+static hprof_basic_type primitiveToBasicTypeAndSize(Primitive::Type prim,
+ size_t *sizeOut)
+{
+ hprof_basic_type ret;
+ size_t size;
+
+ switch (prim) {
+ case Primitive::kPrimBoolean: ret = hprof_basic_boolean; size = 1; break;
+ case Primitive::kPrimChar: ret = hprof_basic_char; size = 2; break;
+ case Primitive::kPrimFloat: ret = hprof_basic_float; size = 4; break;
+ case Primitive::kPrimDouble: ret = hprof_basic_double; size = 8; break;
+ case Primitive::kPrimByte: ret = hprof_basic_byte; size = 1; break;
+ case Primitive::kPrimShort: ret = hprof_basic_short; size = 2; break;
+ default: CHECK(false);
+ case Primitive::kPrimInt: ret = hprof_basic_int; size = 4; break;
+ case Primitive::kPrimLong: ret = hprof_basic_long; size = 8; break;
+ }
+
+ if (sizeOut != NULL) {
+ *sizeOut = size;
+ }
+
+ return ret;
+}
+
+/* Always called when marking objects, but only does
+ * something when ctx->gcScanState is non-zero, which is usually
+ * only true when marking the root set or unreachable
+ * objects. Used to add rootset references to obj.
+ */
+int hprofMarkRootObject(hprof_context_t *ctx, const Object *obj, jobject jniObj)
+{
+ hprof_record_t *rec = &ctx->curRec;
+ int err;
+ hprof_heap_tag_t heapTag = (hprof_heap_tag_t)ctx->gcScanState;
+
+ if (heapTag == 0) {
+ return 0;
+ }
+
+ if (ctx->objectsInSegment >= OBJECTS_PER_SEGMENT ||
+ rec->length >= BYTES_PER_SEGMENT)
+ {
+ /* This flushes the old segment and starts a new one.
+ */
+ hprofStartNewRecord(ctx, HPROF_TAG_HEAP_DUMP_SEGMENT, HPROF_TIME);
+ ctx->objectsInSegment = 0;
+ }
+
+ switch (heapTag) {
+ /* ID: object ID
+ */
+ case HPROF_ROOT_UNKNOWN:
+ case HPROF_ROOT_STICKY_CLASS:
+ case HPROF_ROOT_MONITOR_USED:
+ case HPROF_ROOT_INTERNED_STRING:
+ case HPROF_ROOT_FINALIZING:
+ case HPROF_ROOT_DEBUGGER:
+ case HPROF_ROOT_REFERENCE_CLEANUP:
+ case HPROF_ROOT_VM_INTERNAL:
+ hprofAddU1ToRecord(rec, heapTag);
+ hprofAddIdToRecord(rec, (hprof_object_id)obj);
+ break;
+
+ /* ID: object ID
+ * ID: JNI global ref ID
+ */
+ case HPROF_ROOT_JNI_GLOBAL:
+ hprofAddU1ToRecord(rec, heapTag);
+ hprofAddIdToRecord(rec, (hprof_object_id)obj);
+ hprofAddIdToRecord(rec, (hprof_id)jniObj);
+ break;
+
+ /* ID: object ID
+ * uint32_t: thread serial number
+ * uint32_t: frame number in stack trace (-1 for empty)
+ */
+ case HPROF_ROOT_JNI_LOCAL:
+ case HPROF_ROOT_JNI_MONITOR:
+ case HPROF_ROOT_JAVA_FRAME:
+ hprofAddU1ToRecord(rec, heapTag);
+ hprofAddIdToRecord(rec, (hprof_object_id)obj);
+ hprofAddU4ToRecord(rec, ctx->gcThreadSerialNumber);
+ hprofAddU4ToRecord(rec, (uint32_t)-1);
+ break;
+
+ /* ID: object ID
+ * uint32_t: thread serial number
+ */
+ case HPROF_ROOT_NATIVE_STACK:
+ case HPROF_ROOT_THREAD_BLOCK:
+ hprofAddU1ToRecord(rec, heapTag);
+ hprofAddIdToRecord(rec, (hprof_object_id)obj);
+ hprofAddU4ToRecord(rec, ctx->gcThreadSerialNumber);
+ break;
+
+ /* ID: thread object ID
+ * uint32_t: thread serial number
+ * uint32_t: stack trace serial number
+ */
+ case HPROF_ROOT_THREAD_OBJECT:
+ hprofAddU1ToRecord(rec, heapTag);
+ hprofAddIdToRecord(rec, (hprof_object_id)obj);
+ hprofAddU4ToRecord(rec, ctx->gcThreadSerialNumber);
+ hprofAddU4ToRecord(rec, (uint32_t)-1); //xxx
+ break;
+
+ default:
+ err = 0;
+ break;
+ }
+
+ ctx->objectsInSegment++;
+
+ return err;
+}
+
+static int stackTraceSerialNumber(const void *obj)
+{
+ return HPROF_NULL_STACK_TRACE;
+}
+
+int hprofDumpHeapObject(hprof_context_t *ctx, const Object *obj)
+{
+ Class* clazz;
+ hprof_record_t *rec = &ctx->curRec;
+ HprofHeapId desiredHeap;
+
+ desiredHeap = false ? HPROF_HEAP_ZYGOTE : HPROF_HEAP_APP; // TODO: zygote objects?
+
+ if (ctx->objectsInSegment >= OBJECTS_PER_SEGMENT ||
+ rec->length >= BYTES_PER_SEGMENT)
+ {
+ /* This flushes the old segment and starts a new one.
+ */
+ hprofStartNewRecord(ctx, HPROF_TAG_HEAP_DUMP_SEGMENT, HPROF_TIME);
+ ctx->objectsInSegment = 0;
+
+ /* Starting a new HEAP_DUMP resets the heap to default.
+ */
+ ctx->currentHeap = HPROF_HEAP_DEFAULT;
+ }
+
+ if (desiredHeap != ctx->currentHeap) {
+ hprof_string_id nameId;
+
+ /* This object is in a different heap than the current one.
+ * Emit a HEAP_DUMP_INFO tag to change heaps.
+ */
+ hprofAddU1ToRecord(rec, HPROF_HEAP_DUMP_INFO);
+ hprofAddU4ToRecord(rec, (uint32_t)desiredHeap); // uint32_t: heap id
+ switch (desiredHeap) {
+ case HPROF_HEAP_APP:
+ nameId = hprofLookupStringId("app");
+ break;
+ case HPROF_HEAP_ZYGOTE:
+ nameId = hprofLookupStringId("zygote");
+ break;
+ default:
+ /* Internal error. */
+ LOG(ERROR) << "Unexpected desiredHeap";
+ nameId = hprofLookupStringId("<ILLEGAL>");
+ break;
+ }
+ hprofAddIdToRecord(rec, nameId);
+ ctx->currentHeap = desiredHeap;
+ }
+
+ clazz = obj->GetClass();
+
+ if (clazz == NULL) {
+ /* This object will bother HprofReader, because it has a NULL
+ * class, so just don't dump it. It could be
+ * gDvm.unlinkedJavaLangClass or it could be an object just
+ * allocated which hasn't been initialized yet.
+ */
+ } else {
+ if (obj->IsClass()) {
+ Class* thisClass = (Class*)obj;
+ /* obj is a ClassObject.
+ */
+ size_t sFieldCount = thisClass->NumStaticFields();
+ if (sFieldCount != 0) {
+ int byteLength = sFieldCount*sizeof(JValue); // TODO bogus; fields are packed
+ /* Create a byte array to reflect the allocation of the
+ * StaticField array at the end of this class.
+ */
+ hprofAddU1ToRecord(rec, HPROF_PRIMITIVE_ARRAY_DUMP);
+ hprofAddIdToRecord(rec, CLASS_STATICS_ID(obj));
+ hprofAddU4ToRecord(rec, stackTraceSerialNumber(obj));
+ hprofAddU4ToRecord(rec, byteLength);
+ hprofAddU1ToRecord(rec, hprof_basic_byte);
+ for (int i = 0; i < byteLength; i++) {
+ hprofAddU1ToRecord(rec, 0);
+ }
+ }
+
+ hprofAddU1ToRecord(rec, HPROF_CLASS_DUMP);
+ hprofAddIdToRecord(rec, hprofLookupClassId(thisClass));
+ hprofAddU4ToRecord(rec, stackTraceSerialNumber(thisClass));
+ hprofAddIdToRecord(rec, hprofLookupClassId(thisClass->GetSuperClass()));
+ hprofAddIdToRecord(rec, (hprof_object_id)thisClass->GetClassLoader());
+ hprofAddIdToRecord(rec, (hprof_object_id)0); // no signer
+ hprofAddIdToRecord(rec, (hprof_object_id)0); // no prot domain
+ hprofAddIdToRecord(rec, (hprof_id)0); // reserved
+ hprofAddIdToRecord(rec, (hprof_id)0); // reserved
+ if (obj->IsClassClass()) {
+ // ClassObjects have their static fields appended, so
+ // aren't all the same size. But they're at least this
+ // size.
+ hprofAddU4ToRecord(rec, sizeof(Class)); // instance size
+ } else {
+ hprofAddU4ToRecord(rec, thisClass->GetObjectSize()); // instance size
+ }
+
+ hprofAddU2ToRecord(rec, 0); // empty const pool
+
+ /* Static fields
+ */
+ if (sFieldCount == 0) {
+ hprofAddU2ToRecord(rec, (uint16_t)0);
+ } else {
+ hprofAddU2ToRecord(rec, (uint16_t)(sFieldCount+1));
+ hprofAddIdToRecord(rec,
+ hprofLookupStringId(STATIC_OVERHEAD_NAME));
+ hprofAddU1ToRecord(rec, hprof_basic_object);
+ hprofAddIdToRecord(rec, CLASS_STATICS_ID(obj));
+
+ for (size_t i = 0; i < sFieldCount; ++i) {
+ hprof_basic_type t;
+ size_t size;
+ Field* f = thisClass->GetStaticField(i);
+
+ t = signatureToBasicTypeAndSize(f->GetTypeDescriptor(), &size);
+ hprofAddIdToRecord(rec, hprofLookupStringId(f->GetName()));
+ hprofAddU1ToRecord(rec, t);
+ if (size == 1) {
+ hprofAddU1ToRecord(rec, (uint8_t)f->GetByte(NULL));
+ } else if (size == 2) {
+ hprofAddU2ToRecord(rec, (uint16_t)f->GetShort(NULL));
+ } else if (size == 4) {
+ hprofAddU4ToRecord(rec, (uint32_t)f->GetInt(NULL));
+ } else if (size == 8) {
+ hprofAddU8ToRecord(rec, (uint64_t)f->GetLong(NULL));
+ } else {
+ CHECK(false);
+ }
+ }
+ }
+
+ /* Instance fields for this class (no superclass fields)
+ */
+ int iFieldCount = thisClass->NumInstanceFields();
+ hprofAddU2ToRecord(rec, (uint16_t)iFieldCount);
+ for (int i = 0; i < iFieldCount; ++i) {
+ Field* f = thisClass->GetInstanceField(i);
+ hprof_basic_type t;
+
+ t = signatureToBasicTypeAndSize(f->GetTypeDescriptor(), NULL);
+ hprofAddIdToRecord(rec, hprofLookupStringId(f->GetName()));
+ hprofAddU1ToRecord(rec, t);
+ }
+ } else if (clazz->IsArrayClass()) {
+ Array *aobj = (Array *)obj;
+ uint32_t length = aobj->GetLength();
+
+ if (obj->IsObjectArray()) {
+ /* obj is an object array.
+ */
+ hprofAddU1ToRecord(rec, HPROF_OBJECT_ARRAY_DUMP);
+
+ hprofAddIdToRecord(rec, (hprof_object_id)obj);
+ hprofAddU4ToRecord(rec, stackTraceSerialNumber(obj));
+ hprofAddU4ToRecord(rec, length);
+ hprofAddIdToRecord(rec, hprofLookupClassId(clazz));
+
+ /* Dump the elements, which are always objects or NULL.
+ */
+ hprofAddIdListToRecord(rec,
+ (const hprof_object_id *)aobj->GetRawData(), length);
+ } else {
+ hprof_basic_type t;
+ size_t size;
+
+ t = primitiveToBasicTypeAndSize(clazz->GetComponentType()->
+ GetPrimitiveType(), &size);
+
+ /* obj is a primitive array.
+ */
+#if DUMP_PRIM_DATA
+ hprofAddU1ToRecord(rec, HPROF_PRIMITIVE_ARRAY_DUMP);
+#else
+ hprofAddU1ToRecord(rec, HPROF_PRIMITIVE_ARRAY_NODATA_DUMP);
+#endif
+
+ hprofAddIdToRecord(rec, (hprof_object_id)obj);
+ hprofAddU4ToRecord(rec, stackTraceSerialNumber(obj));
+ hprofAddU4ToRecord(rec, length);
+ hprofAddU1ToRecord(rec, t);
+
+#if DUMP_PRIM_DATA
+ /* Dump the raw, packed element values.
+ */
+ if (size == 1) {
+ hprofAddU1ListToRecord(rec, (const uint8_t *)aobj->GetRawData(),
+ length);
+ } else if (size == 2) {
+ hprofAddU2ListToRecord(rec, (const uint16_t *)(void *)aobj->GetRawData(),
+ length);
+ } else if (size == 4) {
+ hprofAddU4ListToRecord(rec, (const uint32_t *)(void *)aobj->GetRawData(),
+ length);
+ } else if (size == 8) {
+ hprofAddU8ListToRecord(rec, (const uint64_t *)aobj->GetRawData(),
+ length);
+ }
+#endif
+ }
+ } else {
+ const Class* sclass;
+ size_t sizePatchOffset, savedLen;
+
+ /* obj is an instance object.
+ */
+ hprofAddU1ToRecord(rec, HPROF_INSTANCE_DUMP);
+ hprofAddIdToRecord(rec, (hprof_object_id)obj);
+ hprofAddU4ToRecord(rec, stackTraceSerialNumber(obj));
+ hprofAddIdToRecord(rec, hprofLookupClassId(clazz));
+
+ /* Reserve some space for the length of the instance
+ * data, which we won't know until we're done writing
+ * it.
+ */
+ sizePatchOffset = rec->length;
+ hprofAddU4ToRecord(rec, 0x77777777);
+
+ /* Write the instance data; fields for this
+ * class, followed by super class fields, and so on.
+ */
+ sclass = clazz;
+ while (sclass != NULL) {
+ int ifieldCount = sclass->NumInstanceFields();
+ for (int i = 0; i < ifieldCount; i++) {
+ Field* f = sclass->GetInstanceField(i);
+ size_t size;
+
+ (void) signatureToBasicTypeAndSize(f->GetTypeDescriptor(), &size);
+ if (size == 1) {
+ hprofAddU1ToRecord(rec, f->GetByte(obj));
+ } else if (size == 2) {
+ hprofAddU2ToRecord(rec, f->GetChar(obj));
+ } else if (size == 4) {
+ hprofAddU4ToRecord(rec, f->GetInt(obj));
+ } else if (size == 8) {
+ hprofAddU8ToRecord(rec, f->GetLong(obj));
+ } else {
+ CHECK(false);
+ }
+ }
+
+ sclass = sclass->GetSuperClass();
+ }
+
+ /* Patch the instance field length.
+ */
+ savedLen = rec->length;
+ rec->length = sizePatchOffset;
+ hprofAddU4ToRecord(rec, savedLen - (sizePatchOffset + 4));
+ rec->length = savedLen;
+ }
+ }
+
+ ctx->objectsInSegment++;
+
+ return 0;
+}
+
+} // namespace hprof
+
+} // namespace art
diff --git a/src/hprof/hprof_output.cc b/src/hprof/hprof_output.cc
new file mode 100644
index 0000000..5dfdb31
--- /dev/null
+++ b/src/hprof/hprof_output.cc
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <sys/time.h>
+#include <cutils/open_memstream.h>
+#include <time.h>
+#include <errno.h>
+#include <stdio.h>
+#include "hprof.h"
+#include "stringprintf.h"
+#include "logging.h"
+
+namespace art {
+
+namespace hprof {
+
+#define HPROF_MAGIC_STRING "JAVA PROFILE 1.0.3"
+
+#define U2_TO_BUF_BE(buf, offset, value) \
+ do { \
+ unsigned char *buf_ = (unsigned char *)(buf); \
+ int offset_ = (int)(offset); \
+ uint16_t value_ = (uint16_t)(value); \
+ buf_[offset_ + 0] = (unsigned char)(value_ >> 8); \
+ buf_[offset_ + 1] = (unsigned char)(value_ ); \
+ } while (0)
+
+#define U4_TO_BUF_BE(buf, offset, value) \
+ do { \
+ unsigned char *buf_ = (unsigned char *)(buf); \
+ int offset_ = (int)(offset); \
+ uint32_t value_ = (uint32_t)(value); \
+ buf_[offset_ + 0] = (unsigned char)(value_ >> 24); \
+ buf_[offset_ + 1] = (unsigned char)(value_ >> 16); \
+ buf_[offset_ + 2] = (unsigned char)(value_ >> 8); \
+ buf_[offset_ + 3] = (unsigned char)(value_ ); \
+ } while (0)
+
+#define U8_TO_BUF_BE(buf, offset, value) \
+ do { \
+ unsigned char *buf_ = (unsigned char *)(buf); \
+ int offset_ = (int)(offset); \
+ uint64_t value_ = (uint64_t)(value); \
+ buf_[offset_ + 0] = (unsigned char)(value_ >> 56); \
+ buf_[offset_ + 1] = (unsigned char)(value_ >> 48); \
+ buf_[offset_ + 2] = (unsigned char)(value_ >> 40); \
+ buf_[offset_ + 3] = (unsigned char)(value_ >> 32); \
+ buf_[offset_ + 4] = (unsigned char)(value_ >> 24); \
+ buf_[offset_ + 5] = (unsigned char)(value_ >> 16); \
+ buf_[offset_ + 6] = (unsigned char)(value_ >> 8); \
+ buf_[offset_ + 7] = (unsigned char)(value_ ); \
+ } while (0)
+
+/*
+ * Initialize an hprof context struct.
+ *
+ * This will take ownership of "fileName".
+ */
+void hprofContextInit(hprof_context_t *ctx, char *fileName, int fd,
+ bool writeHeader, bool directToDdms)
+{
+ memset(ctx, 0, sizeof (*ctx));
+
+ /*
+ * 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).
+ */
+ FILE *fp = open_memstream(&ctx->fileDataPtr, &ctx->fileDataSize);
+ if (fp == NULL) {
+ /* not expected */
+ LOG(ERROR) << StringPrintf("hprof: open_memstream failed: %s", strerror(errno));
+ CHECK(false);
+ }
+
+ ctx->directToDdms = directToDdms;
+ ctx->fileName = fileName;
+ ctx->memFp = fp;
+ ctx->fd = fd;
+
+ ctx->curRec.allocLen = 128;
+ ctx->curRec.body = (unsigned char *)malloc(ctx->curRec.allocLen);
+//xxx check for/return an error
+
+ if (writeHeader) {
+ char magic[] = HPROF_MAGIC_STRING;
+ unsigned char buf[4];
+ struct timeval now;
+ uint64_t nowMs;
+
+ /* Write the file header.
+ *
+ * [uint8_t]*: NUL-terminated magic string.
+ */
+ fwrite(magic, 1, sizeof(magic), fp);
+
+ /* uint32_t: size of identifiers. We're using addresses
+ * as IDs, so make sure a pointer fits.
+ */
+ U4_TO_BUF_BE(buf, 0, sizeof(void *));
+ fwrite(buf, 1, sizeof(uint32_t), fp);
+
+ /* The current time, in milliseconds since 0:00 GMT, 1/1/70.
+ */
+ if (gettimeofday(&now, NULL) < 0) {
+ nowMs = 0;
+ } else {
+ nowMs = (uint64_t)now.tv_sec * 1000 + now.tv_usec / 1000;
+ }
+
+ /* uint32_t: high word of the 64-bit time.
+ */
+ U4_TO_BUF_BE(buf, 0, (uint32_t)(nowMs >> 32));
+ fwrite(buf, 1, sizeof(uint32_t), fp);
+
+ /* uint32_t: low word of the 64-bit time.
+ */
+ U4_TO_BUF_BE(buf, 0, (uint32_t)(nowMs & 0xffffffffULL));
+ fwrite(buf, 1, sizeof(uint32_t), fp); //xxx fix the time
+ }
+}
+
+int hprofFlushRecord(hprof_record_t *rec, FILE *fp)
+{
+ if (rec->dirty) {
+ unsigned char headBuf[sizeof (uint8_t) + 2 * sizeof (uint32_t)];
+ int nb;
+
+ headBuf[0] = rec->tag;
+ U4_TO_BUF_BE(headBuf, 1, rec->time);
+ U4_TO_BUF_BE(headBuf, 5, rec->length);
+
+ nb = fwrite(headBuf, 1, sizeof(headBuf), fp);
+ if (nb != sizeof(headBuf)) {
+ return UNIQUE_ERROR();
+ }
+ nb = fwrite(rec->body, 1, rec->length, fp);
+ if (nb != (int)rec->length) {
+ return UNIQUE_ERROR();
+ }
+
+ rec->dirty = false;
+ }
+//xxx if we used less than half (or whatever) of allocLen, shrink the buffer.
+
+ return 0;
+}
+
+int hprofFlushCurrentRecord(hprof_context_t *ctx)
+{
+ return hprofFlushRecord(&ctx->curRec, ctx->memFp);
+}
+
+int hprofStartNewRecord(hprof_context_t *ctx, uint8_t tag, uint32_t time)
+{
+ hprof_record_t *rec = &ctx->curRec;
+ int err;
+
+ err = hprofFlushRecord(rec, ctx->memFp);
+ if (err != 0) {
+ return err;
+ } else if (rec->dirty) {
+ return UNIQUE_ERROR();
+ }
+
+ rec->dirty = true;
+ rec->tag = tag;
+ rec->time = time;
+ rec->length = 0;
+
+ return 0;
+}
+
+static inline int guaranteeRecordAppend(hprof_record_t *rec, size_t nmore)
+{
+ size_t minSize;
+
+ minSize = rec->length + nmore;
+ if (minSize > rec->allocLen) {
+ unsigned char *newBody;
+ size_t newAllocLen;
+
+ newAllocLen = rec->allocLen * 2;
+ if (newAllocLen < minSize) {
+ newAllocLen = rec->allocLen + nmore + nmore/2;
+ }
+ newBody = (unsigned char *)realloc(rec->body, newAllocLen);
+ if (newBody != NULL) {
+ rec->body = newBody;
+ rec->allocLen = newAllocLen;
+ } else {
+//TODO: set an error flag so future ops will fail
+ return UNIQUE_ERROR();
+ }
+ }
+
+ CHECK(rec->length + nmore <= rec->allocLen);
+ return 0;
+}
+
+int hprofAddU1ListToRecord(hprof_record_t *rec, const uint8_t *values,
+ size_t numValues)
+{
+ int err;
+
+ err = guaranteeRecordAppend(rec, numValues);
+ if (err != 0) {
+ return err;
+ }
+
+ memcpy(rec->body + rec->length, values, numValues);
+ rec->length += numValues;
+
+ return 0;
+}
+
+int hprofAddU1ToRecord(hprof_record_t *rec, uint8_t value)
+{
+ int err;
+
+ err = guaranteeRecordAppend(rec, 1);
+ if (err != 0) {
+ return err;
+ }
+
+ rec->body[rec->length++] = value;
+
+ return 0;
+}
+
+int hprofAddUtf8StringToRecord(hprof_record_t *rec, const char *str)
+{
+ /* The terminating NUL character is NOT written.
+ */
+//xxx don't do a strlen; add and grow as necessary, until NUL
+ return hprofAddU1ListToRecord(rec, (const uint8_t *)str, strlen(str));
+}
+
+int hprofAddU2ListToRecord(hprof_record_t *rec, const uint16_t *values,
+ size_t numValues)
+{
+ int err = guaranteeRecordAppend(rec, numValues * 2);
+ if (err != 0) {
+ return err;
+ }
+
+//xxx this can be way smarter
+//xxx also, don't do this bytewise if aligned and on a matching-endian arch
+ unsigned char *insert = rec->body + rec->length;
+ for (size_t i = 0; i < numValues; i++) {
+ U2_TO_BUF_BE(insert, 0, *values++);
+ insert += sizeof(*values);
+ }
+ rec->length += numValues * 2;
+
+ return 0;
+}
+
+int hprofAddU2ToRecord(hprof_record_t *rec, uint16_t value)
+{
+ return hprofAddU2ListToRecord(rec, &value, 1);
+}
+
+int hprofAddU4ListToRecord(hprof_record_t *rec, const uint32_t *values,
+ size_t numValues)
+{
+ int err = guaranteeRecordAppend(rec, numValues * 4);
+ if (err != 0) {
+ return err;
+ }
+
+//xxx this can be way smarter
+//xxx also, don't do this bytewise if aligned and on a matching-endian arch
+ unsigned char *insert = rec->body + rec->length;
+ for (size_t i = 0; i < numValues; i++) {
+ U4_TO_BUF_BE(insert, 0, *values++);
+ insert += sizeof(*values);
+ }
+ rec->length += numValues * 4;
+
+ return 0;
+}
+
+int hprofAddU4ToRecord(hprof_record_t *rec, uint32_t value)
+{
+ return hprofAddU4ListToRecord(rec, &value, 1);
+}
+
+int hprofAddU8ListToRecord(hprof_record_t *rec, const uint64_t *values,
+ size_t numValues)
+{
+ int err = guaranteeRecordAppend(rec, numValues * 8);
+ if (err != 0) {
+ return err;
+ }
+
+//xxx this can be way smarter
+//xxx also, don't do this bytewise if aligned and on a matching-endian arch
+ unsigned char *insert = rec->body + rec->length;
+ for (size_t i = 0; i < numValues; i++) {
+ U8_TO_BUF_BE(insert, 0, *values++);
+ insert += sizeof(*values);
+ }
+ rec->length += numValues * 8;
+
+ return 0;
+}
+
+int hprofAddU8ToRecord(hprof_record_t *rec, uint64_t value)
+{
+ return hprofAddU8ListToRecord(rec, &value, 1);
+}
+
+} // namespace hprof
+
+} // namespace art
diff --git a/src/hprof/hprof_string.cc b/src/hprof/hprof_string.cc
new file mode 100644
index 0000000..7fb4027
--- /dev/null
+++ b/src/hprof/hprof_string.cc
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Common string pool for the profiler
+ */
+#include "hprof.h"
+#include "object.h"
+#include "unordered_set.h"
+#include "logging.h"
+
+namespace art {
+
+namespace hprof {
+
+typedef std::tr1::unordered_set<String*, StringHashCode> StringSet;
+typedef std::tr1::unordered_set<String*, StringHashCode>::iterator StringSetIterator; // TODO: equals by VALUE not REFERENCE
+static Mutex strings_lock_("hprof strings");
+static StringSet strings_;
+
+int hprofStartup_String() {
+ return 0;
+}
+
+int hprofShutdown_String() {
+ return 0;
+}
+
+hprof_string_id hprofLookupStringId(String* string) {
+ MutexLock mu(strings_lock_);
+ std::pair<StringSetIterator, bool> result = strings_.insert(string);
+ String* present = *result.first;
+ return (hprof_string_id) present;
+}
+
+hprof_string_id hprofLookupStringId(const char* string) {
+ return hprofLookupStringId(String::AllocFromModifiedUtf8(string)); // TODO: leaks? causes GC?
+}
+
+hprof_string_id hprofLookupStringId(std::string string) {
+ return hprofLookupStringId(string.c_str()); // TODO: leaks? causes GC?
+}
+
+int hprofDumpStrings(hprof_context_t *ctx) {
+ MutexLock mu(strings_lock_);
+
+ hprof_record_t *rec = &ctx->curRec;
+
+ for (StringSetIterator it = strings_.begin(); it != strings_.end(); ++it) {
+ String* string = *it;
+ CHECK(string != NULL);
+
+ int err = hprofStartNewRecord(ctx, HPROF_TAG_STRING, HPROF_TIME);
+ if (err != 0) {
+ return err;
+ }
+
+ /* STRING format:
+ *
+ * ID: ID for this string
+ * [uint8_t]*: UTF8 characters for string (NOT NULL terminated)
+ * (the record format encodes the length)
+ *
+ * We use the address of the string data as its ID.
+ */
+ err = hprofAddU4ToRecord(rec, (uint32_t) string);
+ if (err != 0) {
+ return err;
+ }
+ err = hprofAddUtf8StringToRecord(rec, string->ToModifiedUtf8().c_str()); // TODO: leak?
+ if (err != 0) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+} // namespace hprof
+
+} // namespace art
diff --git a/src/object.h b/src/object.h
index 1051985..46c30cd 100644
--- a/src/object.h
+++ b/src/object.h
@@ -396,7 +396,7 @@
void SetDeclaringClass(Class *new_declaring_class);
- const String* GetName() const;
+ String* GetName() const;
void SetName(String* new_name);
@@ -2501,7 +2501,13 @@
DISALLOW_IMPLICIT_CONSTRUCTORS(String);
};
-inline const String* Field::GetName() const {
+struct StringHashCode {
+ int32_t operator()(art::String* string) const {
+ return string->GetHashCode();
+ }
+};
+
+inline String* Field::GetName() const {
DCHECK(GetDeclaringClass()->IsLoaded() || GetDeclaringClass()->IsErroneous());
String* result = GetFieldObject<String*>(OFFSET_OF_OBJECT_MEMBER(Field, name_), false);
DCHECK(result != NULL);