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/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