incidentd: parsing ps dump into proto.

Also changing from execv to execvp so that we don't have to specify the full command path.

Bug: 65750831
Test: atest incident_helper_test
Change-Id: I92191afff4e7f9a6d08ea22ecfc2de5623d3bde5
diff --git a/Android.bp b/Android.bp
index 00f42d2..69ee848 100644
--- a/Android.bp
+++ b/Android.bp
@@ -728,6 +728,7 @@
         "core/proto/android/os/kernelwake.proto",
         "core/proto/android/os/pagetypeinfo.proto",
         "core/proto/android/os/procrank.proto",
+        "core/proto/android/os/ps.proto",
         "core/proto/android/os/system_properties.proto",
     ],
 
diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
index 4bf956a..e23e80a 100644
--- a/cmds/incident_helper/src/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -52,6 +52,12 @@
     return toLowerStr(trimDefault(s));
 }
 
+static inline bool isNumber(const std::string& s) {
+    std::string::const_iterator it = s.begin();
+    while (it != s.end() && std::isdigit(*it)) ++it;
+    return !s.empty() && it == s.end();
+}
+
 // This is similiar to Split in android-base/file.h, but it won't add empty string
 static void split(const std::string& line, std::vector<std::string>& words,
         const trans_func& func, const std::string& delimiters) {
@@ -86,24 +92,80 @@
     return record;
 }
 
+bool getColumnIndices(std::vector<int>& indices, const char** headerNames, const std::string& line) {
+    indices.clear();
+
+    size_t lastIndex = 0;
+    int i = 0;
+    while (headerNames[i] != NULL) {
+        string s = headerNames[i];
+        lastIndex = line.find(s, lastIndex);
+        if (lastIndex == string::npos) {
+            fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
+            return false;
+        }
+        lastIndex += s.length();
+        indices.push_back(lastIndex);
+        i++;
+    }
+
+    return true;
+}
+
 record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) {
     record_t record;
     int lastIndex = 0;
+    int lastBeginning = 0;
     int lineSize = (int)line.size();
     for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) {
         int idx = *it;
-        if (lastIndex > idx || idx > lineSize) {
-            record.clear(); // The indices is wrong, return empty;
+        if (idx <= lastIndex) {
+            // We saved up until lastIndex last time, so we should start at
+            // lastIndex + 1 this time.
+            idx = lastIndex + 1;
+        }
+        if (idx > lineSize) {
+            if (lastIndex < idx && lastIndex < lineSize) {
+                // There's a little bit more for us to save, which we'll do
+                // outside of the loop.
+                break;
+            }
+            // If we're past the end of the line AND we've already saved everything up to the end.
+            fprintf(stderr, "index wrong: lastIndex: %d, idx: %d, lineSize: %d\n", lastIndex, idx, lineSize);
+            record.clear(); // The indices are wrong, return empty.
             return record;
         }
         while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos);
         record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex)));
+        lastBeginning = lastIndex;
         lastIndex = idx;
     }
-    record.push_back(trimDefault(line.substr(lastIndex, lineSize - lastIndex)));
+    if (lineSize - lastIndex > 0) {
+        int beginning = lastIndex;
+        if (record.size() == indices.size()) {
+            // We've already encountered all of the columns...put whatever is
+            // left in the last column.
+            record.pop_back();
+            beginning = lastBeginning;
+        }
+        record.push_back(trimDefault(line.substr(beginning, lineSize - beginning)));
+    }
     return record;
 }
 
