blob: 71ef4900885670c6623b887bcaf53d5faecf6461 [file] [log] [blame]
Bob Leecdacef52009-07-30 18:17:37 -07001/*
2 * Copyright (C) 2009 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 * Native support for dalvik.system.SamplingProfiler
19 */
20
21#define LOG_TAG "SamplingProfiler"
22
23#include <cutils/log.h>
24
25#include "Dalvik.h"
26#include "native/InternalNativePriv.h"
27
Bob Lee9dc72a32009-09-04 18:28:16 -070028// ~20k
Bob Leecdacef52009-07-30 18:17:37 -070029#define INITIAL_CAPACITY 1024
30
Bob Lee9dc72a32009-09-04 18:28:16 -070031// ~80k
Bob Leecdacef52009-07-30 18:17:37 -070032#define MAX_CAPACITY 4096
33
34typedef enum {
35 /** The "event thread". */
36 EVENT_THREAD,
37 /** Not the "event thread". */
38 OTHER_THREAD
39} ThreadType;
40
41#define THREAD_TYPE_SIZE (OTHER_THREAD + 1)
42
43typedef enum {
44 /** Executing bytecode. */
45 RUNNING_THREAD,
Bob Leecdacef52009-07-30 18:17:37 -070046 /** Waiting on a lock or VM resource. */
47 SUSPENDED_THREAD
48} ThreadState;
49
50#define THREAD_STATE_SIZE (SUSPENDED_THREAD + 1)
51
Bob Lee9dc72a32009-09-04 18:28:16 -070052typedef enum {
53 /** This method is in the call stack. */
54 CALLING_METHOD,
55 /** VM is in this method. */
56 LEAF_METHOD
57} MethodState;
58
59#define METHOD_STATE_SIZE (LEAF_METHOD + 1)
60
Bob Leecdacef52009-07-30 18:17:37 -070061/** SampleSet entry. */
62typedef struct {
63 /** Entry key. */
64 const Method* method; // 4 bytes
65 /** Sample counts for method divided by thread type and state. */
Bob Lee9dc72a32009-09-04 18:28:16 -070066 u2 counts[THREAD_TYPE_SIZE][THREAD_STATE_SIZE][METHOD_STATE_SIZE]; // 16B
Bob Leecdacef52009-07-30 18:17:37 -070067} MethodCount;
68
69/**
70 * Set of MethodCount entries.
71 *
72 * Note: If we ever support class unloading, we'll need to make this a GC root
73 * so the methods don't get reclaimed.
74 */
75typedef struct {
76 /** Hash collisions. */
77 int collisions;
78 /** Number of entries in set. */
79 int size;
80 /** Number of slots. */
81 int capacity;
82 /** Maximum number of entries this set can hold. 3/4 capacity. */
83 int maxSize;
84 /** Used to convert a hash to an entry index. */
85 int mask;
86 /** Entry table. */
87 MethodCount* entries;
88 /** The event thread. */
89 Thread* eventThread;
90} SampleSet;
91
92/**
93 * Initializes an empty set with the given capacity (which must be a power of
94 * two). Allocates memory for the entry array which must be freed.
95 */
96static SampleSet newSampleSet(int capacity) {
97 SampleSet set;
98 set.collisions = 0;
99 set.size = 0;
100 set.capacity = capacity;
101 set.maxSize = (capacity >> 2) * 3; // 3/4 capacity
102 set.mask = capacity - 1;
103 set.entries = (MethodCount*) calloc(sizeof(MethodCount), capacity);
104 set.eventThread = NULL;
105 return set;
106}
107
108/** Hashes the given pointer. */
109static u4 hash(const void* p) {
110 u4 h = (u4) p;
111
112 // This function treats its argument as seed for a Marsaglia
113 // xorshift random number generator, and produces the next
114 // value. The particular xorshift parameters used here tend to
115 // spread bits downward, to better cope with keys that differ
116 // only in upper bits, which otherwise excessively collide in
117 // small tables.
118 h ^= h >> 11;
119 h ^= h << 7;
120 return h ^ (h >> 16);
121}
122
123/** Doubles capacity of SampleSet. */
124static void expand(SampleSet* oldSet) {
125 // TODO: Handle newSet.entries == NULL
126 SampleSet newSet = newSampleSet(oldSet->capacity << 1);
127 LOGI("Expanding sample set capacity to %d.", newSet.capacity);
128 int oldIndex;
129 MethodCount* oldEntries = oldSet->entries;
130 for (oldIndex = 0; oldIndex < oldSet->size; oldIndex++) {
131 MethodCount oldEntry = oldEntries[oldIndex];
132 if (oldEntry.method != NULL) {
133 // Find the first empty slot.
134 int start = hash(oldEntry.method) & newSet.mask;
135 int i = start;
136 while (newSet.entries[i].method != NULL) {
137 i = (i + 1) & newSet.mask;
138 }
139
140 // Copy the entry into the empty slot.
141 newSet.entries[i] = oldEntry;
142 newSet.collisions += (i != start);
143 }
144 }
145 free(oldEntries);
146 newSet.size = oldSet->size;
147 newSet.eventThread = oldSet->eventThread;
148 *oldSet = newSet;
149}
150
151/** Increments counter for method in set. */
152static void countMethod(SampleSet* set, const Method* method,
Bob Lee9dc72a32009-09-04 18:28:16 -0700153 ThreadType threadType, ThreadState threadState,
154 MethodState methodState) {
Bob Leecdacef52009-07-30 18:17:37 -0700155 MethodCount* entries = set->entries;
156 int start = hash(method) & set->mask;
157 int i;
158 for (i = start;; i = (i + 1) & set->mask) {
159 MethodCount* entry = &entries[i];
160
161 if (entry->method == method) {
162 // We found an existing entry.
Bob Lee9dc72a32009-09-04 18:28:16 -0700163 entry->counts[threadType][threadState][methodState]++;
Bob Leecdacef52009-07-30 18:17:37 -0700164 return;
165 }
166
167 if (entry->method == NULL) {
168 // Add a new entry.
169 if (set->size < set->maxSize) {
170 entry->method = method;
Bob Lee9dc72a32009-09-04 18:28:16 -0700171 entry->counts[threadType][threadState][methodState] = 1;
Bob Leecdacef52009-07-30 18:17:37 -0700172 set->collisions += (i != start);
173 set->size++;
174 } else {
175 if (set->capacity < MAX_CAPACITY) {
176 // The set is 3/4 full. Expand it, and then add the entry.
177 expand(set);
Bob Lee9dc72a32009-09-04 18:28:16 -0700178 countMethod(set, method, threadType, threadState,
179 methodState);
Bob Leecdacef52009-07-30 18:17:37 -0700180 } else {
181 // Don't add any more entries.
182 // TODO: Should we replace the LRU entry?
183 }
184 }
185 return;
186 }
187 }
188}
189
190/** Clears all entries from sample set. */
191static void clearSampleSet(SampleSet* set) {
192 set->collisions = 0;
193 set->size = 0;
194 memset(set->entries, 0, set->capacity * sizeof(MethodCount));
195}
196
197/**
198 * Collects a sample from a single, possibly running thread.
199 */
200static void sample(SampleSet* set, Thread* thread) {
201 ThreadType threadType = thread == set->eventThread
202 ? EVENT_THREAD : OTHER_THREAD;
203
204 ThreadState threadState;
Bob Lee9dc72a32009-09-04 18:28:16 -0700205 switch (dvmGetNativeThreadStatus(thread)) {
Bob Leecdacef52009-07-30 18:17:37 -0700206 case THREAD_RUNNING: threadState = RUNNING_THREAD; break;
Bob Lee9dc72a32009-09-04 18:28:16 -0700207 case THREAD_NATIVE: dvmAbort();
208 default: threadState = SUSPENDED_THREAD; // includes PAGING
Bob Leecdacef52009-07-30 18:17:37 -0700209 }
210
211 /*
212 * This code reads the stack concurrently, so it needs to defend against
213 * garbage data that will certainly result from the stack changing out
214 * from under us.
215 */
216
217 // Top of the stack.
218 void* stackTop = thread->interpStackStart;
219
220 void* currentFrame = thread->curFrame;
221 if (currentFrame == NULL) {
222 return;
223 }
224
Bob Lee9dc72a32009-09-04 18:28:16 -0700225 MethodState methodState = LEAF_METHOD;
Bob Leecdacef52009-07-30 18:17:37 -0700226 while (true) {
227 StackSaveArea* saveArea = SAVEAREA_FROM_FP(currentFrame);
228
229 const Method* method = saveArea->method;
230 // Count the method now. We'll validate later that it's a real Method*.
231 if (method != NULL) {
Bob Lee9dc72a32009-09-04 18:28:16 -0700232 countMethod(set, method, threadType, threadState, methodState);
233 methodState = CALLING_METHOD;
Bob Leecdacef52009-07-30 18:17:37 -0700234 }
235
236 void* callerFrame = saveArea->prevFrame;
237 if (callerFrame == NULL // No more callers.
238 || callerFrame > stackTop // Stack underflow!
239 || callerFrame < currentFrame // Wrong way!
240 ) {
241 break;
242 }
243
244 currentFrame = callerFrame;
245 }
246}
247
248/**
249 * Collects samples.
250 */
251static void Dalvik_dalvik_system_SamplingProfiler_sample(const u4* args,
252 JValue* pResult) {
253 SampleSet* set = (SampleSet*) args[0];
254 dvmLockThreadList(dvmThreadSelf());
255 Thread* thread = gDvm.threadList;
256 int sampledThreads = 0;
257 Thread* self = dvmThreadSelf();
258 while (thread != NULL) {
259 if (thread != self) {
260 sample(set, thread);
261 sampledThreads++;
262 }
263 thread = thread->next;
264 }
265 dvmUnlockThreadList();
266 RETURN_INT(sampledThreads);
267}
268
269/**
270 * Gets the number of methods in the sample set.
271 */
272static void Dalvik_dalvik_system_SamplingProfiler_size(const u4* args,
273 JValue* pResult) {
274 SampleSet* set = (SampleSet*) args[0];
275 RETURN_INT(set->size);
276}
277
278/**
279 * Gets the number of collisions in the sample set.
280 */
281static void Dalvik_dalvik_system_SamplingProfiler_collisions(const u4* args,
282 JValue* pResult) {
283 SampleSet* set = (SampleSet*) args[0];
284 RETURN_INT(set->collisions);
285}
286
287/**
288 * Returns true if the method is in the given table.
289 */
290static bool inTable(const Method* method, const Method* table,
291 int tableLength) {
292 if (tableLength < 1) {
293 return false;
294 }
295
296 const Method* last = table + (tableLength - 1);
297
298 // Cast to char* to handle misaligned pointers.
299 return (char*) method >= (char*) table
300 && (char*) method <= (char*) last;
301}
302
303/** Entry in a hash of method counts by class. */
304typedef struct mcw {
305 /** Decorated method count. */
306 MethodCount* methodCount;
307
308 /** Shortcut to methodCount->method->clazz. */
309 ClassObject* clazz;
310 /** Pointer to class name that enables us to chop off the first char. */
311 const char* className;
312 /** Cached string lengths. */
313 u2 classNameLength;
314 u2 methodNameLength;
315
316 /** Next method in the same class. */
317 struct mcw* next;
318} MethodCountWrapper;
319
320/** Returns true if we can trim the first and last chars in the class name. */
321static bool isNormalClassName(const char* clazzName, int length) {
322 return (length >= 2) && (clazzName[0] == 'L')
323 && (clazzName[length - 1] == ';');
324}
325
326/**
327 * Heurtistically guesses whether or not 'method' actually points to a Method
328 * struct.
329 */
Andy McFaddene2729682009-08-20 16:13:05 -0700330static bool isValidMethod(const Method* method) {
Bob Leecdacef52009-07-30 18:17:37 -0700331 if (!dvmLinearAllocContains(method, sizeof(Method))) {
332 LOGW("Method* is not in linear allocation table.");
333 return false;
334 }
335 ClassObject* clazz = method->clazz;
336 if (!dvmIsValidObject((Object*) clazz)) {
337 LOGW("method->clazz doesn't point to an object at all.");
338 return false;
339 }
340 if (clazz->obj.clazz != gDvm.classJavaLangClass) {
341 LOGW("method->clazz doesn't point to a ClassObject.");
342 return false;
343 }
344
345 // No need to validate the tables because we don't actually read them.
346 if (!inTable(method, clazz->directMethods, clazz->directMethodCount)
347 && !inTable(method, clazz->virtualMethods,
348 clazz->virtualMethodCount)) {
349 LOGW("Method not found in associated ClassObject.");
350 return false;
351 }
352
353 // We're pretty sure at this point that we're looking at a real Method*.
354 // The only alternative is that 'method' points to the middle of a Method
355 // struct and whatever ->clazz resolves to relative to that random
356 // address happens to point to the right ClassObject*. We could mod
357 // the address to ensure that the Method* is aligned as expected, but it's
358 // probably not worth the overhead.
359 return true;
360}
361
362/** Converts slashes to dots in the given class name. */
363static void slashesToDots(char* s, int length) {
364 int i;
365 for (i = 0; i < length; i++) {
366 if (s[i] == '/') {
367 s[i] = '.';
368 }
369 }
370}
371
372/**
373 * Compares class pointers from two method count wrappers. Used in the by-class
374 * hash table.
375 */
376static int compareMethodCountClasses(const void* tableItem,
377 const void* looseItem) {
378 const MethodCountWrapper* a = (MethodCountWrapper*) tableItem;
379 const MethodCountWrapper* b = (MethodCountWrapper*) looseItem;
380 u4 serialA = a->clazz->serialNumber;
381 u4 serialB = b->clazz->serialNumber;
382 return serialA == serialB ? 0 : (serialA < serialB ? -1 : 1);
383}
384
385/**
386 * Calculates amount of memory needed for the given class in the final
387 * snapshot and adds the result to arg.
388 */
389static int calculateSnapshotEntrySize(void* data, void* arg) {
390 MethodCountWrapper* wrapper = (MethodCountWrapper*) data;
391
392 const char* className = wrapper->clazz->descriptor;
393 wrapper->classNameLength = strlen(className);
394 if (isNormalClassName(className, wrapper->classNameLength)) {
395 // Trim first & last chars.
396 wrapper->className = className + 1;
397 wrapper->classNameLength -= 2;
398 } else {
399 wrapper->className = className;
400 }
401
402 // Size of this class entry.
403 int size = 2; // class name size
404 size += wrapper->classNameLength;
405 size += 2; // number of methods in this class
406 do {
407 wrapper->methodNameLength
408 = strlen(wrapper->methodCount->method->name);
409
410 size += 2; // method name size
411 size += wrapper->methodNameLength;
Bob Lee9dc72a32009-09-04 18:28:16 -0700412 // sample counts
413 size += THREAD_TYPE_SIZE * THREAD_STATE_SIZE * METHOD_STATE_SIZE * 2;
Bob Leecdacef52009-07-30 18:17:37 -0700414 wrapper = wrapper->next;
415 } while (wrapper != NULL);
416
417 int* total = (int*) arg;
418 *total += size;
419
420 return 0;
421}
422
423/** Writes 2 bytes and increments dest pointer. */
424#define writeShort(dest, value) \
425do { \
426 u2 _value = (value); \
427 *dest++ = (char) (_value >> 8); \
428 *dest++ = (char) _value; \
429} while (0);
430
431/** Writes length in 2 bytes and then string, increments dest. */
432#define writeString(dest, s, length) \
433do { \
434 u2 _length = (length); \
435 writeShort(dest, _length); \
436 memcpy(dest, s, _length); \
437 dest += _length; \
438} while (0);
439
440/**
441 * Writes the entry data and advances the pointer (in arg).
442 */
443static int writeSnapshotEntry(void* data, void* arg) {
444 MethodCountWrapper* wrapper = (MethodCountWrapper*) data;
445
446 // We'll copy offset back into offsetPointer at the end.
447 char** offsetPointer = (char**) arg;
448 char* offset = *offsetPointer;
449
450 // Class name.
451 writeString(offset, wrapper->className, wrapper->classNameLength);
452 slashesToDots(offset - wrapper->classNameLength, wrapper->classNameLength);
453
454 // Method count.
455 char* methodCountPointer = offset;
456 u2 methodCount = 0;
457 offset += 2;
458
459 // Method entries.
460 do {
461 // Method name.
462 writeString(offset, wrapper->methodCount->method->name,
463 wrapper->methodNameLength);
464
465 // Sample counts.
Bob Lee9dc72a32009-09-04 18:28:16 -0700466 u2 (*counts)[THREAD_STATE_SIZE][METHOD_STATE_SIZE]
467 = wrapper->methodCount->counts;
468 int type, threadState, methodState;
Bob Leecdacef52009-07-30 18:17:37 -0700469 for (type = 0; type < THREAD_TYPE_SIZE; type++)
Bob Lee9dc72a32009-09-04 18:28:16 -0700470 for (threadState = 0; threadState < THREAD_STATE_SIZE;
471 threadState++)
472 for (methodState = 0; methodState < METHOD_STATE_SIZE;
473 methodState++)
474 writeShort(offset, counts[type][threadState][methodState]);
Bob Leecdacef52009-07-30 18:17:37 -0700475
476 methodCount++;
477 wrapper = wrapper->next;
478 } while (wrapper != NULL);
479
480 // Go back and write method count.
481 writeShort(methodCountPointer, methodCount);
482
483 // Increment original pointer.
484 *offsetPointer = offset;
485 return 0;
486}
487
488/**
489 * Captures the collected samples and clears the sample set.
490 */
491static void Dalvik_dalvik_system_SamplingProfiler_snapshot(const u4* args,
492 JValue* pResult) {
493 /*
494 * Format:
495 * version # (2 bytes)
496 * # of class entries (2 bytes)
497 * ClassEntry...
498 *
499 * ClassEntry:
500 * class name length (2 bytes)
501 * UTF-8 class name
502 * # of method entries (2 bytes)
503 * MethodEntry...
504 *
505 * MethodEntry:
506 * method name length (2 bytes)
507 * UTF-8 method name
Bob Lee9dc72a32009-09-04 18:28:16 -0700508 * CountsByThreadState (for event thread)
509 * CountsByThreadState (for other threads)
Bob Leecdacef52009-07-30 18:17:37 -0700510 *
Bob Lee9dc72a32009-09-04 18:28:16 -0700511 * CountsByThreadState:
512 * CountsByMethodState (for running threads)
513 * CountsByMethodState (for suspended threads)
514 *
515 * CountsByMethodState:
516 * as calling method (2 bytes)
517 * as leaf method (2 bytes)
Bob Leecdacef52009-07-30 18:17:37 -0700518 */
519
520 SampleSet* set = (SampleSet*) args[0];
521 if (set->size == 0) {
522 // No data has been captured.
523 RETURN_PTR(NULL);
524 }
525
526 MethodCountWrapper* wrappers = (MethodCountWrapper*) calloc(set->size,
527 sizeof(MethodCountWrapper));
528 if (wrappers == NULL) {
529 LOGW("Out of memory.");
530 RETURN_PTR(NULL);
531 }
532
533 // Method count wrappers by class.
534 HashTable* byClass = dvmHashTableCreate(set->size, NULL);
535 if (byClass == NULL) {
536 free(wrappers);
537 LOGW("Out of memory.");
538 RETURN_PTR(NULL);
539 }
540
541 // Validate method pointers and index by class.
542 int setIndex;
543 int wrapperIndex;
544 for (setIndex = set->capacity - 1, wrapperIndex = 0;
545 setIndex >= 0 && wrapperIndex < set->size;
546 setIndex--) {
547 MethodCount* mc = &set->entries[setIndex];
Andy McFaddene2729682009-08-20 16:13:05 -0700548 const Method* method = mc->method;
Bob Leecdacef52009-07-30 18:17:37 -0700549 if (method != NULL && isValidMethod(method)) {
550 MethodCountWrapper* wrapper = &wrappers[wrapperIndex];
551 wrapper->methodCount = mc;
552 wrapper->clazz = mc->method->clazz;
553 u4 h = hash(wrapper->clazz);
554 MethodCountWrapper* fromTable = dvmHashTableLookup(byClass, h,
555 wrapper, compareMethodCountClasses, true);
556 if (fromTable != wrapper) {
557 // We already have an entry for this class. Link the new entry.
558 wrapper->next = fromTable->next;
559 fromTable->next = wrapper;
560 }
561 wrapperIndex++;
562 }
563 }
564
565 // Calculate size of snapshot in bytes.
566 int totalSize = 4; // version, # of classes
567 dvmHashForeach(byClass, calculateSnapshotEntrySize, &totalSize);
568
569 // Write snapshot.
570 ArrayObject* snapshot
571 = dvmAllocPrimitiveArray('B', totalSize, ALLOC_DEFAULT);
572 if (snapshot == NULL) {
573 // Not enough memory to hold snapshot.
574 // TODO: Still clear the set or leave it to try again later?
575 LOGW("Out of memory.");
576 free(wrappers);
577 dvmHashTableFree(byClass);
578 RETURN_PTR(NULL);
579 }
580
581 char* offset = (char*) snapshot->contents;
582 writeShort(offset, 1); // version
583 writeShort(offset, dvmHashTableNumEntries(byClass)); // class count
584 dvmHashForeach(byClass, writeSnapshotEntry, &offset);
585
586 // Verify that our size calculation was correct.
587 int actualSize = offset - (char*) snapshot->contents;
588 if (actualSize != totalSize) {
589 LOGE("expected: %d, actual: %d", totalSize, actualSize);
590 abort();
591 }
592
593 dvmHashTableFree(byClass);
594 free(wrappers);
595
596 clearSampleSet(set);
597
598 dvmReleaseTrackedAlloc((Object*) snapshot, NULL);
599 RETURN_PTR(snapshot);
600}
601
602/**
603 * Allocates native memory.
604 */
605static void Dalvik_dalvik_system_SamplingProfiler_allocate(const u4* args,
606 JValue* pResult) {
607 SampleSet* set = (SampleSet*) malloc(sizeof(SampleSet));
608 *set = newSampleSet(INITIAL_CAPACITY);
609 RETURN_INT((jint) set);
610}
611
612/**
Bob Lee9dc72a32009-09-04 18:28:16 -0700613 * Frees native memory.
614 */
615static void Dalvik_dalvik_system_SamplingProfiler_free(const u4* args,
616 JValue* pResult) {
617 SampleSet* set = (SampleSet*) args[0];
618 free(set->entries);
619 free(set);
620 RETURN_VOID();
621}
622
623/**
Bob Leecdacef52009-07-30 18:17:37 -0700624 * Identifies the event thread.
625 */
626static void Dalvik_dalvik_system_SamplingProfiler_setEventThread(const u4* args,
627 JValue* pResult) {
628 SampleSet* set = (SampleSet*) args[0];
629 Object* eventThread = (Object*) args[1]; // java.lang.Thread
630 Object* vmThread = dvmGetFieldObject(eventThread,
631 gDvm.offJavaLangThread_vmThread); // java.lang.VMThread
632 set->eventThread = dvmGetThreadFromThreadObject(vmThread);
633 RETURN_VOID();
634}
635
636const DalvikNativeMethod dvm_dalvik_system_SamplingProfiler[] = {
637 { "collisions", "(I)I", Dalvik_dalvik_system_SamplingProfiler_collisions },
638 { "size", "(I)I", Dalvik_dalvik_system_SamplingProfiler_size },
639 { "sample", "(I)I", Dalvik_dalvik_system_SamplingProfiler_sample },
640 { "snapshot", "(I)[B", Dalvik_dalvik_system_SamplingProfiler_snapshot },
Bob Lee9dc72a32009-09-04 18:28:16 -0700641 { "free", "(I)V", Dalvik_dalvik_system_SamplingProfiler_free },
Bob Leecdacef52009-07-30 18:17:37 -0700642 { "allocate", "()I", Dalvik_dalvik_system_SamplingProfiler_allocate },
643 { "setEventThread", "(ILjava/lang/Thread;)V",
644 Dalvik_dalvik_system_SamplingProfiler_setEventThread },
645 { NULL, NULL, NULL },
Bob Lee9dc72a32009-09-04 18:28:16 -0700646};