/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Native support for dalvik.system.SamplingProfiler
*/
#define LOG_TAG "SamplingProfiler"
#include <cutils/log.h>
#include "Dalvik.h"
#include "native/InternalNativePriv.h"
#include "native/SystemThread.h"
// ~20k
#define INITIAL_CAPACITY 1024
// ~80k
#define MAX_CAPACITY 4096
typedef enum {
/** The "event thread". */
EVENT_THREAD,
/** Not the "event thread". */
OTHER_THREAD
} ThreadType;
#define THREAD_TYPE_SIZE (OTHER_THREAD + 1)
typedef enum {
/** Executing bytecode. */
RUNNING_THREAD,
/** Waiting on a lock or VM resource. */
SUSPENDED_THREAD
} ThreadState;
#define THREAD_STATE_SIZE (SUSPENDED_THREAD + 1)
typedef enum {
/** This method is in the call stack. */
CALLING_METHOD,
/** VM is in this method. */
LEAF_METHOD
} MethodState;
#define METHOD_STATE_SIZE (LEAF_METHOD + 1)
/** SampleSet entry. */
typedef struct {
/** Entry key. */
const Method* method; // 4 bytes
/** Sample counts for method divided by thread type and state. */
u2 counts[THREAD_TYPE_SIZE][THREAD_STATE_SIZE][METHOD_STATE_SIZE]; // 16B
} MethodCount;
/**
* Set of MethodCount entries.
*
* Note: If we ever support class unloading, we'll need to make this a GC root
* so the methods don't get reclaimed.
*/
typedef struct {
/** Hash collisions. */
int collisions;
/** Number of entries in set. */
int size;
/** Number of slots. */
int capacity;
/** Maximum number of entries this set can hold. 3/4 capacity. */
int maxSize;
/** Used to convert a hash to an entry index. */
int mask;
/** Entry table. */
MethodCount* entries;
/** The event thread. */
Thread* eventThread;
} SampleSet;
/**
* Initializes an empty set with the given capacity (which must be a power of
* two). Allocates memory for the entry array which must be freed.
*/
static SampleSet newSampleSet(int capacity) {
SampleSet set;
set.collisions = 0;
set.size = 0;
set.capacity = capacity;
set.maxSize = (capacity >> 2) * 3; // 3/4 capacity
set.mask = capacity - 1;
set.entries = (MethodCount*) calloc(sizeof(MethodCount), capacity);
set.eventThread = NULL;
return set;
}
/** Hashes the given pointer. */
static u4 hash(const void* p) {
u4 h = (u4) p;
// This function treats its argument as seed for a Marsaglia
// xorshift random number generator, and produces the next
// value. The particular xorshift parameters used here tend to
// spread bits downward, to better cope with keys that differ
// only in upper bits, which otherwise excessively collide in
// small tables.
h ^= h >> 11;
h ^= h << 7;
return h ^ (h >> 16);
}
/** Doubles capacity of SampleSet. */
static void expand(SampleSet* oldSet) {
// TODO: Handle newSet.entries == NULL
SampleSet newSet = newSampleSet(oldSet->capacity << 1);
LOGI("Expanding sample set capacity to %d.", newSet.capacity);
int oldIndex;
MethodCount* oldEntries = oldSet->entries;
for (oldIndex = 0; oldIndex < oldSet->size; oldIndex++) {
MethodCount oldEntry = oldEntries[oldIndex];
if (oldEntry.method != NULL) {
// Find the first empty slot.
int start = hash(oldEntry.method) & newSet.mask;
int i = start;
while (newSet.entries[i].method != NULL) {
i = (i + 1) & newSet.mask;
}
// Copy the entry into the empty slot.
newSet.entries[i] = oldEntry;
newSet.collisions += (i != start);
}
}
free(oldEntries);
newSet.size = oldSet->size;
newSet.eventThread = oldSet->eventThread;
*oldSet = newSet;
}
/** Increments counter for method in set. */
static void countMethod(SampleSet* set, const Method* method,
ThreadType threadType, ThreadState threadState,
MethodState methodState) {
MethodCount* entries = set->entries;
int start = hash(method) & set->mask;
int i;
for (i = start;; i = (i + 1) & set->mask) {
MethodCount* entry = &entries[i];
if (entry->method == method) {
// We found an existing entry.
entry->counts[threadType][threadState][methodState]++;
return;
}
if (entry->method == NULL) {
// Add a new entry.
if (set->size < set->maxSize) {
entry->method = method;
entry->counts[threadType][threadState][methodState] = 1;
set->collisions += (i != start);
set->size++;
} else {
if (set->capacity < MAX_CAPACITY) {
// The set is 3/4 full. Expand it, and then add the entry.
expand(set);
countMethod(set, method, threadType, threadState,
methodState);
} else {
// Don't add any more entries.
// TODO: Should we replace the LRU entry?
}
}
return;
}
}
}
/** Clears all entries from sample set. */
static void clearSampleSet(SampleSet* set) {
set->collisions = 0;
set->size = 0;
memset(set->entries, 0, set->capacity * sizeof(MethodCount));
}
/**
* Collects a sample from a single, possibly running thread.
*/
static void sample(SampleSet* set, Thread* thread) {
ThreadType threadType = thread == set->eventThread
? EVENT_THREAD : OTHER_THREAD;
ThreadState threadState;
switch (dvmGetSystemThreadStatus(thread)) {
case THREAD_RUNNING: threadState = RUNNING_THREAD; break;
case THREAD_NATIVE: return; // Something went wrong. Skip this thread.
default: threadState = SUSPENDED_THREAD; // includes PAGING
}
/*
* This code reads the stack concurrently, so it needs to defend against
* garbage data that will certainly result from the stack changing out
* from under us.
*/
// Top of the stack.
void* stackTop = thread->interpStackStart;
void* currentFrame = thread->curFrame;
if (currentFrame == NULL) {
return;
}
MethodState methodState = LEAF_METHOD;
while (true) {
StackSaveArea* saveArea = SAVEAREA_FROM_FP(currentFrame);
const Method* method = saveArea->method;
// Count the method now. We'll validate later that it's a real Method*.
if (method != NULL) {
countMethod(set, method, threadType, threadState, methodState);
methodState = CALLING_METHOD;
}
void* callerFrame = saveArea->prevFrame;
if (callerFrame == NULL // No more callers.
|| callerFrame > stackTop // Stack underflow!
|| callerFrame < currentFrame // Wrong way!
) {
break;
}
currentFrame = callerFrame;
}
}
/**
* Collects samples.
*/
static void Dalvik_dalvik_system_SamplingProfiler_sample(const u4* args,
JValue* pResult) {
SampleSet* set = (SampleSet*) args[0];
dvmLockThreadList(dvmThreadSelf());
Thread* thread = gDvm.threadList;
int sampledThreads = 0;
Thread* self = dvmThreadSelf();
while (thread != NULL) {
if (thread != self) {
sample(set, thread);
sampledThreads++;
}
thread = thread->next;
}
dvmUnlockThreadList();
RETURN_INT(sampledThreads);
}
/**
* Gets the number of methods in the sample set.
*/
static void Dalvik_dalvik_system_SamplingProfiler_size(const u4* args,
JValue* pResult) {
SampleSet* set = (SampleSet*) args[0];
RETURN_INT(set->size);
}
/**
* Gets the number of collisions in the sample set.
*/
static void Dalvik_dalvik_system_SamplingProfiler_collisions(const u4* args,
JValue* pResult) {
SampleSet* set = (SampleSet*) args[0];
RETURN_INT(set->collisions);
}
/**
* Returns true if the method is in the given table.
*/
static bool inTable(const Method* method, const Method* table,
int tableLength) {
if (tableLength < 1) {
return false;
}
const Method* last = table + (tableLength - 1);
// Cast to char* to handle misaligned pointers.
return (char*) method >= (char*) table
&& (char*) method <= (char*) last;
}
/** Entry in a hash of method counts by class. */
typedef struct mcw {
/** Decorated method count. */
MethodCount* methodCount;
/** Shortcut to methodCount->method->clazz. */
ClassObject* clazz;
/** Pointer to class name that enables us to chop off the first char. */
const char* className;
/** Cached string lengths. */
u2 classNameLength;
u2 methodNameLength;
/** Next method in the same class. */
struct mcw* next;
} MethodCountWrapper;
/** Returns true if we can trim the first and last chars in the class name. */
static bool isNormalClassName(const char* clazzName, int length) {
return (length >= 2) && (clazzName[0] == 'L')
&& (clazzName[length - 1] == ';');
}
/**
* Heurtistically guesses whether or not 'method' actually points to a Method
* struct.
*/
static bool isValidMethod(const Method* method) {
if (!dvmLinearAllocContains(method, sizeof(Method))) {
LOGW("Method* is not in linear allocation table.");
return false;
}
ClassObject* clazz = method->clazz;
if (!dvmIsValidObject((Object*) clazz)) {
LOGW("method->clazz doesn't point to an object at all.");
return false;
}
if (clazz->obj.clazz != gDvm.classJavaLangClass) {
LOGW("method->clazz doesn't point to a ClassObject.");
return false;
}
// No need to validate the tables because we don't actually read them.
if (!inTable(method, clazz->directMethods, clazz->directMethodCount)
&& !inTable(method, clazz->virtualMethods,
clazz->virtualMethodCount)) {
LOGW("Method not found in associated ClassObject.");
return false;
}
// We're pretty sure at this point that we're looking at a real Method*.
// The only alternative is that 'method' points to the middle of a Method
// struct and whatever ->clazz resolves to relative to that random
// address happens to point to the right ClassObject*. We could mod
// the address to ensure that the Method* is aligned as expected, but it's
// probably not worth the overhead.
return true;
}
/** Converts slashes to dots in the given class name. */
static void slashesToDots(char* s, int length) {
int i;
for (i = 0; i < length; i++) {
if (s[i] == '/') {
s[i] = '.';
}
}
}
/**
* Compares class pointers from two method count wrappers. Used in the by-class
* hash table.
*/
static int compareMethodCountClasses(const void* tableItem,
const void* looseItem) {
const MethodCountWrapper* a = (MethodCountWrapper*) tableItem;
const MethodCountWrapper* b = (MethodCountWrapper*) looseItem;
u4 serialA = a->clazz->serialNumber;
u4 serialB = b->clazz->serialNumber;
return serialA == serialB ? 0 : (serialA < serialB ? -1 : 1);
}
/**
* Calculates amount of memory needed for the given class in the final
* snapshot and adds the result to arg.
*/
static int calculateSnapshotEntrySize(void* data, void* arg) {
MethodCountWrapper* wrapper = (MethodCountWrapper*) data;
const char* className = wrapper->clazz->descriptor;
wrapper->classNameLength = strlen(className);
if (isNormalClassName(className, wrapper->classNameLength)) {
// Trim first & last chars.
wrapper->className = className + 1;
wrapper->classNameLength -= 2;
} else {
wrapper->className = className;
}
// Size of this class entry.
int size = 2; // class name size
size += wrapper->classNameLength;
size += 2; // number of methods in this class
do {
wrapper->methodNameLength
= strlen(wrapper->methodCount->method->name);
size += 2; // method name size
size += wrapper->methodNameLength;
// sample counts
size += THREAD_TYPE_SIZE * THREAD_STATE_SIZE * METHOD_STATE_SIZE * 2;
wrapper = wrapper->next;
} while (wrapper != NULL);
int* total = (int*) arg;
*total += size;
return 0;
}
/** Writes 2 bytes and increments dest pointer. */
#define writeShort(dest, value) \
do { \
u2 _value = (value); \
*dest++ = (char) (_value >> 8); \
*dest++ = (char) _value; \
} while (0);
/** Writes length in 2 bytes and then string, increments dest. */
#define writeString(dest, s, length) \
do { \
u2 _length = (length); \
writeShort(dest, _length); \
memcpy(dest, s, _length); \
dest += _length; \
} while (0);
/**
* Writes the entry data and advances the pointer (in arg).
*/
static int writeSnapshotEntry(void* data, void* arg) {
MethodCountWrapper* wrapper = (MethodCountWrapper*) data;
// We'll copy offset back into offsetPointer at the end.
char** offsetPointer = (char**) arg;
char* offset = *offsetPointer;
// Class name.
writeString(offset, wrapper->className, wrapper->classNameLength);
slashesToDots(offset - wrapper->classNameLength, wrapper->classNameLength);
// Method count.
char* methodCountPointer = offset;
u2 methodCount = 0;
offset += 2;
// Method entries.
do {
// Method name.
writeString(offset, wrapper->methodCount->method->name,
wrapper->methodNameLength);
// Sample counts.
u2 (*counts)[THREAD_STATE_SIZE][METHOD_STATE_SIZE]
= wrapper->methodCount->counts;
int type, threadState, methodState;
for (type = 0; type < THREAD_TYPE_SIZE; type++)
for (threadState = 0; threadState < THREAD_STATE_SIZE;
threadState++)
for (methodState = 0; methodState < METHOD_STATE_SIZE;
methodState++)
writeShort(offset, counts[type][threadState][methodState]);
methodCount++;
wrapper = wrapper->next;
} while (wrapper != NULL);
// Go back and write method count.
writeShort(methodCountPointer, methodCount);
// Increment original pointer.
*offsetPointer = offset;
return 0;
}
/**
* Captures the collected samples and clears the sample set.
*/
static void Dalvik_dalvik_system_SamplingProfiler_snapshot(const u4* args,
JValue* pResult) {
/*
* Format:
* version # (2 bytes)
* # of class entries (2 bytes)
* ClassEntry...
*
* ClassEntry:
* class name length (2 bytes)
* UTF-8 class name
* # of method entries (2 bytes)
* MethodEntry...
*
* MethodEntry:
* method name length (2 bytes)
* UTF-8 method name
* CountsByThreadState (for event thread)
* CountsByThreadState (for other threads)
*
* CountsByThreadState:
* CountsByMethodState (for running threads)
* CountsByMethodState (for suspended threads)
*
* CountsByMethodState:
* as calling method (2 bytes)
* as leaf method (2 bytes)
*/
SampleSet* set = (SampleSet*) args[0];
if (set->size == 0) {
// No data has been captured.
RETURN_PTR(NULL);
}
MethodCountWrapper* wrappers = (MethodCountWrapper*) calloc(set->size,
sizeof(MethodCountWrapper));
if (wrappers == NULL) {
LOGW("Out of memory.");
RETURN_PTR(NULL);
}
// Method count wrappers by class.
HashTable* byClass = dvmHashTableCreate(set->size, NULL);
if (byClass == NULL) {
free(wrappers);
LOGW("Out of memory.");
RETURN_PTR(NULL);
}
// Validate method pointers and index by class.
int setIndex;
int wrapperIndex;
for (setIndex = set->capacity - 1, wrapperIndex = 0;
setIndex >= 0 && wrapperIndex < set->size;
setIndex--) {
MethodCount* mc = &set->entries[setIndex];
const Method* method = mc->method;
if (method != NULL && isValidMethod(method)) {
MethodCountWrapper* wrapper = &wrappers[wrapperIndex];
wrapper->methodCount = mc;
wrapper->clazz = mc->method->clazz;
u4 h = hash(wrapper->clazz);
MethodCountWrapper* fromTable = dvmHashTableLookup(byClass, h,
wrapper, compareMethodCountClasses, true);
if (fromTable != wrapper) {
// We already have an entry for this class. Link the new entry.
wrapper->next = fromTable->next;
fromTable->next = wrapper;
}
wrapperIndex++;
}
}
// Calculate size of snapshot in bytes.
int totalSize = 4; // version, # of classes
dvmHashForeach(byClass, calculateSnapshotEntrySize, &totalSize);
// Write snapshot.
ArrayObject* snapshot
= dvmAllocPrimitiveArray('B', totalSize, ALLOC_DEFAULT);
if (snapshot == NULL) {
// Not enough memory to hold snapshot.
// TODO: Still clear the set or leave it to try again later?
LOGW("Out of memory.");
free(wrappers);
dvmHashTableFree(byClass);
RETURN_PTR(NULL);
}
char* offset = (char*) snapshot->contents;
writeShort(offset, 1); // version
writeShort(offset, dvmHashTableNumEntries(byClass)); // class count
dvmHashForeach(byClass, writeSnapshotEntry, &offset);
// Verify that our size calculation was correct.
int actualSize = offset - (char*) snapshot->contents;
if (actualSize != totalSize) {
LOGE("expected: %d, actual: %d", totalSize, actualSize);
abort();
}
dvmHashTableFree(byClass);
free(wrappers);
clearSampleSet(set);
dvmReleaseTrackedAlloc((Object*) snapshot, NULL);
RETURN_PTR(snapshot);
}
/**
* Allocates native memory.
*/
static void Dalvik_dalvik_system_SamplingProfiler_allocate(const u4* args,
JValue* pResult) {
SampleSet* set = (SampleSet*) malloc(sizeof(SampleSet));
*set = newSampleSet(INITIAL_CAPACITY);
RETURN_INT((jint) set);
}
/**
* Frees native memory.
*/
static void Dalvik_dalvik_system_SamplingProfiler_free(const u4* args,
JValue* pResult) {
SampleSet* set = (SampleSet*) args[0];
free(set->entries);
free(set);
RETURN_VOID();
}
/**
* Identifies the event thread.
*/
static void Dalvik_dalvik_system_SamplingProfiler_setEventThread(const u4* args,
JValue* pResult) {
SampleSet* set = (SampleSet*) args[0];
Object* eventThread = (Object*) args[1]; // java.lang.Thread
Object* vmThread = dvmGetFieldObject(eventThread,
gDvm.offJavaLangThread_vmThread); // java.lang.VMThread
set->eventThread = dvmGetThreadFromThreadObject(vmThread);
RETURN_VOID();
}
const DalvikNativeMethod dvm_dalvik_system_SamplingProfiler[] = {
{ "collisions", "(I)I", Dalvik_dalvik_system_SamplingProfiler_collisions },
{ "size", "(I)I", Dalvik_dalvik_system_SamplingProfiler_size },
{ "sample", "(I)I", Dalvik_dalvik_system_SamplingProfiler_sample },
{ "snapshot", "(I)[B", Dalvik_dalvik_system_SamplingProfiler_snapshot },
{ "free", "(I)V", Dalvik_dalvik_system_SamplingProfiler_free },
{ "allocate", "()I", Dalvik_dalvik_system_SamplingProfiler_allocate },
{ "setEventThread", "(ILjava/lang/Thread;)V",
Dalvik_dalvik_system_SamplingProfiler_setEventThread },
{ NULL, NULL, NULL },
};