Refactor incident_helper to use protoutil and cppstream plugin.

1. Split the parsers to its own file to prevent all the parsers in one
gaint file.

2. Completely get rid of protobuf-cpp-full in incident_helper, use
ProtoOutputStream and cppstream instead, the incident_helper binary is
reduced from ~500K to ~113K.

3. Write data to protobuf even its values are zero/default, the reason
is for example we have a repeated int32 orders = 1; and people
explicitly append 0 so the total repeated field has 10 values, if zero
is not written to serialized data, this repeated field will only have 9
values which is not what we want at first place. This also aligns with
the default protobuf serialization behavior in incident_helper_test.

4. Use Android.bp for protoutil lib since it is not able to depend on
libs compiled by .mk file, it works the other way.

5. Add a new custom message option for streaming_proto, if specified,
the cppstream will create extra metadata to get field ids by field name.
A Table class is created in incident_helper to use it.

Bug: 67860303
Test: unit tested as well as on device test
Change-Id: I8e136fd15f343a4a623d20910ec64b622b478a3e
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index 0532083..2ef0371 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -8,44 +8,53 @@
         "-O0"
     ],
 
-    srcs: [
-        "IncidentHelper.cpp",
-        "ih_util.cpp",
+    local_include_dirs: [
+        "src/",
+        "src/parsers/",
     ],
 
+    srcs: [
+        "src/parsers/*.cpp",
+        "src/TextParserBase.cpp",
+        "src/ih_util.cpp",
+    ],
+
+    generated_headers: ["gen-platform-proto-constants"],
+
     shared_libs: [
         "libbase",
         "liblog",
-        "libprotobuf-cpp-full",
+        "libprotoutil",
         "libutils",
     ],
-
-    static_libs: [
-        "libplatformprotos",
-    ],
 }
 
 cc_binary {
     name: "incident_helper",
     defaults: ["incident_helper_defaults"],
-    srcs: ["main.cpp"],
+    srcs: ["src/main.cpp"],
 }
 
 
 cc_test {
     name: "incident_helper_test",
     defaults: ["incident_helper_defaults"],
+    local_include_dirs: ["src/"],
 
     srcs: [
-        "tests/IncidentHelper_test.cpp",
-        "tests/ih_util_test.cpp",
+        "tests/*.cpp",
     ],
 
     data: [
         "testdata/*",
     ],
 
+    shared_libs: [
+        "libprotobuf-cpp-full",
+    ],
+
     static_libs: [
         "libgmock",
+        "libplatformprotos"
     ],
-}
\ No newline at end of file
+}
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp
deleted file mode 100644
index 7b06d42..0000000
--- a/cmds/incident_helper/IncidentHelper.cpp
+++ /dev/null
@@ -1,327 +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 "IncidentHelper.h"
-#include "ih_util.h"
-
-#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
-#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
-#include "frameworks/base/core/proto/android/os/procrank.pb.h"
-
-#include <android-base/file.h>
-#include <unistd.h>
-#include <string>
-#include <vector>
-
-using namespace android::base;
-using namespace android::os;
-using namespace google::protobuf;
-using namespace std;
-
-
-static const string TAB_DELIMITER = "\t";
-static const string COMMA_DELIMITER = ",";
-
-static inline int toInt(const string& s) {
-    return atoi(s.c_str());
-}
-
-static inline long toLong(const string& s) {
-    return atol(s.c_str());
-}
-
-/**
- * Sets the given protobuf message when the field name matches one of the
- * fields. It is useful to set values to proto from table-like plain texts.
- */
-static bool
-SetTableField(::google::protobuf::Message* message, string field_name, string field_value) {
-    const Descriptor* descriptor = message->GetDescriptor();
-    const Reflection* reflection = message->GetReflection();
-
-    const FieldDescriptor* field = descriptor->FindFieldByName(field_name);
-    switch (field->type()) {
-        case FieldDescriptor::TYPE_STRING:
-            reflection->SetString(message, field, field_value);
-            return true;
-        case FieldDescriptor::TYPE_INT64:
-            reflection->SetInt64(message, field, toLong(field_value));
-            return true;
-        case FieldDescriptor::TYPE_UINT64:
-            reflection->SetUInt64(message, field, toLong(field_value));
-            return true;
-        case FieldDescriptor::TYPE_INT32:
-            reflection->SetInt32(message, field, toInt(field_value));
-            return true;
-        case FieldDescriptor::TYPE_UINT32:
-            reflection->SetUInt32(message, field, toInt(field_value));
-            return true;
-        default:
-            // Add new scalar types
-            return false;
-    }
-}
-
-// ================================================================================
-status_t NoopParser::Parse(const int in, const int out) const
-{
-    string content;
-    if (!ReadFdToString(in, &content)) {
-        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
-        return -1;
-    }
-    if (!WriteStringToFd(content, out)) {
-        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
-        return -1;
-    }
-    return NO_ERROR;
-}
-
-// ================================================================================
-status_t ReverseParser::Parse(const int in, const int out) const
-{
-    string content;
-    if (!ReadFdToString(in, &content)) {
-        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
-        return -1;
-    }
-    // reverse the content
-    reverse(content.begin(), content.end());
-    if (!WriteStringToFd(content, out)) {
-        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
-        return -1;
-    }
-    return NO_ERROR;
-}
-
-// ================================================================================
-status_t KernelWakesParser::Parse(const int in, const int out) const {
-    Reader reader(in);
-    string line;
-    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 (reader.readLine(&line)) {
-        if (line.empty()) continue;
-        // parse head line
-        if (nline++ == 0) {
-            header = parseHeader(line, TAB_DELIMITER);
-            continue;
-        }
-
-        // parse for each record, the line delimiter is \t only!
-        record = parseRecord(line, TAB_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;
-        }
-
-        WakeupSourceProto* source = proto.add_wakeup_sources();
-        for (int i=0; i<(int)record.size(); i++) {
-            if (!SetTableField(source, 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());
-            }
-        }
-    }
-
-    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;
-}
-
-// ================================================================================
-status_t ProcrankParser::Parse(const int in, const int out) const {
-    Reader reader(in);
-    string line;
-    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) {
-            header = parseHeader(line);
-            continue;
-        }
-
-        if (hasPrefix(&line, "ZRAM:")) {
-            proto.mutable_summary()->mutable_zram()->set_raw_text(line);
-            continue;
-        }
-        if (hasPrefix(&line, "RAM:")) {
-            proto.mutable_summary()->mutable_ram()->set_raw_text(line);
-            continue;
-        }
-
-        record = parseRecord(line);
-        if (record.size() != header.size()) {
-            if (record[record.size() - 1] == "TOTAL") { // TOTAL record
-                ProcessProto* total = proto.mutable_summary()->mutable_total();
-                for (int i=1; i<=(int)record.size(); i++) {
-                    SetTableField(total, header[header.size() - i], record[record.size() - i]);
-                }
-            } 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();
-        for (int i=0; i<(int)record.size(); i++) {
-            if (!SetTableField(process, 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());
-            }
-        }
-    }
-
-    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;
-}
-
-// ================================================================================
-status_t PageTypeInfoParser::Parse(const int in, const int out) const {
-    Reader reader(in);
-    string line;
-    bool migrateTypeSession = false;
-    int pageBlockOrder;
-    header_t blockHeader;
-
-    PageTypeInfo pageTypeInfo;
-
-    while (reader.readLine(&line)) {
-        if (line.empty()) {
-            migrateTypeSession = false;
-            blockHeader.clear();
-            continue;
-        }
-
-        if (hasPrefix(&line, "Page block order:")) {
-            pageBlockOrder = toInt(line);
-            pageTypeInfo.set_page_block_order(pageBlockOrder);
-            continue;
-        }
-        if (hasPrefix(&line, "Pages per block:")) {
-            pageTypeInfo.set_pages_per_block(toInt(line));
-            continue;
-        }
-        if (hasPrefix(&line, "Free pages count per migrate type at order")) {
-            migrateTypeSession = true;
-            continue;
-        }
-        if (hasPrefix(&line, "Number of blocks type")) {
-            blockHeader = parseHeader(line);
-            continue;
-        }
-
-        record_t record = parseRecord(line, COMMA_DELIMITER);
-        if (migrateTypeSession && record.size() == 3) {
-            MigrateTypeProto* migrateType = pageTypeInfo.add_migrate_types();
-            // expect part 0 starts with "Node"
-            if (hasPrefix(&record[0], "Node")) {
-                migrateType->set_node(toInt(record[0]));
-            } else goto ERROR;
-            // expect part 1 starts with "zone"
-            if (hasPrefix(&record[1], "zone")) {
-                migrateType->set_zone(record[1]);
-            } else goto ERROR;
-            // expect part 2 starts with "type"
-            if (hasPrefix(&record[2], "type")) {
-                // expect the rest of part 2 has number of (pageBlockOrder + 2) parts
-                // An example looks like:
-                // header line:      type    0   1   2 3 4 5 6 7 8 9 10
-                // record line: Unmovable  426 279 226 1 1 1 0 0 2 2  0
-                // The pageBlockOrder = 10 and it's zero-indexed. so total parts
-                // are 10 + 1(zero-indexed) + 1(the type part) = 12.
-                record_t pageCounts = parseRecord(record[2]);
-                int pageCountsSize = pageBlockOrder + 2;
-                if ((int)pageCounts.size() != pageCountsSize) goto ERROR;
-
-                migrateType->set_type(pageCounts[0]);
-                for (auto i=1; i<pageCountsSize; i++) {
-                    migrateType->add_free_pages_count(toInt(pageCounts[i]));
-                }
-            } else goto ERROR;
-            continue;
-        }
-
-        if (!blockHeader.empty() && record.size() == 2) {
-            BlockProto* block = pageTypeInfo.add_blocks();
-
-            if (hasPrefix(&record[0], "Node")) {
-                block->set_node(toInt(record[0]));
-            } else goto ERROR;
-
-            if (hasPrefix(&record[1], "zone")) {
-                record_t blockCounts = parseRecord(record[1]);
-                block->set_zone(blockCounts[0]);
-                for (size_t i=0; i<blockHeader.size(); i++) {
-                    if (!SetTableField(block, blockHeader[i], blockCounts[i+1])) goto ERROR;
-                }
-            } else goto ERROR;
-
-            continue;
-        }
-
-ERROR:  // print out error for this single line and continue parsing
-        fprintf(stderr, "[%s]Bad line: %s\n", this->name.string(), line.c_str());
-    }
-
-    if (!reader.ok(&line)) {
-        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
-        return -1;
-    }
-
-    if (!pageTypeInfo.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(), pageTypeInfo.ByteSize());
-    return NO_ERROR;
-}
diff --git a/cmds/incident_helper/src/TextParserBase.cpp b/cmds/incident_helper/src/TextParserBase.cpp
new file mode 100644
index 0000000..a8f9968
--- /dev/null
+++ b/cmds/incident_helper/src/TextParserBase.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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 "TextParserBase.h"
+
+#include <android-base/file.h>
+
+using namespace android::base;
+using namespace std;
+
+// ================================================================================
+status_t NoopParser::Parse(const int in, const int out) const
+{
+    string content;
+    if (!ReadFdToString(in, &content)) {
+        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+        return -1;
+    }
+    if (!WriteStringToFd(content, out)) {
+        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+        return -1;
+    }
+    return NO_ERROR;
+}
+
+// ================================================================================
+status_t ReverseParser::Parse(const int in, const int out) const
+{
+    string content;
+    if (!ReadFdToString(in, &content)) {
+        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+        return -1;
+    }
+    // reverse the content
+    reverse(content.begin(), content.end());
+    if (!WriteStringToFd(content, out)) {
+        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+        return -1;
+    }
+    return NO_ERROR;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/IncidentHelper.h b/cmds/incident_helper/src/TextParserBase.h
similarity index 64%
rename from cmds/incident_helper/IncidentHelper.h
rename to cmds/incident_helper/src/TextParserBase.h
index d24d717..c41612d 100644
--- a/cmds/incident_helper/IncidentHelper.h
+++ b/cmds/incident_helper/src/TextParserBase.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef INCIDENT_HELPER_H
-#define INCIDENT_HELPER_H
+#ifndef TEXT_PARSER_BASE_H
+#define TEXT_PARSER_BASE_H
 
 #include <utils/Errors.h>
 #include <utils/String8.h>
@@ -68,37 +68,4 @@
     virtual status_t Parse(const int in, const int out) const;
 };
 
