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 #define kMaxAllocRecordStackDepth 16 /* max 255 */
47 #define kNumAllocRecords 512 /* MUST be power of 2 */
50 * Record the details of an allocation.
53 ClassObject* clazz; /* class allocated in this block */
54 u4 size; /* total size requested */
55 u2 threadId; /* simple thread ID; could be recycled */
57 /* stack trace elements; unused entries have method==NULL */
59 const Method* method; /* which method we're executing in */
60 int pc; /* current execution offset, in 16-bit units */
61 } stackElem[kMaxAllocRecordStackDepth];
64 * This was going to be either wall-clock time in seconds or monotonic
65 * time in milliseconds since the VM started, to give a rough sense for
66 * how long ago an allocation happened. This adds a system call per
67 * allocation, which is too much overhead.
73 * Initialize a few things. This gets called early, so keep activity to
76 bool dvmAllocTrackerStartup(void)
79 dvmInitMutex(&gDvm.allocTrackerLock);
81 /* initialized when enabled by DDMS */
82 assert(gDvm.allocRecords == NULL);
88 * Release anything we're holding on to.
90 void dvmAllocTrackerShutdown(void)
92 free(gDvm.allocRecords);
93 dvmDestroyMutex(&gDvm.allocTrackerLock);
98 * ===========================================================================
100 * ===========================================================================
104 * Enable allocation tracking. Does nothing if tracking is already enabled.
106 * Returns "true" on success.
108 bool dvmEnableAllocTracker(void)
111 dvmLockMutex(&gDvm.allocTrackerLock);
113 if (gDvm.allocRecords == NULL) {
114 LOGI("Enabling alloc tracker (%d entries, %d frames --> %d bytes)\n",
115 kNumAllocRecords, kMaxAllocRecordStackDepth,
116 sizeof(AllocRecord) * kNumAllocRecords);
117 gDvm.allocRecordHead = gDvm.allocRecordCount = 0;
119 (AllocRecord*) malloc(sizeof(AllocRecord) * kNumAllocRecords);
121 if (gDvm.allocRecords == NULL)
125 dvmUnlockMutex(&gDvm.allocTrackerLock);
130 * Disable allocation tracking. Does nothing if tracking is not enabled.
132 void dvmDisableAllocTracker(void)
134 dvmLockMutex(&gDvm.allocTrackerLock);
136 if (gDvm.allocRecords != NULL) {
137 free(gDvm.allocRecords);
138 gDvm.allocRecords = NULL;
141 dvmUnlockMutex(&gDvm.allocTrackerLock);
145 * Get the last few stack frames.
147 static void getStackFrames(Thread* self, AllocRecord* pRec)
154 while ((fp != NULL) && (stackDepth < kMaxAllocRecordStackDepth)) {
155 const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
156 const Method* method = saveArea->method;
158 if (!dvmIsBreakFrame(fp)) {
159 pRec->stackElem[stackDepth].method = method;
160 if (dvmIsNativeMethod(method)) {
161 pRec->stackElem[stackDepth].pc = 0;
163 assert(saveArea->xtra.currentPc >= method->insns &&
164 saveArea->xtra.currentPc <
165 method->insns + dvmGetMethodInsnsSize(method));
166 pRec->stackElem[stackDepth].pc =
167 (int) (saveArea->xtra.currentPc - method->insns);
172 assert(fp != saveArea->prevFrame);
173 fp = saveArea->prevFrame;
176 /* clear out the rest (normally there won't be any) */
177 while (stackDepth < kMaxAllocRecordStackDepth) {
178 pRec->stackElem[stackDepth].method = NULL;
179 pRec->stackElem[stackDepth].pc = 0;
185 * Add a new allocation to the set.
187 void dvmDoTrackAllocation(ClassObject* clazz, int size)
189 dvmLockMutex(&gDvm.allocTrackerLock);
190 if (gDvm.allocRecords == NULL)
193 Thread* self = dvmThreadSelf();
195 LOGW("alloc tracker: no thread\n");
199 /* advance and clip */
200 if (++gDvm.allocRecordHead == kNumAllocRecords)
201 gDvm.allocRecordHead = 0;
203 AllocRecord* pRec = &gDvm.allocRecords[gDvm.allocRecordHead];
207 pRec->threadId = self->threadId;
208 getStackFrames(self, pRec);
210 if (gDvm.allocRecordCount < kNumAllocRecords)
211 gDvm.allocRecordCount++;
214 dvmUnlockMutex(&gDvm.allocTrackerLock);
219 * ===========================================================================
221 * ===========================================================================
225 The data we send to DDMS contains everything we have recorded.
227 Message header (all values big-endian):
228 (1b) message header len (to allow future expansion); includes itself
229 (1b) entry header len
231 (2b) number of entries
232 (4b) offset to string table from start of message
233 (2b) number of class name strings
234 (2b) number of method name strings
235 (2b) number of source file name strings
237 (4b) total allocation size
239 (2b) allocated object's class name index
241 For each stack frame:
242 (2b) method's class name
244 (2b) method source file
245 (2b) line number, clipped to 32767; -2 if native; -1 if no source
246 (xb) class name strings
247 (xb) method name strings
248 (xb) source file strings
250 As with other DDM traffic, strings are sent as a 4-byte length
251 followed by UTF-16 data.
253 We send up 16-bit unsigned indexes into string tables. In theory there
254 can be (kMaxAllocRecordStackDepth * kNumAllocRecords) unique strings in
255 each table, but in practice there should be far fewer.
257 The chief reason for using a string table here is to keep the size of
258 the DDMS message to a minimum. This is partly to make the protocol
259 efficient, but also because we have to form the whole thing up all at
260 once in a memory buffer.
262 We use separate string tables for class names, method names, and source
263 files to keep the indexes small. There will generally be no overlap
264 between the contents of these tables.
266 const int kMessageHeaderLen = 15;
267 const int kEntryHeaderLen = 9;
268 const int kStackFrameLen = 8;
271 * Return the index of the head element.
273 * We point at the most-recently-written record, so if allocRecordCount is 1
274 * we want to use the current element. Take "head+1" and subtract count
277 * We need to handle underflow in our circular buffer, so we add
278 * kNumAllocRecords and then mask it back down.
280 inline static int headIndex(void)
282 return (gDvm.allocRecordHead+1 + kNumAllocRecords - gDvm.allocRecordCount)
283 & (kNumAllocRecords-1);
287 * Dump the contents of a PointerSet full of character pointers.
289 static void dumpStringTable(PointerSet* strings)
291 int count = dvmPointerSetGetCount(strings);
294 for (i = 0; i < count; i++)
295 printf(" %s\n", (const char*) dvmPointerSetGetEntry(strings, i));
299 * Get the method's source file. If we don't know it, return "" instead
302 static const char* getMethodSourceFile(const Method* method)
304 const char* fileName = dvmGetMethodSourceFile(method);
305 if (fileName == NULL)
311 * Generate string tables.
313 * Our source material is UTF-8 string constants from DEX files. If we
314 * want to be thorough we can generate a hash value for each string and
315 * use the VM hash table implementation, or we can do a quick & dirty job
316 * by just maintaining a list of unique pointers. If the same string
317 * constant appears in multiple DEX files we'll end up with duplicates,
318 * but in practice this shouldn't matter (and if it does, we can uniq-sort
319 * the result in a second pass).
321 static bool populateStringTables(PointerSet* classNames,
322 PointerSet* methodNames, PointerSet* fileNames)
324 int count = gDvm.allocRecordCount;
325 int idx = headIndex();
326 int classCount, methodCount, fileCount; /* debug stats */
328 classCount = methodCount = fileCount = 0;
331 AllocRecord* pRec = &gDvm.allocRecords[idx];
333 dvmPointerSetAddEntry(classNames, pRec->clazz->descriptor);
337 for (i = 0; i < kMaxAllocRecordStackDepth; i++) {
338 if (pRec->stackElem[i].method == NULL)
341 const Method* method = pRec->stackElem[i].method;
342 dvmPointerSetAddEntry(classNames, method->clazz->descriptor);
344 dvmPointerSetAddEntry(methodNames, method->name);
346 dvmPointerSetAddEntry(fileNames, getMethodSourceFile(method));
350 idx = (idx + 1) & (kNumAllocRecords-1);
353 LOGI("class %d/%d, method %d/%d, file %d/%d\n",
354 dvmPointerSetGetCount(classNames), classCount,
355 dvmPointerSetGetCount(methodNames), methodCount,
356 dvmPointerSetGetCount(fileNames), fileCount);
362 * Generate the base info (i.e. everything but the string tables).
364 * This should be called twice. On the first call, "ptr" is NULL and
365 * "baseLen" is zero. The return value is used to allocate a buffer.
366 * On the second call, "ptr" points to a data buffer, and "baseLen"
367 * holds the value from the result of the first call.
369 * The size of the output data is returned.
371 static size_t generateBaseOutput(u1* ptr, size_t baseLen,
372 const PointerSet* classNames, const PointerSet* methodNames,
373 const PointerSet* fileNames)
376 int count = gDvm.allocRecordCount;
377 int idx = headIndex();
379 if (origPtr != NULL) {
380 set1(&ptr[0], kMessageHeaderLen);
381 set1(&ptr[1], kEntryHeaderLen);
382 set1(&ptr[2], kStackFrameLen);
383 set2BE(&ptr[3], count);
384 set4BE(&ptr[5], baseLen);
385 set2BE(&ptr[9], dvmPointerSetGetCount(classNames));
386 set2BE(&ptr[11], dvmPointerSetGetCount(methodNames));
387 set2BE(&ptr[13], dvmPointerSetGetCount(fileNames));
389 ptr += kMessageHeaderLen;
392 AllocRecord* pRec = &gDvm.allocRecords[idx];
396 for (depth = 0; depth < kMaxAllocRecordStackDepth; depth++) {
397 if (pRec->stackElem[depth].method == NULL)
402 if (origPtr != NULL) {
403 set4BE(&ptr[0], pRec->size);
404 set2BE(&ptr[4], pRec->threadId);
406 dvmPointerSetFind(classNames, pRec->clazz->descriptor));
407 set1(&ptr[8], depth);
409 ptr += kEntryHeaderLen;
411 /* convert stack frames */
413 for (i = 0; i < depth; i++) {
414 if (origPtr != NULL) {
415 const Method* method = pRec->stackElem[i].method;
418 lineNum = dvmLineNumFromPC(method, pRec->stackElem[i].pc);
422 set2BE(&ptr[0], dvmPointerSetFind(classNames,
423 method->clazz->descriptor));
424 set2BE(&ptr[2], dvmPointerSetFind(methodNames,
426 set2BE(&ptr[4], dvmPointerSetFind(fileNames,
427 getMethodSourceFile(method)));
428 set2BE(&ptr[6], (u2)lineNum);
430 ptr += kStackFrameLen;
433 idx = (idx + 1) & (kNumAllocRecords-1);
436 return ptr - origPtr;
440 * Compute the size required to store a string table. Includes the length
441 * word and conversion to UTF-16.
443 static size_t computeStringTableSize(PointerSet* strings)
445 int count = dvmPointerSetGetCount(strings);
449 for (i = 0; i < count; i++) {
450 const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
452 size += 4 + dvmUtf8Len(str) * 2;
459 * Convert a UTF-8 string to UTF-16. We also need to byte-swap the values
460 * to big-endian, and we can't assume even alignment on the target.
462 * Returns the string's length, in characters.
464 static int convertUtf8ToUtf16BEUA(u1* utf16Str, const char* utf8Str)
466 u1* origUtf16Str = utf16Str;
468 while (*utf8Str != '\0') {
469 u2 utf16 = dexGetUtf16FromUtf8(&utf8Str); /* advances utf8Str */
470 set2BE(utf16Str, utf16);
474 return (utf16Str - origUtf16Str) / 2;
478 * Output a string table serially.
480 static size_t outputStringTable(PointerSet* strings, u1* ptr)
482 int count = dvmPointerSetGetCount(strings);
486 for (i = 0; i < count; i++) {
487 const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
490 /* copy UTF-8 string to big-endian unaligned UTF-16 */
491 charLen = convertUtf8ToUtf16BEUA(&ptr[4], str);
492 set4BE(&ptr[0], charLen);
494 ptr += 4 + charLen * 2;
497 return ptr - origPtr;
501 * Generate a DDM packet with all of the tracked allocation data.
503 * On success, returns "true" with "*pData" and "*pDataLen" set.
505 bool dvmGenerateTrackedAllocationReport(u1** pData, size_t* pDataLen)
510 dvmLockMutex(&gDvm.allocTrackerLock);
513 * Part 1: generate string tables.
515 PointerSet* classNames = NULL;
516 PointerSet* methodNames = NULL;
517 PointerSet* fileNames = NULL;
520 * Allocate storage. Usually there's 60-120 of each thing (sampled
521 * when max=512), but it varies widely and isn't closely bound to
522 * the number of allocations we've captured. The sets expand quickly
525 classNames = dvmPointerSetAlloc(128);
526 methodNames = dvmPointerSetAlloc(128);
527 fileNames = dvmPointerSetAlloc(128);
528 if (classNames == NULL || methodNames == NULL || fileNames == NULL) {
529 LOGE("Failed allocating pointer sets\n");
533 if (!populateStringTables(classNames, methodNames, fileNames))
537 printf("Classes:\n");
538 dumpStringTable(classNames);
539 printf("Methods:\n");
540 dumpStringTable(methodNames);
542 dumpStringTable(fileNames);
546 * Part 2: compute the size of the output.
548 * (Could also just write to an expanding buffer.)
550 size_t baseSize, totalSize;
551 baseSize = generateBaseOutput(NULL, 0, classNames, methodNames, fileNames);
552 assert(baseSize > 0);
553 totalSize = baseSize;
554 totalSize += computeStringTableSize(classNames);
555 totalSize += computeStringTableSize(methodNames);
556 totalSize += computeStringTableSize(fileNames);
557 LOGI("Generated AT, size is %zd/%zd\n", baseSize, totalSize);
560 * Part 3: allocate a buffer and generate the output.
564 buffer = (u1*) malloc(totalSize);
565 strPtr = buffer + baseSize;
566 generateBaseOutput(buffer, baseSize, classNames, methodNames, fileNames);
567 strPtr += outputStringTable(classNames, strPtr);
568 strPtr += outputStringTable(methodNames, strPtr);
569 strPtr += outputStringTable(fileNames, strPtr);
570 if (strPtr - buffer != (int)totalSize) {
571 LOGE("size mismatch (%d vs %zd)\n", strPtr - buffer, totalSize);
574 //dvmPrintHexDump(buffer, totalSize);
577 *pDataLen = totalSize;
578 buffer = NULL; // don't free -- caller will own
582 dvmPointerSetFree(classNames);
583 dvmPointerSetFree(methodNames);
584 dvmPointerSetFree(fileNames);
586 dvmUnlockMutex(&gDvm.allocTrackerLock);
587 //dvmDumpTrackedAllocations(false);
592 * Dump the tracked allocations to the log file.
594 * If "enable" is set, we try to enable the feature if it's not already
597 void dvmDumpTrackedAllocations(bool enable)
600 dvmEnableAllocTracker();
602 dvmLockMutex(&gDvm.allocTrackerLock);
603 if (gDvm.allocRecords == NULL)
607 * "idx" is the head of the list. We want to start at the end of the
608 * list and move forward to the tail.
610 int idx = headIndex();
611 int count = gDvm.allocRecordCount;
613 LOGI("Tracked allocations, (head=%d count=%d)\n",
614 gDvm.allocRecordHead, count);
616 AllocRecord* pRec = &gDvm.allocRecords[idx];
617 LOGI(" T=%-2d %6d %s\n",
618 pRec->threadId, pRec->size, pRec->clazz->descriptor);
622 for (i = 0; i < kMaxAllocRecordStackDepth; i++) {
623 if (pRec->stackElem[i].method == NULL)
626 const Method* method = pRec->stackElem[i].method;
627 if (dvmIsNativeMethod(method)) {
628 LOGI(" %s.%s (Native)\n",
629 method->clazz->descriptor, method->name);
632 method->clazz->descriptor, method->name,
633 pRec->stackElem[i].pc);
638 /* pause periodically to help logcat catch up */
639 if ((count % 5) == 0)
642 idx = (idx + 1) & (kNumAllocRecords-1);
646 dvmUnlockMutex(&gDvm.allocTrackerLock);
650 if (dvmGenerateTrackedAllocationReport(&data, &dataLen))