This cl implements CommandSection and use it to add procrank.proto Section

Bug: 63863444
Test: manual - create gtests for CommandSection and Procrank Parser following
instructions in the README.md of incidentd and incident_helper on how to
run them.

Change-Id: I099808fd13bf9ed9a564b122f1126b1691a83291
diff --git a/Android.bp b/Android.bp
index eb35ffb..33acffa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -40,6 +40,7 @@
             // needed by the device.
             srcs: [
                 "core/proto/android/os/kernelwake.proto",
+                "core/proto/android/os/procrank.proto",
                 "core/proto/android/service/graphicsstats.proto",
             ],
             shared: {
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index 3ea823c..0532083 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -10,7 +10,7 @@
 
     srcs: [
         "IncidentHelper.cpp",
-        "strutil.cpp",
+        "ih_util.cpp",
     ],
 
     shared_libs: [
@@ -38,6 +38,7 @@
 
     srcs: [
         "tests/IncidentHelper_test.cpp",
+        "tests/ih_util_test.cpp",
     ],
 
     data: [
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp
index 89d8947..aa06595 100644
--- a/cmds/incident_helper/IncidentHelper.cpp
+++ b/cmds/incident_helper/IncidentHelper.cpp
@@ -17,14 +17,13 @@
 #define LOG_TAG "incident_helper"
 
 #include "IncidentHelper.h"
-#include "strutil.h"
+#include "ih_util.h"
 
 #include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+#include "frameworks/base/core/proto/android/os/procrank.pb.h"
 
-#include <algorithm>
 #include <android-base/file.h>
 #include <unistd.h>
-#include <sstream>
 #include <string>
 #include <vector>
 
@@ -67,42 +66,34 @@
 const string KERNEL_WAKEUP_LINE_DELIMITER = "\t";
 
 status_t KernelWakesParser::Parse(const int in, const int out) const {
-    // read the content, this is not memory-efficient though since it loads everything
-    // However the data will be held in proto anyway, and incident_helper is less critical
-    string content;
-    if (!ReadFdToString(in, &content)) {
-        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
-        return -1;
-    }
-
-    istringstream iss(content);
+    Reader reader(in);
     string line;
-    vector<string> header;  // the header of /d/wakeup_sources
-    vector<string> record;  // retain each record
+    header_t header;  // the header of /d/wakeup_sources
+    record_t record;  // retain each record
     int nline = 0;
 
     KernelWakeSources proto;
 
     // parse line by line
-    while (getline(iss, line)) {
+    while (reader.readLine(line)) {
+        if (line.empty()) continue;
         // parse head line
-        if (nline == 0) {
-          split(line, &header);
-          if (!assertHeaders(kernel_wake_headers, header)) {
-            fprintf(stderr, "[%s]Bad header:\n%s\n", this->name.string(), line.c_str());
-            return BAD_VALUE;
-          }
-          nline++;
-          continue;
+        if (nline++ == 0) {
+            split(line, header, KERNEL_WAKEUP_LINE_DELIMITER);
+            if (!assertHeaders(kernel_wake_headers, header)) {
+                fprintf(stderr, "[%s]Bad header:\n%s\n", this->name.string(), line.c_str());
+                return BAD_VALUE;
+            }
+            continue;
         }
 
         // parse for each record, the line delimiter is \t only!
-        split(line, &record, KERNEL_WAKEUP_LINE_DELIMITER);
+        split(line, record, KERNEL_WAKEUP_LINE_DELIMITER);
 
         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;
+            // 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;
         }
 
         WakeupSourceProto* source = proto.add_wakeup_sources();
@@ -118,17 +109,106 @@
         source->set_max_time(atol(record.at(7).c_str()));
         source->set_last_change(atol(record.at(8).c_str()));
         source->set_prevent_suspend_time(atol(record.at(9).c_str()));
-
-        nline++;
     }
 
-    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
+    if (!reader.ok(line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
 
     if (!proto.SerializeToFileDescriptor(out)) {
         fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
         return -1;
     }
-    close(out);
-
+    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
     return NO_ERROR;
 }
+
+// ================================================================================
+const char* procrank_headers[] = {
+    "PID",          // id:  1
+    "Vss",          // id:  2
+    "Rss",          // id:  3
+    "Pss",          // id:  4
+    "Uss",          // id:  5
+    "Swap",         // id:  6
+    "PSwap",        // id:  7
+    "USwap",        // id:  8
+    "ZSwap",        // id:  9
+    "cmdline",      // id: 10
+};
+
+status_t ProcrankParser::Parse(const int in, const int out) const {
+    Reader reader(in);
+    string line, content;
+    header_t header;  // the header of /d/wakeup_sources
+    record_t record;  // retain each record
+    int nline = 0;
+
+    Procrank proto;
+
+    // parse line by line
+    while (reader.readLine(line)) {
+        if (line.empty()) continue;
+
+        // parse head line
+        if (nline++ == 0) {
+            split(line, header);
+            if (!assertHeaders(procrank_headers, header)) {
+                fprintf(stderr, "[%s]Bad header:\n%s\n", this->name.string(), line.c_str());
+                return BAD_VALUE;
+            }
+            continue;
+        }
+
+        split(line, record);
+        if (record.size() != header.size()) {
+            if (record[record.size() - 1] == "TOTAL") { // TOTAL record
+                ProcessProto* total = proto.mutable_summary()->mutable_total();
+                total->set_pss(atol(record.at(0).substr(0, record.at(0).size() - 1).c_str()));
+                total->set_uss(atol(record.at(1).substr(0, record.at(1).size() - 1).c_str()));
+                total->set_swap(atol(record.at(2).substr(0, record.at(2).size() - 1).c_str()));
+                total->set_pswap(atol(record.at(3).substr(0, record.at(3).size() - 1).c_str()));
+                total->set_uswap(atol(record.at(4).substr(0, record.at(4).size() - 1).c_str()));
+                total->set_zswap(atol(record.at(5).substr(0, record.at(5).size() - 1).c_str()));
+            } else if (record[0] == "ZRAM:") {
+                split(line, record, ":");
+                proto.mutable_summary()->mutable_zram()->set_raw_text(record[1]);
+            } else if (record[0] == "RAM:") {
+                split(line, record, ":");
+                proto.mutable_summary()->mutable_ram()->set_raw_text(record[1]);
+            } else {
+                fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
+                    line.c_str());
+            }
+            continue;
+        }
+
+        ProcessProto* process = proto.add_processes();
+        // int32
+        process->set_pid(atoi(record.at(0).c_str()));
+        // int64, remove 'K' at the end
+        process->set_vss(atol(record.at(1).substr(0, record.at(1).size() - 1).c_str()));
+        process->set_rss(atol(record.at(2).substr(0, record.at(2).size() - 1).c_str()));
+        process->set_pss(atol(record.at(3).substr(0, record.at(3).size() - 1).c_str()));
+        process->set_uss(atol(record.at(4).substr(0, record.at(4).size() - 1).c_str()));
+        process->set_swap(atol(record.at(5).substr(0, record.at(5).size() - 1).c_str()));
+        process->set_pswap(atol(record.at(6).substr(0, record.at(6).size() - 1).c_str()));
+        process->set_uswap(atol(record.at(7).substr(0, record.at(7).size() - 1).c_str()));
+        process->set_zswap(atol(record.at(8).substr(0, record.at(8).size() - 1).c_str()));
+        // string
+        process->set_cmdline(record.at(9));
+    }
+
+    if (!reader.ok(line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.SerializeToFileDescriptor(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
+    return NO_ERROR;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/IncidentHelper.h b/cmds/incident_helper/IncidentHelper.h
index 40792a8..736f848 100644
--- a/cmds/incident_helper/IncidentHelper.h
+++ b/cmds/incident_helper/IncidentHelper.h
@@ -70,4 +70,17 @@
     virtual status_t Parse(const int in, const int out) const;
 };
 
+/**
+ * Procrank parser, parses text produced by command procrank
+ */
+extern const char* procrank_headers[];
+
+class ProcrankParser : public TextParserBase {
+public:
+    ProcrankParser() : TextParserBase(String8("ProcrankParser")) {};
+    ~ProcrankParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
 #endif  // INCIDENT_HELPER_H
diff --git a/cmds/incident_helper/README.md b/cmds/incident_helper/README.md
index 967f1e3..866cc20 100644
--- a/cmds/incident_helper/README.md
+++ b/cmds/incident_helper/README.md
@@ -1,5 +1,7 @@
 # incident_helper
 
+It is an executable used to help parsing text format data to protobuf.
+
 ## How to build, deploy, unit test
 
 For the first time, build the test and create an empty directly on device:
@@ -15,3 +17,10 @@
 adb push $OUT/data/nativetest64/incident_helper_test/* /data/nativetest64/incident_helper_test/ && \
 adb shell /data/nativetest64/incident_helper_test/incident_helper_test 2>/dev/null
 ```
+## How to adapt proto changes
+
+If add a new proto file, add it in Android.bp under frameworks/base/ and make incident helper
+
+```
+root$ make -j48 incident_helper
+```
diff --git a/cmds/incident_helper/ih_util.cpp b/cmds/incident_helper/ih_util.cpp
new file mode 100644
index 0000000..bbb625f
--- /dev/null
+++ b/cmds/incident_helper/ih_util.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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 "ih_util.h"
+
+#include <sstream>
+#include <unistd.h>
+
+const ssize_t BUFFER_SIZE = 16 * 1024; // 4KB
+
+std::string trim(const std::string& s, const std::string& whitespace) {
+    const auto head = s.find_first_not_of(whitespace);
+    if (head == std::string::npos) return "";
+
+    const auto tail = s.find_last_not_of(whitespace);
+    return s.substr(head, tail - head + 1);
+}
+
+// This is similiar to Split in android-base/file.h, but it won't add empty string
+void split(const std::string& line, std::vector<std::string>& words, const std::string& delimiters) {
+    words.clear();  // clear the buffer before split
+
+    size_t base = 0;
+    size_t found;
+    while (true) {
+        found = line.find_first_of(delimiters, base);
+        if (found != base) {
+            std::string word = trim(line.substr(base, found - base));
+            if (!word.empty()) {
+                words.push_back(word);
+            }
+        }
+        if (found == line.npos) break;
+        base = found + 1;
+    }
+}
+
+bool assertHeaders(const char* expected[], const std::vector<std::string>& actual) {
+    for (size_t i = 0; i < actual.size(); i++) {
+        if (expected[i] == NULL || std::string(expected[i]) != actual[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
+
+Reader::Reader(const int fd, const size_t capacity)
+        : mFd(fd), mMaxSize(capacity), mBufSize(0), mRead(0), mFlushed(0)
+{
+    mBuf = capacity > 0 ? (char*)malloc(capacity * sizeof(char)) : NULL;
+    mStatus = mFd < 0 ? "Negative fd" : (capacity == 0 ? "Zero buffer capacity" : "");
+}
+
+Reader::~Reader()
+{
+    free(mBuf);
+}
+
+bool Reader::readLine(std::string& line, const char newline) {
+    if (!ok(line)) return false; // bad status
+    std::stringstream ss;
+    while (!EOR()) {
+        // read if available
+        if (mFd != -1 && mBufSize != mMaxSize) {
+            ssize_t amt = 0;
+            if (mRead >= mFlushed) {
+                amt = ::read(mFd, mBuf + mRead, mMaxSize - mRead);
+            } else {
+                amt = ::read(mFd, mBuf + mRead, mFlushed - mRead);
+            }
+            if (amt < 0) {
+                mStatus = "Fail to read from fd";
+                return false;
+            } else if (amt == 0) {
+                close(mFd);
+                mFd = -1;
+            }
+            mRead += amt;
+            mBufSize += amt;
+        }
+
+        bool meetsNewLine = false;
+        if (mBufSize > 0) {
+            int start = mFlushed;
+            int end = mFlushed < mRead ? mRead : mMaxSize;
+            while (mFlushed < end && mBuf[mFlushed++] != newline && mBufSize > 0) mBufSize--;
+            meetsNewLine = (mBuf[mFlushed-1] == newline);
+            if (meetsNewLine) mBufSize--; // deduct the new line character
+            size_t len = meetsNewLine ? mFlushed - start - 1 : mFlushed - start;
+            ss.write(mBuf + start, len);
+        }
+
+        if (mRead >= (int) mMaxSize) mRead = 0;
+        if (mFlushed >= (int) mMaxSize) mFlushed = 0;
+
+        if (EOR() || meetsNewLine) {
+            line.assign(ss.str());
+            return true;
+        }
+    }
+    return false;
+}
+
+bool Reader::ok(std::string& error) {
+    error.assign(mStatus);
+    return mStatus.empty();
+}
diff --git a/cmds/incident_helper/ih_util.h b/cmds/incident_helper/ih_util.h
new file mode 100644
index 0000000..9e0c18e
--- /dev/null
+++ b/cmds/incident_helper/ih_util.h
@@ -0,0 +1,64 @@
+/*
+ * 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 INCIDENT_HELPER_UTIL_H
+#define INCIDENT_HELPER_UTIL_H
+
+#include <string>
+#include <vector>
+#include <sstream>
+
+typedef std::vector<std::string> header_t;
+typedef std::vector<std::string> record_t;
+
+const char DEFAULT_NEWLINE = '\n';
+const std::string DEFAULT_WHITESPACE = " \t";
+
+std::string trim(const std::string& s, const std::string& whitespace = DEFAULT_WHITESPACE);
+
+void split(const std::string& line, std::vector<std::string>& words,
+    const std::string& delimiters = DEFAULT_WHITESPACE);
+
+bool assertHeaders(const char* expected[], const std::vector<std::string>& actual);
+
+/**
+ * Reader class reads data from given fd in streaming fashion.
+ * The buffer size is controlled by capacity parameter.
+ */
+class Reader
+{
+public:
+    Reader(const int fd);
+    Reader(const int fd, const size_t capacity);
+    ~Reader();
+
+    bool readLine(std::string& line, const char newline = DEFAULT_NEWLINE);
+    bool ok(std::string& error);
+
+private:
+    int mFd; // set mFd to -1 when read EOF()
+    const size_t mMaxSize;
+    size_t mBufSize;
+    char* mBuf; // implements a circular buffer
+
+    int mRead;
+    int mFlushed;
+    std::string mStatus;
+    // end of read
+    inline bool EOR() { return mFd == -1 && mBufSize == 0; };
+};
+
+#endif  // INCIDENT_HELPER_UTIL_H
diff --git a/cmds/incident_helper/main.cpp b/cmds/incident_helper/main.cpp
index 1e25c85..333344b 100644
--- a/cmds/incident_helper/main.cpp
+++ b/cmds/incident_helper/main.cpp
@@ -27,12 +27,11 @@
 using namespace std;
 
 static void usage(FILE* out) {
-    fprintf(out, "incident_helper is not designed to run manually, see README.md\n");
-    fprintf(out, "usage: incident_helper -s SECTION -i INPUT -o OUTPUT\n");
+    fprintf(out, "incident_helper is not designed to run manually,");
+    fprintf(out, "it reads from stdin and writes to stdout, see README.md for details.\n");
+    fprintf(out, "usage: incident_helper -s SECTION\n");
     fprintf(out, "REQUIRED:\n");
     fprintf(out, "  -s           section id, must be positive\n");
-    fprintf(out, "  -i           (default stdin) input fd\n");
-    fprintf(out, "  -o           (default stdout) output fd\n");
 }
 
 //=============================================================================
@@ -45,6 +44,8 @@
             return new ReverseParser();
 /* ========================================================================= */
         // IDs larger than 0 are reserved in incident.proto
+        case 2000:
+            return new ProcrankParser();
         case 2002:
             return new KernelWakesParser();
         default:
@@ -59,9 +60,7 @@
     // Parse the args
     int opt;
     int sectionID = 0;
-    int inputFd = STDIN_FILENO;
-    int outputFd = STDOUT_FILENO;
-    while ((opt = getopt(argc, argv, "hs:i:o:")) != -1) {
+    while ((opt = getopt(argc, argv, "hs:")) != -1) {
         switch (opt) {
             case 'h':
                 usage(stdout);
@@ -69,30 +68,14 @@
             case 's':
                 sectionID = atoi(optarg);
                 break;
-            case 'i':
-                inputFd = atoi(optarg);
-                break;
-            case 'o':
-                outputFd = atoi(optarg);
-                break;
         }
     }
 
-    // Check mandatory parameters:
-    if (inputFd < 0) {
-        fprintf(stderr, "invalid input fd: %d\n", inputFd);
-        return 1;
-    }
-    if (outputFd < 0) {
-        fprintf(stderr, "invalid output fd: %d\n", outputFd);
-        return 1;
-    }
-
     fprintf(stderr, "Pasring section %d...\n", sectionID);
     TextParserBase* parser = selectParser(sectionID);
     if (parser != NULL) {
         fprintf(stderr, "Running parser: %s\n", parser->name.string());
-        status_t err = parser->Parse(inputFd, outputFd);
+        status_t err = parser->Parse(STDIN_FILENO, STDOUT_FILENO);
         if (err != NO_ERROR) {
             fprintf(stderr, "Parse error in section %d: %s\n", sectionID, strerror(-err));
             return -1;
diff --git a/cmds/incident_helper/strutil.cpp b/cmds/incident_helper/strutil.cpp
deleted file mode 100644
index 21b04a1..0000000
--- a/cmds/incident_helper/strutil.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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 "strutil.h"
-
-#include <sstream>
-
-std::string trim(const std::string& s, const std::string& whitespace) {
-    const auto head = s.find_first_not_of(whitespace);
-    if (head == std::string::npos) return "";
-
-    const auto tail = s.find_last_not_of(whitespace);
-    return s.substr(head, tail - head + 1);
-}
-
-// This is similiar to Split in android-base/file.h, but it won't add empty string
-void split(const std::string& line, std::vector<std::string>* words, const std::string& delimiters) {
-    words->clear();  // clear the buffer before split
-
-    size_t base = 0;
-    size_t found;
-    while (true) {
-        found = line.find_first_of(delimiters, base);
-        if (found != base) { // ignore empty string
-            // one char before found
-            words->push_back(line.substr(base, found - base));
-        }
-        if (found == line.npos) break;
-        base = found + 1;
-    }
-}
-
-bool assertHeaders(const char* expected[], const std::vector<std::string>& actual) {
-    for (size_t i = 0; i < actual.size(); i++) {
-        if (expected[i] == NULL || std::string(expected[i]) != actual.at(i)) {
-            return false;
-        }
-    }
-    return true;
-}
diff --git a/cmds/incident_helper/strutil.h b/cmds/incident_helper/strutil.h
deleted file mode 100644
index fcc164d..0000000
--- a/cmds/incident_helper/strutil.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 STRUTIL_H
-#define STRUTIL_H
-
-#include <string>
-#include <vector>
-
-const std::string DEFAULT_WHITESPACE = " \t";
-
-std::string trim(const std::string& s, const std::string& whitespace = DEFAULT_WHITESPACE);
-void split(const std::string& line, std::vector<std::string>* words,
-    const std::string& delimiters = DEFAULT_WHITESPACE);
-bool assertHeaders(const char* expected[], const std::vector<std::string>& actual);
-
-#endif  // STRUTIL_H
diff --git a/cmds/incident_helper/testdata/procrank.txt b/cmds/incident_helper/testdata/procrank.txt
new file mode 100644
index 0000000..5d2d8d2
--- /dev/null
+++ b/cmds/incident_helper/testdata/procrank.txt
@@ -0,0 +1,8 @@
+  PID       Vss      Rss      Pss      Uss     Swap    PSwap    USwap    ZSwap  cmdline
+ 1119  2607640K  339564K  180278K  114216K    1584K      46K       0K      10K  system_server
+  649    11016K    1448K      98K      48K     472K     342K     212K      75K  /vendor/bin/qseecomd
+                           ------   ------   ------   ------   ------   ------  ------
+                          1201993K  935300K   88164K   31069K   27612K    6826K  TOTAL
+
+ZRAM: 6828K physical used for 31076K in swap (524284K total swap)
+ RAM: 3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/IncidentHelper_test.cpp b/cmds/incident_helper/tests/IncidentHelper_test.cpp
index 9d5d9e4..ac3e84d 100644
--- a/cmds/incident_helper/tests/IncidentHelper_test.cpp
+++ b/cmds/incident_helper/tests/IncidentHelper_test.cpp
@@ -17,10 +17,12 @@
 #include "IncidentHelper.h"
 
 #include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+#include "frameworks/base/core/proto/android/os/procrank.pb.h"
 
 #include <android-base/file.h>
 #include <android-base/test_utils.h>
 #include <gmock/gmock.h>
+#include <google/protobuf/message.h>
 #include <gtest/gtest.h>
 #include <string.h>
 #include <fcntl.h>
@@ -37,10 +39,19 @@
 class IncidentHelperTest : public Test {
 public:
     virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
 
+    std::string getSerializedString(::google::protobuf::Message& message) {
+        std::string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
     }
 
 protected:
+    TemporaryFile tf;
+
     const std::string kTestPath = GetExecutableDirectory();
     const std::string kTestDataPath = kTestPath + "/testdata/";
 };
@@ -61,9 +72,6 @@
     const std::string testFile = kTestDataPath + "kernel_wakeups.txt";
     KernelWakesParser parser;
     KernelWakeSources expected;
-    std::string expectedStr;
-    TemporaryFile tf;
-    ASSERT_TRUE(tf.fd != -1);
 
     WakeupSourceProto* record1 = expected.add_wakeup_sources();
     record1->set_name("ipc000000ab_ATFWD-daemon");
@@ -89,15 +97,12 @@
     record2->set_last_change(2067286206l);
     record2->set_prevent_suspend_time(0l);
 
-    ASSERT_TRUE(expected.SerializeToFileDescriptor(tf.fd));
-    ASSERT_TRUE(ReadFileToString(tf.path, &expectedStr));
-
     int fd = open(testFile.c_str(), O_RDONLY, 0444);
     ASSERT_TRUE(fd != -1);
 
     CaptureStdout();
     ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), expectedStr);
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
     close(fd);
 }
 
@@ -115,3 +120,54 @@
     EXPECT_THAT(GetCapturedStderr(), StrEq("[KernelWakeSources]Bad header:\nTHIS IS BAD HEADER\n"));
     close(fd);
 }
+
+TEST_F(IncidentHelperTest, ProcrankParser) {
+    const std::string testFile = kTestDataPath + "procrank.txt";
+    ProcrankParser parser;
+    Procrank expected;
+
+    ProcessProto* process1 = expected.add_processes();
+    process1->set_pid(1119);
+    process1->set_vss(2607640);
+    process1->set_rss(339564);
+    process1->set_pss(180278);
+    process1->set_uss(114216);
+    process1->set_swap(1584);
+    process1->set_pswap(46);
+    process1->set_uswap(0);
+    process1->set_zswap(10);
+    process1->set_cmdline("system_server");
+
+    ProcessProto* process2 = expected.add_processes();
+    process2->set_pid(649);
+    process2->set_vss(11016);
+    process2->set_rss(1448);
+    process2->set_pss(98);
+    process2->set_uss(48);
+    process2->set_swap(472);
+    process2->set_pswap(342);
+    process2->set_uswap(212);
+    process2->set_zswap(75);
+    process2->set_cmdline("/vendor/bin/qseecomd");
+
+    ProcessProto* total = expected.mutable_summary()->mutable_total();
+    total->set_pss(1201993);
+    total->set_uss(935300);
+    total->set_swap(88164);
+    total->set_pswap(31069);
+    total->set_uswap(27612);
+    total->set_zswap(6826);
+
+    expected.mutable_summary()->mutable_zram()
+        ->set_raw_text("6828K physical used for 31076K in swap (524284K total swap)");
+    expected.mutable_summary()->mutable_ram()
+        ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
+
+    int fd = open(testFile.c_str(), O_RDONLY, 0444);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
new file mode 100644
index 0000000..5158e0a
--- /dev/null
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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 "ih_util.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace android::base;
+using namespace std;
+using ::testing::StrEq;
+
+TEST(IhUtilTest, Trim) {
+    EXPECT_THAT(trim(" \t 100 00\toooh \t wqrw  "), StrEq("100 00\toooh \t wqrw"));
+    EXPECT_THAT(trim(" \t 100 00\toooh \t wqrw  ", " "), StrEq("\t 100 00\toooh \t wqrw"));
+}
+
+TEST(IhUtilTest, Split) {
+    vector<string> result, expected;
+    split(" \t \t\t ", result);
+    EXPECT_EQ(expected, result);
+
+    split(" \t 100 00\toooh \t wqrw", result);
+    expected = { "100", "00", "oooh", "wqrw" };
+    EXPECT_EQ(expected, result);
+
+    split(" \t 100 00\toooh \t wqrw", result, "\t");
+    expected = { "100 00", "oooh", "wqrw" };
+    EXPECT_EQ(expected, result);
+
+    split("123,456,78_9", result, ",");
+    expected = { "123", "456", "78_9" };
+    EXPECT_EQ(expected, result);
+}
+
+TEST(IhUtilTest, Reader) {
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooo\n", tf.path, false));
+
+    Reader r(tf.fd);
+    string line;
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("test string"));
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("second"));
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("ooo"));
+    ASSERT_FALSE(r.readLine(line));
+    ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderSmallBufSize) {
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooiecccojreo", tf.path, false));
+
+    Reader r(tf.fd, 5);
+    string line;
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("test string"));
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("second"));
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("ooiecccojreo"));
+    ASSERT_FALSE(r.readLine(line));
+    ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderEmpty) {
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile("", tf.path, false));
+
+    Reader r(tf.fd);
+    string line;
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq(""));
+    ASSERT_FALSE(r.readLine(line));
+    ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderMultipleEmptyLines) {
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile("\n\n", tf.path, false));
+
+    Reader r(tf.fd);
+    string line;
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq(""));
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq(""));
+    ASSERT_FALSE(r.readLine(line));
+    EXPECT_THAT(line, StrEq(""));
+    ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderFailedNegativeFd) {
+    Reader r(-123);
+    string line;
+    EXPECT_FALSE(r.readLine(line));
+    EXPECT_FALSE(r.ok(line));
+    EXPECT_THAT(line, StrEq("Negative fd"));
+}
+
+TEST(IhUtilTest, ReaderFailedZeroBufferSize) {
+    Reader r(23, 0);
+    string line;
+    EXPECT_FALSE(r.readLine(line));
+    EXPECT_FALSE(r.ok(line));
+    EXPECT_THAT(line, StrEq("Zero buffer capacity"));
+}
+
+TEST(IhUtilTest, ReaderFailedBadFd) {
+    Reader r(1231432);
+    string line;
+    EXPECT_FALSE(r.readLine(line));
+    EXPECT_FALSE(r.ok(line));
+    EXPECT_THAT(line, StrEq("Fail to read from fd"));
+}
diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc
index d11e3cf..250a7f3 100644
--- a/cmds/incidentd/incidentd.rc
+++ b/cmds/incidentd/incidentd.rc
@@ -12,5 +12,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-#service incidentd /system/bin/incidentd
+# service incidentd /system/bin/incidentd
 #    class main
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index 7888442..03a6d18 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -98,7 +98,7 @@
     bool close() { return !(::close(mFds[0]) || ::close(mFds[1])); }
     ~Fpipe() { close(); }
 