-/**
- * Kernel wakeup sources parser, parses text to protobuf in /d/wakeup_sources
- */
-class KernelWakesParser : public TextParserBase {
-public:
-    KernelWakesParser() : TextParserBase(String8("KernelWakeSources")) {};
-    ~KernelWakesParser() {};
-
-    virtual status_t Parse(const int in, const int out) const;
-};
-
-/**
- * PageTypeInfo parser, parses text to protobuf in /proc/pageinfotype
- */
-class PageTypeInfoParser : public TextParserBase {
-public:
-    PageTypeInfoParser() : TextParserBase(String8("PageTypeInfo")) {};
-    ~PageTypeInfoParser() {};
-
-    virtual status_t Parse(const int in, const int out) const;
-};
-
-/**
- * Procrank parser, parses text produced by command procrank
- */
-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
+#endif // TEXT_PARSER_BASE_H
\ No newline at end of file
diff --git a/cmds/incident_helper/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
similarity index 73%
rename from cmds/incident_helper/ih_util.cpp
rename to cmds/incident_helper/src/ih_util.cpp
index 2ab4b54..c7d1ca2 100644
--- a/cmds/incident_helper/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -87,6 +87,15 @@
     return true;
 }
 
+int toInt(const std::string& s) {
+    return atoi(s.c_str());
+}
+
+long long toLongLong(const std::string& s) {
+    return atoll(s.c_str());
+}
+
+// ==============================================================================
 Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
 
 Reader::Reader(const int fd, const size_t capacity)
