Modified Thread.c to allow sampling profiler thread in zygote. Added a function that
queries the native thread status. Modified the profiler to sample every thread at
a fixed interval instead of adjusting the delay based on the number of threads. This
will make it easier to compare results across apps. Added ability to shut down
the profiler thread. Added code to track whether the method was a leaf or a
caller.
diff --git a/vm/Thread.c b/vm/Thread.c
index 6a1e9aa..4cce9f8 100644
--- a/vm/Thread.c
+++ b/vm/Thread.c
@@ -1295,10 +1295,18 @@
     assert(threadObj != NULL);
 
     if(gDvm.zygote) {
-        dvmThrowException("Ljava/lang/IllegalStateException;",
-            "No new threads in -Xzygote mode");
+        // Allow the sampling profiler thread. We shut it down before forking.
+        StringObject* nameStr = (StringObject*) dvmGetFieldObject(threadObj,
+                    gDvm.offJavaLangThread_name);
+        char* threadName = dvmCreateCstrFromString(nameStr);
+        bool profilerThread = strcmp(threadName, "SamplingProfiler") == 0;
+        free(threadName);
+        if (!profilerThread) {
+            dvmThrowException("Ljava/lang/IllegalStateException;",
+                "No new threads in -Xzygote mode");
 
-        goto fail;
+            goto fail;
+        }
     }
 
     self = dvmThreadSelf();
@@ -2232,6 +2240,11 @@
     dvmUnlockThreadList();
 
     setThreadSelf(NULL);
+
+    if (self->statFile > 0) {
+        close(self->statFile);
+    }
+
     freeThread(self);
 }
 
@@ -3850,3 +3863,115 @@
      */
     gcScanAllThreads();
 }
+
+/* Converts a Linux thread state to a ThreadStatus. */
+static ThreadStatus stateToStatus(char state) {
+    switch (state) {
+        case 'R': return THREAD_RUNNING; // running
+        case 'S': return THREAD_WAIT;    // sleeping in interruptible wait
+        case 'D': return THREAD_WAIT;    // uninterruptible disk sleep
+        case 'Z': return THREAD_ZOMBIE;  // zombie
+        case 'T': return THREAD_WAIT;    // traced or stopped on a signal
+        case 'W': return THREAD_PAGING;  // paging memory
+        default:
+            LOGE("Unexpected state: %c", state);
+            return THREAD_NATIVE;
+    }
+}
+
+/* Reads the state char starting from the beginning of the file. */
+static char readStateFromBeginning(Thread* thread) {
+    char buffer[256];
+    int size = read(thread->statFile, buffer, sizeof(buffer));
+    if (size == 0) {
+        LOGE("EOF trying to read stat file.");
+        return 0;
+    }
+    char* endOfName = (char*) memchr(buffer, ')', sizeof(buffer));
+    if (endOfName == NULL) {
+        LOGE("End of executable name not found.");
+        return 0;
+    }
+    char* state = endOfName + 2;
+    thread->stateOffset = state - buffer;
+    if (*state == 0) {
+        LOGE("State char is 0.");
+    }
+    return *state;
+}
+
+/*
+ * Looks for the state char at the last place we found it. Read from the
+ * beginning if necessary.
+ */
+static char readStateRelatively(Thread* thread) {
+    char buffer[3];
+    // Position file offset at end of executable name.
+    int result = lseek(thread->statFile, thread->stateOffset - 2, SEEK_SET);
+    if (result < 0) {
+        LOGE("lseek() error.");
+        return 0;
+    }
+    int size = read(thread->statFile, buffer, sizeof(buffer));
+    if (size < (int) sizeof(buffer)) {
+        LOGE("EOF trying to read stat file.");
+        return 0;
+    }
+    if (buffer[0] != ')') {
+        // The executable name must have changed.
+        result = lseek(thread->statFile, 0, SEEK_SET);
+        if (result < 0) {
+            LOGE("lseek() error.");
+            return 0;
+        }
+        return readStateFromBeginning(thread);
+    }
+    char state = buffer[2];
+    if (state == 0) {
+        LOGE("State char is 0.");
+    }
+    return state;
+}
+
+ThreadStatus dvmGetNativeThreadStatus(Thread* thread) {
+    // TODO: Add a custom hook so this just reads a char from memory.
+
+    ThreadStatus status = thread->status;
+    if (status != THREAD_NATIVE) {
+        // Return cached status so we don't accidentally return THREAD_NATIVE.
+        return status;
+    }
+
+    if (thread->statFile == -1) {
+        // We tried and failed to open the file earlier. Return current status.
+        return thread->status;
+    }
+
+    // Note: see "man proc" for the format of stat.
+    char state;
+    if (thread->statFile == 0) {
+        // We haven't tried to open the file yet. Do so.
+        char fileName[256];
+        sprintf(fileName, "/proc/self/task/%d/stat", thread->systemTid);
+        thread->statFile = open(fileName, O_RDONLY);
+        if (thread->statFile == NULL) {
+            LOGE("Error opening %s: %s", fileName, strerror(errno));
+            thread->statFile = -1;
+            return thread->status;
+        }
+        state = readStateFromBeginning(thread);
+    } else {
+        state = readStateRelatively(thread);
+    }
+
+    if (state == 0) {
+        close(thread->statFile);
+        thread->statFile = -1;
+        return thread->status;
+    }
+    ThreadStatus nativeStatus = stateToStatus(state);
+
+    // The thread status could have changed from NATIVE.
+    status = thread->status;
+    return status == THREAD_NATIVE ? nativeStatus : status;
+}