+void printRecord(const record_t& record) {
+    fprintf(stderr, "Record: { ");
+    if (record.size() == 0) {
+        fprintf(stderr, "}\n");
+        return;
+    }
+    for(size_t i = 0; i < record.size(); ++i) {
+        if(i != 0) fprintf(stderr, "\", ");
+        fprintf(stderr, "\"%s", record[i].c_str());
+    }
+    fprintf(stderr, "\" }\n");
+}
+
 bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) {
     const auto head = line->find_first_not_of(DEFAULT_WHITESPACE);
     if (head == std::string::npos) return false;
@@ -210,7 +272,10 @@
 void
 Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize)
 {
-    if (mFields.find(field) == mFields.end()) return;
+    if (mFields.find(field) == mFields.end()) {
+        fprintf(stderr, "Field '%s' not found", string(field).c_str());
+        return;
+    }
 
     map<std::string, int> enu;
     for (int i = 0; i < enumSize; i++) {
@@ -268,6 +333,8 @@
                 }
             } else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) {
                 proto->write(found, mEnumValuesByName[value]);
+            } else if (isNumber(value)) {
+                proto->write(found, toInt(value));
             } else {
                 return false;
             }
diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h
index 58ef290..b063b2f 100644
--- a/cmds/incident_helper/src/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -56,12 +56,23 @@
 record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
 
 /**
+ * Gets the list of end indices of each word in the line and places it in the given vector,
+ * clearing out the vector beforehand. These indices can be used with parseRecordByColumns.
+ * Will return false if there was a problem getting the indices. headerNames
+ * must be NULL terminated.
+ */
+bool getColumnIndices(std::vector<int>& indices, const char* headerNames[], const std::string& line);
+
+/**
  * When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters.
  * This function allows to parse record by its header's column position' indices, must in ascending order.
  * At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters.
  */
 record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE);
 