@@ -151,3 +160,57 @@
     error->assign(mStatus);
     return mStatus.empty();
 }
+
+// ==============================================================================
+Table::Table(const char* names[], const uint64_t ids[], const int count)
+        :mFieldNames(names),
+         mFieldIds(ids),
+         mFieldCount(count)
+{
+}
+
+Table::~Table()
+{
+}
+
+bool
+Table::insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value)
+{
+    uint64_t found = 0;
+    for (int i=0; i<mFieldCount; i++) {
+        if (strcmp(name.c_str(), mFieldNames[i]) == 0) {
+            found = mFieldIds[i];
+            break;
+        }
+    }
+
+    switch (found & FIELD_TYPE_MASK) {
+        case FIELD_TYPE_DOUBLE:
+        case FIELD_TYPE_FLOAT:
+            // TODO: support parse string to float/double
+            return false;
+        case FIELD_TYPE_STRING:
+        case FIELD_TYPE_BYTES:
+            proto.write(found, value);
+            break;
+        case FIELD_TYPE_INT64:
+        case FIELD_TYPE_SINT64:
+        case FIELD_TYPE_UINT64:
+        case FIELD_TYPE_FIXED64:
+        case FIELD_TYPE_SFIXED64:
+            proto.write(found, toLongLong(value));
+            break;
+        case FIELD_TYPE_BOOL:
+        case FIELD_TYPE_ENUM:
+        case FIELD_TYPE_INT32:
+        case FIELD_TYPE_SINT32:
+        case FIELD_TYPE_UINT32:
+        case FIELD_TYPE_FIXED32:
+        case FIELD_TYPE_SFIXED32:
+            proto.write(found, toInt(value));
+            break;
+        default:
+            return false;
+    }
+    return true;
+}
diff --git a/cmds/incident_helper/ih_util.h b/cmds/incident_helper/src/ih_util.h
similarity index 79%
rename from cmds/incident_helper/ih_util.h
rename to cmds/incident_helper/src/ih_util.h
index ce5baee..86761e9 100644
--- a/cmds/incident_helper/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -21,6 +21,10 @@
 #include <vector>
 #include <sstream>
 
