2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Allocation tracking and reporting. We maintain a circular buffer with
19 * the most recent allocations. The data can be viewed through DDMS.
21 * There are two basic approaches: manage the buffer with atomic updates
22 * and do a system-wide suspend when DDMS requests it, or protect all
23 * accesses with a mutex. The former is potentially more efficient, but
24 * the latter is much simpler and more reliable.
26 * Ideally we'd just use the object heap allocation mutex to guard this
27 * structure, but at the point we grab that (under dvmMalloc()) we're just
28 * allocating a collection of bytes and no longer have the class reference.
29 * Because this is an optional feature it's best to leave the existing
30 * code undisturbed and just use an additional lock.
32 * We don't currently track allocations of class objects. We could, but
33 * with the possible exception of Proxy objects they're not that interesting.
35 * TODO: if we add support for class unloading, we need to add the class
36 * references here to the root set (or just disable class unloading while
39 * TODO: consider making the parameters configurable, so DDMS can decide
40 * how many allocations it wants to see and what the stack depth should be.
41 * Changing the window size is easy, changing the max stack depth is harder
42 * because we go from an array of fixed-size structs to variable-sized data.
46 #ifdef HAVE_ANDROID_OS
47 #include "cutils/properties.h"
48 static bool isPowerOfTwo(int x) { return (x & (x - 1)) == 0; }
51 #define kMaxAllocRecordStackDepth 16 /* max 255 */
53 #define kDefaultNumAllocRecords 64*1024 /* MUST be power of 2 */
56 * Record the details of an allocation.
59 ClassObject* clazz; /* class allocated in this block */
60 u4 size; /* total size requested */
61 u2 threadId; /* simple thread ID; could be recycled */
63 /* stack trace elements; unused entries have method==NULL */
65 const Method* method; /* which method we're executing in */
66 int pc; /* current execution offset, in 16-bit units */
67 } stackElem[kMaxAllocRecordStackDepth];
71 * Initialize a few things. This gets called early, so keep activity to
74 bool dvmAllocTrackerStartup()
77 dvmInitMutex(&gDvm.allocTrackerLock);
79 /* initialized when enabled by DDMS */
80 assert(gDvm.allocRecords == NULL);
86 * Release anything we're holding on to.
88 void dvmAllocTrackerShutdown()
90 free(gDvm.allocRecords);
91 dvmDestroyMutex(&gDvm.allocTrackerLock);
96 * ===========================================================================
98 * ===========================================================================
101 static int getAllocRecordMax() {
102 #ifdef HAVE_ANDROID_OS
103 // Check whether there's a system property overriding the number of records.
104 const char* propertyName = "dalvik.vm.allocTrackerMax";
105 char allocRecordMaxString[PROPERTY_VALUE_MAX];
106 if (property_get(propertyName, allocRecordMaxString, "") > 0) {
108 size_t value = strtoul(allocRecordMaxString, &end, 10);
110 ALOGE("Ignoring %s '%s' --- invalid", propertyName, allocRecordMaxString);
111 return kDefaultNumAllocRecords;
113 if (!isPowerOfTwo(value)) {
114 ALOGE("Ignoring %s '%s' --- not power of two", propertyName, allocRecordMaxString);
115 return kDefaultNumAllocRecords;
120 return kDefaultNumAllocRecords;
124 * Enable allocation tracking. Does nothing if tracking is already enabled.
126 * Returns "true" on success.
128 bool dvmEnableAllocTracker()
131 dvmLockMutex(&gDvm.allocTrackerLock);
133 if (gDvm.allocRecords == NULL) {
134 gDvm.allocRecordMax = getAllocRecordMax();
136 ALOGI("Enabling alloc tracker (%d entries, %d frames --> %d bytes)",
137 gDvm.allocRecordMax, kMaxAllocRecordStackDepth,
138 sizeof(AllocRecord) * gDvm.allocRecordMax);
139 gDvm.allocRecordHead = gDvm.allocRecordCount = 0;
140 gDvm.allocRecords = (AllocRecord*) malloc(sizeof(AllocRecord) * gDvm.allocRecordMax);
142 if (gDvm.allocRecords == NULL)
146 dvmUnlockMutex(&gDvm.allocTrackerLock);
151 * Disable allocation tracking. Does nothing if tracking is not enabled.
153 void dvmDisableAllocTracker()
155 dvmLockMutex(&gDvm.allocTrackerLock);
157 if (gDvm.allocRecords != NULL) {
158 free(gDvm.allocRecords);
159 gDvm.allocRecords = NULL;
162 dvmUnlockMutex(&gDvm.allocTrackerLock);
166 * Get the last few stack frames.
168 static void getStackFrames(Thread* self, AllocRecord* pRec)
173 fp = self->interpSave.curFrame;
175 while ((fp != NULL) && (stackDepth < kMaxAllocRecordStackDepth)) {
176 const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
177 const Method* method = saveArea->method;
179 if (!dvmIsBreakFrame((u4*) fp)) {
180 pRec->stackElem[stackDepth].method = method;
181 if (dvmIsNativeMethod(method)) {
182 pRec->stackElem[stackDepth].pc = 0;
184 assert(saveArea->xtra.currentPc >= method->insns &&
185 saveArea->xtra.currentPc <
186 method->insns + dvmGetMethodInsnsSize(method));
187 pRec->stackElem[stackDepth].pc =
188 (int) (saveArea->xtra.currentPc - method->insns);
193 assert(fp != saveArea->prevFrame);
194 fp = saveArea->prevFrame;
197 /* clear out the rest (normally there won't be any) */
198 while (stackDepth < kMaxAllocRecordStackDepth) {
199 pRec->stackElem[stackDepth].method = NULL;
200 pRec->stackElem[stackDepth].pc = 0;
206 * Add a new allocation to the set.
208 void dvmDoTrackAllocation(ClassObject* clazz, size_t size)
210 Thread* self = dvmThreadSelf();
212 ALOGW("alloc tracker: no thread");
216 dvmLockMutex(&gDvm.allocTrackerLock);
217 if (gDvm.allocRecords == NULL) {
218 dvmUnlockMutex(&gDvm.allocTrackerLock);
222 /* advance and clip */
223 if (++gDvm.allocRecordHead == gDvm.allocRecordMax)
224 gDvm.allocRecordHead = 0;
226 AllocRecord* pRec = &gDvm.allocRecords[gDvm.allocRecordHead];
230 pRec->threadId = self->threadId;
231 getStackFrames(self, pRec);
233 if (gDvm.allocRecordCount < gDvm.allocRecordMax)
234 gDvm.allocRecordCount++;
236 dvmUnlockMutex(&gDvm.allocTrackerLock);
241 * ===========================================================================
243 * ===========================================================================
247 The data we send to DDMS contains everything we have recorded.
249 Message header (all values big-endian):
250 (1b) message header len (to allow future expansion); includes itself
251 (1b) entry header len
253 (2b) number of entries
254 (4b) offset to string table from start of message
255 (2b) number of class name strings
256 (2b) number of method name strings
257 (2b) number of source file name strings
259 (4b) total allocation size
261 (2b) allocated object's class name index
263 For each stack frame:
264 (2b) method's class name
266 (2b) method source file
267 (2b) line number, clipped to 32767; -2 if native; -1 if no source
268 (xb) class name strings
269 (xb) method name strings
270 (xb) source file strings
272 As with other DDM traffic, strings are sent as a 4-byte length
273 followed by UTF-16 data.
275 We send up 16-bit unsigned indexes into string tables. In theory there
276 can be (kMaxAllocRecordStackDepth * gDvm.allocRecordMax) unique strings in
277 each table, but in practice there should be far fewer.
279 The chief reason for using a string table here is to keep the size of
280 the DDMS message to a minimum. This is partly to make the protocol
281 efficient, but also because we have to form the whole thing up all at
282 once in a memory buffer.
284 We use separate string tables for class names, method names, and source
285 files to keep the indexes small. There will generally be no overlap
286 between the contents of these tables.
288 const int kMessageHeaderLen = 15;
289 const int kEntryHeaderLen = 9;
290 const int kStackFrameLen = 8;
293 * Return the index of the head element.
295 * We point at the most-recently-written record, so if allocRecordCount is 1
296 * we want to use the current element. Take "head+1" and subtract count
299 * We need to handle underflow in our circular buffer, so we add
300 * gDvm.allocRecordMax and then mask it back down.
302 inline static int headIndex()
304 return (gDvm.allocRecordHead+1 + gDvm.allocRecordMax - gDvm.allocRecordCount)
305 & (gDvm.allocRecordMax-1);
309 * Dump the contents of a PointerSet full of character pointers.
311 static void dumpStringTable(PointerSet* strings)
313 int count = dvmPointerSetGetCount(strings);
316 for (i = 0; i < count; i++)
317 printf(" %s\n", (const char*) dvmPointerSetGetEntry(strings, i));
321 * Get the method's source file. If we don't know it, return "" instead
324 static const char* getMethodSourceFile(const Method* method)
326 const char* fileName = dvmGetMethodSourceFile(method);
327 if (fileName == NULL)
333 * Generate string tables.
335 * Our source material is UTF-8 string constants from DEX files. If we
336 * want to be thorough we can generate a hash value for each string and
337 * use the VM hash table implementation, or we can do a quick & dirty job
338 * by just maintaining a list of unique pointers. If the same string
339 * constant appears in multiple DEX files we'll end up with duplicates,
340 * but in practice this shouldn't matter (and if it does, we can uniq-sort
341 * the result in a second pass).
343 static bool populateStringTables(PointerSet* classNames,
344 PointerSet* methodNames, PointerSet* fileNames)
346 int count = gDvm.allocRecordCount;
347 int idx = headIndex();
348 int classCount, methodCount, fileCount; /* debug stats */
350 classCount = methodCount = fileCount = 0;
353 AllocRecord* pRec = &gDvm.allocRecords[idx];
355 dvmPointerSetAddEntry(classNames, pRec->clazz->descriptor);
359 for (i = 0; i < kMaxAllocRecordStackDepth; i++) {
360 if (pRec->stackElem[i].method == NULL)
363 const Method* method = pRec->stackElem[i].method;
364 dvmPointerSetAddEntry(classNames, method->clazz->descriptor);
366 dvmPointerSetAddEntry(methodNames, method->name);
368 dvmPointerSetAddEntry(fileNames, getMethodSourceFile(method));
372 idx = (idx + 1) & (gDvm.allocRecordMax-1);
375 ALOGI("class %d/%d, method %d/%d, file %d/%d",
376 dvmPointerSetGetCount(classNames), classCount,
377 dvmPointerSetGetCount(methodNames), methodCount,
378 dvmPointerSetGetCount(fileNames), fileCount);
384 * Generate the base info (i.e. everything but the string tables).
386 * This should be called twice. On the first call, "ptr" is NULL and
387 * "baseLen" is zero. The return value is used to allocate a buffer.
388 * On the second call, "ptr" points to a data buffer, and "baseLen"
389 * holds the value from the result of the first call.
391 * The size of the output data is returned.
393 static size_t generateBaseOutput(u1* ptr, size_t baseLen,
394 const PointerSet* classNames, const PointerSet* methodNames,
395 const PointerSet* fileNames)
398 int count = gDvm.allocRecordCount;
399 int idx = headIndex();
401 if (origPtr != NULL) {
402 set1(&ptr[0], kMessageHeaderLen);
403 set1(&ptr[1], kEntryHeaderLen);
404 set1(&ptr[2], kStackFrameLen);
405 set2BE(&ptr[3], count);
406 set4BE(&ptr[5], baseLen);
407 set2BE(&ptr[9], dvmPointerSetGetCount(classNames));
408 set2BE(&ptr[11], dvmPointerSetGetCount(methodNames));
409 set2BE(&ptr[13], dvmPointerSetGetCount(fileNames));
411 ptr += kMessageHeaderLen;
414 AllocRecord* pRec = &gDvm.allocRecords[idx];
418 for (depth = 0; depth < kMaxAllocRecordStackDepth; depth++) {
419 if (pRec->stackElem[depth].method == NULL)
424 if (origPtr != NULL) {
425 set4BE(&ptr[0], pRec->size);
426 set2BE(&ptr[4], pRec->threadId);
428 dvmPointerSetFind(classNames, pRec->clazz->descriptor));
429 set1(&ptr[8], depth);
431 ptr += kEntryHeaderLen;
433 /* convert stack frames */
435 for (i = 0; i < depth; i++) {
436 if (origPtr != NULL) {
437 const Method* method = pRec->stackElem[i].method;
440 lineNum = dvmLineNumFromPC(method, pRec->stackElem[i].pc);
444 set2BE(&ptr[0], dvmPointerSetFind(classNames,
445 method->clazz->descriptor));
446 set2BE(&ptr[2], dvmPointerSetFind(methodNames,
448 set2BE(&ptr[4], dvmPointerSetFind(fileNames,
449 getMethodSourceFile(method)));
450 set2BE(&ptr[6], (u2)lineNum);
452 ptr += kStackFrameLen;
455 idx = (idx + 1) & (gDvm.allocRecordMax-1);
458 return ptr - origPtr;
462 * Compute the size required to store a string table. Includes the length
463 * word and conversion to UTF-16.
465 static size_t computeStringTableSize(PointerSet* strings)
467 int count = dvmPointerSetGetCount(strings);
471 for (i = 0; i < count; i++) {
472 const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
474 size += 4 + dvmUtf8Len(str) * 2;
481 * Convert a UTF-8 string to UTF-16. We also need to byte-swap the values
482 * to big-endian, and we can't assume even alignment on the target.
484 * Returns the string's length, in characters.
486 static int convertUtf8ToUtf16BEUA(u1* utf16Str, const char* utf8Str)
488 u1* origUtf16Str = utf16Str;
490 while (*utf8Str != '\0') {
491 u2 utf16 = dexGetUtf16FromUtf8(&utf8Str); /* advances utf8Str */
492 set2BE(utf16Str, utf16);
496 return (utf16Str - origUtf16Str) / 2;
500 * Output a string table serially.
502 static size_t outputStringTable(PointerSet* strings, u1* ptr)
504 int count = dvmPointerSetGetCount(strings);
508 for (i = 0; i < count; i++) {
509 const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
512 /* copy UTF-8 string to big-endian unaligned UTF-16 */
513 charLen = convertUtf8ToUtf16BEUA(&ptr[4], str);
514 set4BE(&ptr[0], charLen);
516 ptr += 4 + charLen * 2;
519 return ptr - origPtr;
523 * Generate a DDM packet with all of the tracked allocation data.
525 * On success, returns "true" with "*pData" and "*pDataLen" set.
527 bool dvmGenerateTrackedAllocationReport(u1** pData, size_t* pDataLen)
532 dvmLockMutex(&gDvm.allocTrackerLock);
535 * Part 1: generate string tables.
537 PointerSet* classNames = NULL;
538 PointerSet* methodNames = NULL;
539 PointerSet* fileNames = NULL;
542 * Allocate storage. Usually there's 60-120 of each thing (sampled
543 * when max=512), but it varies widely and isn't closely bound to
544 * the number of allocations we've captured. The sets expand quickly
547 classNames = dvmPointerSetAlloc(128);
548 methodNames = dvmPointerSetAlloc(128);
549 fileNames = dvmPointerSetAlloc(128);
550 if (classNames == NULL || methodNames == NULL || fileNames == NULL) {
551 ALOGE("Failed allocating pointer sets");
555 if (!populateStringTables(classNames, methodNames, fileNames))
559 printf("Classes:\n");
560 dumpStringTable(classNames);
561 printf("Methods:\n");
562 dumpStringTable(methodNames);
564 dumpStringTable(fileNames);
568 * Part 2: compute the size of the output.
570 * (Could also just write to an expanding buffer.)
572 size_t baseSize, totalSize;
573 baseSize = generateBaseOutput(NULL, 0, classNames, methodNames, fileNames);
574 assert(baseSize > 0);
575 totalSize = baseSize;
576 totalSize += computeStringTableSize(classNames);
577 totalSize += computeStringTableSize(methodNames);
578 totalSize += computeStringTableSize(fileNames);
579 ALOGI("Generated AT, size is %zd/%zd", baseSize, totalSize);
582 * Part 3: allocate a buffer and generate the output.
586 buffer = (u1*) malloc(totalSize);
587 strPtr = buffer + baseSize;
588 generateBaseOutput(buffer, baseSize, classNames, methodNames, fileNames);
589 strPtr += outputStringTable(classNames, strPtr);
590 strPtr += outputStringTable(methodNames, strPtr);
591 strPtr += outputStringTable(fileNames, strPtr);
592 if (strPtr - buffer != (int)totalSize) {
593 ALOGE("size mismatch (%d vs %zd)", strPtr - buffer, totalSize);
596 //dvmPrintHexDump(buffer, totalSize);
599 *pDataLen = totalSize;
600 buffer = NULL; // don't free -- caller will own
604 dvmPointerSetFree(classNames);
605 dvmPointerSetFree(methodNames);
606 dvmPointerSetFree(fileNames);
608 dvmUnlockMutex(&gDvm.allocTrackerLock);
609 //dvmDumpTrackedAllocations(false);
614 * Dump the tracked allocations to the log file.
616 * If "enable" is set, we try to enable the feature if it's not already
619 void dvmDumpTrackedAllocations(bool enable)
622 dvmEnableAllocTracker();
624 dvmLockMutex(&gDvm.allocTrackerLock);
625 if (gDvm.allocRecords == NULL) {
626 dvmUnlockMutex(&gDvm.allocTrackerLock);
631 * "idx" is the head of the list. We want to start at the end of the
632 * list and move forward to the tail.
634 int idx = headIndex();
635 int count = gDvm.allocRecordCount;
637 ALOGI("Tracked allocations, (head=%d count=%d)",
638 gDvm.allocRecordHead, count);
640 AllocRecord* pRec = &gDvm.allocRecords[idx];
641 ALOGI(" T=%-2d %6d %s",
642 pRec->threadId, pRec->size, pRec->clazz->descriptor);
645 for (int i = 0; i < kMaxAllocRecordStackDepth; i++) {
646 if (pRec->stackElem[i].method == NULL)
649 const Method* method = pRec->stackElem[i].method;
650 if (dvmIsNativeMethod(method)) {
651 ALOGI(" %s.%s (Native)",
652 method->clazz->descriptor, method->name);
655 method->clazz->descriptor, method->name,
656 pRec->stackElem[i].pc);
661 /* pause periodically to help logcat catch up */
662 if ((count % 5) == 0)
665 idx = (idx + 1) & (gDvm.allocRecordMax-1);
668 dvmUnlockMutex(&gDvm.allocTrackerLock);
672 if (dvmGenerateTrackedAllocationReport(&data, &dataLen))