+/** Prints record_t to stderr */
+void printRecord(const record_t& record);
+
 /**
  * When the line starts/ends with the given key, the function returns true
  * as well as the line argument is changed to the rest trimmed part of the original.
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index ab92473..8c6cd78 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -22,6 +22,7 @@
 #include "parsers/KernelWakesParser.h"
 #include "parsers/PageTypeInfoParser.h"
 #include "parsers/ProcrankParser.h"
+#include "parsers/PsParser.h"
 #include "parsers/SystemPropertiesParser.h"
 
 #include <android-base/file.h>
@@ -64,6 +65,8 @@
             return new CpuInfoParser();
         case 2004:
             return new CpuFreqParser();
+        case 2005:
+            return new PsParser();
         case 2006:
             return new BatteryTypeParser();
         default:
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
index 3faca00..d73de54 100644
--- a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
@@ -49,6 +49,7 @@
     vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
     record_t record;
     int nline = 0;
+    int diff = 0;
     bool nextToSwap = false;
     bool nextToUsage = false;
 
@@ -107,18 +108,10 @@
             header = parseHeader(line, "[ %]");
             nextToUsage = false;
 
-            // NAME is not in the list since the last split index is default to the end of line.
-            const char* headerNames[11] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD" };
-            size_t lastIndex = 0;
-            for (int i = 0; i < 11; i++) {
-                string s = headerNames[i];
-                lastIndex = line.find(s, lastIndex);
-                if (lastIndex == string::npos) {
-                    fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
-                    return -1;
-                }
-                lastIndex += s.length();
-                columnIndices.push_back(lastIndex);
+            // NAME is not in the list since we need to modify the end of the CMD index.
+            const char* headerNames[] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD", NULL };
+            if (!getColumnIndices(columnIndices, headerNames, line)) {
+                return -1;
             }
             // Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces.
             // for example: ... CMD             NAME
@@ -128,12 +121,20 @@
             int endCMD = columnIndices.back();
             columnIndices.pop_back();
             columnIndices.push_back(line.find("NAME", endCMD) - 1);
+            // Add NAME index to complete the column list.
+            columnIndices.push_back(columnIndices.back() + 4);
             continue;
         }
 
         record = parseRecordByColumns(line, columnIndices);
-        if (record.size() != header.size()) {
-            fprintf(stderr, "[%s]Line %d has missing fields:\n%s\n", this->name.string(), nline, line.c_str());
+        diff = record.size() - header.size();
+        if (diff < 0) {
+            fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+            printRecord(record);
+            continue;
+        } else if (diff > 0) {
+            fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+            printRecord(record);
             continue;
         }
 
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
index ada4a5d..cae51ab 100644
--- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -47,10 +47,14 @@
         // parse for each record, the line delimiter is \t only!
         record = parseRecord(line, TAB_DELIMITER);
 
-        if (record.size() != header.size()) {
+        if (record.size() < header.size()) {
             // TODO: log this to incident report!
             fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
             continue;
+        } else if (record.size() > header.size()) {
+            // TODO: log this to incident report!
+            fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.string(), nline, line.c_str());
+            continue;
         }
 
         long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES);
diff --git a/cmds/incident_helper/src/parsers/PsParser.cpp b/cmds/incident_helper/src/parsers/PsParser.cpp
new file mode 100644
index 0000000..e9014ca
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PsParser.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/ps.proto.h"
+#include "ih_util.h"
+#include "PsParser.h"
+
+using namespace android::os;
+
+status_t PsParser::Parse(const int in, const int out) const {
+    Reader reader(in);
+    string line;
+    header_t header;  // the header of /d/wakeup_sources
+    vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
+    record_t record;  // retain each record
+    int nline = 0;
+    int diff = 0;
+
+    ProtoOutputStream proto;
+    Table table(PsDumpProto::Process::_FIELD_NAMES, PsDumpProto::Process::_FIELD_IDS, PsDumpProto::Process::_FIELD_COUNT);
+    const char* pcyNames[] = { "fg", "bg", "ta" };
+    const int pcyValues[] = {PsDumpProto::Process::POLICY_FG, PsDumpProto::Process::POLICY_BG, PsDumpProto::Process::POLICY_TA};
+    table.addEnumTypeMap("pcy", pcyNames, pcyValues, 3);
+    const char* sNames[] = { "D", "R", "S", "T", "t", "X", "Z" };
+    const int sValues[] = {PsDumpProto::Process::STATE_D, PsDumpProto::Process::STATE_R, PsDumpProto::Process::STATE_S, PsDumpProto::Process::STATE_T, PsDumpProto::Process::STATE_TRACING, PsDumpProto::Process::STATE_X, PsDumpProto::Process::STATE_Z};
+    table.addEnumTypeMap("s", sNames, sValues, 7);
+
+    // Parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+
+        if (nline++ == 0) {
+            header = parseHeader(line, DEFAULT_WHITESPACE);
+
+            const char* headerNames[] = { "LABEL", "USER", "PID", "TID", "PPID", "VSZ", "RSS", "WCHAN", "ADDR", "S", "PRI", "NI", "RTPRIO", "SCH", "PCY", "TIME", "CMD", NULL };
+            if (!getColumnIndices(columnIndices, headerNames, line)) {
+                return -1;
+            }
+
+            continue;
+        }
+
+        record = parseRecordByColumns(line, columnIndices);
+
+        diff = record.size() - header.size();
+        if (diff < 0) {
+            // TODO: log this to incident report!
+            fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+            printRecord(record);
+            continue;
+        } else if (diff > 0) {
+            // TODO: log this to incident report!
+            fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+            printRecord(record);
+            continue;
+        }
+
+        long long token = proto.start(PsDumpProto::PROCESSES);
+        for (int i=0; i<(int)record.size(); i++) {
+            if (!table.insertField(&proto, header[i], record[i])) {
+                fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
+                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
+            }
+        }
+        proto.end(token);
+    }
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/PsParser.h b/cmds/incident_helper/src/parsers/PsParser.h
new file mode 100644
index 0000000..9488e40
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PsParser.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef PS_PARSER_H
+#define PS_PARSER_H
+
+#include "TextParserBase.h"
+
+/**
+ * PS parser, parses output of 'ps' command to protobuf.
+ */
+class PsParser : public TextParserBase {
+public:
+    PsParser() : TextParserBase(String8("Ps")) {};
+    ~PsParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // PS_PARSER_H
diff --git a/cmds/incident_helper/testdata/ps.txt b/cmds/incident_helper/testdata/ps.txt
new file mode 100644
index 0000000..72dafc2c4
--- /dev/null
+++ b/cmds/incident_helper/testdata/ps.txt
@@ -0,0 +1,9 @@
+LABEL                          USER           PID   TID  PPID     VSZ    RSS WCHAN            ADDR S PRI  NI RTPRIO SCH PCY     TIME CMD
+u:r:init:s0                    root             1     1     0   15816   2636 SyS_epoll_wait      0 S  19   0      -   0  fg 00:00:01 init
+u:r:kernel:s0                  root             2     2     0       0      0 kthreadd            0 S  19   0      -   0  fg 00:00:00 kthreadd
+u:r:surfaceflinger:s0          system         499   534     1   73940  22024 futex_wait_queue_me 0 S  42  -9      2   1  fg 00:00:00 EventThread
+u:r:hal_gnss_default:s0        gps            670  2004     1   43064   7272 poll_schedule_timeout 0 S 19  0      -   0  fg 00:00:00 Loc_hal_worker
+u:r:platform_app:s0:c512,c768  u0_a48        1660  1976   806 4468612 138328 binder_thread_read  0 S  35 -16      -   0  ta 00:00:00 HwBinder:1660_1
+u:r:perfd:s0                   root          1939  1946     1   18132   2088 __skb_recv_datagram 7b9782fd14 S 19 0 -  0     00:00:00 perfd
+u:r:perfd:s0                   root          1939  1955     1   18132   2088 do_sigtimedwait 7b9782ff6c S 19 0    -   0     00:00:00 POSIX timer 0
+u:r:shell:s0                   shell         2645  2645   802   11664   2972 0          7f67a2f8b4 R  19   0      -   0  fg 00:00:00 ps
diff --git a/cmds/incident_helper/tests/PsParser_test.cpp b/cmds/incident_helper/tests/PsParser_test.cpp
new file mode 100644
index 0000000..1f03a7f
--- /dev/null
+++ b/cmds/incident_helper/tests/PsParser_test.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "PsParser.h"
+
+#include "frameworks/base/core/proto/android/os/ps.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message_lite.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class PsParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(PsParserTest, Normal) {
+    const string testFile = kTestDataPath + "ps.txt";
+    PsParser parser;
+    PsDumpProto expected;
+    PsDumpProto got;
+
+    PsDumpProto::Process* record1 = expected.add_processes();
+    record1->set_label("u:r:init:s0");
+    record1->set_user("root");
+    record1->set_pid(1);
+    record1->set_tid(1);
+    record1->set_ppid(0);
+    record1->set_vsz(15816);
+    record1->set_rss(2636);
+    record1->set_wchan("SyS_epoll_wait");
+    record1->set_addr("0");
+    record1->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record1->set_pri(19);
+    record1->set_ni(0);
+    record1->set_rtprio("-");
+    record1->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record1->set_pcy(PsDumpProto::Process::POLICY_FG);
+    record1->set_time("00:00:01");
+    record1->set_cmd("init");
+
+    PsDumpProto::Process* record2 = expected.add_processes();
+    record2->set_label("u:r:kernel:s0");
+    record2->set_user("root");
+    record2->set_pid(2);
+    record2->set_tid(2);
+    record2->set_ppid(0);
+    record2->set_vsz(0);
+    record2->set_rss(0);
+    record2->set_wchan("kthreadd");
+    record2->set_addr("0");
+    record2->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record2->set_pri(19);
+    record2->set_ni(0);
+    record2->set_rtprio("-");
+    record2->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record2->set_pcy(PsDumpProto::Process::POLICY_FG);
+    record2->set_time("00:00:00");
+    record2->set_cmd("kthreadd");
+
+    PsDumpProto::Process* record3 = expected.add_processes();
+    record3->set_label("u:r:surfaceflinger:s0");
+    record3->set_user("system");
+    record3->set_pid(499);
+    record3->set_tid(534);
+    record3->set_ppid(1);
+    record3->set_vsz(73940);
+    record3->set_rss(22024);
+    record3->set_wchan("futex_wait_queue_me");
+    record3->set_addr("0");
+    record3->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record3->set_pri(42);
+    record3->set_ni(-9);
+    record3->set_rtprio("2");
+    record3->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_FIFO);
+    record3->set_pcy(PsDumpProto::Process::POLICY_FG);
+    record3->set_time("00:00:00");
+    record3->set_cmd("EventThread");
+
+    PsDumpProto::Process* record4 = expected.add_processes();
+    record4->set_label("u:r:hal_gnss_default:s0");
+    record4->set_user("gps");
+    record4->set_pid(670);
+    record4->set_tid(2004);
+    record4->set_ppid(1);
+    record4->set_vsz(43064);
+    record4->set_rss(7272);
+    record4->set_wchan("poll_schedule_timeout");
+    record4->set_addr("0");
+    record4->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record4->set_pri(19);
+    record4->set_ni(0);
+    record4->set_rtprio("-");
+    record4->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record4->set_pcy(PsDumpProto::Process::POLICY_FG);
+    record4->set_time("00:00:00");
+    record4->set_cmd("Loc_hal_worker");
+
+    PsDumpProto::Process* record5 = expected.add_processes();
+    record5->set_label("u:r:platform_app:s0:c512,c768");
+    record5->set_user("u0_a48");
+    record5->set_pid(1660);
+    record5->set_tid(1976);
+    record5->set_ppid(806);
+    record5->set_vsz(4468612);
+    record5->set_rss(138328);
+    record5->set_wchan("binder_thread_read");
+    record5->set_addr("0");
+    record5->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record5->set_pri(35);
+    record5->set_ni(-16);
+    record5->set_rtprio("-");
+    record5->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record5->set_pcy(PsDumpProto::Process::POLICY_TA);
+    record5->set_time("00:00:00");
+    record5->set_cmd("HwBinder:1660_1");
+
+    PsDumpProto::Process* record6 = expected.add_processes();
+    record6->set_label("u:r:perfd:s0");
+    record6->set_user("root");
+    record6->set_pid(1939);
+    record6->set_tid(1946);
+    record6->set_ppid(1);
+    record6->set_vsz(18132);
+    record6->set_rss(2088);
+    record6->set_wchan("__skb_recv_datagram");
+    record6->set_addr("7b9782fd14");
+    record6->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record6->set_pri(19);
+    record6->set_ni(0);
+    record6->set_rtprio("-");
+    record6->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record6->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN);
+    record6->set_time("00:00:00");
+    record6->set_cmd("perfd");
+
+    PsDumpProto::Process* record7 = expected.add_processes();
+    record7->set_label("u:r:perfd:s0");
+    record7->set_user("root");
+    record7->set_pid(1939);
+    record7->set_tid(1955);
+    record7->set_ppid(1);
+    record7->set_vsz(18132);
+    record7->set_rss(2088);
+    record7->set_wchan("do_sigtimedwait");
+    record7->set_addr("7b9782ff6c");
+    record7->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+    record7->set_pri(19);
+    record7->set_ni(0);
+    record7->set_rtprio("-");
+    record7->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record7->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN);
+    record7->set_time("00:00:00");
+    record7->set_cmd("POSIX timer 0");
+
+    PsDumpProto::Process* record8 = expected.add_processes();
+    record8->set_label("u:r:shell:s0");
+    record8->set_user("shell");
+    record8->set_pid(2645);
+    record8->set_tid(2645);
+    record8->set_ppid(802);
+    record8->set_vsz(11664);
+    record8->set_rss(2972);
+    record8->set_wchan("0");
+    record8->set_addr("7f67a2f8b4");
+    record8->set_s(PsDumpProto_Process_ProcessStateCode_STATE_R);
+    record8->set_pri(19);
+    record8->set_ni(0);
+    record8->set_rtprio("-");
+    record8->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+    record8->set_pcy(PsDumpProto::Process::POLICY_FG);
+    record8->set_time("00:00:00");
+    record8->set_cmd("ps");
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    got.ParseFromString(GetCapturedStdout());
+    bool matches = true;
+
+    if (got.processes_size() != expected.processes_size()) {
+        fprintf(stderr, "Got %d processes, want %d\n", got.processes_size(), expected.processes_size());
+        matches = false;
+    } else {
+        int n = got.processes_size();
+        for (int i = 0; i < n; i++) {
+            PsDumpProto::Process g = got.processes(i);
+            PsDumpProto::Process e = expected.processes(i);
+
+            if (g.label() != e.label()) {
+                fprintf(stderr, "prcs[%d]: Invalid label. Got %s, want %s\n", i, g.label().c_str(), e.label().c_str());
+                matches = false;
+            }
+            if (g.user() != e.user()) {
+                fprintf(stderr, "prcs[%d]: Invalid user. Got %s, want %s\n", i, g.user().c_str(), e.user().c_str());
+                matches = false;
+            }
+            if (g.pid() != e.pid()) {
+                fprintf(stderr, "prcs[%d]: Invalid pid. Got %d, want %d\n", i, g.pid(), e.pid());
+                matches = false;
+            }
+            if (g.tid() != e.tid()) {
+                fprintf(stderr, "prcs[%d]: Invalid tid. Got %d, want %d\n", i, g.tid(), e.tid());
+                matches = false;
+            }
+            if (g.ppid() != e.ppid()) {
+                fprintf(stderr, "prcs[%d]: Invalid ppid. Got %d, want %d\n", i, g.ppid(), e.ppid());
+                matches = false;
+            }
+            if (g.vsz() != e.vsz()) {
+                fprintf(stderr, "prcs[%d]: Invalid vsz. Got %d, want %d\n", i, g.vsz(), e.vsz());
+                matches = false;
+            }
+            if (g.rss() != e.rss()) {
+                fprintf(stderr, "prcs[%d]: Invalid rss. Got %d, want %d\n", i, g.rss(), e.rss());
+                matches = false;
+            }
+            if (g.wchan() != e.wchan()) {
+                fprintf(stderr, "prcs[%d]: Invalid wchan. Got %s, want %s\n", i, g.wchan().c_str(), e.wchan().c_str());
+                matches = false;
+            }
+            if (g.addr() != e.addr()) {
+                fprintf(stderr, "prcs[%d]: Invalid addr. Got %s, want %s\n", i, g.addr().c_str(), e.addr().c_str());
+                matches = false;
+            }
+            if (g.s() != e.s()) {
+                fprintf(stderr, "prcs[%d]: Invalid s. Got %u, want %u\n", i, g.s(), e.s());
+                matches = false;
+            }
+            if (g.pri() != e.pri()) {
+                fprintf(stderr, "prcs[%d]: Invalid pri. Got %d, want %d\n", i, g.pri(), e.pri());
+                matches = false;
+            }
+            if (g.ni() != e.ni()) {
+                fprintf(stderr, "prcs[%d]: Invalid ni. Got %d, want %d\n", i, g.ni(), e.ni());
+                matches = false;
+            }
+            if (g.rtprio() != e.rtprio()) {
+                fprintf(stderr, "prcs[%d]: Invalid rtprio. Got %s, want %s\n", i, g.rtprio().c_str(), e.rtprio().c_str());
+                matches = false;
+            }
+            if (g.sch() != e.sch()) {
+                fprintf(stderr, "prcs[%d]: Invalid sch. Got %u, want %u\n", i, g.sch(), e.sch());
+                matches = false;
+            }
+            if (g.pcy() != e.pcy()) {
+                fprintf(stderr, "prcs[%d]: Invalid pcy. Got %u, want %u\n", i, g.pcy(), e.pcy());
+                matches = false;
+            }
+            if (g.time() != e.time()) {
+                fprintf(stderr, "prcs[%d]: Invalid time. Got %s, want %s\n", i, g.time().c_str(), e.time().c_str());
+                matches = false;
+            }
+            if (g.cmd() != e.cmd()) {
+                fprintf(stderr, "prcs[%d]: Invalid cmd. Got %s, want %s\n", i, g.cmd().c_str(), e.cmd().c_str());
+                matches = false;
+            }
+        }
+    }
+
+    EXPECT_TRUE(matches);
+    close(fd);
+}
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
index 5740b33..7b8cf52 100644
--- a/cmds/incident_helper/tests/ih_util_test.cpp
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -71,11 +71,29 @@
     EXPECT_EQ(expected, result);
 
     result = parseRecordByColumns("abc \t2345  6789 ", indices);