+#include <android/util/ProtoOutputStream.h>
+
+using namespace android::util;
+
 typedef std::vector<std::string> header_t;
 typedef std::vector<std::string> record_t;
 typedef std::string (*trans_func) (const std::string&);
@@ -52,6 +56,12 @@
 bool hasPrefix(std::string* line, const char* key);
 
 /**
+ * Converts string to the desired type
+ */
+int toInt(const std::string& s);
+long long toLongLong(const std::string& s);
+
+/**
  * Reader class reads data from given fd in streaming fashion.
  * The buffer size is controlled by capacity parameter.
  */
@@ -78,4 +88,22 @@
     inline bool EOR() { return mFd == -1 && mBufSize == 0; };
 };
 
+/**
+ * The class contains a mapping between table headers to its field ids.
+ * And allow users to insert the field values to proto based on its header name.
+ */
+class Table
+{
+public:
+    Table(const char* names[], const uint64_t ids[], const int count);
+    ~Table();
+
+    bool insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value);
+
+private:
+    const char** mFieldNames;
+    const uint64_t* mFieldIds;
+    const int mFieldCount;
+};
+
 #endif  // INCIDENT_HELPER_UTIL_H
diff --git a/cmds/incident_helper/main.cpp b/cmds/incident_helper/src/main.cpp
similarity index 96%
rename from cmds/incident_helper/main.cpp
rename to cmds/incident_helper/src/main.cpp
index 52ff777..3da87b9c 100644
--- a/cmds/incident_helper/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,7 +16,9 @@
 
 #define LOG_TAG "incident_helper"
 
