Show CPU# and utime/stime in the Dalvik thread dump.

This adds the "CPU number last executed on" from /proc/stat to
the Dalvik thread output.  This may come in handy when looking at
thread dumps.  While I was at it I added the utime/stime values,
which may not be all that useful since they represent lifetime usage
rather than recent usage.

Output appears on the schedstat line:
  | schedstat=( 2930542006 9197204583 1284 ) ut=287 st=6 core=0

The routine that parses /proc/stat, previously only used for DDMS, has
been generalized.  This also fixes a problem with parsing threads
whose name includes a space.

I also changed this and the /proc/schedstat code to use /proc/self
instead of /proc/%d + getpid().

Bug 2884342.

Change-Id: Iec85bc929005044427ebbb468bfa0c9693444bca
diff --git a/vm/Ddm.c b/vm/Ddm.c
index 774ed3f..3cd3a80 100644
--- a/vm/Ddm.c
+++ b/vm/Ddm.c
@@ -364,95 +364,6 @@
 }
 
 /*
- * Get some per-thread stats.
- *
- * This is currently generated by opening the appropriate "stat" file
- * in /proc and reading the pile of stuff that comes out.
- */
-static bool getThreadStats(pid_t pid, pid_t tid, unsigned long* pUtime,
-    unsigned long* pStime)
-{
-    /*
-    int pid;
-    char comm[128];
-    char state;
-    int ppid, pgrp, session, tty_nr, tpgid;
-    unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime;
-    long cutime, cstime, priority, nice, zero, itrealvalue;
-    unsigned long starttime, vsize;
-    long rss;
-    unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip;
-    unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap;
-    int exit_signal, processor;
-    unsigned long rt_priority, policy;
-
-    scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld "
-          "%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu "
-          "%lu %lu %lu %d %d %lu %lu",
-        &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid,
-        &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime,
-        &cutime, &cstime, &priority, &nice, &zero, &itrealvalue,
-        &starttime, &vsize, &rss, &rlim, &startcode, &endcode,
-        &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore,
-        &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor,
-        &rt_priority, &policy);
-    */
-
-    char nameBuf[64];
-    int i, fd;
-
-    /*
-     * Open and read the appropriate file.  This is expected to work on
-     * Linux but will fail on other platforms (e.g. Mac sim).
-     */
-    sprintf(nameBuf, "/proc/%d/task/%d/stat", (int) pid, (int) tid);
-    fd = open(nameBuf, O_RDONLY);
-    if (fd < 0) {
-        LOGV("Unable to open '%s': %s\n", nameBuf, strerror(errno));
-        return false;
-    }
-
-    char lineBuf[512];      // > 2x typical
-    int cc;
-    cc = read(fd, lineBuf, sizeof(lineBuf)-1);
-    if (cc <= 0) {
-        const char* msg = (cc == 0) ? "unexpected EOF" : strerror(errno);
-        LOGI("Unable to read '%s': %s\n", nameBuf, msg);
-        close(fd);
-        return false;
-    }
-    lineBuf[cc] = '\0';
-
-    /*
-     * Skip whitespace-separated tokens.
-     */
-    static const char* kWhitespace = " ";
-    char* cp = lineBuf;
-    for (i = 0; i < 13; i++) {
-        cp += strcspn(cp, kWhitespace);     // skip token
-        cp += strspn(cp, kWhitespace);      // skip whitespace
-    }
-
-    /*
-     * Grab the values we want.
-     */
-    char* endp;
-    *pUtime = strtoul(cp, &endp, 10);
-    if (endp == cp)
-        LOGI("Warning: strtoul failed on utime ('%.30s...')\n", cp);
-
-    cp += strcspn(cp, kWhitespace);
-    cp += strspn(cp, kWhitespace);
-
-    *pStime = strtoul(cp, &endp, 10);
-    if (endp == cp)
-        LOGI("Warning: strtoul failed on stime ('%.30s...')\n", cp);
-
-    close(fd);
-    return true;
-}
-
-/*
  * Generate the contents of a THST chunk.  The data encompasses all known
  * threads.
  *
@@ -502,14 +413,13 @@
     set2BE(buf+2, (u2) threadCount);
     buf += kHeaderLen;
 
-    pid_t pid = getpid();
     for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
-        unsigned long utime, stime;
         bool isDaemon = false;
 
-        if (!getThreadStats(pid, thread->systemTid, &utime, &stime)) {
-            // failed; drop in empty values
-            utime = stime = 0;
+        ProcStatData procStatData;
+        if (!dvmGetThreadStats(&procStatData, thread->systemTid)) {
+            /* failed; show zero */
+            memset(&procStatData, 0, sizeof(procStatData));
         }
 
         Object* threadObj = thread->threadObj;
