From b44f7d46b647e24d8ea4fdf45742bbcbbfb03113 Mon Sep 17 00:00:00 2001 From: Yi Jin Date: Fri, 21 Jul 2017 12:12:59 -0700 Subject: [PATCH] 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 --- Android.bp | 1 + cmds/incident_helper/Android.bp | 3 +- cmds/incident_helper/IncidentHelper.cpp | 144 ++++++-- cmds/incident_helper/IncidentHelper.h | 13 + cmds/incident_helper/README.md | 9 + cmds/incident_helper/ih_util.cpp | 124 +++++++ cmds/incident_helper/ih_util.h | 64 ++++ cmds/incident_helper/main.cpp | 31 +- cmds/incident_helper/strutil.cpp | 55 ---- cmds/incident_helper/strutil.h | 30 -- cmds/incident_helper/testdata/procrank.txt | 8 + .../tests/IncidentHelper_test.cpp | 70 +++- cmds/incident_helper/tests/ih_util_test.cpp | 137 ++++++++ cmds/incidentd/incidentd.rc | 2 +- cmds/incidentd/src/FdBuffer.h | 2 +- cmds/incidentd/src/Reporter.cpp | 2 +- cmds/incidentd/src/Section.cpp | 311 +++++++++++------- cmds/incidentd/src/Section.h | 17 +- cmds/incidentd/src/section_list.cpp | 1 + cmds/incidentd/tests/FdBuffer_test.cpp | 17 +- cmds/incidentd/tests/Section_test.cpp | 53 ++- core/proto/android/os/incident.proto | 3 +- core/proto/android/os/kernelwake.proto | 1 + core/proto/android/os/procrank.proto | 82 +++++ 24 files changed, 898 insertions(+), 282 deletions(-) create mode 100644 cmds/incident_helper/ih_util.cpp create mode 100644 cmds/incident_helper/ih_util.h delete mode 100644 cmds/incident_helper/strutil.cpp delete mode 100644 cmds/incident_helper/strutil.h create mode 100644 cmds/incident_helper/testdata/procrank.txt create mode 100644 cmds/incident_helper/tests/ih_util_test.cpp create mode 100644 core/proto/android/os/procrank.proto diff --git a/Android.bp b/Android.bp index eb35ffb1cb18d..33acffa306dac 100644 --- a/Android.bp +++ b/Android.bp @@ -40,6 +40,7 @@ cc_library { // 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 3ea823c209068..053208313b00b 100644 --- a/cmds/incident_helper/Android.bp +++ b/cmds/incident_helper/Android.bp @@ -10,7 +10,7 @@ cc_defaults { srcs: [ "IncidentHelper.cpp", - "strutil.cpp", + "ih_util.cpp", ], shared_libs: [ @@ -38,6 +38,7 @@ cc_test { 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 89d8947136906..aa0659574e2f4 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 #include #include -#include #include #include @@ -67,42 +66,34 @@ const char* kernel_wake_headers[] = { 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 header; // the header of /d/wakeup_sources - vector 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 @@ status_t KernelWakesParser::Parse(const int in, const int out) const { 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 40792a893a564..736f848a1a5d4 100644 --- a/cmds/incident_helper/IncidentHelper.h +++ b/cmds/incident_helper/IncidentHelper.h @@ -70,4 +70,17 @@ public: 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 967f1e334c48c..866cc202d44d2 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 @@ 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 ``` +## 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 0000000000000..bbb625f28c48d --- /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 +#include + +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& 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& 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 0000000000000..9e0c18ee74985 --- /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 +#include +#include + +typedef std::vector header_t; +typedef std::vector 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& words, + const std::string& delimiters = DEFAULT_WHITESPACE); + +bool assertHeaders(const char* expected[], const std::vector& 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 1e25c8561f515..333344b8ce865 100644 --- a/cmds/incident_helper/main.cpp +++ b/cmds/incident_helper/main.cpp @@ -27,12 +27,11 @@ 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, "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 @@ static TextParserBase* selectParser(int section) { 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 @@ int main(int argc, char** argv) { // 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 @@ int main(int argc, char** argv) { 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 21b04a1ebc3f0..0000000000000 --- 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 - -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* 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& 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 fcc164dc42ec9..0000000000000 --- 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 -#include - -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* words, - const std::string& delimiters = DEFAULT_WHITESPACE); -bool assertHeaders(const char* expected[], const std::vector& 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 0000000000000..5d2d8d29a5cd9 --- /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 9d5d9e4d0cd7f..ac3e84d4b68d6 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 #include #include +#include #include #include #include @@ -37,10 +39,19 @@ using ::testing::internal::GetCapturedStdout; 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 @@ 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"); @@ -89,15 +97,12 @@ TEST_F(IncidentHelperTest, KernelWakesParser) { 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 @@ TEST_F(IncidentHelperTest, KernelWakesParserBadHeaders) { 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 0000000000000..5158e0a285bc5 --- /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 +#include +#include +#include +#include + +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 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 d11e3cf70567c..250a7f34cce71 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 7888442d9d5fa..03a6d1865036f 100644 --- a/cmds/incidentd/src/FdBuffer.h +++ b/cmds/incidentd/src/FdBuffer.h @@ -98,7 +98,7 @@ public: 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 ba157de637ce2..2a4f89e187801 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 ddb54c13f6bbd..8ef681769619f 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -19,6 +19,7 @@ #include "Section.h" #include "protobuf.h" +#include #include #include #include @@ -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(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 @@ Section::WriteHeader(ReportRequestSet* requests, size_t size) const } // ================================================================================ -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(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 @@ WorkerThreadSection::Execute(ReportRequestSet* requests) const 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 @@ WorkerThreadSection::Execute(ReportRequestSet* requests) const } // 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 @@ WorkerThreadSection::Execute(ReportRequestSet* requests) const } // ================================================================================ -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; iname.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 35740e9771d56..93b4848f5bd8b 100644 --- a/cmds/incidentd/src/Section.h +++ b/cmds/incidentd/src/Section.h @@ -19,22 +19,26 @@ #include "FdBuffer.h" +#include #include #include #include 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 @@ public: 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 @@ public: 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 72c54d2cdded9..80ddb86da5091 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 ca5eec89cea78..ba8b77a8bfacc 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 @@ TEST_F(FdBufferTest, ReadTimeout) { 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 @@ TEST_F(FdBufferTest, ReadInStreamAndWrite) { 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 @@ TEST_F(FdBufferTest, ReadInStreamAndWriteAllAtOnce) { 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 @@ TEST_F(FdBufferTest, ReadInStreamEmpty) { 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 @@ TEST_F(FdBufferTest, ReadInStreamMoreThan4MB) { 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 @@ TEST_F(FdBufferTest, ReadInStreamTimeOut) { 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 18322b6d0d0d1..93771ff30b640 100644 --- a/cmds/incidentd/tests/Section_test.cpp +++ b/cmds/incidentd/tests/Section_test.cpp @@ -22,6 +22,8 @@ #include #include +const int QUICK_TIMEOUT_MS = 100; + using namespace android::base; using namespace std; using ::testing::StrEq; @@ -62,7 +64,56 @@ TEST(SectionTest, FileSection) { 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 5914f5665eb75..76e683f51b4ec 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/power.proto"; 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 @@ message IncidentProto { //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 6ab1d29ff8076..e0b62aa757448 100644 --- a/core/proto/android/os/kernelwake.proto +++ b/core/proto/android/os/kernelwake.proto @@ -26,6 +26,7 @@ message KernelWakeSources { 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 0000000000000..c7dbf4d8c09aa --- /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; +}