This cl does the following things:
0) Implements a skeleton of incident_helper
1) Implements FileSection class which calls incident_helper to parse
file content to protobuf
2) Adds Kernel Wake Sources to incident.proto and makes it parsed by
FileSection
3) Adds basic gtests to test FdBuffer, io_utils, FileSection
implementation
Bug: 62923266
Bug: 62926061
Test: manual - push incidentd, incident_helper and incident to my device
and verify kernel wakeup sources file is able to be parsed.
Change-Id: I2aa6b6158d962ce70e6fa6c8a9c42213a45ff41c
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
new file mode 100644
index 0000000..3ea823c
--- /dev/null
+++ b/cmds/incident_helper/Android.bp
@@ -0,0 +1,50 @@
+cc_defaults {
+ name: "incident_helper_defaults",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-g",
+ "-O0"
+ ],
+
+ srcs: [
+ "IncidentHelper.cpp",
+ "strutil.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libprotobuf-cpp-full",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libplatformprotos",
+ ],
+}
+
+cc_binary {
+ name: "incident_helper",
+ defaults: ["incident_helper_defaults"],
+ srcs: ["main.cpp"],
+}
+
+
+cc_test {
+ name: "incident_helper_test",
+ defaults: ["incident_helper_defaults"],
+
+ srcs: [
+ "tests/IncidentHelper_test.cpp",
+ ],
+
+ data: [
+ "testdata/*",
+ ],
+
+ static_libs: [
+ "libgmock",
+ ],
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp
new file mode 100644
index 0000000..89d8947
--- /dev/null
+++ b/cmds/incident_helper/IncidentHelper.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 "strutil.h"
+
+#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+
+#include <algorithm>
+#include <android-base/file.h>
+#include <unistd.h>
+#include <sstream>
+#include <string>
+#include <vector>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+
+// ================================================================================
+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;
+}
+
+// ================================================================================
+// This list must be in order and sync with kernelwake.proto
+const char* kernel_wake_headers[] = {
+ "name", // id: 1
+ "active_count", // id: 2
+ "event_count", // id: 3
+ "wakeup_count", // id: 4
+ "expire_count", // id: 5
+ "active_since", // id: 6
+ "total_time", // id: 7
+ "max_time", // id: 8
+ "last_change", // id: 9
+ "prevent_suspend_time", // id: 10
+};
+
+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);
+ string line;
+ vector<string> header; // the header of /d/wakeup_sources
+ vector<string> record; // retain each record
+ int nline = 0;
+
+ KernelWakeSources proto;
+
+ // parse line by line
+ while (getline(iss, line)) {
+ // 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;
+ }
+
+ // parse for each record, the line delimiter is \t only!
+ 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;
+ }
+
+ WakeupSourceProto* source = proto.add_wakeup_sources();
+ source->set_name(record.at(0).c_str());
+ // below are int32
+ source->set_active_count(atoi(record.at(1).c_str()));
+ source->set_event_count(atoi(record.at(2).c_str()));
+ source->set_wakeup_count(atoi(record.at(3).c_str()));
+ source->set_expire_count(atoi(record.at(4).c_str()));
+ // below are int64
+ source->set_active_since(atol(record.at(5).c_str()));
+ source->set_total_time(atol(record.at(6).c_str()));
+ 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 (!proto.SerializeToFileDescriptor(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+ close(out);
+
+ return NO_ERROR;
+}
diff --git a/cmds/incident_helper/IncidentHelper.h b/cmds/incident_helper/IncidentHelper.h
new file mode 100644
index 0000000..40792a8
--- /dev/null
+++ b/cmds/incident_helper/IncidentHelper.h
@@ -0,0 +1,73 @@
+/*
+ * 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_H
+#define INCIDENT_HELPER_H
+
+#include <utils/Errors.h>
+#include <utils/String8.h>
+
+using namespace android;
+
+/**
+ * Base class for text parser
+ */
+class TextParserBase {
+public:
+ String8 name;
+
+ TextParserBase(String8 name) : name(name) {};
+ virtual ~TextParserBase() {};
+
+ virtual status_t Parse(const int in, const int out) const = 0;
+};
+
+/**
+ * This parser is used for testing only, results in timeout.
+ */
+class TimeoutParser : public TextParserBase {
+public:
+ TimeoutParser() : TextParserBase(String8("TimeoutParser")) {};
+ ~TimeoutParser() {};
+
+ virtual status_t Parse(const int /** in */, const int /** out */) const { while (true); };
+};
+
+/**
+ * This parser is used for testing only, results in reversed input text.
+ */
+class ReverseParser : public TextParserBase {
+public:
+ ReverseParser() : TextParserBase(String8("ReverseParser")) {};
+ ~ReverseParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+/**
+ * Kernel wakeup sources parser, parses text to protobuf in /d/wakeup_sources
+ */
+extern const char* kernel_wake_headers[];
+
+class KernelWakesParser : public TextParserBase {
+public:
+ KernelWakesParser() : TextParserBase(String8("KernelWakeSources")) {};
+ ~KernelWakesParser() {};
+
+ 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
new file mode 100644
index 0000000..967f1e3
--- /dev/null
+++ b/cmds/incident_helper/README.md
@@ -0,0 +1,17 @@
+# incident_helper
+
+## How to build, deploy, unit test
+
+For the first time, build the test and create an empty directly on device:
+
+```
+root$ make -j incident_helper_test && adb shell mkdir /data/nativetest64/incident_helper_test
+```
+
+Run the test on a device
+
+```
+root$ mmm -j frameworks/base/cmds/incident_helper && \
+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
+```
diff --git a/cmds/incident_helper/main.cpp b/cmds/incident_helper/main.cpp
new file mode 100644
index 0000000..1e25c85
--- /dev/null
+++ b/cmds/incident_helper/main.cpp
@@ -0,0 +1,106 @@
+/*
+ * 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 <android-base/file.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+using namespace android::base;
+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, "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");
+}
+
+//=============================================================================
+static TextParserBase* selectParser(int section) {
+ switch (section) {
+ // IDs smaller than or equal to 0 are reserved for testing
+ case -1:
+ return new TimeoutParser();
+ case 0:
+ return new ReverseParser();
+/* ========================================================================= */
+ // IDs larger than 0 are reserved in incident.proto
+ case 2002:
+ return new KernelWakesParser();
+ default:
+ return NULL;
+ }
+}
+
+//=============================================================================
+int main(int argc, char** argv) {
+ fprintf(stderr, "Start incident_helper...\n");
+
+ // 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) {
+ switch (opt) {
+ case 'h':
+ usage(stdout);
+ return 0;
+ 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);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "Parse error in section %d: %s\n", sectionID, strerror(-err));
+ return -1;
+ }
+
+ delete parser;
+ }
+ fprintf(stderr, "Finish section %d, exiting...\n", sectionID);
+
+ return 0;
+}
diff --git a/cmds/incident_helper/strutil.cpp b/cmds/incident_helper/strutil.cpp
new file mode 100644
index 0000000..21b04a1
--- /dev/null
+++ b/cmds/incident_helper/strutil.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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
new file mode 100644
index 0000000..fcc164d
--- /dev/null
+++ b/cmds/incident_helper/strutil.h
@@ -0,0 +1,30 @@
+/*
+ * 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/kernel_wakeups.txt b/cmds/incident_helper/testdata/kernel_wakeups.txt
new file mode 100644
index 0000000..7f627cf
--- /dev/null
+++ b/cmds/incident_helper/testdata/kernel_wakeups.txt
@@ -0,0 +1,3 @@
+name active_count event_count wakeup_count expire_count active_since total_time max_time last_change prevent_suspend_time
+ipc000000ab_ATFWD-daemon 8 8 0 0 0 0 0 131348 0
+ipc000000aa_ATFWD-daemon 143 143 0 0 0 123 3 2067286206 0
\ No newline at end of file
diff --git a/cmds/incident_helper/testdata/kernel_wakeups_bad_headers.txt b/cmds/incident_helper/testdata/kernel_wakeups_bad_headers.txt
new file mode 100644
index 0000000..4914d2e
--- /dev/null
+++ b/cmds/incident_helper/testdata/kernel_wakeups_bad_headers.txt
@@ -0,0 +1,3 @@
+THIS IS BAD HEADER
+ipc000000ab_ATFWD-daemon 8 8 0 0 0 0 0 131348 0
+ipc000000aa_ATFWD-daemon 143 143 0 0 0 123 3 2067286206 0
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/IncidentHelper_test.cpp b/cmds/incident_helper/tests/IncidentHelper_test.cpp
new file mode 100644
index 0000000..9d5d9e4
--- /dev/null
+++ b/cmds/incident_helper/tests/IncidentHelper_test.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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 <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+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 {
+
+ }
+
+protected:
+ const std::string kTestPath = GetExecutableDirectory();
+ const std::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 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");
+ 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);
+
+ 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);
+ close(fd);
+}
+
+TEST_F(IncidentHelperTest, KernelWakesParserBadHeaders) {
+ const std::string testFile = kTestDataPath + "kernel_wakeups_bad_headers.txt";
+ KernelWakesParser parser;
+
+ int fd = open(testFile.c_str(), O_RDONLY, 0444);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ CaptureStderr();
+ ASSERT_EQ(BAD_VALUE, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_THAT(GetCapturedStdout(), StrEq(""));
+ EXPECT_THAT(GetCapturedStderr(), StrEq("[KernelWakeSources]Bad header:\nTHIS IS BAD HEADER\n"));
+ close(fd);
+}