@@ -521,8 +431,8 @@
         set4BE(buf+0, thread->threadId);
         set1(buf+4, thread->status);
         set4BE(buf+5, thread->systemTid);
-        set4BE(buf+9, utime);
-        set4BE(buf+13, stime);
+        set4BE(buf+9, procStatData.utime);
+        set4BE(buf+13, procStatData.stime);
         set1(buf+17, isDaemon);
 
         buf += kBytesPerEntry;
diff --git a/vm/Misc.c b/vm/Misc.c
index 2a1e244..b4c8a6a 100644
--- a/vm/Misc.c
+++ b/vm/Misc.c
@@ -715,3 +715,120 @@
     }
     return base;
 }
+
+
+/*
+ * Get some per-thread stats.
+ *
+ * This is currently generated by opening the appropriate "stat" file
+ * in /proc and reading the pile of stuff that comes out.
+ */
+bool dvmGetThreadStats(ProcStatData* pData, pid_t tid)
+{
+    /*
+    int pid;
+    char comm[128];
+    char state;
+    int ppid, pgrp, session, tty_nr, tpgid;
+    unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime;
+    long cutime, cstime, priority, nice, zero, itrealvalue;
+    unsigned long starttime, vsize;
+    long rss;
+    unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip;
+    unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap;
+    int exit_signal, processor;
+    unsigned long rt_priority, policy;
+
+    scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld "
+          "%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu "
+          "%lu %lu %lu %d %d %lu %lu",
+        &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid,
+        &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime,
+        &cutime, &cstime, &priority, &nice, &zero, &itrealvalue,
+        &starttime, &vsize, &rss, &rlim, &startcode, &endcode,
+        &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore,
+        &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor,
+        &rt_priority, &policy);
+
+        (new: delayacct_blkio_ticks %llu (since Linux 2.6.18))
+    */
+
+    char nameBuf[64];
+    int i, fd;
+
+    /*
+     * Open and read the appropriate file.  This is expected to work on
+     * Linux but will fail on other platforms (e.g. Mac sim).
+     */
+    sprintf(nameBuf, "/proc/self/task/%d/stat", (int) tid);
+    fd = open(nameBuf, O_RDONLY);
+    if (fd < 0) {
+        LOGV("Unable to open '%s': %s\n", nameBuf, strerror(errno));
+        return false;
+    }
+
+    char lineBuf[512];      /* > 2x typical */
+    int cc = read(fd, lineBuf, sizeof(lineBuf)-1);
+    if (cc <= 0) {
+        const char* msg = (cc == 0) ? "unexpected EOF" : strerror(errno);
+        LOGI("Unable to read '%s': %s\n", nameBuf, msg);
+        close(fd);
+        return false;
+    }
+    close(fd);
+    lineBuf[cc] = '\0';
+
+    /*
+     * Skip whitespace-separated tokens.  For the most part we can assume
+     * that tokens do not contain spaces, and are separated by exactly one
+     * space character.  The only exception is the second field ("comm")
+     * which may contain spaces but is surrounded by parenthesis.
+     */
+    char* cp = strchr(lineBuf, ')');
+    if (cp == NULL)
+        goto parse_fail;
+    cp++;
+    for (i = 2; i < 13; i++) {
+        cp = strchr(cp+1, ' ');
+        if (cp == NULL)
+            goto parse_fail;
+    }
+
+    /*
+     * Grab utime/stime.
+     */
+    char* endp;
+    pData->utime = strtoul(cp+1, &endp, 10);
+    if (endp == cp+1)
+        LOGI("Warning: strtoul failed on utime ('%.30s...')\n", cp);
+
+    cp = strchr(cp+1, ' ');
+    if (cp == NULL)
+        goto parse_fail;
+
+    pData->stime = strtoul(cp+1, &endp, 10);
+    if (endp == cp+1)
+        LOGI("Warning: strtoul failed on stime ('%.30s...')\n", cp);
+
+    /*
+     * Skip more stuff we don't care about.
+     */
+    for (i = 14; i < 38; i++) {
+        cp = strchr(cp+1, ' ');
+        if (cp == NULL)
+            goto parse_fail;
+    }
+
+    /*
+     * Grab processor number.
+     */
+    pData->processor = strtol(cp+1, &endp, 10);
+    if (endp == cp+1)
+        LOGI("Warning: strtoul failed on processor ('%.30s...')\n", cp);
+
+    return true;
+
+parse_fail:
+    LOGI("stat parse failed (%s)\n", lineBuf);
+    return false;
+}
diff --git a/vm/Misc.h b/vm/Misc.h
index 358a97b..e188705 100644
--- a/vm/Misc.h
+++ b/vm/Misc.h
@@ -306,4 +306,14 @@
  */
 void *dvmAllocRegion(size_t size, int prot, const char *name);
 
