Implement Cpu Freq Section

When poll from sysfs, revents return POLLERR by default, handles
this edge case in this cl.

Bug: 68774444
Test: unit tested + on device tests
Change-Id: I23540299c026d3e7676497f56690e9f8646a47bd
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index 5ebe9bd..5c9a468 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "incident_helper"
 
+#include "parsers/CpuFreqParser.h"
 #include "parsers/CpuInfoParser.h"
 #include "parsers/KernelWakesParser.h"
 #include "parsers/PageTypeInfoParser.h"
@@ -57,6 +58,8 @@
             return new KernelWakesParser();
         case 2003:
             return new CpuInfoParser();
+        case 2004:
+            return new CpuFreqParser();
         default:
             return NULL;
     }
diff --git a/cmds/incident_helper/src/parsers/CpuFreqParser.cpp b/cmds/incident_helper/src/parsers/CpuFreqParser.cpp
new file mode 100644
index 0000000..02f1ce7
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/CpuFreqParser.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 <unistd.h>
+
+#include "frameworks/base/core/proto/android/os/cpufreq.proto.h"
+#include "ih_util.h"
+#include "CpuFreqParser.h"
+
+using namespace android::os;
+
+status_t
+CpuFreqParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+
+    // parse header
+    reader.readLine(&line);
+    header_t header = parseHeader(line, TAB_DELIMITER);
+    if (header.size() < 1) {
+        fprintf(stderr, "Bad header: %s\n", line.c_str());
+        return BAD_VALUE;
+    }
+    const int numCpus = (int)header.size() - 1;
+    vector<pair<int, long long>> cpucores[numCpus];
+
+    // parse freq and time
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+
+        record_t record = parseRecord(line, TAB_DELIMITER);
+        if (record.size() != header.size()) {
+            fprintf(stderr, "Bad line: %s\n", line.c_str());
+            continue;
+        }
+
+        int freq = toInt(record[0]);
+        for (int i=0; i<numCpus; i++) {
+            if (strcmp(record[i+1].c_str(), "N/A") == 0) {
+                continue;
+            }
+            cpucores[i].push_back(make_pair(freq, toLongLong(record[i+1])));
+        }
+    }
+
+    ProtoOutputStream proto;
+
+    long jiffyHz = sysconf(_SC_CLK_TCK);
+    proto.write(CpuFreq::JIFFY_HZ, (int)jiffyHz);
+
+    for (int i=0; i<numCpus; i++) {
+        long long token = proto.start(CpuFreq::CPU_FREQS);
+        proto.write(CpuFreqStats::CPU_NAME, header[i+1]);
+        for (vector<pair<int, long long>>::iterator it = cpucores[i].begin(); it != cpucores[i].end(); it++) {
+            long long stateToken = proto.start(CpuFreqStats::TIMES);
+            proto.write(CpuFreqStats::TimeInState::STATE_KHZ, it->first);
+            proto.write(CpuFreqStats::TimeInState::TIME_JIFFY, it->second);
+            proto.end(stateToken);
+        }
+        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/CpuFreqParser.h b/cmds/incident_helper/src/parsers/CpuFreqParser.h
new file mode 100644
index 0000000..470d568
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/CpuFreqParser.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 CPU_FREQ_PARSER_H
+#define CPU_FREQ_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Cpu frequency parser, parses text in /sys/devices/system/cpu/cpufreq/all_time_in_state
+ */
+class CpuFreqParser : public TextParserBase {
+public:
+    CpuFreqParser() : TextParserBase(String8("CpuFreqParser")) {};
+    ~CpuFreqParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // CPU_FREQ_PARSER_H
diff --git a/cmds/incident_helper/testdata/cpufreq.txt b/cmds/incident_helper/testdata/cpufreq.txt
new file mode 100644
index 0000000..6472839
--- /dev/null
+++ b/cmds/incident_helper/testdata/cpufreq.txt
@@ -0,0 +1,6 @@
+freq		cpu0		cpu1		cpu2		cpu3		
+307200		23860761		23860761		23890935		23890935		
+384000		83124		83124		29383		29383		
+748800		N/A		N/A		10547		10547		
+768000		22652		22652		N/A		N/A		
+825600		N/A		N/A		13173		13173			
diff --git a/cmds/incident_helper/tests/CpuFreqParser_test.cpp b/cmds/incident_helper/tests/CpuFreqParser_test.cpp
new file mode 100644
index 0000000..1c2f9e5
--- /dev/null
+++ b/cmds/incident_helper/tests/CpuFreqParser_test.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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 "CpuFreqParser.h"
+
+#include "frameworks/base/core/proto/android/os/cpufreq.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 CpuFreqParserTest : 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(CpuFreqParserTest, Success) {
+    const string testFile = kTestDataPath + "cpufreq.txt";
+    CpuFreqParser parser;
+    CpuFreq expected;
+
+    long jiffyHz = sysconf(_SC_CLK_TCK);
+    expected.set_jiffy_hz(jiffyHz);
+
+    CpuFreqStats::TimeInState* state;
+
+    CpuFreqStats* cpu0 = expected.add_cpu_freqs();
+    cpu0->set_cpu_name("cpu0");
+    state = cpu0->add_times();
+    state->set_state_khz(307200);
+    state->set_time_jiffy(23860761);
+    state = cpu0->add_times();
+    state->set_state_khz(384000);
+    state->set_time_jiffy(83124);
+    state = cpu0->add_times();
+    state->set_state_khz(768000);
+    state->set_time_jiffy(22652);
+
+    CpuFreqStats* cpu1 = expected.add_cpu_freqs();
+    cpu1->set_cpu_name("cpu1");
+    state = cpu1->add_times();
+    state->set_state_khz(307200);
+    state->set_time_jiffy(23860761);
+    state = cpu1->add_times();
+    state->set_state_khz(384000);
+    state->set_time_jiffy(83124);
+    state = cpu1->add_times();
+    state->set_state_khz(768000);
+    state->set_time_jiffy(22652);
+
+    CpuFreqStats* cpu2 = expected.add_cpu_freqs();
+    cpu2->set_cpu_name("cpu2");
+    state = cpu2->add_times();
+    state->set_state_khz(307200);
+    state->set_time_jiffy(23890935);
+    state = cpu2->add_times();
+    state->set_state_khz(384000);
+    state->set_time_jiffy(29383);
+    state = cpu2->add_times();
+    state->set_state_khz(748800);
+    state->set_time_jiffy(10547);
+    state = cpu2->add_times();
+    state->set_state_khz(825600);
+    state->set_time_jiffy(13173);
+
+    CpuFreqStats* cpu3 = expected.add_cpu_freqs();
+    cpu3->set_cpu_name("cpu3");
+    state = cpu3->add_times();
+    state->set_state_khz(307200);
+    state->set_time_jiffy(23890935);
+    state = cpu3->add_times();
+    state->set_state_khz(384000);
+    state->set_time_jiffy(29383);
+    state = cpu3->add_times();
+    state->set_state_khz(748800);
+    state->set_time_jiffy(10547);
+    state = cpu3->add_times();
+    state->set_state_khz(825600);
+    state->set_time_jiffy(13173);
+
+    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/CpuInfoParser_test.cpp b/cmds/incident_helper/tests/CpuInfoParser_test.cpp
index 57ad15c..bbc14bc 100644
--- a/cmds/incident_helper/tests/CpuInfoParser_test.cpp
+++ b/cmds/incident_helper/tests/CpuInfoParser_test.cpp
@@ -56,7 +56,7 @@
     const string kTestDataPath = kTestPath + "/testdata/";
 };
 
-TEST_F(CpuInfoParserTest, HasSwapInfo) {
+TEST_F(CpuInfoParserTest, Success) {
     const string testFile = kTestDataPath + "cpuinfo.txt";
     CpuInfoParser parser;
     CpuInfo expected;
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index b7633a4..30dd339 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -26,6 +26,7 @@
 #include <unistd.h>
 #include <wait.h>
 
+const bool DEBUG = false;
 const ssize_t BUFFER_SIZE = 16 * 1024; // 16 KB
 const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max
 
@@ -71,9 +72,11 @@
             mTimedOut = true;
             break;
         } else if (count < 0) {
+            if (DEBUG) ALOGD("poll failed: %s", strerror(errno));
             return -errno;
         } else {
             if ((pfds.revents & POLLERR) != 0) {
+                if (DEBUG) ALOGD("return event has error %s", strerror(errno));
                 return errno != 0 ? -errno : UNKNOWN_ERROR;
             } else {
                 ssize_t amt = ::read(fd, mBuffer.writeBuffer(), mBuffer.currentToWrite());
@@ -81,6 +84,7 @@
                     if (errno == EAGAIN || errno == EWOULDBLOCK) {
                         continue;
                     } else {
+                        if (DEBUG) ALOGD("Fail to read %d: %s", fd, strerror(errno));
                         return -errno;
                     }
                 } else if (amt == 0) {
@@ -95,7 +99,7 @@
 }
 
 status_t
-FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs)
+FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs, const bool isSysfs)
 {
     struct pollfd pfds[] = {
         { .fd = fd,     .events = POLLIN  },
@@ -135,12 +139,18 @@
             mTimedOut = true;
             break;
         } else if (count < 0) {
+            if (DEBUG) ALOGD("Fail to poll: %s", strerror(errno));
             return -errno;
         }
 
         // make sure no errors occur on any fds
         for (int i = 0; i < 3; ++i) {
             if ((pfds[i].revents & POLLERR) != 0) {
+                if (i == 0 && isSysfs) {
+                    if (DEBUG) ALOGD("fd %d is sysfs, ignore its POLLERR return value", fd);
+                    continue;
+                }
+                if (DEBUG) ALOGD("fd[%d]=%d returns error events: %s", i, fd, strerror(errno));
                 return errno != 0 ? -errno : UNKNOWN_ERROR;
             }
         }
@@ -155,6 +165,7 @@
             }
             if (amt < 0) {
                 if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
+                    if (DEBUG) ALOGD("Fail to read fd %d: %s", fd, strerror(errno));
                     return -errno;
                 } // otherwise just continue
             } else if (amt == 0) {  // reach EOF so don't have to poll pfds[0].
@@ -176,6 +187,7 @@
             }
             if (amt < 0) {
                 if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
+                    if (DEBUG) ALOGD("Fail to write toFd %d: %s", toFd, strerror(errno));
                     return -errno;
                 } // otherwise just continue
             } else {
@@ -202,6 +214,7 @@
         ssize_t amt = ::read(fromFd, mBuffer.writeBuffer(), mBuffer.currentToWrite());
         if (amt < 0) {
             if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
+                if (DEBUG) ALOGD("Fail to read fromFd %d: %s", fromFd, strerror(errno));
                 return -errno;
             } // otherwise just continue
         } else if (amt == 0) {
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index 8857ae7..48dc855 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -47,8 +47,10 @@
      * and stores the processed data from 'fromFd' in memory for later usage.
      * This function behaves in a streaming fashion in order to save memory usage.
      * Returns NO_ERROR if there were no errors or if we timed out.
+     *
+     * Poll will return POLLERR if fd is from sysfs, handle this edge case.
      */
-    status_t readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs);
+    status_t readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs, const bool isSysfs=false);
 
     /**
      * Whether we timed out.
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 892bcca..c08b9ea 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -232,6 +232,7 @@
      mFilename(filename)
 {
     name = filename;
+    mIsSysfs = strncmp(filename, "/sys/", 5) == 0;
 }
 
 FileSection::~FileSection() {}
@@ -264,7 +265,7 @@
 
     // parent process
     status_t readStatus = buffer.readProcessedDataInStream(fd, p2cPipe.writeFd(), c2pPipe.readFd(),
-            this->timeoutMs);
+            this->timeoutMs, mIsSysfs);
     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",
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index 0a1e03e..64558a6 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -69,6 +69,7 @@
 
 private:
     const char* mFilename;
+    bool mIsSysfs; // sysfs files are pollable but return POLLERR by default, handle it separately
 };
 
 /**