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 * Android's method call profiling goodies. 19 */ 20#include "Dalvik.h" 21 22#ifdef WITH_PROFILER // -- include rest of file 23 24#include <stdlib.h> 25#include <stddef.h> 26#include <string.h> 27#include <sys/time.h> 28#include <time.h> 29#include <sys/mman.h> 30#include <sched.h> 31#include <errno.h> 32#include <fcntl.h> 33 34#include <cutils/open_memstream.h> 35 36#ifdef HAVE_ANDROID_OS 37# define UPDATE_MAGIC_PAGE 1 38#endif 39 40/* 41 * File format: 42 * header 43 * record 0 44 * record 1 45 * ... 46 * 47 * Header format: 48 * u4 magic ('SLOW') 49 * u2 version 50 * u2 offset to data 51 * u8 start date/time in usec 52 * 53 * Record format: 54 * u1 thread ID 55 * u4 method ID | method action 56 * u4 time delta since start, in usec 57 * 58 * 32 bits of microseconds is 70 minutes. 59 * 60 * All values are stored in little-endian order. 61 */ 62#define TRACE_REC_SIZE 9 63#define TRACE_MAGIC 0x574f4c53 64#define TRACE_HEADER_LEN 32 65 66#define FILL_PATTERN 0xeeeeeeee 67 68 69/* 70 * Get the wall-clock date/time, in usec. 71 */ 72static inline u8 getTimeInUsec() 73{ 74 struct timeval tv; 75 76 gettimeofday(&tv, NULL); 77 return tv.tv_sec * 1000000LL + tv.tv_usec; 78} 79 80/* 81 * Get the current time, in microseconds. 82 * 83 * This can mean one of two things. In "global clock" mode, we get the 84 * same time across all threads. If we use CLOCK_THREAD_CPUTIME_ID, we 85 * get a per-thread CPU usage timer. The latter is better, but a bit 86 * more complicated to implement. 87 */ 88static inline u8 getClock() 89{ 90#if defined(HAVE_POSIX_CLOCKS) 91 struct timespec tm; 92 93 clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm); 94 //assert(tm.tv_nsec >= 0 && tm.tv_nsec < 1*1000*1000*1000); 95 if (!(tm.tv_nsec >= 0 && tm.tv_nsec < 1*1000*1000*1000)) { 96 LOGE("bad nsec: %ld\n", tm.tv_nsec); 97 dvmAbort(); 98 } 99 100 return tm.tv_sec * 1000000LL + tm.tv_nsec / 1000; 101#else 102 struct timeval tv; 103 104 gettimeofday(&tv, NULL); 105 return tv.tv_sec * 1000000LL + tv.tv_usec; 106#endif 107} 108 109/* 110 * Write little-endian data. 111 */ 112static inline void storeShortLE(u1* buf, u2 val) 113{ 114 *buf++ = (u1) val; 115 *buf++ = (u1) (val >> 8); 116} 117static inline void storeIntLE(u1* buf, u4 val) 118{ 119 *buf++ = (u1) val; 120 *buf++ = (u1) (val >> 8); 121 *buf++ = (u1) (val >> 16); 122 *buf++ = (u1) (val >> 24); 123} 124static inline void storeLongLE(u1* buf, u8 val) 125{ 126 *buf++ = (u1) val; 127 *buf++ = (u1) (val >> 8); 128 *buf++ = (u1) (val >> 16); 129 *buf++ = (u1) (val >> 24); 130 *buf++ = (u1) (val >> 32); 131 *buf++ = (u1) (val >> 40); 132 *buf++ = (u1) (val >> 48); 133 *buf++ = (u1) (val >> 56); 134} 135 136/* 137 * Boot-time init. 138 */ 139bool dvmProfilingStartup(void) 140{ 141 /* 142 * Initialize "dmtrace" method profiling. 143 */ 144 memset(&gDvm.methodTrace, 0, sizeof(gDvm.methodTrace)); 145 dvmInitMutex(&gDvm.methodTrace.startStopLock); 146 pthread_cond_init(&gDvm.methodTrace.threadExitCond, NULL); 147 148 ClassObject* clazz = 149 dvmFindClassNoInit("Ldalvik/system/VMDebug;", NULL); 150 assert(clazz != NULL); 151 gDvm.methodTrace.gcMethod = 152 dvmFindDirectMethodByDescriptor(clazz, "startGC", "()V"); 153 gDvm.methodTrace.classPrepMethod = 154 dvmFindDirectMethodByDescriptor(clazz, "startClassPrep", "()V"); 155 if (gDvm.methodTrace.gcMethod == NULL || 156 gDvm.methodTrace.classPrepMethod == NULL) 157 { 158 LOGE("Unable to find startGC or startClassPrep\n"); 159 return false; 160 } 161 162 assert(!dvmCheckException(dvmThreadSelf())); 163 164 /* 165 * Allocate storage for instruction counters. 166 */ 167 gDvm.executedInstrCounts = (int*) malloc(kNumDalvikInstructions * sizeof(int)); 168 if (gDvm.executedInstrCounts == NULL) 169 return false; 170 memset(gDvm.executedInstrCounts, 0, kNumDalvikInstructions * sizeof(int)); 171 172#ifdef UPDATE_MAGIC_PAGE 173 /* 174 * If we're running on the emulator, there's a magic page into which 175 * we can put interpreted method information. This allows interpreted 176 * methods to show up in the emulator's code traces. 177 * 178 * We could key this off of the "ro.kernel.qemu" property, but there's 179 * no real harm in doing this on a real device. 180 */ 181 int fd = open("/dev/qemu_trace", O_RDWR); 182 if (fd < 0) { 183 LOGV("Unable to open /dev/qemu_trace\n"); 184 } else { 185 gDvm.emulatorTracePage = mmap(0, SYSTEM_PAGE_SIZE, PROT_READ|PROT_WRITE, 186 MAP_SHARED, fd, 0); 187 close(fd); 188 if (gDvm.emulatorTracePage == MAP_FAILED) { 189 LOGE("Unable to mmap /dev/qemu_trace\n"); 190 gDvm.emulatorTracePage = NULL; 191 } else { 192 *(u4*) gDvm.emulatorTracePage = 0; 193 } 194 } 195#else 196 assert(gDvm.emulatorTracePage == NULL); 197#endif 198 199 return true; 200} 201 202/* 203 * Free up profiling resources. 204 */ 205void dvmProfilingShutdown(void) 206{ 207#ifdef UPDATE_MAGIC_PAGE 208 if (gDvm.emulatorTracePage != NULL) 209 munmap(gDvm.emulatorTracePage, SYSTEM_PAGE_SIZE); 210#endif 211 free(gDvm.executedInstrCounts); 212} 213 214/* 215 * Update the "active profilers" count. 216 * 217 * "count" should be +1 or -1. 218 */ 219static void updateActiveProfilers(int count) 220{ 221 int oldValue, newValue; 222 223 do { 224 oldValue = gDvm.activeProfilers; 225 newValue = oldValue + count; 226 if (newValue < 0) { 227 LOGE("Can't have %d active profilers\n", newValue); 228 dvmAbort(); 229 } 230 } while (!ATOMIC_CMP_SWAP(&gDvm.activeProfilers, oldValue, newValue)); 231 232 LOGD("+++ active profiler count now %d\n", newValue); 233#if defined(WITH_JIT) 234 dvmCompilerStateRefresh(); 235#endif 236} 237 238 239/* 240 * Reset the "cpuClockBase" field in all threads. 241 */ 242static void resetCpuClockBase(void) 243{ 244 Thread* thread; 245 246 dvmLockThreadList(NULL); 247 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { 248 thread->cpuClockBaseSet = false; 249 thread->cpuClockBase = 0; 250 } 251 dvmUnlockThreadList(); 252} 253 254/* 255 * Dump the thread list to the specified file. 256 */ 257static void dumpThreadList(FILE* fp) 258{ 259 Thread* thread; 260 261 dvmLockThreadList(NULL); 262 for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { 263 char* name = dvmGetThreadName(thread); 264 265 fprintf(fp, "%d\t%s\n", thread->threadId, name); 266 free(name); 267 } 268 dvmUnlockThreadList(); 269} 270 271/* 272 * This is a dvmHashForeach callback. 273 */ 274static int dumpMarkedMethods(void* vclazz, void* vfp) 275{ 276 DexStringCache stringCache; 277 ClassObject* clazz = (ClassObject*) vclazz; 278 FILE* fp = (FILE*) vfp; 279 Method* meth; 280 char* name; 281 int i, lineNum; 282 283 dexStringCacheInit(&stringCache); 284 285 for (i = 0; i < clazz->virtualMethodCount; i++) { 286 meth = &clazz->virtualMethods[i]; 287 if (meth->inProfile) { 288 name = dvmDescriptorToName(meth->clazz->descriptor); 289 fprintf(fp, "0x%08x\t%s\t%s\t%s\t%s\t%d\n", (int) meth, 290 name, meth->name, 291 dexProtoGetMethodDescriptor(&meth->prototype, &stringCache), 292 dvmGetMethodSourceFile(meth), dvmLineNumFromPC(meth, 0)); 293 meth->inProfile = false; 294 free(name); 295 } 296 } 297 298 for (i = 0; i < clazz->directMethodCount; i++) { 299 meth = &clazz->directMethods[i]; 300 if (meth->inProfile) { 301 name = dvmDescriptorToName(meth->clazz->descriptor); 302 fprintf(fp, "0x%08x\t%s\t%s\t%s\t%s\t%d\n", (int) meth, 303 name, meth->name, 304 dexProtoGetMethodDescriptor(&meth->prototype, &stringCache), 305 dvmGetMethodSourceFile(meth), dvmLineNumFromPC(meth, 0)); 306 meth->inProfile = false; 307 free(name); 308 } 309 } 310 311 dexStringCacheRelease(&stringCache); 312 313 return 0; 314} 315 316/* 317 * Dump the list of "marked" methods to the specified file. 318 */ 319static void dumpMethodList(FILE* fp) 320{ 321 dvmHashTableLock(gDvm.loadedClasses); 322 dvmHashForeach(gDvm.loadedClasses, dumpMarkedMethods, (void*) fp); 323 dvmHashTableUnlock(gDvm.loadedClasses); 324} 325 326/* 327 * Start method tracing. Method tracing is global to the VM (i.e. we 328 * trace all threads). 329 * 330 * This opens the output file (if an already open fd has not been supplied, 331 * and we're not going direct to DDMS) and allocates the data buffer. 332 * 333 * On failure, we throw an exception and return. 334 */ 335void dvmMethodTraceStart(const char* traceFileName, int traceFd, int bufferSize, 336 int flags, bool directToDdms) 337{ 338 MethodTraceState* state = &gDvm.methodTrace; 339 340 assert(bufferSize > 0); 341 342 dvmLockMutex(&state->startStopLock); 343 while (state->traceEnabled != 0) { 344 LOGI("TRACE start requested, but already in progress; stopping\n"); 345 dvmUnlockMutex(&state->startStopLock); 346 dvmMethodTraceStop(); 347 dvmLockMutex(&state->startStopLock); 348 } 349 updateActiveProfilers(1); 350 LOGI("TRACE STARTED: '%s' %dKB\n", traceFileName, bufferSize / 1024); 351 352 /* 353 * Allocate storage and open files. 354 * 355 * We don't need to initialize the buffer, but doing so might remove 356 * some fault overhead if the pages aren't mapped until touched. 357 */ 358 state->buf = (u1*) malloc(bufferSize); 359 if (state->buf == NULL) { 360 dvmThrowException("Ljava/lang/InternalError;", "buffer alloc failed"); 361 goto fail; 362 } 363 if (!directToDdms) { 364 if (traceFd < 0) { 365 state->traceFile = fopen(traceFileName, "w"); 366 } else { 367 state->traceFile = fdopen(traceFd, "w"); 368 } 369 if (state->traceFile == NULL) { 370 int err = errno; 371 LOGE("Unable to open trace file '%s': %s\n", 372 traceFileName, strerror(err)); 373 dvmThrowExceptionFmt("Ljava/lang/RuntimeException;", 374 "Unable to open trace file '%s': %s", 375 traceFileName, strerror(err)); 376 goto fail; 377 } 378 } 379 memset(state->buf, (char)FILL_PATTERN, bufferSize); 380 381 state->directToDdms = directToDdms; 382 state->bufferSize = bufferSize; 383 state->overflow = false; 384 385 /* 386 * Enable alloc counts if we've been requested to do so. 387 */ 388 state->flags = flags; 389 if ((flags & TRACE_ALLOC_COUNTS) != 0) 390 dvmStartAllocCounting(); 391 392 /* reset our notion of the start time for all CPU threads */ 393 resetCpuClockBase(); 394 395 state->startWhen = getTimeInUsec(); 396 397 /* 398 * Output the header. 399 */ 400 memset(state->buf, 0, TRACE_HEADER_LEN); 401 storeIntLE(state->buf + 0, TRACE_MAGIC); 402 storeShortLE(state->buf + 4, TRACE_VERSION); 403 storeShortLE(state->buf + 6, TRACE_HEADER_LEN); 404 storeLongLE(state->buf + 8, state->startWhen); 405 state->curOffset = TRACE_HEADER_LEN; 406 407 MEM_BARRIER(); 408 409 /* 410 * Set the "enabled" flag. Once we do this, threads will wait to be 411 * signaled before exiting, so we have to make sure we wake them up. 412 */ 413 state->traceEnabled = true; 414 dvmUnlockMutex(&state->startStopLock); 415 return; 416 417fail: 418 updateActiveProfilers(-1); 419 if (state->traceFile != NULL) { 420 fclose(state->traceFile); 421 state->traceFile = NULL; 422 } 423 if (state->buf != NULL) { 424 free(state->buf); 425 state->buf = NULL; 426 } 427 dvmUnlockMutex(&state->startStopLock); 428} 429 430/* 431 * Run through the data buffer and pull out the methods that were visited. 432 * Set a mark so that we know which ones to output. 433 */ 434static void markTouchedMethods(int endOffset) 435{ 436 u1* ptr = gDvm.methodTrace.buf + TRACE_HEADER_LEN; 437 u1* end = gDvm.methodTrace.buf + endOffset; 438 unsigned int methodVal; 439 Method* method; 440 441 while (ptr < end) { 442 methodVal = *(ptr+1) | (*(ptr+2) << 8) | (*(ptr+3) << 16) 443 | (*(ptr+4) << 24); 444 method = (Method*) METHOD_ID(methodVal); 445 446 method->inProfile = true; 447 ptr += TRACE_REC_SIZE; 448 } 449} 450 451/* 452 * Compute the amount of overhead in a clock call, in nsec. 453 * 454 * This value is going to vary depending on what else is going on in the 455 * system. When examined across several runs a pattern should emerge. 456 */ 457static u4 getClockOverhead(void) 458{ 459 u8 calStart, calElapsed; 460 int i; 461 462 calStart = getClock(); 463 for (i = 1000 * 4; i > 0; i--) { 464 getClock(); 465 getClock(); 466 getClock(); 467 getClock(); 468 getClock(); 469 getClock(); 470 getClock(); 471 getClock(); 472 } 473 474 calElapsed = getClock() - calStart; 475 return (int) (calElapsed / (8*4)); 476} 477 478/* 479 * Returns "true" if method tracing is currently active. 480 */ 481bool dvmIsMethodTraceActive(void) 482{ 483 const MethodTraceState* state = &gDvm.methodTrace; 484 return state->traceEnabled; 485} 486 487/* 488 * Stop method tracing. We write the buffer to disk and generate a key 489 * file so we can interpret it. 490 */ 491void dvmMethodTraceStop(void) 492{ 493 MethodTraceState* state = &gDvm.methodTrace; 494 u8 elapsed; 495 496 /* 497 * We need this to prevent somebody from starting a new trace while 498 * we're in the process of stopping the old. 499 */ 500 dvmLockMutex(&state->startStopLock); 501 502 if (!state->traceEnabled) { 503 /* somebody already stopped it, or it was never started */ 504 LOGD("TRACE stop requested, but not running\n"); 505 dvmUnlockMutex(&state->startStopLock); 506 return; 507 } else { 508 updateActiveProfilers(-1); 509 } 510 511 /* compute elapsed time */ 512 elapsed = getTimeInUsec() - state->startWhen; 513 514 /* 515 * Globally disable it, and allow other threads to notice. We want 516 * to stall here for at least as long as dvmMethodTraceAdd needs 517 * to finish. There's no real risk though -- it will take a while to 518 * write the data to disk, and we don't clear the buffer pointer until 519 * after that completes. 520 */ 521 state->traceEnabled = false; 522 MEM_BARRIER(); 523 sched_yield(); 524 usleep(250 * 1000); 525 526 if ((state->flags & TRACE_ALLOC_COUNTS) != 0) 527 dvmStopAllocCounting(); 528 529 /* 530 * It's possible under some circumstances for a thread to have advanced 531 * the data pointer but not written the method value. It's possible 532 * (though less likely) for the data pointer to be advanced, or partial 533 * data written, while we're doing work here. 534 * 535 * To avoid seeing partially-written data, we grab state->curOffset here, 536 * and use our local copy from here on. We then scan through what's 537 * already written. If we see the fill pattern in what should be the 538 * method pointer, we cut things off early. (If we don't, we'll fail 539 * when we dereference the pointer.) 540 * 541 * There's a theoretical possibility of interrupting another thread 542 * after it has partially written the method pointer, in which case 543 * we'll likely crash when we dereference it. The possibility of 544 * this actually happening should be at or near zero. Fixing it 545 * completely could be done by writing the thread number last and 546 * using a sentinel value to indicate a partially-written record, 547 * but that requires memory barriers. 548 */ 549 int finalCurOffset = state->curOffset; 550 551 if (finalCurOffset > TRACE_HEADER_LEN) { 552 u4 fillVal = METHOD_ID(FILL_PATTERN); 553 u1* scanPtr = state->buf + TRACE_HEADER_LEN; 554 555 while (scanPtr < state->buf + finalCurOffset) { 556 u4 methodVal = scanPtr[1] | (scanPtr[2] << 8) | (scanPtr[3] << 16) 557 | (scanPtr[4] << 24); 558 if (METHOD_ID(methodVal) == fillVal) { 559 u1* scanBase = state->buf + TRACE_HEADER_LEN; 560 LOGW("Found unfilled record at %d (of %d)\n", 561 (scanPtr - scanBase) / TRACE_REC_SIZE, 562 (finalCurOffset - TRACE_HEADER_LEN) / TRACE_REC_SIZE); 563 finalCurOffset = scanPtr - state->buf; 564 break; 565 } 566 567 scanPtr += TRACE_REC_SIZE; 568 } 569 } 570 571 LOGI("TRACE STOPPED%s: writing %d records\n", 572 state->overflow ? " (NOTE: overflowed buffer)" : "", 573 (finalCurOffset - TRACE_HEADER_LEN) / TRACE_REC_SIZE); 574 if (gDvm.debuggerActive) { 575 LOGW("WARNING: a debugger is active; method-tracing results " 576 "will be skewed\n"); 577 } 578 579 /* 580 * Do a quick calibration test to see how expensive our clock call is. 581 */ 582 u4 clockNsec = getClockOverhead(); 583 584 markTouchedMethods(finalCurOffset); 585 586 char* memStreamPtr; 587 size_t memStreamSize; 588 if (state->directToDdms) { 589 assert(state->traceFile == NULL); 590 state->traceFile = open_memstream(&memStreamPtr, &memStreamSize); 591 if (state->traceFile == NULL) { 592 /* not expected */ 593 LOGE("Unable to open memstream\n"); 594 dvmAbort(); 595 } 596 } 597 assert(state->traceFile != NULL); 598 599 fprintf(state->traceFile, "%cversion\n", TOKEN_CHAR); 600 fprintf(state->traceFile, "%d\n", TRACE_VERSION); 601 fprintf(state->traceFile, "data-file-overflow=%s\n", 602 state->overflow ? "true" : "false"); 603#if defined(HAVE_POSIX_CLOCKS) 604 fprintf(state->traceFile, "clock=thread-cpu\n"); 605#else 606 fprintf(state->traceFile, "clock=global\n"); 607#endif 608 fprintf(state->traceFile, "elapsed-time-usec=%llu\n", elapsed); 609 fprintf(state->traceFile, "num-method-calls=%d\n", 610 (finalCurOffset - TRACE_HEADER_LEN) / TRACE_REC_SIZE); 611 fprintf(state->traceFile, "clock-call-overhead-nsec=%d\n", clockNsec); 612 fprintf(state->traceFile, "vm=dalvik\n"); 613 if ((state->flags & TRACE_ALLOC_COUNTS) != 0) { 614 fprintf(state->traceFile, "alloc-count=%d\n", 615 gDvm.allocProf.allocCount); 616 fprintf(state->traceFile, "alloc-size=%d\n", 617 gDvm.allocProf.allocSize); 618 fprintf(state->traceFile, "gc-count=%d\n", 619 gDvm.allocProf.gcCount); 620 } 621 fprintf(state->traceFile, "%cthreads\n", TOKEN_CHAR); 622 dumpThreadList(state->traceFile); 623 fprintf(state->traceFile, "%cmethods\n", TOKEN_CHAR); 624 dumpMethodList(state->traceFile); 625 fprintf(state->traceFile, "%cend\n", TOKEN_CHAR); 626 627 if (state->directToDdms) { 628 /* 629 * Data is in two places: memStreamPtr and state->buf. Send 630 * the whole thing to DDMS, wrapped in an MPSE packet. 631 */ 632 fflush(state->traceFile); 633 634 struct iovec iov[2]; 635 iov[0].iov_base = memStreamPtr; 636 iov[0].iov_len = memStreamSize; 637 iov[1].iov_base = state->buf; 638 iov[1].iov_len = finalCurOffset; 639 dvmDbgDdmSendChunkV(CHUNK_TYPE("MPSE"), iov, 2); 640 } else { 641 /* append the profiling data */ 642 if (fwrite(state->buf, finalCurOffset, 1, state->traceFile) != 1) { 643 int err = errno; 644 LOGE("trace fwrite(%d) failed, errno=%d\n", finalCurOffset, err); 645 dvmThrowExceptionFmt("Ljava/lang/RuntimeException;", 646 "Trace data write failed: %s", strerror(err)); 647 } 648 } 649 650 /* done! */ 651 free(state->buf); 652 state->buf = NULL; 653 fclose(state->traceFile); 654 state->traceFile = NULL; 655 656 /* wake any threads that were waiting for profiling to complete */ 657 int cc = pthread_cond_broadcast(&state->threadExitCond); 658 assert(cc == 0); 659 dvmUnlockMutex(&state->startStopLock); 660} 661 662 663/* 664 * We just did something with a method. Emit a record. 665 * 666 * Multiple threads may be banging on this all at once. We use atomic ops 667 * rather than mutexes for speed. 668 */ 669void dvmMethodTraceAdd(Thread* self, const Method* method, int action) 670{ 671 MethodTraceState* state = &gDvm.methodTrace; 672 u4 clockDiff, methodVal; 673 int oldOffset, newOffset; 674 u1* ptr; 675 676 /* 677 * We can only access the per-thread CPU clock from within the 678 * thread, so we have to initialize the base time on the first use. 679 * (Looks like pthread_getcpuclockid(thread, &id) will do what we 680 * want, but it doesn't appear to be defined on the device.) 681 */ 682 if (!self->cpuClockBaseSet) { 683 self->cpuClockBase = getClock(); 684 self->cpuClockBaseSet = true; 685 //LOGI("thread base id=%d 0x%llx\n", 686 // self->threadId, self->cpuClockBase); 687 } 688 689 /* 690 * Advance "curOffset" atomically. 691 */ 692 do { 693 oldOffset = state->curOffset; 694 newOffset = oldOffset + TRACE_REC_SIZE; 695 if (newOffset > state->bufferSize) { 696 state->overflow = true; 697 return; 698 } 699 } while (!ATOMIC_CMP_SWAP(&state->curOffset, oldOffset, newOffset)); 700 701 //assert(METHOD_ACTION((u4) method) == 0); 702 703 u8 now = getClock(); 704 clockDiff = (u4) (now - self->cpuClockBase); 705 706 methodVal = METHOD_COMBINE((u4) method, action); 707 708 /* 709 * Write data into "oldOffset". 710 */ 711 ptr = state->buf + oldOffset; 712 *ptr++ = self->threadId; 713 *ptr++ = (u1) methodVal; 714 *ptr++ = (u1) (methodVal >> 8); 715 *ptr++ = (u1) (methodVal >> 16); 716 *ptr++ = (u1) (methodVal >> 24); 717 *ptr++ = (u1) clockDiff; 718 *ptr++ = (u1) (clockDiff >> 8); 719 *ptr++ = (u1) (clockDiff >> 16); 720 *ptr++ = (u1) (clockDiff >> 24); 721} 722 723/* 724 * We just did something with a method. Emit a record by setting a value 725 * in a magic memory location. 726 */ 727void dvmEmitEmulatorTrace(const Method* method, int action) 728{ 729#ifdef UPDATE_MAGIC_PAGE 730 /* 731 * We store the address of the Dalvik bytecodes to the memory-mapped 732 * trace page for normal Java methods. We also trace calls to native 733 * functions by storing the address of the native function to the 734 * trace page. 735 * Abstract methods don't have any bytecodes, so we don't trace them. 736 * (Abstract methods are never called, but in Dalvik they can be 737 * because we do a "late trap" to a native method to generate the 738 * abstract method exception.) 739 */ 740 if (dvmIsAbstractMethod(method)) 741 return; 742 743 u4* pMagic = (u4*) gDvm.emulatorTracePage; 744 u4 addr; 745 746 if (dvmIsNativeMethod(method)) { 747 /* 748 * The "action" parameter is one of: 749 * 0 = ENTER 750 * 1 = EXIT 751 * 2 = UNROLL 752 * To help the trace tools reconstruct the runtime stack containing 753 * a mix of Java plus native methods, we add 4 to the action if this 754 * is a native method. 755 */ 756 action += 4; 757 758 /* 759 * Get the address of the native function. 760 * This isn't the right address -- how do I get it? 761 * Fortunately, the trace tools can get by without the address, but 762 * it would be nice to fix this. 763 */ 764 addr = (u4) method->nativeFunc; 765 } else { 766 /* 767 * The dexlist output shows the &DexCode.insns offset value, which 768 * is offset from the start of the base DEX header. Method.insns 769 * is the absolute address, effectively offset from the start of 770 * the optimized DEX header. We either need to return the 771 * optimized DEX base file address offset by the right amount, or 772 * take the "real" address and subtract off the size of the 773 * optimized DEX header. 774 * 775 * Would be nice to factor this out at dexlist time, but we can't count 776 * on having access to the correct optimized DEX file. 777 */ 778 assert(method->insns != NULL); 779 const DexOptHeader* pOptHdr = method->clazz->pDvmDex->pDexFile->pOptHeader; 780 addr = (u4) method->insns - pOptHdr->dexOffset; 781 } 782 783 *(pMagic+action) = addr; 784 LOGVV("Set %p = 0x%08x (%s.%s)\n", 785 pMagic+action, addr, method->clazz->descriptor, method->name); 786#endif 787} 788 789/* 790 * The GC calls this when it's about to start. We add a marker to the 791 * trace output so the tool can exclude the GC cost from the results. 792 */ 793void dvmMethodTraceGCBegin(void) 794{ 795 TRACE_METHOD_ENTER(dvmThreadSelf(), gDvm.methodTrace.gcMethod); 796} 797void dvmMethodTraceGCEnd(void) 798{ 799 TRACE_METHOD_EXIT(dvmThreadSelf(), gDvm.methodTrace.gcMethod); 800} 801 802/* 803 * The class loader calls this when it's loading or initializing a class. 804 */ 805void dvmMethodTraceClassPrepBegin(void) 806{ 807 TRACE_METHOD_ENTER(dvmThreadSelf(), gDvm.methodTrace.classPrepMethod); 808} 809void dvmMethodTraceClassPrepEnd(void) 810{ 811 TRACE_METHOD_EXIT(dvmThreadSelf(), gDvm.methodTrace.classPrepMethod); 812} 813 814 815/* 816 * Enable emulator trace info. 817 */ 818void dvmEmulatorTraceStart(void) 819{ 820 /* If we could not map the emulator trace page, then do not enable tracing */ 821 if (gDvm.emulatorTracePage == NULL) 822 return; 823 824 updateActiveProfilers(1); 825 826 /* in theory we should make this an atomic inc; in practice not important */ 827 gDvm.emulatorTraceEnableCount++; 828 if (gDvm.emulatorTraceEnableCount == 1) 829 LOGD("--- emulator method traces enabled\n"); 830} 831 832/* 833 * Disable emulator trace info. 834 */ 835void dvmEmulatorTraceStop(void) 836{ 837 if (gDvm.emulatorTraceEnableCount == 0) { 838 LOGE("ERROR: emulator tracing not enabled\n"); 839 return; 840 } 841 updateActiveProfilers(-1); 842 /* in theory we should make this an atomic inc; in practice not important */ 843 gDvm.emulatorTraceEnableCount--; 844 if (gDvm.emulatorTraceEnableCount == 0) 845 LOGD("--- emulator method traces disabled\n"); 846} 847 848 849/* 850 * Start instruction counting. 851 */ 852void dvmStartInstructionCounting() 853{ 854 updateActiveProfilers(1); 855 /* in theory we should make this an atomic inc; in practice not important */ 856 gDvm.instructionCountEnableCount++; 857} 858 859/* 860 * Start instruction counting. 861 */ 862void dvmStopInstructionCounting() 863{ 864 if (gDvm.instructionCountEnableCount == 0) { 865 LOGE("ERROR: instruction counting not enabled\n"); 866 dvmAbort(); 867 } 868 updateActiveProfilers(-1); 869 gDvm.instructionCountEnableCount--; 870} 871 872 873/* 874 * Start alloc counting. Note this doesn't affect the "active profilers" 875 * count, since the interpreter loop is not involved. 876 */ 877void dvmStartAllocCounting(void) 878{ 879 gDvm.allocProf.enabled = true; 880} 881 882/* 883 * Stop alloc counting. 884 */ 885void dvmStopAllocCounting(void) 886{ 887 gDvm.allocProf.enabled = false; 888} 889 890#endif /*WITH_PROFILER*/ 891