-    inline status_t init() { return pipe(mFds); }
+    inline bool init() { return pipe(mFds) != -1; }
     inline int readFd() const { return mFds[0]; }
     inline int writeFd() const { return mFds[1]; }
 
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index ba157de6..2a4f89e 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -35,7 +35,7 @@
 /**
  * The directory where the incident reports are stored.
  */
-static const String8 INCIDENT_DIRECTORY("/data/incidents");
+static const String8 INCIDENT_DIRECTORY("/data/misc/incidents");
 
 // ================================================================================
 static status_t write_all(int fd, uint8_t const* buf, size_t size)
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index ddb54c1..8ef6817 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -19,6 +19,7 @@
 #include "Section.h"
 #include "protobuf.h"
 
+#include <private/android_filesystem_config.h>
 #include <binder/IServiceManager.h>
 #include <mutex>
 #include <stdio.h>
@@ -29,15 +30,68 @@
 
 using namespace std;
 
-const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds
-const int64_t INCIDENT_HELPER_TIMEOUT_MS = 5 * 1000;  // 5 seconds
+const int   WAIT_MAX = 5;
+const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000};
 const char* INCIDENT_HELPER = "/system/bin/incident_helper";
-const uid_t IH_UID = 9999;  // run incident_helper as nobody
-const gid_t IH_GID = 9999;
+
+static pid_t
+forkAndExecuteIncidentHelper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe)
+{
+    const char* ihArgs[] { INCIDENT_HELPER, "-s", to_string(id).c_str(), NULL };
+
+    // fork used in multithreaded environment, avoid adding unnecessary code in child process
+    pid_t pid = fork();
+    if (pid == 0) {
+        // child process executes incident helper as nobody
+        if (setgid(AID_NOBODY) == -1) {
+            ALOGW("%s can't change gid: %s", name, strerror(errno));
+            _exit(EXIT_FAILURE);
+        }
+        if (setuid(AID_NOBODY) == -1) {
+            ALOGW("%s can't change uid: %s", name, strerror(errno));
+            _exit(EXIT_FAILURE);
+        }
+
+        if (dup2(p2cPipe.readFd(),  STDIN_FILENO)  != 0 || !p2cPipe.close() ||
+            dup2(c2pPipe.writeFd(), STDOUT_FILENO) != 1 || !c2pPipe.close()) {
+            ALOGW("%s can't setup stdin and stdout for incident helper", name);
+            _exit(EXIT_FAILURE);
+        }
+
+        execv(INCIDENT_HELPER, const_cast<char**>(ihArgs));
+
+        ALOGW("%s failed in incident helper process: %s", name, strerror(errno));
+        _exit(EXIT_FAILURE); // always exits with failure if any
+    }
+    // close the fds used in incident helper
+    close(p2cPipe.readFd());
+    close(c2pPipe.writeFd());
+    return pid;
+}
+
+static status_t killChild(pid_t pid) {
+    int status;
+    kill(pid, SIGKILL);
+    if (waitpid(pid, &status, 0) == -1) return -1;
+    return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status);
+}
+
+static status_t waitForChild(pid_t pid) {
+    int status;
+    bool died = false;
+    // wait for child to report status up to 1 seconds
+    for(int loop = 0; !died && loop < WAIT_MAX; loop++) {
+        if (waitpid(pid, &status, WNOHANG) == pid) died = true;
+        // sleep for 0.2 second
+        nanosleep(&WAIT_INTERVAL_NS, NULL);
+    }
+    if (!died) return killChild(pid);
+    return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status);
+}
 
 // ================================================================================
