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
This commit is contained in:
Yi Jin
2017-07-21 12:12:59 -07:00
parent 85ef0c0e8e
commit b44f7d46b6
24 changed files with 898 additions and 282 deletions

View File

@@ -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: {

View File

@@ -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: [

View File

@@ -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 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<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 @@ 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;
}

View File

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

View File

@@ -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
```

View File

@@ -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();
}

View File

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

View File

@@ -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;

View File

@@ -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;
}

View File

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

View File

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

View File

@@ -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 @@ 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);
}

View File

@@ -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"));
}

View File

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

View File

@@ -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]; }

View File

@@ -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)

View File

@@ -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 @@ 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<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 @@ 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; 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;
}

View File

@@ -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 @@ 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);
};
/**

View File

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

View File

@@ -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
}

View File

@@ -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, 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));
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}