Home | History | Annotate | Download | only in vm
      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 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 }
    654 
    655