-#include "IncidentHelper.h"
+#include "parsers/KernelWakesParser.h"
+#include "parsers/PageTypeInfoParser.h"
+#include "parsers/ProcrankParser.h"
 
 #include <android-base/file.h>
 #include <getopt.h>
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
new file mode 100644
index 0000000..cc4a1e1
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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/kernelwake.proto.h"
+#include "ih_util.h"
+#include "KernelWakesParser.h"
+
+using namespace android::os;
+
+const std::string LINE_DELIMITER = "\t";
+
+status_t
+KernelWakesParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    header_t header;  // the header of /d/wakeup_sources
+    record_t record;  // retain each record
+    int nline = 0;
+
+    ProtoOutputStream proto;
+    Table table(WakeupSourceProto::_FIELD_NAMES, WakeupSourceProto::_FIELD_IDS, WakeupSourceProto::_FIELD_COUNT);
+
+    // parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+        // parse head line
+        if (nline++ == 0) {
+            header = parseHeader(line, LINE_DELIMITER);
+            continue;
+        }
+
+        // parse for each record, the line delimiter is \t only!
+        record = parseRecord(line, 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;
+        }
+
+        long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES);
+        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/KernelWakesParser.h b/cmds/incident_helper/src/parsers/KernelWakesParser.h
new file mode 100644
index 0000000..aabab7c
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.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 KERNEL_WAKES_PARSER_H
+#define KERNEL_WAKES_PARSER_H
+
+#include "TextParserBase.h"
+
+/**
+ * Kernel wakeup sources parser, parses text to protobuf in /d/wakeup_sources
+ */
+class KernelWakesParser : public TextParserBase {
+public:
+    KernelWakesParser() : TextParserBase(String8("KernelWakeSources")) {};
+    ~KernelWakesParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // KERNEL_WAKES_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
new file mode 100644
index 0000000..6047bd1
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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/pagetypeinfo.proto.h"
+#include "ih_util.h"
+#include "PageTypeInfoParser.h"
+
+using namespace android::os;
+
+const std::string LINE_DELIMITER = ",";
+
+status_t
+PageTypeInfoParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    bool migrateTypeSession = false;
+    int pageBlockOrder;
+    header_t blockHeader;
+
+    ProtoOutputStream proto;
+    Table table(BlockProto::_FIELD_NAMES, BlockProto::_FIELD_IDS, BlockProto::_FIELD_COUNT);
+
+    while (reader.readLine(&line)) {
+        if (line.empty()) {
+            migrateTypeSession = false;
+            blockHeader.clear();
+            continue;
+        }
+
+        if (hasPrefix(&line, "Page block order:")) {
+            pageBlockOrder = toInt(line);
+            proto.write(PageTypeInfo::PAGE_BLOCK_ORDER, pageBlockOrder);
+            continue;
+        }
+        if (hasPrefix(&line, "Pages per block:")) {
+            proto.write(PageTypeInfo::PAGES_PER_BLOCK, toInt(line));
+            continue;
+        }
+        if (hasPrefix(&line, "Free pages count per migrate type at order")) {
+            migrateTypeSession = true;
+            continue;
+        }
+        if (hasPrefix(&line, "Number of blocks type")) {
+            blockHeader = parseHeader(line);
+            continue;
+        }
+
+        record_t record = parseRecord(line, LINE_DELIMITER);
+        if (migrateTypeSession && record.size() == 3) {
+            long long token = proto.start(PageTypeInfo::MIGRATE_TYPES);
+            // expect part 0 starts with "Node"
+            if (hasPrefix(&record[0], "Node")) {
+                proto.write(MigrateTypeProto::NODE, toInt(record[0]));
+            } else return BAD_VALUE;
+            // expect part 1 starts with "zone"
+            if (hasPrefix(&record[1], "zone")) {
+                proto.write(MigrateTypeProto::ZONE, record[1]);
+            } else return BAD_VALUE;
+            // expect part 2 starts with "type"
+            if (hasPrefix(&record[2], "type")) {
+                // expect the rest of part 2 has number of (pageBlockOrder + 2) parts
+                // An example looks like:
+                // header line:      type    0   1   2 3 4 5 6 7 8 9 10
+                // record line: Unmovable  426 279 226 1 1 1 0 0 2 2  0
+                // The pageBlockOrder = 10 and it's zero-indexed. so total parts
+                // are 10 + 1(zero-indexed) + 1(the type part) = 12.
+                record_t pageCounts = parseRecord(record[2]);
+                int pageCountsSize = pageBlockOrder + 2;
+                if ((int)pageCounts.size() != pageCountsSize) return BAD_VALUE;
+
+                proto.write(MigrateTypeProto::TYPE, pageCounts[0]);
+                for (auto i=1; i<pageCountsSize; i++) {
+                    proto.write(MigrateTypeProto::FREE_PAGES_COUNT, toInt(pageCounts[i]));
+                }
+            } else return BAD_VALUE;
+
+            proto.end(token);
+        } else if (!blockHeader.empty() && record.size() == 2) {
+            long long token = proto.start(PageTypeInfo::BLOCKS);
+            if (hasPrefix(&record[0], "Node")) {
+                proto.write(BlockProto::NODE, toInt(record[0]));
+            } else return BAD_VALUE;
+
+            if (hasPrefix(&record[1], "zone")) {
+                record_t blockCounts = parseRecord(record[1]);
+                proto.write(BlockProto::ZONE, blockCounts[0]);
+
+                for (size_t i=0; i<blockHeader.size(); i++) {
+                    if (!table.insertField(proto, blockHeader[i], blockCounts[i+1])) {
+                        return BAD_VALUE;
+                    }
+                }
+            } else return BAD_VALUE;
+            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;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.h b/cmds/incident_helper/src/parsers/PageTypeInfoParser.h
new file mode 100644
index 0000000..fb84d91
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.h
@@ -0,0 +1,35 @@
+/*
+ * 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 PAGE_TYPE_INFO_PARSER_H
+#define PAGE_TYPE_INFO_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * PageTypeInfo parser, parses text to protobuf in /proc/pageinfotype
+ */
+class PageTypeInfoParser : public TextParserBase {
+public:
+    PageTypeInfoParser() : TextParserBase(String8("PageTypeInfo")) {};
+    ~PageTypeInfoParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // PAGE_TYPE_INFO_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.cpp b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
new file mode 100644
index 0000000..93f970f
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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/procrank.proto.h"
+#include "ih_util.h"
+#include "ProcrankParser.h"
+
+using namespace android::os;
+
+status_t
+ProcrankParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    header_t header;  // the header of /d/wakeup_sources
+    record_t record;  // retain each record
+    int nline = 0;
+
+    ProtoOutputStream proto;
+    Table table(ProcessProto::_FIELD_NAMES, ProcessProto::_FIELD_IDS, ProcessProto::_FIELD_COUNT);
+    string zram, ram, total;
+
+    // parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+
+        // parse head line
+        if (nline++ == 0) {
+            header = parseHeader(line);
+            continue;
+        }
+
+        if (hasPrefix(&line, "ZRAM:")) {
+            zram = line;
+            continue;
+        }
+        if (hasPrefix(&line, "RAM:")) {
+            ram = line;
+            continue;
+        }
+
+        record = parseRecord(line);
+        if (record.size() != header.size()) {
+            if (record[record.size() - 1] == "TOTAL") { // TOTAL record
+                total = line;
+            } else {
+                fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
+                    line.c_str());
+            }
+            continue;
+        }
+
+        long long token = proto.start(Procrank::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);
+    }
+
+    // add summary
+    long long token = proto.start(Procrank::SUMMARY);
+    if (!total.empty()) {
+        record = parseRecord(total);
+        long long token = proto.start(SummaryProto::TOTAL);
+        for (int i=(int)record.size(); i>0; i--) {
+            table.insertField(proto, header[header.size() - i].c_str(), record[record.size() - i].c_str());
+        }
+        proto.end(token);
+    }
+    if (!zram.empty()) {
+        long long token = proto.start(SummaryProto::ZRAM);
+        proto.write(ZramProto::RAW_TEXT, zram);
+        proto.end(token);
+    }
+    if (!ram.empty()) {
+        long long token = proto.start(SummaryProto::RAM);
+        proto.write(RamProto::RAW_TEXT, ram);
+        proto.end(token);
+    }
+    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/ProcrankParser.h b/cmds/incident_helper/src/parsers/ProcrankParser.h
new file mode 100644
index 0000000..5d0ee48
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.h
@@ -0,0 +1,35 @@
+/*
+ * 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 PROCRANK_PARSER_H
+#define PROCRANK_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Procrank parser, parses text produced by command procrank
+ */
+class ProcrankParser : public TextParserBase {
+public:
+    ProcrankParser() : TextParserBase(String8("ProcrankParser")) {};
+    ~ProcrankParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // PROCRANK_PARSER_H
diff --git a/cmds/incident_helper/testdata/kernel_wakeups_short.txt b/cmds/incident_helper/testdata/kernel_wakeups_short.txt
new file mode 100644
index 0000000..a51926e
--- /dev/null
+++ b/cmds/incident_helper/testdata/kernel_wakeups_short.txt
@@ -0,0 +1,3 @@
+name	active_count	last_change
+ab	8	123456123456
+df	143	0
diff --git a/cmds/incident_helper/tests/IncidentHelper_test.cpp b/cmds/incident_helper/tests/IncidentHelper_test.cpp
deleted file mode 100644
index c44a163..0000000
--- a/cmds/incident_helper/tests/IncidentHelper_test.cpp
+++ /dev/null
@@ -1,255 +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.
- */
-
-#include "IncidentHelper.h"
-
-#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
-#include "frameworks/base/core/proto/android/os/pagetypeinfo.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>
-
-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 IncidentHelperTest : public Test {
-public:
-    virtual void SetUp() override {
-        ASSERT_TRUE(tf.fd != -1);
-    }
-
-    string getSerializedString(::google::protobuf::Message& message) {
-        string expectedStr;
-        message.SerializeToFileDescriptor(tf.fd);
-        ReadFileToString(tf.path, &expectedStr);
-        return expectedStr;
-    }
-
-protected:
-    TemporaryFile tf;
-
-    const string kTestPath = GetExecutableDirectory();
-    const string kTestDataPath = kTestPath + "/testdata/";
-};
-
-TEST_F(IncidentHelperTest, ReverseParser) {
-    ReverseParser parser;
-    TemporaryFile tf;
-
-    ASSERT_TRUE(tf.fd != -1);
-    ASSERT_TRUE(WriteStringToFile("TestData", tf.path, false));
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(tf.fd, STDOUT_FILENO));
-    EXPECT_THAT(GetCapturedStdout(), StrEq("ataDtseT"));
-}
-
-TEST_F(IncidentHelperTest, KernelWakesParser) {
-    const string testFile = kTestDataPath + "kernel_wakeups.txt";
-    KernelWakesParser parser;
-    KernelWakeSources expected;
-
-    WakeupSourceProto* record1 = expected.add_wakeup_sources();
-    record1->set_name("ipc000000ab_ATFWD-daemon");
-    record1->set_active_count(8);
-    record1->set_event_count(8);
-    record1->set_wakeup_count(0);
-    record1->set_expire_count(0);
-    record1->set_active_since(0l);
-    record1->set_total_time(0l);
-    record1->set_max_time(0l);
-    record1->set_last_change(131348l);
-    record1->set_prevent_suspend_time(0l);
-
-    WakeupSourceProto* record2 = expected.add_wakeup_sources();
-    record2->set_name("ipc000000aa_ATFWD-daemon");
-    record2->set_active_count(143);
-    record2->set_event_count(143);
-    record2->set_wakeup_count(0);
-    record2->set_expire_count(0);
-    record2->set_active_since(0l);
-    record2->set_total_time(123l);
-    record2->set_max_time(3l);
-    record2->set_last_change(2067286206l);
-    record2->set_prevent_suspend_time(0l);
-
-    int fd = open(testFile.c_str(), O_RDONLY);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
-
-TEST_F(IncidentHelperTest, ProcrankParser) {
-    const 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);
-    total->set_cmdline("TOTAL");
-
-    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);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
-
-TEST_F(IncidentHelperTest, ProcrankParserShortHeader) {
-    const string testFile = kTestDataPath + "procrank_short.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_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_cmdline("/vendor/bin/qseecomd");
-
-    ProcessProto* total = expected.mutable_summary()->mutable_total();
-    total->set_pss(1201993);
-    total->set_uss(935300);
-    total->set_cmdline("TOTAL");
-
-    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);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
-
-TEST_F(IncidentHelperTest, PageTypeInfoParser) {
-    const string testFile = kTestDataPath + "pagetypeinfo.txt";
-    PageTypeInfoParser parser;
-    PageTypeInfo expected;
-
-    expected.set_page_block_order(10);
-    expected.set_pages_per_block(1024);
-
-    MigrateTypeProto* mt1 = expected.add_migrate_types();
-    mt1->set_node(0);
-    mt1->set_zone("DMA");
-    mt1->set_type("Unmovable");
-    int arr1[] = { 426, 279, 226, 1, 1, 1, 0, 0, 2, 2, 0};
-    for (auto i=0; i<11; i++) {
-        mt1->add_free_pages_count(arr1[i]);
-    }
-
-    MigrateTypeProto* mt2 = expected.add_migrate_types();
-    mt2->set_node(0);
-    mt2->set_zone("Normal");
-    mt2->set_type("Reclaimable");
-    int arr2[] = { 953, 773, 437, 154, 92, 26, 15, 14, 12, 7, 0};
-    for (auto i=0; i<11; i++) {
-        mt2->add_free_pages_count(arr2[i]);
-    }
-
-    BlockProto* block1 = expected.add_blocks();
-    block1->set_node(0);
-    block1->set_zone("DMA");
-    block1->set_unmovable(74);
-    block1->set_reclaimable(9);
-    block1->set_movable(337);
-    block1->set_cma(41);
-    block1->set_reserve(1);
-    block1->set_isolate(0);
-
-
-    BlockProto* block2 = expected.add_blocks();
-    block2->set_node(0);
-    block2->set_zone("Normal");
-    block2->set_unmovable(70);
-    block2->set_reclaimable(12);
-    block2->set_movable(423);
-    block2->set_cma(0);
-    block2->set_reserve(1);
-    block2->set_isolate(0);
-
-    int fd = open(testFile.c_str(), O_RDONLY);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/KernelWakesParser_test.cpp b/cmds/incident_helper/tests/KernelWakesParser_test.cpp
new file mode 100644
index 0000000..a8fa6208
--- /dev/null
+++ b/cmds/incident_helper/tests/KernelWakesParser_test.cpp
@@ -0,0 +1,119 @@
+/*
+ * 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 "KernelWakesParser.h"
+
+#include "frameworks/base/core/proto/android/os/kernelwake.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>
+
+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 KernelWakesParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+    string getSerializedString(::google::protobuf::Message& message) {
+        string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(KernelWakesParserTest, Short) {
+    const string testFile = kTestDataPath + "kernel_wakeups_short.txt";
+    KernelWakesParser parser;
+    KernelWakeSources expected;
+
+    WakeupSourceProto* record1 = expected.add_wakeup_sources();
+    record1->set_name("ab");
+    record1->set_active_count(8);
+    record1->set_last_change(123456123456LL);
+
+    WakeupSourceProto* record2 = expected.add_wakeup_sources();
+    record2->set_name("df");
+    record2->set_active_count(143);
+    record2->set_last_change(0LL);
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
+
+TEST_F(KernelWakesParserTest, Normal) {
+    const string testFile = kTestDataPath + "kernel_wakeups.txt";
+    KernelWakesParser parser;
+    KernelWakeSources expected;
+
+    WakeupSourceProto* record1 = expected.add_wakeup_sources();
+    record1->set_name("ipc000000ab_ATFWD-daemon");
+    record1->set_active_count(8);
+    record1->set_event_count(8);
+    record1->set_wakeup_count(0);
+    record1->set_expire_count(0);
+    record1->set_active_since(0l);
+    record1->set_total_time(0l);
+    record1->set_max_time(0l);
+    record1->set_last_change(131348LL);
+    record1->set_prevent_suspend_time(0LL);
+
+    WakeupSourceProto* record2 = expected.add_wakeup_sources();
+    record2->set_name("ipc000000aa_ATFWD-daemon");
+    record2->set_active_count(143);
+    record2->set_event_count(143);
+    record2->set_wakeup_count(0);
+    record2->set_expire_count(0);
+    record2->set_active_since(0l);
+    record2->set_total_time(123l);
+    record2->set_max_time(3l);
+    record2->set_last_change(2067286206LL);
+    record2->set_prevent_suspend_time(0LL);
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    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/PageTypeInfoParser_test.cpp b/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp
new file mode 100644
index 0000000..de64e70
--- /dev/null
+++ b/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp
@@ -0,0 +1,113 @@
+/*
+ * 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 "PageTypeInfoParser.h"
+
+#include "frameworks/base/core/proto/android/os/pagetypeinfo.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>
+
+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 PageTypeInfoParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+    string getSerializedString(::google::protobuf::Message& message) {
+        string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(PageTypeInfoParserTest, Success) {
+    const string testFile = kTestDataPath + "pagetypeinfo.txt";
+    PageTypeInfoParser parser;
+    PageTypeInfo expected;
+
+    expected.set_page_block_order(10);
+    expected.set_pages_per_block(1024);
+
+    MigrateTypeProto* mt1 = expected.add_migrate_types();
+    mt1->set_node(0);
+    mt1->set_zone("DMA");
+    mt1->set_type("Unmovable");
+    int arr1[] = { 426, 279, 226, 1, 1, 1, 0, 0, 2, 2, 0};
+    for (auto i=0; i<11; i++) {
+        mt1->add_free_pages_count(arr1[i]);
+    }
+
+    MigrateTypeProto* mt2 = expected.add_migrate_types();
+    mt2->set_node(0);
+    mt2->set_zone("Normal");
+    mt2->set_type("Reclaimable");
+    int arr2[] = { 953, 773, 437, 154, 92, 26, 15, 14, 12, 7, 0};
+    for (auto i=0; i<11; i++) {
+        mt2->add_free_pages_count(arr2[i]);
+    }
+
+    BlockProto* block1 = expected.add_blocks();
+    block1->set_node(0);
+    block1->set_zone("DMA");
+    block1->set_unmovable(74);
+    block1->set_reclaimable(9);
+    block1->set_movable(337);
+    block1->set_cma(41);
+    block1->set_reserve(1);
+    block1->set_isolate(0);
+
+
+    BlockProto* block2 = expected.add_blocks();
+    block2->set_node(0);
+    block2->set_zone("Normal");
+    block2->set_unmovable(70);
+    block2->set_reclaimable(12);
+    block2->set_movable(423);
+    block2->set_cma(0);
+    block2->set_reserve(1);
+    block2->set_isolate(0);
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/ProcrankParser_test.cpp b/cmds/incident_helper/tests/ProcrankParser_test.cpp
new file mode 100644
index 0000000..e86647a
--- /dev/null
+++ b/cmds/incident_helper/tests/ProcrankParser_test.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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 "ProcrankParser.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>
+
+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 ProcrankParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+    string getSerializedString(::google::protobuf::Message& message) {
+        string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(ProcrankParserTest, HasSwapInfo) {
+    const 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);
+    total->set_cmdline("TOTAL");
+
+    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);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
+
+TEST_F(ProcrankParserTest, NoSwapInfo) {
+    const string testFile = kTestDataPath + "procrank_short.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_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_cmdline("/vendor/bin/qseecomd");
+
+    ProcessProto* total = expected.mutable_summary()->mutable_total();
+    total->set_pss(1201993);
+    total->set_uss(935300);
+    total->set_cmdline("TOTAL");
+
+    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);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}