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:
@@ -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: {
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
124
cmds/incident_helper/ih_util.cpp
Normal file
124
cmds/incident_helper/ih_util.cpp
Normal 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();
|
||||
}
|
||||
64
cmds/incident_helper/ih_util.h
Normal file
64
cmds/incident_helper/ih_util.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
8
cmds/incident_helper/testdata/procrank.txt
vendored
Normal file
8
cmds/incident_helper/testdata/procrank.txt
vendored
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
137
cmds/incident_helper/tests/ih_util_test.cpp
Normal file
137
cmds/incident_helper/tests/ih_util_test.cpp
Normal 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"));
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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]; }
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
82
core/proto/android/os/procrank.proto
Normal file
82
core/proto/android/os/procrank.proto
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user