OSDN Git Service

merge in klp-release history after reset to klp-dev
[android-x86/dalvik.git] / vm / AllocTracker.cpp
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /*
18  * Allocation tracking and reporting.  We maintain a circular buffer with
19  * the most recent allocations.  The data can be viewed through DDMS.
20  *
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.
25  *
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.
31  *
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.
34  *
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
37  * this is active).
38  *
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.
43  */
44 #include "Dalvik.h"
45
46 #ifdef HAVE_ANDROID_OS
47 #include "cutils/properties.h"
48 static bool isPowerOfTwo(int x) { return (x & (x - 1)) == 0; }
49 #endif
50
51 #define kMaxAllocRecordStackDepth   16      /* max 255 */
52
53 #define kDefaultNumAllocRecords 64*1024 /* MUST be power of 2 */
54
55 /*
56  * Record the details of an allocation.
57  */
58 struct AllocRecord {
59     ClassObject*    clazz;      /* class allocated in this block */
60     u4              size;       /* total size requested */
61     u2              threadId;   /* simple thread ID; could be recycled */
62
63     /* stack trace elements; unused entries have method==NULL */
64     struct {
65         const Method* method;   /* which method we're executing in */
66         int         pc;         /* current execution offset, in 16-bit units */
67     } stackElem[kMaxAllocRecordStackDepth];
68 };
69
70 /*
71  * Initialize a few things.  This gets called early, so keep activity to
72  * a minimum.
73  */
74 bool dvmAllocTrackerStartup()
75 {
76     /* prep locks */
77     dvmInitMutex(&gDvm.allocTrackerLock);
78
79     /* initialized when enabled by DDMS */
80     assert(gDvm.allocRecords == NULL);
81
82     return true;
83 }
84
85 /*
86  * Release anything we're holding on to.
87  */
88 void dvmAllocTrackerShutdown()
89 {
90     free(gDvm.allocRecords);
91     dvmDestroyMutex(&gDvm.allocTrackerLock);
92 }
93
94
95 /*
96  * ===========================================================================
97  *      Collection
98  * ===========================================================================
99  */
100
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) {
107         char* end;
108         size_t value = strtoul(allocRecordMaxString, &end, 10);
109         if (*end != '\0') {
110             ALOGE("Ignoring %s '%s' --- invalid", propertyName, allocRecordMaxString);
111             return kDefaultNumAllocRecords;
112         }
113         if (!isPowerOfTwo(value)) {
114             ALOGE("Ignoring %s '%s' --- not power of two", propertyName, allocRecordMaxString);
115             return kDefaultNumAllocRecords;
116         }
117         return value;
118     }
119 #endif
120     return kDefaultNumAllocRecords;
121 }
122
123 /*
124  * Enable allocation tracking.  Does nothing if tracking is already enabled.
125  *
126  * Returns "true" on success.
127  */
128 bool dvmEnableAllocTracker()
129 {
130     bool result = true;
131     dvmLockMutex(&gDvm.allocTrackerLock);
132
133     if (gDvm.allocRecords == NULL) {
134         gDvm.allocRecordMax = getAllocRecordMax();
135
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);
141
142         if (gDvm.allocRecords == NULL)
143             result = false;
144     }
145
146     dvmUnlockMutex(&gDvm.allocTrackerLock);
147     return result;
148 }
149
150 /*
151  * Disable allocation tracking.  Does nothing if tracking is not enabled.
152  */
153 void dvmDisableAllocTracker()
154 {
155     dvmLockMutex(&gDvm.allocTrackerLock);
156
157     if (gDvm.allocRecords != NULL) {
158         free(gDvm.allocRecords);
159         gDvm.allocRecords = NULL;
160     }
161
162     dvmUnlockMutex(&gDvm.allocTrackerLock);
163 }
164
165 /*
166  * Get the last few stack frames.
167  */
168 static void getStackFrames(Thread* self, AllocRecord* pRec)
169 {
170     int stackDepth = 0;
171     void* fp;
172
173     fp = self->interpSave.curFrame;
174
175     while ((fp != NULL) && (stackDepth < kMaxAllocRecordStackDepth)) {
176         const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
177         const Method* method = saveArea->method;
178
179         if (!dvmIsBreakFrame((u4*) fp)) {
180             pRec->stackElem[stackDepth].method = method;
181             if (dvmIsNativeMethod(method)) {
182                 pRec->stackElem[stackDepth].pc = 0;
183             } else {
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);
189             }
190             stackDepth++;
191         }
192
193         assert(fp != saveArea->prevFrame);
194         fp = saveArea->prevFrame;
195     }
196
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;
201         stackDepth++;
202     }
203 }
204
205 /*
206  * Add a new allocation to the set.
207  */
208 void dvmDoTrackAllocation(ClassObject* clazz, size_t size)
209 {
210     Thread* self = dvmThreadSelf();
211     if (self == NULL) {
212         ALOGW("alloc tracker: no thread");
213         return;
214     }
215
216     dvmLockMutex(&gDvm.allocTrackerLock);
217     if (gDvm.allocRecords == NULL) {
218         dvmUnlockMutex(&gDvm.allocTrackerLock);
219         return;
220     }
221
222     /* advance and clip */
223     if (++gDvm.allocRecordHead == gDvm.allocRecordMax)
224         gDvm.allocRecordHead = 0;
225
226     AllocRecord* pRec = &gDvm.allocRecords[gDvm.allocRecordHead];
227
228     pRec->clazz = clazz;
229     pRec->size = size;
230     pRec->threadId = self->threadId;
231     getStackFrames(self, pRec);
232
233     if (gDvm.allocRecordCount < gDvm.allocRecordMax)
234         gDvm.allocRecordCount++;
235
236     dvmUnlockMutex(&gDvm.allocTrackerLock);
237 }
238
239
240 /*
241  * ===========================================================================
242  *      Reporting
243  * ===========================================================================
244  */
245
246 /*
247 The data we send to DDMS contains everything we have recorded.
248
249 Message header (all values big-endian):
250   (1b) message header len (to allow future expansion); includes itself
251   (1b) entry header len
252   (1b) stack frame 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
258   For each entry:
259     (4b) total allocation size
260     (2b) threadId
261     (2b) allocated object's class name index
262     (1b) stack depth
263     For each stack frame:
264       (2b) method's class name
265       (2b) method 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
271
272   As with other DDM traffic, strings are sent as a 4-byte length
273   followed by UTF-16 data.
274
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.
278
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.
283
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.
287 */
288 const int kMessageHeaderLen = 15;
289 const int kEntryHeaderLen = 9;
290 const int kStackFrameLen = 8;
291
292 /*
293  * Return the index of the head element.
294  *
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
297  * from it.
298  *
299  * We need to handle underflow in our circular buffer, so we add
300  * gDvm.allocRecordMax and then mask it back down.
301  */
302 inline static int headIndex()
303 {
304     return (gDvm.allocRecordHead+1 + gDvm.allocRecordMax - gDvm.allocRecordCount)
305             & (gDvm.allocRecordMax-1);
306 }
307
308 /*
309  * Dump the contents of a PointerSet full of character pointers.
310  */
311 static void dumpStringTable(PointerSet* strings)
312 {
313     int count = dvmPointerSetGetCount(strings);
314     int i;
315
316     for (i = 0; i < count; i++)
317         printf("  %s\n", (const char*) dvmPointerSetGetEntry(strings, i));
318 }
319
320 /*
321  * Get the method's source file.  If we don't know it, return "" instead
322  * of a NULL pointer.
323  */
324 static const char* getMethodSourceFile(const Method* method)
325 {
326     const char* fileName = dvmGetMethodSourceFile(method);
327     if (fileName == NULL)
328         fileName = "";
329     return fileName;
330 }
331
332 /*
333  * Generate string tables.
334  *
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).
342  */
343 static bool populateStringTables(PointerSet* classNames,
344     PointerSet* methodNames, PointerSet* fileNames)
345 {
346     int count = gDvm.allocRecordCount;
347     int idx = headIndex();
348     int classCount, methodCount, fileCount;         /* debug stats */
349
350     classCount = methodCount = fileCount = 0;
351
352     while (count--) {
353         AllocRecord* pRec = &gDvm.allocRecords[idx];
354
355         dvmPointerSetAddEntry(classNames, pRec->clazz->descriptor);
356         classCount++;
357
358         int i;
359         for (i = 0; i < kMaxAllocRecordStackDepth; i++) {
360             if (pRec->stackElem[i].method == NULL)
361                 break;
362
363             const Method* method = pRec->stackElem[i].method;
364             dvmPointerSetAddEntry(classNames, method->clazz->descriptor);
365             classCount++;
366             dvmPointerSetAddEntry(methodNames, method->name);
367             methodCount++;
368             dvmPointerSetAddEntry(fileNames, getMethodSourceFile(method));
369             fileCount++;
370         }
371
372         idx = (idx + 1) & (gDvm.allocRecordMax-1);
373     }
374
375     ALOGI("class %d/%d, method %d/%d, file %d/%d",
376         dvmPointerSetGetCount(classNames), classCount,
377         dvmPointerSetGetCount(methodNames), methodCount,
378         dvmPointerSetGetCount(fileNames), fileCount);
379
380     return true;
381 }
382
383 /*
384  * Generate the base info (i.e. everything but the string tables).
385  *
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.
390  *
391  * The size of the output data is returned.
392  */
393 static size_t generateBaseOutput(u1* ptr, size_t baseLen,
394     const PointerSet* classNames, const PointerSet* methodNames,
395     const PointerSet* fileNames)
396 {
397     u1* origPtr = ptr;
398     int count = gDvm.allocRecordCount;
399     int idx = headIndex();
400
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));
410     }
411     ptr += kMessageHeaderLen;
412
413     while (count--) {
414         AllocRecord* pRec = &gDvm.allocRecords[idx];
415
416         /* compute depth */
417         int  depth;
418         for (depth = 0; depth < kMaxAllocRecordStackDepth; depth++) {
419             if (pRec->stackElem[depth].method == NULL)
420                 break;
421         }
422
423         /* output header */
424         if (origPtr != NULL) {
425             set4BE(&ptr[0], pRec->size);
426             set2BE(&ptr[4], pRec->threadId);
427             set2BE(&ptr[6],
428                 dvmPointerSetFind(classNames, pRec->clazz->descriptor));
429             set1(&ptr[8], depth);
430         }
431         ptr += kEntryHeaderLen;
432
433         /* convert stack frames */
434         int i;
435         for (i = 0; i < depth; i++) {
436             if (origPtr != NULL) {
437                 const Method* method = pRec->stackElem[i].method;
438                 int lineNum;
439
440                 lineNum = dvmLineNumFromPC(method, pRec->stackElem[i].pc);
441                 if (lineNum > 32767)
442                     lineNum = 32767;
443
444                 set2BE(&ptr[0], dvmPointerSetFind(classNames,
445                         method->clazz->descriptor));
446                 set2BE(&ptr[2], dvmPointerSetFind(methodNames,
447                         method->name));
448                 set2BE(&ptr[4], dvmPointerSetFind(fileNames,
449                         getMethodSourceFile(method)));
450                 set2BE(&ptr[6], (u2)lineNum);
451             }
452             ptr += kStackFrameLen;
453         }
454
455         idx = (idx + 1) & (gDvm.allocRecordMax-1);
456     }
457
458     return ptr - origPtr;
459 }
460
461 /*
462  * Compute the size required to store a string table.  Includes the length
463  * word and conversion to UTF-16.
464  */
465 static size_t computeStringTableSize(PointerSet* strings)
466 {
467     int count = dvmPointerSetGetCount(strings);
468     size_t size = 0;
469     int i;
470
471     for (i = 0; i < count; i++) {
472         const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
473
474         size += 4 + dvmUtf8Len(str) * 2;
475     }
476
477     return size;
478 }
479
480 /*
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.
483  *
484  * Returns the string's length, in characters.
485  */
486 static int convertUtf8ToUtf16BEUA(u1* utf16Str, const char* utf8Str)
487 {
488     u1* origUtf16Str = utf16Str;
489
490     while (*utf8Str != '\0') {
491         u2 utf16 = dexGetUtf16FromUtf8(&utf8Str);       /* advances utf8Str */
492         set2BE(utf16Str, utf16);
493         utf16Str += 2;
494     }
495
496     return (utf16Str - origUtf16Str) / 2;
497 }
498
499 /*
500  * Output a string table serially.
501  */
502 static size_t outputStringTable(PointerSet* strings, u1* ptr)
503 {
504     int count = dvmPointerSetGetCount(strings);
505     u1* origPtr = ptr;
506     int i;
507
508     for (i = 0; i < count; i++) {
509         const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
510         int charLen;
511
512         /* copy UTF-8 string to big-endian unaligned UTF-16 */
513         charLen = convertUtf8ToUtf16BEUA(&ptr[4], str);
514         set4BE(&ptr[0], charLen);
515
516         ptr += 4 + charLen * 2;
517     }
518
519     return ptr - origPtr;
520 }
521
522 /*
523  * Generate a DDM packet with all of the tracked allocation data.
524  *
525  * On success, returns "true" with "*pData" and "*pDataLen" set.
526  */
527 bool dvmGenerateTrackedAllocationReport(u1** pData, size_t* pDataLen)
528 {
529     bool result = false;
530     u1* buffer = NULL;
531
532     dvmLockMutex(&gDvm.allocTrackerLock);
533
534     /*
535      * Part 1: generate string tables.
536      */
537     PointerSet* classNames = NULL;
538     PointerSet* methodNames = NULL;
539     PointerSet* fileNames = NULL;
540
541     /*
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
545      * if needed.
546      */
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");
552         goto bail;
553     }
554
555     if (!populateStringTables(classNames, methodNames, fileNames))
556         goto bail;
557
558     if (false) {
559         printf("Classes:\n");
560         dumpStringTable(classNames);
561         printf("Methods:\n");
562         dumpStringTable(methodNames);
563         printf("Files:\n");
564         dumpStringTable(fileNames);
565     }
566
567     /*
568      * Part 2: compute the size of the output.
569      *
570      * (Could also just write to an expanding buffer.)
571      */
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);
580
581     /*
582      * Part 3: allocate a buffer and generate the output.
583      */
584     u1* strPtr;
585
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);
594         dvmAbort();
595     }
596     //dvmPrintHexDump(buffer, totalSize);
597
598     *pData = buffer;
599     *pDataLen = totalSize;
600     buffer = NULL;          // don't free -- caller will own
601     result = true;
602
603 bail:
604     dvmPointerSetFree(classNames);
605     dvmPointerSetFree(methodNames);
606     dvmPointerSetFree(fileNames);
607     free(buffer);
608     dvmUnlockMutex(&gDvm.allocTrackerLock);
609     //dvmDumpTrackedAllocations(false);
610     return result;
611 }
612
613 /*
614  * Dump the tracked allocations to the log file.
615  *
616  * If "enable" is set, we try to enable the feature if it's not already
617  * active.
618  */
619 void dvmDumpTrackedAllocations(bool enable)
620 {
621     if (enable)
622         dvmEnableAllocTracker();
623
624     dvmLockMutex(&gDvm.allocTrackerLock);
625     if (gDvm.allocRecords == NULL) {
626         dvmUnlockMutex(&gDvm.allocTrackerLock);
627         return;
628     }
629
630     /*
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.
633      */
634     int idx = headIndex();
635     int count = gDvm.allocRecordCount;
636
637     ALOGI("Tracked allocations, (head=%d count=%d)",
638         gDvm.allocRecordHead, count);
639     while (count--) {
640         AllocRecord* pRec = &gDvm.allocRecords[idx];
641         ALOGI(" T=%-2d %6d %s",
642             pRec->threadId, pRec->size, pRec->clazz->descriptor);
643
644         if (true) {
645             for (int i = 0; i < kMaxAllocRecordStackDepth; i++) {
646                 if (pRec->stackElem[i].method == NULL)
647                     break;
648
649                 const Method* method = pRec->stackElem[i].method;
650                 if (dvmIsNativeMethod(method)) {
651                     ALOGI("    %s.%s (Native)",
652                         method->clazz->descriptor, method->name);
653                 } else {
654                     ALOGI("    %s.%s +%d",
655                         method->clazz->descriptor, method->name,
656                         pRec->stackElem[i].pc);
657                 }
658             }
659         }
660
661         /* pause periodically to help logcat catch up */
662         if ((count % 5) == 0)
663             usleep(40000);
664
665         idx = (idx + 1) & (gDvm.allocRecordMax-1);
666     }
667
668     dvmUnlockMutex(&gDvm.allocTrackerLock);
669     if (false) {
670         u1* data;
671         size_t dataLen;
672         if (dvmGenerateTrackedAllocationReport(&data, &dataLen))
673             free(data);
674     }
675 }