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 } 676