OSDN Git Service

am af242824: Revert "Fix a type error in the allocation of non-moving arrays." per...
[android-x86/dalvik.git] / vm / AllocTracker.c
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 #define kMaxAllocRecordStackDepth   16      /* max 255 */
47 #define kNumAllocRecords            512     /* MUST be power of 2 */
48
49 /*
50  * Record the details of an allocation.
51  */
52 struct AllocRecord {
53     ClassObject*    clazz;      /* class allocated in this block */
54     u4              size;       /* total size requested */
55     u2              threadId;   /* simple thread ID; could be recycled */
56
57     /* stack trace elements; unused entries have method==NULL */
58     struct {
59         const Method* method;   /* which method we're executing in */
60         int         pc;         /* current execution offset, in 16-bit units */
61     } stackElem[kMaxAllocRecordStackDepth];
62
63     /*
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.
68      */
69     //u4      timestamp;
70 };
71
72 /*
73  * Initialize a few things.  This gets called early, so keep activity to
74  * a minimum.
75  */
76 bool dvmAllocTrackerStartup(void)
77 {
78     /* prep locks */
79     dvmInitMutex(&gDvm.allocTrackerLock);
80
81     /* initialized when enabled by DDMS */
82     assert(gDvm.allocRecords == NULL);
83
84     return true;
85 }
86
87 /*
88  * Release anything we're holding on to.
89  */
90 void dvmAllocTrackerShutdown(void)
91 {
92     free(gDvm.allocRecords);
93     dvmDestroyMutex(&gDvm.allocTrackerLock);
94 }
95
96
97 /*
98  * ===========================================================================
99  *      Collection
100  * ===========================================================================
101  */
102
103 /*
104  * Enable allocation tracking.  Does nothing if tracking is already enabled.
105  *
106  * Returns "true" on success.
107  */
108 bool dvmEnableAllocTracker(void)
109 {
110     bool result = true;
111     dvmLockMutex(&gDvm.allocTrackerLock);
112
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;
118         gDvm.allocRecords =
119             (AllocRecord*) malloc(sizeof(AllocRecord) * kNumAllocRecords);
120
121         if (gDvm.allocRecords == NULL)
122             result = false;
123     }
124
125     dvmUnlockMutex(&gDvm.allocTrackerLock);
126     return result;
127 }
128
129 /*
130  * Disable allocation tracking.  Does nothing if tracking is not enabled.
131  */
132 void dvmDisableAllocTracker(void)
133 {
134     dvmLockMutex(&gDvm.allocTrackerLock);
135
136     if (gDvm.allocRecords != NULL) {
137         free(gDvm.allocRecords);
138         gDvm.allocRecords = NULL;
139     }
140
141     dvmUnlockMutex(&gDvm.allocTrackerLock);
142 }
143
144 /*
145  * Get the last few stack frames.
146  */
147 static void getStackFrames(Thread* self, AllocRecord* pRec)
148 {
149     int stackDepth = 0;
150     void* fp;
151
152     fp = self->curFrame;
153
154     while ((fp != NULL) && (stackDepth < kMaxAllocRecordStackDepth)) {
155         const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
156         const Method* method = saveArea->method;
157
158         if (!dvmIsBreakFrame(fp)) {
159             pRec->stackElem[stackDepth].method = method;
160             if (dvmIsNativeMethod(method)) {
161                 pRec->stackElem[stackDepth].pc = 0;
162             } else {
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);
168             }
169             stackDepth++;
170         }
171
172         assert(fp != saveArea->prevFrame);
173         fp = saveArea->prevFrame;
174     }
175
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;
180         stackDepth++;
181     }
182 }
183
184 /*
185  * Add a new allocation to the set.
186  */
187 void dvmDoTrackAllocation(ClassObject* clazz, int size)
188 {
189     dvmLockMutex(&gDvm.allocTrackerLock);
190     if (gDvm.allocRecords == NULL)
191         goto bail;
192
193     Thread* self = dvmThreadSelf();
194     if (self == NULL) {
195         LOGW("alloc tracker: no thread\n");
196         goto bail;
197     }
198
199     /* advance and clip */
200     if (++gDvm.allocRecordHead == kNumAllocRecords)
201         gDvm.allocRecordHead = 0;
202
203     AllocRecord* pRec = &gDvm.allocRecords[gDvm.allocRecordHead];
204
205     pRec->clazz = clazz;
206     pRec->size = size;
207     pRec->threadId = self->threadId;
208     getStackFrames(self, pRec);
209
210     if (gDvm.allocRecordCount < kNumAllocRecords)
211         gDvm.allocRecordCount++;
212
213 bail:
214     dvmUnlockMutex(&gDvm.allocTrackerLock);
215 }
216
217
218 /*
219  * ===========================================================================
220  *      Reporting
221  * ===========================================================================
222  */
223
224 /*
225 The data we send to DDMS contains everything we have recorded.
226
227 Message header (all values big-endian):
228   (1b) message header len (to allow future expansion); includes itself
229   (1b) entry header len
230   (1b) stack frame 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
236   For each entry:
237     (4b) total allocation size
238     (2b) threadId
239     (2b) allocated object's class name index
240     (1b) stack depth
241     For each stack frame:
242       (2b) method's class name
243       (2b) method 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
249
250   As with other DDM traffic, strings are sent as a 4-byte length
251   followed by UTF-16 data.
252
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.
256
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.
261
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.
265 */
266 const int kMessageHeaderLen = 15;
267 const int kEntryHeaderLen = 9;
268 const int kStackFrameLen = 8;
269
270 /*
271  * Return the index of the head element.
272  *
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
275  * from it.
276  *
277  * We need to handle underflow in our circular buffer, so we add
278  * kNumAllocRecords and then mask it back down.
279  */
280 inline static int headIndex(void)
281 {
282     return (gDvm.allocRecordHead+1 + kNumAllocRecords - gDvm.allocRecordCount)
283         & (kNumAllocRecords-1);
284 }
285
286 /*
287  * Dump the contents of a PointerSet full of character pointers.
288  */
289 static void dumpStringTable(PointerSet* strings)
290 {
291     int count = dvmPointerSetGetCount(strings);
292     int i;
293
294     for (i = 0; i < count; i++)
295         printf("  %s\n", (const char*) dvmPointerSetGetEntry(strings, i));
296 }
297
298 /*
299  * Get the method's source file.  If we don't know it, return "" instead
300  * of a NULL pointer.
301  */
302 static const char* getMethodSourceFile(const Method* method)
303 {
304     const char* fileName = dvmGetMethodSourceFile(method);
305     if (fileName == NULL)
306         fileName = "";
307     return fileName;
308 }
309
310 /*
311  * Generate string tables.
312  *
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).
320  */
321 static bool populateStringTables(PointerSet* classNames,
322     PointerSet* methodNames, PointerSet* fileNames)
323 {
324     int count = gDvm.allocRecordCount;
325     int idx = headIndex();
326     int classCount, methodCount, fileCount;         /* debug stats */
327
328     classCount = methodCount = fileCount = 0;
329
330     while (count--) {
331         AllocRecord* pRec = &gDvm.allocRecords[idx];
332
333         dvmPointerSetAddEntry(classNames, pRec->clazz->descriptor);
334         classCount++;
335
336         int i;
337         for (i = 0; i < kMaxAllocRecordStackDepth; i++) {
338             if (pRec->stackElem[i].method == NULL)
339                 break;
340
341             const Method* method = pRec->stackElem[i].method;
342             dvmPointerSetAddEntry(classNames, method->clazz->descriptor);
343             classCount++;
344             dvmPointerSetAddEntry(methodNames, method->name);
345             methodCount++;
346             dvmPointerSetAddEntry(fileNames, getMethodSourceFile(method));
347             fileCount++;
348         }
349
350         idx = (idx + 1) & (kNumAllocRecords-1);
351     }
352
353     LOGI("class %d/%d, method %d/%d, file %d/%d\n",
354         dvmPointerSetGetCount(classNames), classCount,
355         dvmPointerSetGetCount(methodNames), methodCount,
356         dvmPointerSetGetCount(fileNames), fileCount);
357
358     return true;
359 }
360
361 /*
362  * Generate the base info (i.e. everything but the string tables).
363  *
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.
368  *
369  * The size of the output data is returned.
370  */
371 static size_t generateBaseOutput(u1* ptr, size_t baseLen,
372     const PointerSet* classNames, const PointerSet* methodNames,
373     const PointerSet* fileNames)
374 {
375     u1* origPtr = ptr;
376     int count = gDvm.allocRecordCount;
377     int idx = headIndex();
378
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));
388     }
389     ptr += kMessageHeaderLen;
390
391     while (count--) {
392         AllocRecord* pRec = &gDvm.allocRecords[idx];
393
394         /* compute depth */
395         int  depth;
396         for (depth = 0; depth < kMaxAllocRecordStackDepth; depth++) {
397             if (pRec->stackElem[depth].method == NULL)
398                 break;
399         }
400
401         /* output header */
402         if (origPtr != NULL) {
403             set4BE(&ptr[0], pRec->size);
404             set2BE(&ptr[4], pRec->threadId);
405             set2BE(&ptr[6],
406                 dvmPointerSetFind(classNames, pRec->clazz->descriptor));
407             set1(&ptr[8], depth);
408         }
409         ptr += kEntryHeaderLen;
410
411         /* convert stack frames */
412         int i;
413         for (i = 0; i < depth; i++) {
414             if (origPtr != NULL) {
415                 const Method* method = pRec->stackElem[i].method;
416                 int lineNum;
417
418                 lineNum = dvmLineNumFromPC(method, pRec->stackElem[i].pc);
419                 if (lineNum > 32767)
420                     lineNum = 32767;
421
422                 set2BE(&ptr[0], dvmPointerSetFind(classNames,
423                         method->clazz->descriptor));
424                 set2BE(&ptr[2], dvmPointerSetFind(methodNames,
425                         method->name));
426                 set2BE(&ptr[4], dvmPointerSetFind(fileNames,
427                         getMethodSourceFile(method)));
428                 set2BE(&ptr[6], (u2)lineNum);
429             }
430             ptr += kStackFrameLen;
431         }
432
433         idx = (idx + 1) & (kNumAllocRecords-1);
434     }
435
436     return ptr - origPtr;
437 }
438
439 /*
440  * Compute the size required to store a string table.  Includes the length
441  * word and conversion to UTF-16.
442  */
443 static size_t computeStringTableSize(PointerSet* strings)
444 {
445     int count = dvmPointerSetGetCount(strings);
446     size_t size = 0;
447     int i;
448
449     for (i = 0; i < count; i++) {
450         const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
451
452         size += 4 + dvmUtf8Len(str) * 2;
453     }
454
455     return size;
456 }
457
458 /*
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.
461  *
462  * Returns the string's length, in characters.
463  */
464 static int convertUtf8ToUtf16BEUA(u1* utf16Str, const char* utf8Str)
465 {
466     u1* origUtf16Str = utf16Str;
467
468     while (*utf8Str != '\0') {
469         u2 utf16 = dexGetUtf16FromUtf8(&utf8Str);       /* advances utf8Str */
470         set2BE(utf16Str, utf16);
471         utf16Str += 2;
472     }
473
474     return (utf16Str - origUtf16Str) / 2;
475 }
476
477 /*
478  * Output a string table serially.
479  */
480 static size_t outputStringTable(PointerSet* strings, u1* ptr)
481 {
482     int count = dvmPointerSetGetCount(strings);
483     u1* origPtr = ptr;
484     int i;
485
486     for (i = 0; i < count; i++) {
487         const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
488         int charLen;
489
490         /* copy UTF-8 string to big-endian unaligned UTF-16 */
491         charLen = convertUtf8ToUtf16BEUA(&ptr[4], str);
492         set4BE(&ptr[0], charLen);
493
494         ptr += 4 + charLen * 2;
495     }
496
497     return ptr - origPtr;
498 }
499
500 /*
501  * Generate a DDM packet with all of the tracked allocation data.
502  *
503  * On success, returns "true" with "*pData" and "*pDataLen" set.
504  */
505 bool dvmGenerateTrackedAllocationReport(u1** pData, size_t* pDataLen)
506 {
507     bool result = false;
508     u1* buffer = NULL;
509
510     dvmLockMutex(&gDvm.allocTrackerLock);
511
512     /*
513      * Part 1: generate string tables.
514      */
515     PointerSet* classNames = NULL;
516     PointerSet* methodNames = NULL;
517     PointerSet* fileNames = NULL;
518
519     /*
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
523      * if needed.
524      */
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");
530         goto bail;
531     }
532
533     if (!populateStringTables(classNames, methodNames, fileNames))
534         goto bail;
535
536     if (false) {
537         printf("Classes:\n");
538         dumpStringTable(classNames);
539         printf("Methods:\n");
540         dumpStringTable(methodNames);
541         printf("Files:\n");
542         dumpStringTable(fileNames);
543     }
544
545     /*
546      * Part 2: compute the size of the output.
547      *
548      * (Could also just write to an expanding buffer.)
549      */
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);
558
559     /*
560      * Part 3: allocate a buffer and generate the output.
561      */
562     u1* strPtr;
563
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);
572         dvmAbort();
573     }
574     //dvmPrintHexDump(buffer, totalSize);
575
576     *pData = buffer;
577     *pDataLen = totalSize;
578     buffer = NULL;          // don't free -- caller will own
579     result = true;
580
581 bail:
582     dvmPointerSetFree(classNames);
583     dvmPointerSetFree(methodNames);
584     dvmPointerSetFree(fileNames);
585     free(buffer);
586     dvmUnlockMutex(&gDvm.allocTrackerLock);
587     //dvmDumpTrackedAllocations(false);
588     return result;
589 }
590
591 /*
592  * Dump the tracked allocations to the log file.
593  *
594  * If "enable" is set, we try to enable the feature if it's not already
595  * active.
596  */
597 void dvmDumpTrackedAllocations(bool enable)
598 {
599     if (enable)
600         dvmEnableAllocTracker();
601
602     dvmLockMutex(&gDvm.allocTrackerLock);
603     if (gDvm.allocRecords == NULL)
604         goto bail;
605
606     /*
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.
609      */
610     int idx = headIndex();
611     int count = gDvm.allocRecordCount;
612
613     LOGI("Tracked allocations, (head=%d count=%d)\n",
614         gDvm.allocRecordHead, count);
615     while (count--) {
616         AllocRecord* pRec = &gDvm.allocRecords[idx];
617         LOGI(" T=%-2d %6d %s\n",
618             pRec->threadId, pRec->size, pRec->clazz->descriptor);
619
620         if (true) {
621             int i;
622             for (i = 0; i < kMaxAllocRecordStackDepth; i++) {
623                 if (pRec->stackElem[i].method == NULL)
624                     break;
625
626                 const Method* method = pRec->stackElem[i].method;
627                 if (dvmIsNativeMethod(method)) {
628                     LOGI("    %s.%s (Native)\n",
629                         method->clazz->descriptor, method->name);
630                 } else {
631                     LOGI("    %s.%s +%d\n",
632                         method->clazz->descriptor, method->name,
633                         pRec->stackElem[i].pc);
634                 }
635             }
636         }
637
638         /* pause periodically to help logcat catch up */
639         if ((count % 5) == 0)
640             usleep(40000);
641
642         idx = (idx + 1) & (kNumAllocRecords-1);
643     }
644
645 bail:
646     dvmUnlockMutex(&gDvm.allocTrackerLock);
647     if (false) {
648         u1* data;
649         size_t dataLen;
650         if (dvmGenerateTrackedAllocationReport(&data, &dataLen))
651             free(data);
652     }
653 }