+/*
+ * Get some per-thread stats from /proc/self/task/N/stat.
+ */
+typedef struct {
+    unsigned long utime;    /* number of jiffies scheduled in user mode */
+    unsigned long stime;    /* number of jiffies scheduled in kernel mode */
+    int processor;          /* number of CPU that last executed thread */
+} ProcStatData;
+bool dvmGetThreadStats(ProcStatData* pData, pid_t tid);
+
 #endif /*_DALVIK_MISC*/
diff --git a/vm/Thread.c b/vm/Thread.c
index 8fed98b..1836876 100644
--- a/vm/Thread.c
+++ b/vm/Thread.c
@@ -3508,7 +3508,6 @@
     int policy;                 // pthread policy
     struct sched_param sp;      // pthread scheduling parameters
     char schedstatBuf[64];      // contents of /proc/[pid]/task/[tid]/schedstat
-    int schedstatFd;
 
     /*
      * Get the java.lang.Thread object.  This function gets called from
@@ -3583,19 +3582,33 @@
         thread->systemTid, getpriority(PRIO_PROCESS, thread->systemTid),
         policy, sp.sched_priority, schedulerGroupBuf, (int)thread->handle);
 
-    snprintf(schedstatBuf, sizeof(schedstatBuf), "/proc/%d/task/%d/schedstat",
-             getpid(), thread->systemTid);
-    schedstatFd = open(schedstatBuf, O_RDONLY);
+    /* get some bits from /proc/self/stat */
+    ProcStatData procStatData;
+    if (!dvmGetThreadStats(&procStatData, thread->systemTid)) {
+        /* failed, use zeroed values */
+        memset(&procStatData, 0, sizeof(procStatData));
+    }
+
+    /* grab the scheduler stats for this thread */
+    snprintf(schedstatBuf, sizeof(schedstatBuf), "/proc/self/task/%d/schedstat",
+             thread->systemTid);
+    int schedstatFd = open(schedstatBuf, O_RDONLY);
+    strcpy(schedstatBuf, "0 0 0");          /* show this if open/read fails */
     if (schedstatFd >= 0) {
-        int bytes;
+        ssize_t bytes;
         bytes = read(schedstatFd, schedstatBuf, sizeof(schedstatBuf) - 1);
         close(schedstatFd);
-        if (bytes > 1) {
-            schedstatBuf[bytes-1] = 0;  // trailing newline
-            dvmPrintDebugMessage(target, "  | schedstat=( %s )\n", schedstatBuf);
+        if (bytes >= 1) {
+            schedstatBuf[bytes-1] = '\0';   /* remove trailing newline */
         }
     }
 
+    /* show what we got */
+    dvmPrintDebugMessage(target,
+        "  | schedstat=( %s ) utm=%lu stm=%lu core=%d\n",
+        schedstatBuf, procStatData.utime, procStatData.stime,
+        procStatData.processor);
+
 #ifdef WITH_MONITOR_TRACKING
     if (!isRunning) {
         LockedObjectData* lod = thread->pLockedObjects;