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