-Section::Section(int i)
-    :id(i)
+Section::Section(int i, const int64_t timeoutMs)
+    :id(i), timeoutMs(timeoutMs)
 {
 }
 
@@ -55,106 +109,57 @@
 }
 
 // ================================================================================
-FileSection::FileSection(int id, const char* filename)
-        : Section(id), mFilename(filename) {
-    name = "cat ";
-    name += filename;
+FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs)
+        : Section(id, timeoutMs), mFilename(filename) {
+    name = filename;
 }
 
 FileSection::~FileSection() {}
 
 status_t FileSection::Execute(ReportRequestSet* requests) const {
-    Fpipe p2cPipe;
-    Fpipe c2pPipe;
-    FdBuffer buffer;
-
-    // initiate pipes to pass data to/from incident_helper
-    if (p2cPipe.init() == -1) {
-        return -errno;
-    }
-    if (c2pPipe.init() == -1) {
-        return -errno;
-    }
-
-    // fork a child process
-    pid_t pid = fork();
-
-    if (pid == -1) {
-        ALOGW("FileSection '%s' failed to fork", this->name.string());
-        return -errno;
-    }
-
-    // child process
-    if (pid == 0) {
-        if (setgid(IH_GID) == -1) {
-            ALOGW("FileSection '%s' can't change gid: %s", this->name.string(), strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-        if (setuid(IH_UID) == -1) {
-            ALOGW("FileSection '%s' can't change uid: %s", this->name.string(), strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-
-        if (dup2(p2cPipe.readFd(), STDIN_FILENO) != 0 || !p2cPipe.close()) {
-            ALOGW("FileSection '%s' failed to set up stdin: %s", this->name.string(), strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-        if (dup2(c2pPipe.writeFd(), STDOUT_FILENO) != 1 || !c2pPipe.close()) {
-            ALOGW("FileSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-
-        // execute incident_helper to parse raw file data and generate protobuf
-        char sectionID[8];  // section id is expected to be smaller than 8 digits
-        sprintf(sectionID, "%d", this->id);
-        const char* args[]{INCIDENT_HELPER, "-s", sectionID, NULL};
-        execv(INCIDENT_HELPER, const_cast<char**>(args));
-
-        ALOGW("FileSection '%s' failed in child process: %s", this->name.string(), strerror(errno));
-        return -1;
-    }
-
-    // parent process
-
-    // close fds used in child process
-    close(p2cPipe.readFd());
-    close(c2pPipe.writeFd());
-
-    // read from mFilename and pump buffer to incident_helper
-    status_t err = NO_ERROR;
-    int fd = open(mFilename, O_RDONLY);
+    // read from mFilename first, make sure the file is available
+    // add O_CLOEXEC to make sure it is closed when exec incident helper
+    int fd = open(mFilename, O_RDONLY | O_CLOEXEC, 0444);
     if (fd == -1) {
        ALOGW("FileSection '%s' failed to open file", this->name.string());
        return -errno;
     }
 
-    err = buffer.readProcessedDataInStream(fd,
-        p2cPipe.writeFd(), c2pPipe.readFd(), INCIDENT_HELPER_TIMEOUT_MS);
-    if (err != NO_ERROR) {
-        ALOGW("FileSection '%s' failed to read data from incident helper: %s",
-            this->name.string(), strerror(-err));
-        kill(pid, SIGKILL); // kill child process if meets error
-        return err;
-    }
-
-    if (buffer.timedOut()) {
-        ALOGW("FileSection '%s' timed out reading from incident helper!", this->name.string());
-        kill(pid, SIGKILL); // kill the child process if timed out
-    }
-
-    // has to block here to reap child process
-    int status;
-    int w = waitpid(pid, &status, 0);
-    if (w < 0 || status == -1) {
-        ALOGW("FileSection '%s' abnormal child process: %s", this->name.string(), strerror(-err));
+    FdBuffer buffer;
+    Fpipe p2cPipe;
+    Fpipe c2pPipe;
+    // initiate pipes to pass data to/from incident_helper
+    if (!p2cPipe.init() || !c2pPipe.init()) {
+        ALOGW("FileSection '%s' failed to setup pipes", this->name.string());
         return -errno;
     }
 
-    // write parsed data to reporter
-    ALOGD("section '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
+    pid_t pid = forkAndExecuteIncidentHelper(this->id, this->name.string(), p2cPipe, c2pPipe);
+    if (pid == -1) {
+        ALOGW("FileSection '%s' failed to fork", this->name.string());
+        return -errno;
+    }
+
+    // parent process
+    status_t readStatus = buffer.readProcessedDataInStream(fd, p2cPipe.writeFd(), c2pPipe.readFd(),
+            this->timeoutMs);
+    if (readStatus != NO_ERROR || buffer.timedOut()) {
+        ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s, kill: %s",
+            this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
+            strerror(-killChild(pid)));
+        return readStatus;
+    }
+
+    status_t ihStatus = waitForChild(pid);
+    if (ihStatus != NO_ERROR) {
+        ALOGW("FileSection '%s' abnormal child process: %s", this->name.string(), strerror(-ihStatus));
+        return ihStatus;
+    }
+
+    ALOGD("FileSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
             (int)buffer.durationMs());
     WriteHeader(requests, buffer.size());
-    err = buffer.write(requests);
+    status_t err = buffer.write(requests);
     if (err != NO_ERROR) {
         ALOGW("FileSection '%s' failed writing: %s", this->name.string(), strerror(-err));
         return err;
@@ -263,7 +268,7 @@
     pthread_attr_destroy(&attr);
 
     // Loop reading until either the timeout or the worker side is done (i.e. eof).
-    err = buffer.read(data->readFd(), REMOTE_CALL_TIMEOUT_MS);
+    err = buffer.read(data->readFd(), this->timeoutMs);
     if (err != NO_ERROR) {
         // TODO: Log this error into the incident report.
         ALOGW("WorkerThreadSection '%s' reader failed with error '%s'", this->name.string(),
@@ -309,7 +314,7 @@
     }
 
     // Write the data that was collected
-    ALOGD("section '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(),
+    ALOGD("WorkerThreadSection '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(),
             (int)buffer.durationMs());
     WriteHeader(requests, buffer.size());
     err = buffer.write(requests);
@@ -322,42 +327,116 @@
 }
 
 // ================================================================================
-CommandSection::CommandSection(int id, const char* first, ...)
-    :Section(id)
+void CommandSection::init(const char* command, va_list args)
+{
+    va_list copied_args;
+    va_copy(copied_args, args);
+    int numOfArgs = 0;
+    while(va_arg(args, const char*) != NULL) {
+        numOfArgs++;
+    }
+
+    // allocate extra 1 for command and 1 for NULL terminator
+    mCommand = (const char**)malloc(sizeof(const char*) * (numOfArgs + 2));
+
+    mCommand[0] = command;
+    name = command;
+    for (int i=0; i<numOfArgs; i++) {
+        const char* arg = va_arg(copied_args, const char*);
+        mCommand[i+1] = arg;
+        name += " ";
+        name += arg;
+    }
+    mCommand[numOfArgs+1] = NULL;
+    va_end(copied_args);
+}
+
+CommandSection::CommandSection(int id, const int64_t timeoutMs, const char* command, ...)
+        : Section(id, timeoutMs)
 {
     va_list args;
-    int count = 0;
-
-    va_start(args, first);
-    while (va_arg(args, const char*) != NULL) {
-        count++;
-    }
+    va_start(args, command);
+    init(command, args);
     va_end(args);
+}
 
-    mCommand = (const char**)malloc(sizeof(const char*) * count);
-
-    mCommand[0] = first;
-    name = first;
-    name += " ";
-    va_start(args, first);
-    for (int i=0; i<count; i++) {
-        const char* arg = va_arg(args, const char*);
-        mCommand[i+1] = arg;
-        if (arg != NULL) {
-            name += va_arg(args, const char*);
-            name += " ";
-        }
-    }
+CommandSection::CommandSection(int id, const char* command, ...)
+        : Section(id)
+{
+    va_list args;
+    va_start(args, command);
+    init(command, args);
     va_end(args);
 }
 
 CommandSection::~CommandSection()
 {
+    free(mCommand);
 }
 
 status_t
-CommandSection::Execute(ReportRequestSet* /*requests*/) const
+CommandSection::Execute(ReportRequestSet* requests) const
 {
+    FdBuffer buffer;
+    Fpipe cmdPipe;
+    Fpipe ihPipe;
+
+    if (!cmdPipe.init() || !ihPipe.init()) {
+        ALOGW("CommandSection '%s' failed to setup pipes", this->name.string());
+        return -errno;
+    }
+
+    pid_t cmdPid = fork();
+    if (cmdPid == -1) {
+        ALOGW("CommandSection '%s' failed to fork", this->name.string());
+        return -errno;
+    }
+    // child process to execute the command as root
+    if (cmdPid == 0) {
+        // replace command's stdout with ihPipe's write Fd
+        if (dup2(cmdPipe.writeFd(), STDOUT_FILENO) != 1 || !ihPipe.close() || !cmdPipe.close()) {
+            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);
+        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
+    }
+    pid_t ihPid = forkAndExecuteIncidentHelper(this->id, this->name.string(), cmdPipe, ihPipe);
+    if (ihPid == -1) {
+        ALOGW("CommandSection '%s' failed to fork", this->name.string());
+        return -errno;
+    }
+
+    close(cmdPipe.writeFd());
+    status_t readStatus = buffer.read(ihPipe.readFd(), this->timeoutMs);
+    if (readStatus != NO_ERROR || buffer.timedOut()) {
+        ALOGW("CommandSection '%s' failed to read data from incident helper: %s, "
+            "timedout: %s, kill command: %s, kill incident helper: %s",
+            this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
+            strerror(-killChild(cmdPid)), strerror(-killChild(ihPid)));
+        return readStatus;
+    }
+
+    // TODO: wait for command here has one trade-off: the failed status of command won't be detected until
+    //       buffer timeout, but it has advatage on starting the data stream earlier.
+    status_t cmdStatus = waitForChild(cmdPid);
+    status_t ihStatus  = waitForChild(ihPid);
+    if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) {
+        ALOGW("CommandSection '%s' abnormal child processes, return status: command: %s, incidnet helper: %s",
+            this->name.string(), strerror(-cmdStatus), strerror(-ihStatus));
+        return cmdStatus != NO_ERROR ? cmdStatus : ihStatus;
+    }
+
+    ALOGD("CommandSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
+            (int)buffer.durationMs());
+    WriteHeader(requests, buffer.size());
+    status_t err = buffer.write(requests);
+    if (err != NO_ERROR) {
+        ALOGW("CommandSection '%s' failed writing: %s", this->name.string(), strerror(-err));
+        return err;
+    }
     return NO_ERROR;
 }
 
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index 35740e9..93b4848 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -19,22 +19,26 @@
 
 #include "FdBuffer.h"
 
+#include <stdarg.h>
 #include <utils/String8.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
 
 using namespace android;
 
+const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds
+
 /**
  * Base class for sections
  */
 class Section
 {
 public:
-    int id;
+    const int id;
+    const int64_t timeoutMs; // each section must have a timeout
     String8 name;
 
-    Section(int id);
+    Section(int id, const int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS);
     virtual ~Section();
 
     virtual status_t Execute(ReportRequestSet* requests) const = 0;
@@ -48,7 +52,7 @@
 class FileSection : public Section
 {
 public:
-    FileSection(int id, const char* filename);
+    FileSection(int id, const char* filename, const int64_t timeoutMs = 5000 /* 5 seconds */);
     virtual ~FileSection();
 
     virtual status_t Execute(ReportRequestSet* requests) const;
@@ -77,13 +81,18 @@
 class CommandSection : public Section
 {
 public:
-    CommandSection(int id, const char* first, ...);
+    CommandSection(int id, const int64_t timeoutMs, const char* command, ...);
+
+    CommandSection(int id, const char* command, ...);
+
     virtual ~CommandSection();
 
     virtual status_t Execute(ReportRequestSet* requests) const;
 
 private:
     const char** mCommand;
+
+    void init(const char* command, va_list args);
 };
 
 /**
diff --git a/cmds/incidentd/src/section_list.cpp b/cmds/incidentd/src/section_list.cpp
index 72c54d2..80ddb86 100644
--- a/cmds/incidentd/src/section_list.cpp
+++ b/cmds/incidentd/src/section_list.cpp
@@ -21,6 +21,7 @@
  */
 const Section* SECTION_LIST[] = {
     // Linux Services
+    new CommandSection(2000, "/system/xbin/procrank", NULL),
     new FileSection(2002, "/d/wakeup_sources"),
 
     // System Services
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index ca5eec8..ba8b77a 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -25,6 +25,7 @@
 
 const int READ_TIMEOUT = 5 * 1000;
 const int BUFFER_SIZE = 16 * 1024;
+const int QUICK_TIMEOUT_MS = 100;
 const std::string HEAD = "[OK]";
 
 using namespace android;
@@ -101,11 +102,11 @@
             write(c2pPipe.writeFd(), "poo", 3);
             sleep(1);
         }
-        exit(EXIT_FAILURE);
+        _exit(EXIT_FAILURE);
     } else {
         close(c2pPipe.writeFd());
 
-        status_t status = buffer.read(c2pPipe.readFd(), 500);
+        status_t status = buffer.read(c2pPipe.readFd(), QUICK_TIMEOUT_MS);
         ASSERT_EQ(NO_ERROR, status);
         EXPECT_TRUE(buffer.timedOut());
 
@@ -129,7 +130,7 @@
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
         // Must exit here otherwise the child process will continue executing the test binary.
-        exit(EXIT_SUCCESS);
+        _exit(EXIT_SUCCESS);
     } else {
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
@@ -161,7 +162,7 @@
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
         // Must exit here otherwise the child process will continue executing the test binary.
-        exit(EXIT_SUCCESS);
+        _exit(EXIT_SUCCESS);
     } else {
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
@@ -186,7 +187,7 @@
         ASSERT_TRUE(DoDataStream(p2cPipe.readFd(), c2pPipe.writeFd()));
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
-        exit(EXIT_SUCCESS);
+        _exit(EXIT_SUCCESS);
     } else {
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
@@ -212,7 +213,7 @@
         ASSERT_TRUE(DoDataStream(p2cPipe.readFd(), c2pPipe.writeFd()));
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
-        exit(EXIT_SUCCESS);
+        _exit(EXIT_SUCCESS);
     } else {
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
@@ -239,13 +240,13 @@
         while (true) {
             sleep(1);
         }
-        exit(EXIT_FAILURE);
+        _exit(EXIT_FAILURE);
     } else {
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
 
         ASSERT_EQ(NO_ERROR, buffer.readProcessedDataInStream(tf.fd,
-            p2cPipe.writeFd(), c2pPipe.readFd(), 100));
+            p2cPipe.writeFd(), c2pPipe.readFd(), QUICK_TIMEOUT_MS));
         EXPECT_TRUE(buffer.timedOut());
         kill(pid, SIGKILL); // reap the child process
     }
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 18322b6..93771ff 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -22,6 +22,8 @@
 #include <gtest/gtest.h>
 #include <string.h>
 
+const int QUICK_TIMEOUT_MS = 100;
+
 using namespace android::base;
 using namespace std;
 using ::testing::StrEq;
@@ -62,7 +64,56 @@
 TEST(SectionTest, FileSectionTimeout) {
     TemporaryFile tf;
     // id -1 is timeout parser
-    FileSection fs(-1, tf.path);
+    FileSection fs(-1, tf.path, QUICK_TIMEOUT_MS);
     ReportRequestSet requests;
     ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionConstructor) {
+    CommandSection cs1(1, "echo", "\"this is a test\"", "ooo", NULL);
+    CommandSection cs2(2, "single_command", NULL);
+    CommandSection cs3(1, 3123, "echo", "\"this is a test\"", "ooo", NULL);
+    CommandSection cs4(2, 43214, "single_command", NULL);
+
+    EXPECT_THAT(cs1.name.string(), StrEq("echo \"this is a test\" ooo"));
+    EXPECT_THAT(cs2.name.string(), StrEq("single_command"));
+    EXPECT_EQ(3123, cs3.timeoutMs);
+    EXPECT_EQ(43214, cs4.timeoutMs);
+    EXPECT_THAT(cs3.name.string(), StrEq("echo \"this is a test\" ooo"));
+    EXPECT_THAT(cs4.name.string(), StrEq("single_command"));
+}
+
+TEST(SectionTest, CommandSectionEcho) {
+    CommandSection cs(0, "/system/bin/echo", "about", NULL);
+    ReportRequestSet requests;
+    requests.setMainFd(STDOUT_FILENO);
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+    EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\x06\ntuoba"));
+}
+
+TEST(SectionTest, CommandSectionCommandTimeout) {
+    CommandSection cs(0, QUICK_TIMEOUT_MS, "/system/bin/yes", NULL);
+    ReportRequestSet requests;
+    ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionIncidentHelperTimeout) {
+    CommandSection cs(-1, QUICK_TIMEOUT_MS, "/system/bin/echo", "about", NULL);
+    ReportRequestSet requests;
+    requests.setMainFd(STDOUT_FILENO);
+    ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionBadCommand) {
+    CommandSection cs(0, "echo", "about", NULL);
+    ReportRequestSet requests;
+    ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionBadCommandAndTimeout) {
+    CommandSection cs(-1, QUICK_TIMEOUT_MS, "nonexistcommand", "-opt", NULL);
+    ReportRequestSet requests;
+    // timeout will return first
+    ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
 }
\ No newline at end of file
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 5914f56..76e683f 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -31,6 +31,7 @@
 import "frameworks/base/core/proto/android/service/print.proto";
 import "frameworks/base/core/proto/android/providers/settings.proto";
 import "frameworks/base/core/proto/android/os/kernelwake.proto";
+import "frameworks/base/core/proto/android/os/procrank.proto";
 
 package android.os;
 
@@ -53,7 +54,7 @@
     //SystemProperties system_properties = 1000;
 
     // Linux services
-    //Procrank procrank = 2000;
+    Procrank procrank = 2000;
     //PageTypeInfo page_type_info = 2001;
     KernelWakeSources kernel_wake_sources = 2002;
 
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index 6ab1d29..e0b62aa 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -26,6 +26,7 @@
     repeated WakeupSourceProto wakeup_sources = 1;
 }
 
+// Next Tag: 11
 message WakeupSourceProto {
     // Name of the event which triggers application processor
     string name = 1;
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
new file mode 100644
index 0000000..c7dbf4d
--- /dev/null
+++ b/core/proto/android/os/procrank.proto
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 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 = "proto3";
+
+option java_multiple_files = true;
+option java_outer_classname = "ProcrankProto";
+
+package android.os;
+
+//Memory usage of running processes
+message Procrank {
+    // Currently running process
+    repeated ProcessProto processes = 1;
+
+    // Summary
+    SummaryProto summary = 2;
+}
+
+// Next Tag: 11
+message ProcessProto {
+    // ID of the process
+    int32 pid = 1;
+
+    // virtual set size, unit KB
+    int64 vss = 2;
+
+    // resident set size, unit KB
+    int64 rss = 3;
+
+    // proportional set size, unit KB
+    int64 pss = 4;
+
+    // unique set size, unit KB
+    int64 uss = 5;
+
+    // swap size, unit KB
+    int64 swap = 6;
+
+    // proportional swap size, unit KB
+    int64 pswap = 7;
+
+    // unique swap size, unit KB
+    int64 uswap = 8;
+
+    // zswap size, unit KB
+    int64 zswap = 9;
+
+    // process command
+    string cmdline = 10;
+}
+
+// Next Tag: 3
+message SummaryProto {
+    ProcessProto total = 1;
+
+    ZramProto zram = 2;
+
+    RamProto ram = 3;
+}
+
+// TODO: sync on how to use these values
+message ZramProto {
+    string raw_text = 1;
+}
+
+message RamProto {
+    string raw_text = 1;
+}