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()
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()
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()
111 dvmLockMutex(&gDvm.allocTrackerLock);
113 if (gDvm.allocRecords == NULL) {
114 ALOGI("Enabling alloc tracker (%d entries, %d frames --> %d bytes)",
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()
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)
152 fp = self->interpSave.curFrame;
154 while ((fp != NULL) && (stackDepth < kMaxAllocRecordStackDepth)) {
155 const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
156 const Method* method = saveArea->method;
158 if (!dvmIsBreakFrame((u4*) 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, size_t size)
189 Thread* self = dvmThreadSelf();
191 ALOGW("alloc tracker: no thread");
195 dvmLockMutex(&gDvm.allocTrackerLock);
196 if (gDvm.allocRecords == NULL) {
197 dvmUnlockMutex(&gDvm.allocTrackerLock);
201 /* advance and clip */
202 if (++gDvm.allocRecordHead == kNumAllocRecords)
203 gDvm.allocRecordHead = 0;
205 AllocRecord* pRec = &gDvm.allocRecords[gDvm.allocRecordHead];
209 pRec->threadId = self->threadId;
210 getStackFrames(self, pRec);
212 if (gDvm.allocRecordCount < kNumAllocRecords)
213 gDvm.allocRecordCount++;
215 dvmUnlockMutex(&gDvm.allocTrackerLock);
220 * ===========================================================================
222 * ===========================================================================
226 The data we send to DDMS contains everything we have recorded.
228 Message header (all values big-endian):
229 (1b) message header len (to allow future expansion); includes itself
230 (1b) entry header len
232 (2b) number of entries
233 (4b) offset to string table from start of message
234 (2b) number of class name strings
235 (2b) number of method name strings
236 (2b) number of source file name strings
238 (4b) total allocation size
240 (2b) allocated object's class name index
242 For each stack frame:
243 (2b) method's class name
245 (2b) method source file
246 (2b) line number, clipped to 32767; -2 if native; -1 if no source
247 (xb) class name strings
248 (xb) method name strings
249 (xb) source file strings
251 As with other DDM traffic, strings are sent as a 4-byte length
252 followed by UTF-16 data.
254 We send up 16-bit unsigned indexes into string tables. In theory there
255 can be (kMaxAllocRecordStackDepth * kNumAllocRecords) unique strings in
256 each table, but in practice there should be far fewer.
258 The chief reason for using a string table here is to keep the size of
259 the DDMS message to a minimum. This is partly to make the protocol
260 efficient, but also because we have to form the whole thing up all at
261 once in a memory buffer.
263 We use separate string tables for class names, method names, and source
264 files to keep the indexes small. There will generally be no overlap
265 between the contents of these tables.
267 const int kMessageHeaderLen = 15;
268 const int kEntryHeaderLen = 9;
269 const int kStackFrameLen = 8;
272 * Return the index of the head element.
274 * We point at the most-recently-written record, so if allocRecordCount is 1
275 * we want to use the current element. Take "head+1" and subtract count
278 * We need to handle underflow in our circular buffer, so we add
279 * kNumAllocRecords and then mask it back down.
281 inline static int headIndex()
283 return (gDvm.allocRecordHead+1 + kNumAllocRecords - gDvm.allocRecordCount)
284 & (kNumAllocRecords-1);
288 * Dump the contents of a PointerSet full of character pointers.
290 static void dumpStringTable(PointerSet* strings)
292 int count = dvmPointerSetGetCount(strings);
295 for (i = 0; i < count; i++)
296 printf(" %s\n", (const char*) dvmPointerSetGetEntry(strings, i));
300 * Get the method's source file. If we don't know it, return "" instead
303 static const char* getMethodSourceFile(const Method* method)
305 const char* fileName = dvmGetMethodSourceFile(method);
306 if (fileName == NULL)
312 * Generate string tables.
314 * Our source material is UTF-8 string constants from DEX files. If we
315 * want to be thorough we can generate a hash value for each string and
316 * use the VM hash table implementation, or we can do a quick & dirty job
317 * by just maintaining a list of unique pointers. If the same string
318 * constant appears in multiple DEX files we'll end up with duplicates,
319 * but in practice this shouldn't matter (and if it does, we can uniq-sort
320 * the result in a second pass).
322 static bool populateStringTables(PointerSet* classNames,
323 PointerSet* methodNames, PointerSet* fileNames)
325 int count = gDvm.allocRecordCount;
326 int idx = headIndex();
327 int classCount, methodCount, fileCount; /* debug stats */
329 classCount = methodCount = fileCount = 0;
332 AllocRecord* pRec = &gDvm.allocRecords[idx];
334 dvmPointerSetAddEntry(classNames, pRec->clazz->descriptor);
338 for (i = 0; i < kMaxAllocRecordStackDepth; i++) {
339 if (pRec->stackElem[i].method == NULL)
342 const Method* method = pRec->stackElem[i].method;
343 dvmPointerSetAddEntry(classNames, method->clazz->descriptor);
345 dvmPointerSetAddEntry(methodNames, method->name);
347 dvmPointerSetAddEntry(fileNames, getMethodSourceFile(method));
351 idx = (idx + 1) & (kNumAllocRecords-1);
354 ALOGI("class %d/%d, method %d/%d, file %d/%d",
355 dvmPointerSetGetCount(classNames), classCount,
356 dvmPointerSetGetCount(methodNames), methodCount,
357 dvmPointerSetGetCount(fileNames), fileCount);
363 * Generate the base info (i.e. everything but the string tables).
365 * This should be called twice. On the first call, "ptr" is NULL and
366 * "baseLen" is zero. The return value is used to allocate a buffer.
367 * On the second call, "ptr" points to a data buffer, and "baseLen"
368 * holds the value from the result of the first call.
370 * The size of the output data is returned.
372 static size_t generateBaseOutput(u1* ptr, size_t baseLen,
373 const PointerSet* classNames, const PointerSet* methodNames,
374 const PointerSet* fileNames)
377 int count = gDvm.allocRecordCount;
378 int idx = headIndex();
380 if (origPtr != NULL) {
381 set1(&ptr[0], kMessageHeaderLen);
382 set1(&ptr[1], kEntryHeaderLen);
383 set1(&ptr[2], kStackFrameLen);
384 set2BE(&ptr[3], count);
385 set4BE(&ptr[5], baseLen);
386 set2BE(&ptr[9], dvmPointerSetGetCount(classNames));
387 set2BE(&ptr[11], dvmPointerSetGetCount(methodNames));
388 set2BE(&ptr[13], dvmPointerSetGetCount(fileNames));
390 ptr += kMessageHeaderLen;
393 AllocRecord* pRec = &gDvm.allocRecords[idx];
397 for (depth = 0; depth < kMaxAllocRecordStackDepth; depth++) {
398 if (pRec->stackElem[depth].method == NULL)
403 if (origPtr != NULL) {
404 set4BE(&ptr[0], pRec->size);
405 set2BE(&ptr[4], pRec->threadId);
407 dvmPointerSetFind(classNames, pRec->clazz->descriptor));
408 set1(&ptr[8], depth);
410 ptr += kEntryHeaderLen;
412 /* convert stack frames */
414 for (i = 0; i < depth; i++) {
415 if (origPtr != NULL) {
416 const Method* method = pRec->stackElem[i].method;
419 lineNum = dvmLineNumFromPC(method, pRec->stackElem[i].pc);
423 set2BE(&ptr[0], dvmPointerSetFind(classNames,
424 method->clazz->descriptor));
425 set2BE(&ptr[2], dvmPointerSetFind(methodNames,
427 set2BE(&ptr[4], dvmPointerSetFind(fileNames,
428 getMethodSourceFile(method)));
429 set2BE(&ptr[6], (u2)lineNum);
431 ptr += kStackFrameLen;
434 idx = (idx + 1) & (kNumAllocRecords-1);
437 return ptr - origPtr;
441 * Compute the size required to store a string table. Includes the length
442 * word and conversion to UTF-16.
444 static size_t computeStringTableSize(PointerSet* strings)
446 int count = dvmPointerSetGetCount(strings);
450 for (i = 0; i < count; i++) {
451 const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
453 size += 4 + dvmUtf8Len(str) * 2;
460 * Convert a UTF-8 string to UTF-16. We also need to byte-swap the values
461 * to big-endian, and we can't assume even alignment on the target.
463 * Returns the string's length, in characters.
465 static int convertUtf8ToUtf16BEUA(u1* utf16Str, const char* utf8Str)
467 u1* origUtf16Str = utf16Str;
469 while (*utf8Str != '\0') {
470 u2 utf16 = dexGetUtf16FromUtf8(&utf8Str); /* advances utf8Str */
471 set2BE(utf16Str, utf16);
475 return (utf16Str - origUtf16Str) / 2;
479 * Output a string table serially.
481 static size_t outputStringTable(PointerSet* strings, u1* ptr)
483 int count = dvmPointerSetGetCount(strings);
487 for (i = 0; i < count; i++) {
488 const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
491 /* copy UTF-8 string to big-endian unaligned UTF-16 */
492 charLen = convertUtf8ToUtf16BEUA(&ptr[4], str);
493 set4BE(&ptr[0], charLen);
495 ptr += 4 + charLen * 2;
498 return ptr - origPtr;
502 * Generate a DDM packet with all of the tracked allocation data.
504 * On success, returns "true" with "*pData" and "*pDataLen" set.
506 bool dvmGenerateTrackedAllocationReport(u1** pData, size_t* pDataLen)
511 dvmLockMutex(&gDvm.allocTrackerLock);
514 * Part 1: generate string tables.
516 PointerSet* classNames = NULL;
517 PointerSet* methodNames = NULL;
518 PointerSet* fileNames = NULL;
521 * Allocate storage. Usually there's 60-120 of each thing (sampled
522 * when max=512), but it varies widely and isn't closely bound to
523 * the number of allocations we've captured. The sets expand quickly
526 classNames = dvmPointerSetAlloc(128);
527 methodNames = dvmPointerSetAlloc(128);
528 fileNames = dvmPointerSetAlloc(128);
529 if (classNames == NULL || methodNames == NULL || fileNames == NULL) {
530 ALOGE("Failed allocating pointer sets");
534 if (!populateStringTables(classNames, methodNames, fileNames))
538 printf("Classes:\n");
539 dumpStringTable(classNames);
540 printf("Methods:\n");
541 dumpStringTable(methodNames);
543 dumpStringTable(fileNames);
547 * Part 2: compute the size of the output.
549 * (Could also just write to an expanding buffer.)
551 size_t baseSize, totalSize;
552 baseSize = generateBaseOutput(NULL, 0, classNames, methodNames, fileNames);
553 assert(baseSize > 0);
554 totalSize = baseSize;
555 totalSize += computeStringTableSize(classNames);
556 totalSize += computeStringTableSize(methodNames);
557 totalSize += computeStringTableSize(fileNames);
558 ALOGI("Generated AT, size is %zd/%zd", baseSize, totalSize);
561 * Part 3: allocate a buffer and generate the output.
565 buffer = (u1*) malloc(totalSize);
566 strPtr = buffer + baseSize;
567 generateBaseOutput(buffer, baseSize, classNames, methodNames, fileNames);
568 strPtr += outputStringTable(classNames, strPtr);
569 strPtr += outputStringTable(methodNames, strPtr);
570 strPtr += outputStringTable(fileNames, strPtr);
571 if (strPtr - buffer != (int)totalSize) {
572 ALOGE("size mismatch (%d vs %zd)", strPtr - buffer, totalSize);
575 //dvmPrintHexDump(buffer, totalSize);
578 *pDataLen = totalSize;
579 buffer = NULL; // don't free -- caller will own
583 dvmPointerSetFree(classNames);
584 dvmPointerSetFree(methodNames);
585 dvmPointerSetFree(fileNames);
587 dvmUnlockMutex(&gDvm.allocTrackerLock);
588 //dvmDumpTrackedAllocations(false);
593 * Dump the tracked allocations to the log file.
595 * If "enable" is set, we try to enable the feature if it's not already
598 void dvmDumpTrackedAllocations(bool enable)
601 dvmEnableAllocTracker();
603 dvmLockMutex(&gDvm.allocTrackerLock);
604 if (gDvm.allocRecords == NULL) {
605 dvmUnlockMutex(&gDvm.allocTrackerLock);
610 * "idx" is the head of the list. We want to start at the end of the
611 * list and move forward to the tail.
613 int idx = headIndex();
614 int count = gDvm.allocRecordCount;
616 ALOGI("Tracked allocations, (head=%d count=%d)",
617 gDvm.allocRecordHead, count);
619 AllocRecord* pRec = &gDvm.allocRecords[idx];
620 ALOGI(" T=%-2d %6d %s",
621 pRec->threadId, pRec->size, pRec->clazz->descriptor);
624 for (int i = 0; i < kMaxAllocRecordStackDepth; i++) {
625 if (pRec->stackElem[i].method == NULL)
628 const Method* method = pRec->stackElem[i].method;
629 if (dvmIsNativeMethod(method)) {
630 ALOGI(" %s.%s (Native)",
631 method->clazz->descriptor, method->name);
634 method->clazz->descriptor, method->name,
635 pRec->stackElem[i].pc);
640 /* pause periodically to help logcat catch up */
641 if ((count % 5) == 0)
644 idx = (idx + 1) & (kNumAllocRecords-1);
647 dvmUnlockMutex(&gDvm.allocTrackerLock);
651 if (dvmGenerateTrackedAllocationReport(&data, &dataLen))