-    expected = { "abc", "2345", "6789" };
+    expected = { "abc", "2345  6789" };
     EXPECT_EQ(expected, result);
 
-    result = parseRecordByColumns("abc \t23456789 bob", indices);
-    expected = { "abc", "23456789", "bob" };
+    std::string extraColumn1 = "abc \t23456789 bob";
+    std::string emptyMidColm = "abc \t         bob";
+    std::string longFirstClm = "abcdefgt\t6789 bob";
+    std::string lngFrstEmpty = "abcdefgt\t     bob";
+
+    result = parseRecordByColumns(extraColumn1, indices);
+    expected = { "abc", "23456789 bob" };
+    EXPECT_EQ(expected, result);
+
+    // 2nd column should be treated as an empty entry.
+    result = parseRecordByColumns(emptyMidColm, indices);
+    expected = { "abc", "bob" };
+    EXPECT_EQ(expected, result);
+
+    result = parseRecordByColumns(longFirstClm, indices);
+    expected = { "abcdefgt", "6789 bob" };
+    EXPECT_EQ(expected, result);
+
+    result = parseRecordByColumns(lngFrstEmpty, indices);
+    expected = { "abcdefgt", "bob" };
     EXPECT_EQ(expected, result);
 }
 
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 1bf795b..22053ef 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -521,7 +521,7 @@
             ALOGW("CommandSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno));
             _exit(EXIT_FAILURE);
         }
-        execv(this->mCommand[0], (char *const *) this->mCommand);
+        execvp(this->mCommand[0], (char *const *) this->mCommand);
         int err = errno; // record command error code
         ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno));
         _exit(err); // exit with command error code
diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto
index 522ff24..cd151e2 100644
--- a/core/proto/android/os/cpuinfo.proto
+++ b/core/proto/android/os/cpuinfo.proto
@@ -81,7 +81,9 @@
         optional string virt = 8;   // virtual memory size, i.e. 14.0G, 13.5M
         optional string res = 9;    // Resident size, i.e. 0, 3.1G
 
-        // How Android memory manager will treat the task
+        // How Android memory manager will treat the task.
+        // TODO: use PsDumpProto.Process.Policy instead once we extern variables
+        // and are able to include the same .h file in two files.
         enum Policy {
             POLICY_UNKNOWN = 0;
             POLICY_fg = 1;  // foreground, the name is lower case for parsing the value
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 09c08a9..a6db31f 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -25,6 +25,7 @@
 import "frameworks/base/core/proto/android/os/kernelwake.proto";
 import "frameworks/base/core/proto/android/os/pagetypeinfo.proto";
 import "frameworks/base/core/proto/android/os/procrank.proto";
+import "frameworks/base/core/proto/android/os/ps.proto";
 import "frameworks/base/core/proto/android/os/system_properties.proto";
 import "frameworks/base/core/proto/android/providers/settings.proto";
 import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
@@ -66,7 +67,7 @@
     // Device information
     optional SystemPropertiesProto system_properties = 1000 [
         (section).type = SECTION_COMMAND,
-        (section).args = "/system/bin/getprop"
+        (section).args = "getprop"
     ];
 
     // Linux services
@@ -87,7 +88,7 @@
 
     optional CpuInfo cpu_info = 2003 [
         (section).type = SECTION_COMMAND,
-        (section).args = "/system/bin/top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
+        (section).args = "top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
     ];
 
     optional CpuFreq cpu_freq = 2004 [
@@ -95,6 +96,11 @@
         (section).args = "/sys/devices/system/cpu/cpufreq/all_time_in_state"
     ];
 
+    optional PsDumpProto processes_and_threads = 2005 [
+        (section).type = SECTION_COMMAND,
+        (section).args = "ps -A -T -Z -O pri,nice,rtprio,sched,pcy,time"
+    ];
+
     optional BatteryTypeProto battery_type = 2006 [
         (section).type = SECTION_FILE,
         (section).args = "/sys/class/power_supply/bms/battery_type"
diff --git a/core/proto/android/os/ps.proto b/core/proto/android/os/ps.proto
new file mode 100644
index 0000000..88c6609
--- /dev/null
+++ b/core/proto/android/os/ps.proto
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+syntax = "proto2";
+
+package android.os;
+
+option java_multiple_files = true;
+
+message PsDumpProto {
+    message Process {
+        // Security label, most commonly used for SELinux context data.
+        optional string label = 1;
+        optional string user = 2;
+        // Process ID number.
+        optional int32 pid = 3;
+        // The unique number representing a dispatchable entity (alias lwp,
+        // spid).  This value may also appear as: a process ID (pid); a process
+        // group ID (pgrp); a session ID for the session leader (sid); a thread
+        // group ID for the thread group leader (tgid); and a tty process group
+        // ID for the process group leader (tpgid).
+        optional int32 tid = 4;
+        // Parent process ID.
+        optional int32 ppid = 5;
+        // Virtual set size (memory size) of the process, in KiB.
+        optional int32 vsz = 6;
+        // Resident set size. How many physical pages are associated with the
+        // process; real memory usage, in KiB.
+        optional int32 rss = 7;
+        // Name of the kernel function in which the process is sleeping, a "-"
+        // if the process is running, or a "*" if the process is multi-threaded
+        // and ps is not displaying threads.
+        optional string wchan = 8;
+        // Memory address of the process.
+        optional string addr = 9;
+
+        enum ProcessStateCode {
+            STATE_UNKNOWN = 0;
+            // Uninterruptible sleep (usually IO).
+            STATE_D = 1;
+            // Running or runnable (on run queue).
+            STATE_R = 2;
+            // Interruptible sleep (waiting for an event to complete).
+            STATE_S = 3;
+            // Stopped by job control signal.
+            STATE_T = 4;
+            // Stopped by debugger during the tracing.
+            STATE_TRACING = 5;
+            // Dead (should never be seen).
+            STATE_X = 6;
+            // Defunct ("zombie") process. Terminated but not reaped by its
+            // parent.
+            STATE_Z = 7;
+        }
+        // Minimal state display
+        optional ProcessStateCode s = 10;
+        // Priority of the process. Higher number means lower priority.
+        optional int32 pri = 11;
+        // Nice value. This ranges from 19 (nicest) to -20 (not nice to others).
+        optional sint32 ni = 12;
+        // Realtime priority.
+        optional string rtprio = 13; // Number or -
+
+        enum SchedulingPolicy {
+            option allow_alias = true;
+
+            // Regular names conflict with macros defined in
+            // bionic/libc/kernel/uapi/linux/sched.h.
+            SCH_OTHER = 0;
+            SCH_NORMAL = 0;
+
+            SCH_FIFO = 1;
+            SCH_RR = 2;
+            SCH_BATCH = 3;
+            SCH_ISO = 4;
+            SCH_IDLE = 5;
+        }
+        // Scheduling policy of the process.
+        optional SchedulingPolicy sch = 14;
+
+        // How Android memory manager will treat the task
+        enum Policy {
+            POLICY_UNKNOWN = 0;
+            // Foreground.
+            POLICY_FG = 1;
+            // Background.
+            POLICY_BG = 2;
+            POLICY_TA = 3;  // TODO: figure out what this value is
+        }
+        optional Policy pcy = 15;
+        // Total CPU time, "[DD-]HH:MM:SS" format.
+        optional string time = 16;
+        // Command with all its arguments as a string.
+        optional string cmd = 17;
+    }
+    repeated Process processes = 1;
+}