summaryrefslogtreecommitdiff
path: root/cmds
diff options
context:
space:
mode:
authorYi Jin <jinyithu@google.com>2017-11-01 17:08:27 -0700
committerYi Jin <jinyithu@google.com>2017-11-10 17:34:07 -0800
commite2f7f79d023f0b3ba2fee374492dde61f525ece6 (patch)
tree8ea7efe3b552f1cb23c2f4c2ef379be6fb02f19b /cmds
parent9a753af26b2ce27c10ad215aa70cf1bcd44d7915 (diff)
Implement Cpu Info Section
Support carriage return in Read class, and add a new way to parse lines which is not able to split purly by delimiters Bug: 65642861 Test: unit test and on device test Change-Id: Ib82dd4e458bb7d2fa33462b23fbe11b828325916
Diffstat (limited to 'cmds')
-rw-r--r--cmds/incident_helper/src/ih_util.cpp222
-rw-r--r--cmds/incident_helper/src/ih_util.h62
-rw-r--r--cmds/incident_helper/src/main.cpp3
-rw-r--r--cmds/incident_helper/src/parsers/CpuInfoParser.cpp161
-rw-r--r--cmds/incident_helper/src/parsers/CpuInfoParser.h36
-rw-r--r--cmds/incident_helper/src/parsers/KernelWakesParser.cpp8
-rw-r--r--cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp24
-rw-r--r--cmds/incident_helper/src/parsers/ProcrankParser.cpp8
-rw-r--r--cmds/incident_helper/testdata/cpuinfo.txt15
-rw-r--r--cmds/incident_helper/tests/CpuInfoParser_test.cpp158
-rw-r--r--cmds/incident_helper/tests/ih_util_test.cpp85
11 files changed, 638 insertions, 144 deletions
diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
index c7d1ca231a03..0b51e66c2108 100644
--- a/cmds/incident_helper/src/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -22,19 +22,28 @@
#include <sstream>
#include <unistd.h>
-const ssize_t BUFFER_SIZE = 16 * 1024; // 4KB
-
+bool isValidChar(char c) {
+ uint8_t v = (uint8_t)c;
+ return (v >= (uint8_t)'a' && v <= (uint8_t)'z')
+ || (v >= (uint8_t)'A' && v <= (uint8_t)'Z')
+ || (v >= (uint8_t)'0' && v <= (uint8_t)'9')
+ || (v == (uint8_t)'_');
+}
-static std::string trim(const std::string& s) {
- const auto head = s.find_first_not_of(DEFAULT_WHITESPACE);
+static std::string trim(const std::string& s, const std::string& chars) {
+ const auto head = s.find_first_not_of(chars);
if (head == std::string::npos) return "";
- const auto tail = s.find_last_not_of(DEFAULT_WHITESPACE);
+ const auto tail = s.find_last_not_of(chars);
return s.substr(head, tail - head + 1);
}
+static std::string trimDefault(const std::string& s) {
+ return trim(s, DEFAULT_WHITESPACE);
+}
+
static std::string trimHeader(const std::string& s) {
- std::string res = trim(s);
+ std::string res = trimDefault(s);
std::transform(res.begin(), res.end(), res.begin(), ::tolower);
return res;
}
@@ -68,22 +77,68 @@ header_t parseHeader(const std::string& line, const std::string& delimiters) {
record_t parseRecord(const std::string& line, const std::string& delimiters) {
record_t record;
- trans_func f = &trim;
+ trans_func f = &trimDefault;
split(line, record, f, delimiters);
return record;
}
-bool hasPrefix(std::string* line, const char* key) {
+record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) {
+ record_t record;
+ int lastIndex = 0;
+ int lineSize = (int)line.size();
+ for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) {
+ int idx = *it;
+ if (lastIndex > idx || idx > lineSize) {
+ record.clear(); // The indices is wrong, return empty;
+ return record;
+ }
+ while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos);
+ record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex)));
+ lastIndex = idx;
+ }
+ record.push_back(trimDefault(line.substr(lastIndex, lineSize - lastIndex)));
+ return record;
+}
+
+bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) {
const auto head = line->find_first_not_of(DEFAULT_WHITESPACE);
if (head == std::string::npos) return false;
- auto i = 0;
- auto j = head;
+ int len = (int)line->length();
+ int i = 0;
+ int j = head;
while (key[i] != '\0') {
- if (j >= line->size() || key[i++] != line->at(j++)) {
+ if (j >= len || key[i++] != line->at(j++)) {
+ return false;
+ }
+ }
+
+ if (endAtDelimiter) {
+ // this means if the line only have prefix or no delimiter, we still return false.
+ if (j == len || isValidChar(line->at(j))) return false;
+ }
+
+ line->assign(trimDefault(line->substr(j)));
+ return true;
+}
+
+bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter) {
+ const auto tail = line->find_last_not_of(DEFAULT_WHITESPACE);
+ if (tail == std::string::npos) return false;
+ int i = 0;
+ while (key[++i] != '\0'); // compute the size of the key
+ int j = tail;
+ while (i > 0) {
+ if (j < 0 || key[--i] != line->at(j--)) {
return false;
}
}
- line->assign(trim(line->substr(j)));
+
+ if (endAtDelimiter) {
+ // this means if the line only have suffix or no delimiter, we still return false.
+ if (j < 0 || isValidChar(line->at(j))) return false;
+ }
+
+ line->assign(trimDefault(line->substr(0, j+1)));
return true;
}
@@ -95,65 +150,36 @@ long long toLongLong(const std::string& s) {
return atoll(s.c_str());
}
-// ==============================================================================
-Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
+double toDouble(const std::string& s) {
+ return atof(s.c_str());
+}
-Reader::Reader(const int fd, const size_t capacity)
- : mFd(fd), mMaxSize(capacity), mBufSize(0), mRead(0), mFlushed(0)
+// ==============================================================================
+Reader::Reader(const int fd)
{
- mBuf = capacity > 0 ? (char*)malloc(capacity * sizeof(char)) : NULL;
- mStatus = mFd < 0 ? "Negative fd" : (capacity == 0 ? "Zero buffer capacity" : "");
+ mFile = fdopen(fd, "r");
+ mStatus = mFile == NULL ? "Invalid fd " + std::to_string(fd) : "";
}
Reader::~Reader()
{
- free(mBuf);
-}
-
-bool Reader::readLine(std::string* line, const char newline) {
- if (!ok(line)) return false; // bad status
- line->clear();
- 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 (mFile != NULL) fclose(mFile);
+}
- if (mRead >= (int) mMaxSize) mRead = 0;
- if (mFlushed >= (int) mMaxSize) mFlushed = 0;
+bool Reader::readLine(std::string* line) {
+ if (mFile == NULL) return false;
- if (EOR() || meetsNewLine) {
- line->assign(ss.str());
- return true;
- }
+ char* buf = NULL;
+ size_t len = 0;
+ ssize_t read = getline(&buf, &len, mFile);
+ if (read != -1) {
+ std::string s(buf);
+ line->assign(trim(s, DEFAULT_NEWLINE));
+ } else if (errno == EINVAL) {
+ mStatus = "Bad Argument";
}
- return false;
+ free(buf);
+ return read != -1;
}
bool Reader::ok(std::string* error) {
@@ -162,10 +188,41 @@ bool Reader::ok(std::string* error) {
}
// ==============================================================================
+static int
+lookupName(const char** names, const int size, const char* name)
+{
+ for (int i=0; i<size; i++) {
+ if (strcmp(name, names[i]) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+EnumTypeMap::EnumTypeMap(const char* enumNames[], const uint32_t enumValues[], const int enumCount)
+ :mEnumNames(enumNames),
+ mEnumValues(enumValues),
+ mEnumCount(enumCount)
+{
+}
+
+EnumTypeMap::~EnumTypeMap()
+{
+}
+
+int
+EnumTypeMap::parseValue(const std::string& value)
+{
+ int index = lookupName(mEnumNames, mEnumCount, value.c_str());
+ if (index < 0) return mEnumValues[0]; // Assume value 0 is default
+ return mEnumValues[index];
+}
+
Table::Table(const char* names[], const uint64_t ids[], const int count)
:mFieldNames(names),
mFieldIds(ids),
- mFieldCount(count)
+ mFieldCount(count),
+ mEnums()
{
}
@@ -173,41 +230,54 @@ Table::~Table()
{
}
+void
+Table::addEnumTypeMap(const char* field, const char* enumNames[], const uint32_t enumValues[], const int enumSize)
+{
+ int index = lookupName(mFieldNames, mFieldCount, field);
+ if (index < 0) return;
+
+ EnumTypeMap enu(enumNames, enumValues, enumSize);
+ mEnums[index] = enu;
+}
+
bool
-Table::insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value)
+Table::insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value)
{
- uint64_t found = 0;
- for (int i=0; i<mFieldCount; i++) {
- if (strcmp(name.c_str(), mFieldNames[i]) == 0) {
- found = mFieldIds[i];
- break;
- }
- }
+ int index = lookupName(mFieldNames, mFieldCount, name.c_str());
+ if (index < 0) return false;
+ uint64_t found = mFieldIds[index];
switch (found & FIELD_TYPE_MASK) {
case FIELD_TYPE_DOUBLE:
case FIELD_TYPE_FLOAT:
- // TODO: support parse string to float/double
- return false;
+ proto->write(found, toDouble(value));
+ break;
case FIELD_TYPE_STRING:
case FIELD_TYPE_BYTES:
- proto.write(found, value);
+ proto->write(found, value);
break;
case FIELD_TYPE_INT64:
case FIELD_TYPE_SINT64:
case FIELD_TYPE_UINT64:
case FIELD_TYPE_FIXED64:
case FIELD_TYPE_SFIXED64:
- proto.write(found, toLongLong(value));
+ proto->write(found, toLongLong(value));
break;
case FIELD_TYPE_BOOL:
+ return false;
case FIELD_TYPE_ENUM:
+ if (mEnums.find(index) == mEnums.end()) {
+ // forget to add enum type mapping
+ return false;
+ }
+ proto->write(found, mEnums[index].parseValue(value));
+ break;
case FIELD_TYPE_INT32:
case FIELD_TYPE_SINT32:
case FIELD_TYPE_UINT32:
case FIELD_TYPE_FIXED32:
case FIELD_TYPE_SFIXED32:
- proto.write(found, toInt(value));
+ proto->write(found, toInt(value));
break;
default:
return false;
diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h
index 86761e93f49c..e8366fa599e2 100644
--- a/cmds/incident_helper/src/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -17,9 +17,9 @@
#ifndef INCIDENT_HELPER_UTIL_H
#define INCIDENT_HELPER_UTIL_H
+#include <map>
#include <string>
#include <vector>
-#include <sstream>
#include <android/util/ProtoOutputStream.h>
@@ -29,8 +29,13 @@ typedef std::vector<std::string> header_t;
typedef std::vector<std::string> record_t;
typedef std::string (*trans_func) (const std::string&);
-const char DEFAULT_NEWLINE = '\n';
const std::string DEFAULT_WHITESPACE = " \t";
+const std::string DEFAULT_NEWLINE = "\r\n";
+const std::string TAB_DELIMITER = "\t";
+const std::string COMMA_DELIMITER = ",";
+
+// returns true if c is a-zA-Z0-9 or underscore _
+bool isValidChar(char c);
/**
* When a text has a table format like this
@@ -47,19 +52,33 @@ header_t parseHeader(const std::string& line, const std::string& delimiters = DE
record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
/**
- * When the line starts with the given key, the function returns true
- * as well as the line argument is changed to the rest part of the original.
+ * When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters.
+ * This function allows to parse record by its header's column position' indices, must in ascending order.
+ * At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters.
+ */
+record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE);
+
+/**
+ * When the line starts/ends with the given key, the function returns true
+ * as well as the line argument is changed to the rest trimmed part of the original.
* e.g. "ZRAM: 6828K physical used for 31076K in swap (524284K total swap)" becomes
* "6828K physical used for 31076K in swap (524284K total swap)" when given key "ZRAM:",
* otherwise the line is not changed.
+ *
+ * In order to prevent two values have same prefix which cause entering to incorrect conditions,
+ * stripPrefix and stripSuffix can turn on a flag that requires the ending char in the line must not be a valid
+ * character or digits, this feature is off by default.
+ * i.e. ABC%some value, ABCD%other value
*/
-bool hasPrefix(std::string* line, const char* key);
+bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter = false);
+bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter = false);
/**
* Converts string to the desired type
*/
int toInt(const std::string& s);
long long toLongLong(const std::string& s);
+double toDouble(const std::string& s);
/**
* Reader class reads data from given fd in streaming fashion.
@@ -69,23 +88,29 @@ 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 readLine(std::string* line);
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;
+ FILE* mFile;
std::string mStatus;
- // end of read
- inline bool EOR() { return mFd == -1 && mBufSize == 0; };
+};
+
+class EnumTypeMap
+{
+public:
+ EnumTypeMap() {};
+ EnumTypeMap(const char* enumNames[], const uint32_t enumValues[], const int enumCount);
+ ~EnumTypeMap();
+
+ int parseValue(const std::string& value);
+
+private:
+ const char** mEnumNames;
+ const uint32_t* mEnumValues;
+ int mEnumCount;
};
/**
@@ -98,12 +123,15 @@ public:
Table(const char* names[], const uint64_t ids[], const int count);
~Table();
- bool insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value);
+ // Add enum names to values for parsing purpose.
+ void addEnumTypeMap(const char* field, const char* enumNames[], const uint32_t enumValues[], const int enumSize);
+ bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value);
private:
const char** mFieldNames;
const uint64_t* mFieldIds;
const int mFieldCount;
+ map<int, EnumTypeMap> mEnums;
};
#endif // INCIDENT_HELPER_UTIL_H
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index 3da87b9c801b..5ebe9bd31be5 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "incident_helper"
+#include "parsers/CpuInfoParser.h"
#include "parsers/KernelWakesParser.h"
#include "parsers/PageTypeInfoParser.h"
#include "parsers/ProcrankParser.h"
@@ -54,6 +55,8 @@ static TextParserBase* selectParser(int section) {
return new PageTypeInfoParser();
case 2002:
return new KernelWakesParser();
+ case 2003:
+ return new CpuInfoParser();
default:
return NULL;
}
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
new file mode 100644
index 000000000000..3faca00c1b88
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
@@ -0,0 +1,161 @@
+/*
+ * 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 <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/cpuinfo.proto.h"
+#include "ih_util.h"
+#include "CpuInfoParser.h"
+
+using namespace android::os;
+
+static void writeSuffixLine(ProtoOutputStream* proto, uint64_t fieldId,
+ const string& line, const string& delimiter,
+ const int count, const char* names[], const uint64_t ids[])
+{
+ record_t record = parseRecord(line, delimiter);
+ long long token = proto->start(fieldId);
+ for (int i=0; i<(int)record.size(); i++) {
+ for (int j=0; j<count; j++) {
+ if (stripSuffix(&record[i], names[j], true)) {
+ proto->write(ids[j], toInt(record[i]));
+ break;
+ }
+ }
+ }
+ proto->end(token);
+}
+
+status_t
+CpuInfoParser::Parse(const int in, const int out) const
+{
+ Reader reader(in);
+ string line;
+ header_t header;
+ vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
+ record_t record;
+ int nline = 0;
+ bool nextToSwap = false;
+ bool nextToUsage = false;
+
+ ProtoOutputStream proto;
+ Table table(CpuInfo::Task::_FIELD_NAMES, CpuInfo::Task::_FIELD_IDS, CpuInfo::Task::_FIELD_COUNT);
+ table.addEnumTypeMap("s", CpuInfo::Task::_ENUM_STATUS_NAMES,
+ CpuInfo::Task::_ENUM_STATUS_VALUES, CpuInfo::Task::_ENUM_STATUS_COUNT);
+ table.addEnumTypeMap("pcy", CpuInfo::Task::_ENUM_POLICY_NAMES,
+ CpuInfo::Task::_ENUM_POLICY_VALUES, CpuInfo::Task::_ENUM_POLICY_COUNT);
+
+ // parse line by line
+ while (reader.readLine(&line)) {
+ if (line.empty()) continue;
+
+ nline++;
+
+ if (stripPrefix(&line, "Tasks:")) {
+ writeSuffixLine(&proto, CpuInfo::TASK_STATS, line, COMMA_DELIMITER,
+ CpuInfo::TaskStats::_FIELD_COUNT,
+ CpuInfo::TaskStats::_FIELD_NAMES,
+ CpuInfo::TaskStats::_FIELD_IDS);
+ continue;
+ }
+ if (stripPrefix(&line, "Mem:")) {
+ writeSuffixLine(&proto, CpuInfo::MEM, line, COMMA_DELIMITER,
+ CpuInfo::MemStats::_FIELD_COUNT,
+ CpuInfo::MemStats::_FIELD_NAMES,
+ CpuInfo::MemStats::_FIELD_IDS);
+ continue;
+ }
+ if (stripPrefix(&line, "Swap:")) {
+ writeSuffixLine(&proto, CpuInfo::SWAP, line, COMMA_DELIMITER,
+ CpuInfo::MemStats::_FIELD_COUNT,
+ CpuInfo::MemStats::_FIELD_NAMES,
+ CpuInfo::MemStats::_FIELD_IDS);
+ nextToSwap = true;
+ continue;
+ }
+
+ if (nextToSwap) {
+ writeSuffixLine(&proto, CpuInfo::CPU_USAGE, line, DEFAULT_WHITESPACE,
+ CpuInfo::CpuUsage::_FIELD_COUNT,
+ CpuInfo::CpuUsage::_FIELD_NAMES,
+ CpuInfo::CpuUsage::_FIELD_IDS);
+ nextToUsage = true;
+ nextToSwap = false;
+ continue;
+ }
+
+ // Header of tasks must be next to usage line
+ if (nextToUsage) {
+ // How to parse Header of Tasks:
+ // PID TID USER PR NI[%CPU]S VIRT RES PCY CMD NAME
+ // After parsing, header = { PID, TID, USER, PR, NI, CPU, S, VIRT, RES, PCY, CMD, NAME }
+ // And columnIndices will contain end index of each word.
+ header = parseHeader(line, "[ %]");
+ nextToUsage = false;
+
+ // NAME is not in the list since the last split index is default to the end of line.
+ const char* headerNames[11] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD" };
+ size_t lastIndex = 0;
+ for (int i = 0; i < 11; i++) {
+ string s = headerNames[i];
+ lastIndex = line.find(s, lastIndex);
+ if (lastIndex == string::npos) {
+ fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
+ return -1;
+ }
+ lastIndex += s.length();
+ columnIndices.push_back(lastIndex);
+ }
+ // Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces.
+ // for example: ... CMD NAME
+ // ... Jit thread pool com.google.android.gms.feedback
+ // If use end index of CMD, parsed result = { "Jit", "thread pool com.google.android.gms.feedback" }
+ // If use start index of NAME, parsed result = { "Jit thread pool", "com.google.android.gms.feedback" }
+ int endCMD = columnIndices.back();
+ columnIndices.pop_back();
+ columnIndices.push_back(line.find("NAME", endCMD) - 1);
+ continue;
+ }
+
+ record = parseRecordByColumns(line, columnIndices);
+ if (record.size() != header.size()) {
+ fprintf(stderr, "[%s]Line %d has missing fields:\n%s\n", this->name.string(), nline, line.c_str());
+ continue;
+ }
+
+ long long token = proto.start(CpuInfo::TASKS);
+ for (int i=0; i<(int)record.size(); i++) {
+ if (!table.insertField(&proto, header[i], record[i])) {
+ fprintf(stderr, "[%s]Line %d fails to insert field %s with value %s\n",
+ this->name.string(), nline, header[i].c_str(), record[i].c_str());
+ }
+ }
+ proto.end(token);
+ }
+
+ if (!reader.ok(&line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!proto.flush(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.h b/cmds/incident_helper/src/parsers/CpuInfoParser.h
new file mode 100644
index 000000000000..f57bb4e169bd
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.h
@@ -0,0 +1,36 @@
+/*
+ * 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 CPU_INFO_PARSER_H
+#define CPU_INFO_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Cpu info parser, parses text produced by command
+ * 'top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name'
+ */
+class CpuInfoParser : public TextParserBase {
+public:
+ CpuInfoParser() : TextParserBase(String8("CpuInfoParser")) {};
+ ~CpuInfoParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif // CPU_INFO_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
index cc4a1e1ecfa2..ada4a5d0ffe2 100644
--- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -23,8 +23,6 @@
using namespace android::os;
-const std::string LINE_DELIMITER = "\t";
-
status_t
KernelWakesParser::Parse(const int in, const int out) const
{
@@ -42,12 +40,12 @@ KernelWakesParser::Parse(const int in, const int out) const
if (line.empty()) continue;
// parse head line
if (nline++ == 0) {
- header = parseHeader(line, LINE_DELIMITER);
+ header = parseHeader(line, TAB_DELIMITER);
continue;
}
// parse for each record, the line delimiter is \t only!
- record = parseRecord(line, LINE_DELIMITER);
+ record = parseRecord(line, TAB_DELIMITER);
if (record.size() != header.size()) {
// TODO: log this to incident report!
@@ -57,7 +55,7 @@ KernelWakesParser::Parse(const int in, const int out) const
long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES);
for (int i=0; i<(int)record.size(); i++) {
- if (!table.insertField(proto, header[i], record[i])) {
+ if (!table.insertField(&proto, header[i], record[i])) {
fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
this->name.string(), nline, header[i].c_str(), record[i].c_str());
}
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
index 6047bd189b95..f1b93ff9ec41 100644
--- a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
@@ -23,8 +23,6 @@
using namespace android::os;
-const std::string LINE_DELIMITER = ",";
-
status_t
PageTypeInfoParser::Parse(const int in, const int out) const
{
@@ -44,37 +42,37 @@ PageTypeInfoParser::Parse(const int in, const int out) const
continue;
}
- if (hasPrefix(&line, "Page block order:")) {
+ if (stripPrefix(&line, "Page block order:")) {
pageBlockOrder = toInt(line);
proto.write(PageTypeInfo::PAGE_BLOCK_ORDER, pageBlockOrder);
continue;
}
- if (hasPrefix(&line, "Pages per block:")) {
+ if (stripPrefix(&line, "Pages per block:")) {
proto.write(PageTypeInfo::PAGES_PER_BLOCK, toInt(line));
continue;
}
- if (hasPrefix(&line, "Free pages count per migrate type at order")) {
+ if (stripPrefix(&line, "Free pages count per migrate type at order")) {
migrateTypeSession = true;
continue;
}
- if (hasPrefix(&line, "Number of blocks type")) {
+ if (stripPrefix(&line, "Number of blocks type")) {
blockHeader = parseHeader(line);
continue;
}
- record_t record = parseRecord(line, LINE_DELIMITER);
+ record_t record = parseRecord(line, COMMA_DELIMITER);
if (migrateTypeSession && record.size() == 3) {
long long token = proto.start(PageTypeInfo::MIGRATE_TYPES);
// expect part 0 starts with "Node"
- if (hasPrefix(&record[0], "Node")) {
+ if (stripPrefix(&record[0], "Node")) {
proto.write(MigrateTypeProto::NODE, toInt(record[0]));
} else return BAD_VALUE;
// expect part 1 starts with "zone"
- if (hasPrefix(&record[1], "zone")) {
+ if (stripPrefix(&record[1], "zone")) {
proto.write(MigrateTypeProto::ZONE, record[1]);
} else return BAD_VALUE;
// expect part 2 starts with "type"
- if (hasPrefix(&record[2], "type")) {
+ if (stripPrefix(&record[2], "type")) {
// expect the rest of part 2 has number of (pageBlockOrder + 2) parts
// An example looks like:
// header line: type 0 1 2 3 4 5 6 7 8 9 10
@@ -94,16 +92,16 @@ PageTypeInfoParser::Parse(const int in, const int out) const
proto.end(token);
} else if (!blockHeader.empty() && record.size() == 2) {
long long token = proto.start(PageTypeInfo::BLOCKS);
- if (hasPrefix(&record[0], "Node")) {
+ if (stripPrefix(&record[0], "Node")) {
proto.write(BlockProto::NODE, toInt(record[0]));
} else return BAD_VALUE;
- if (hasPrefix(&record[1], "zone")) {
+ if (stripPrefix(&record[1], "zone")) {
record_t blockCounts = parseRecord(record[1]);
proto.write(BlockProto::ZONE, blockCounts[0]);
for (size_t i=0; i<blockHeader.size(); i++) {
- if (!table.insertField(proto, blockHeader[i], blockCounts[i+1])) {
+ if (!table.insertField(&proto, blockHeader[i], blockCounts[i+1])) {
return BAD_VALUE;
}
}
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.cpp b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
index 93f970f820d9..a4eb0fdfd988 100644
--- a/cmds/incident_helper/src/parsers/ProcrankParser.cpp
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
@@ -46,11 +46,11 @@ ProcrankParser::Parse(const int in, const int out) const
continue;
}
- if (hasPrefix(&line, "ZRAM:")) {
+ if (stripPrefix(&line, "ZRAM:")) {
zram = line;
continue;
}
- if (hasPrefix(&line, "RAM:")) {
+ if (stripPrefix(&line, "RAM:")) {
ram = line;
continue;
}
@@ -68,7 +68,7 @@ ProcrankParser::Parse(const int in, const int out) const
long long token = proto.start(Procrank::PROCESSES);
for (int i=0; i<(int)record.size(); i++) {
- if (!table.insertField(proto, header[i], record[i])) {
+ if (!table.insertField(&proto, header[i], record[i])) {
fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
this->name.string(), nline, header[i].c_str(), record[i].c_str());
}
@@ -82,7 +82,7 @@ ProcrankParser::Parse(const int in, const int out) const
record = parseRecord(total);
long long token = proto.start(SummaryProto::TOTAL);
for (int i=(int)record.size(); i>0; i--) {
- table.insertField(proto, header[header.size() - i].c_str(), record[record.size() - i].c_str());
+ table.insertField(&proto, header[header.size() - i].c_str(), record[record.size() - i].c_str());
}
proto.end(token);
}
diff --git a/cmds/incident_helper/testdata/cpuinfo.txt b/cmds/incident_helper/testdata/cpuinfo.txt
new file mode 100644
index 000000000000..ec4a83960698
--- /dev/null
+++ b/cmds/incident_helper/testdata/cpuinfo.txt
@@ -0,0 +1,15 @@
+Tasks: 2038 total, 1 running,2033 sleeping, 0 stopped, 0 zombie
+
+Mem: 3842668k total, 3761936k used, 80732k free, 220188k buffers
+
+Swap: 524284k total, 25892k used, 498392k free, 1316952k cached
+
+400%cpu 17%user 0%nice 43%sys 338%idle 0%iow 0%irq 1%sirq 0%host
+
+ PID TID USER PR NI[%CPU]S VIRT RES PCY CMD NAME
+
+
+29438 29438 rootabcdefghij 20 0 57.9 R 14M 3.8M top test top
+ 916 916 system 18 -2 1.4 S 4.6G 404M fg system_server system_server
+ 28 28 root -2 0 1.4 S 0 0 bg rcuc/3 [rcuc/3]
+ 27 27 root RT 0 1.4 S 0 0 ta migration/3 [migration/3] \ No newline at end of file
diff --git a/cmds/incident_helper/tests/CpuInfoParser_test.cpp b/cmds/incident_helper/tests/CpuInfoParser_test.cpp
new file mode 100644
index 000000000000..57ad15cf3910
--- /dev/null
+++ b/cmds/incident_helper/tests/CpuInfoParser_test.cpp
@@ -0,0 +1,158 @@
+/*
+ * 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 "CpuInfoParser.h"
+
+#include "frameworks/base/core/proto/android/os/cpuinfo.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>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class CpuInfoParserTest : public Test {
+public:
+ virtual void SetUp() override {
+ ASSERT_TRUE(tf.fd != -1);
+ }
+
+ string getSerializedString(::google::protobuf::Message& message) {
+ string expectedStr;
+ message.SerializeToFileDescriptor(tf.fd);
+ ReadFileToString(tf.path, &expectedStr);
+ return expectedStr;
+ }
+
+protected:
+ TemporaryFile tf;
+
+ const string kTestPath = GetExecutableDirectory();
+ const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(CpuInfoParserTest, HasSwapInfo) {
+ const string testFile = kTestDataPath + "cpuinfo.txt";
+ CpuInfoParser parser;
+ CpuInfo expected;
+
+ CpuInfo::TaskStats* taskStats = expected.mutable_task_stats();
+ taskStats->set_total(2038);
+ taskStats->set_running(1);
+ taskStats->set_sleeping(2033);
+ taskStats->set_stopped(0);
+ taskStats->set_zombie(0);
+
+ CpuInfo::MemStats* mem = expected.mutable_mem();
+ mem->set_total(3842668);
+ mem->set_used(3761936);
+ mem->set_free(80732);
+ mem->set_buffers(220188);
+
+ CpuInfo::MemStats* swap = expected.mutable_swap();
+ swap->set_total(524284);
+ swap->set_used(25892);
+ swap->set_free(498392);
+ swap->set_cached(1316952);
+
+ CpuInfo::CpuUsage* usage = expected.mutable_cpu_usage();
+ usage->set_cpu(400);
+ usage->set_user(17);
+ usage->set_nice(0);
+ usage->set_sys(43);
+ usage->set_idle(338);
+ usage->set_iow(0);
+ usage->set_irq(0);
+ usage->set_sirq(1);
+ usage->set_host(0);
+
+ // This is a special line which is able to be parsed by the CpuInfoParser
+ CpuInfo::Task* task1 = expected.add_tasks();
+ task1->set_pid(29438);
+ task1->set_tid(29438);
+ task1->set_user("rootabcdefghij");
+ task1->set_pr("20");
+ task1->set_ni(0);
+ task1->set_cpu(57.9);
+ task1->set_s(CpuInfo::Task::STATUS_R);
+ task1->set_virt("14M");
+ task1->set_res("3.8M");
+ task1->set_pcy(CpuInfo::Task::POLICY_UNKNOWN);
+ task1->set_cmd("top test");
+ task1->set_name("top");
+
+ CpuInfo::Task* task2 = expected.add_tasks();
+ task2->set_pid(916);
+ task2->set_tid(916);
+ task2->set_user("system");
+ task2->set_pr("18");
+ task2->set_ni(-2);
+ task2->set_cpu(1.4);
+ task2->set_s(CpuInfo::Task::STATUS_S);
+ task2->set_virt("4.6G");
+ task2->set_res("404M");
+ task2->set_pcy(CpuInfo::Task::POLICY_fg);
+ task2->set_cmd("system_server");
+ task2->set_name("system_server");
+
+ CpuInfo::Task* task3 = expected.add_tasks();
+ task3->set_pid(28);
+ task3->set_tid(28);
+ task3->set_user("root");
+ task3->set_pr("-2");
+ task3->set_ni(0);
+ task3->set_cpu(1.4);
+ task3->set_s(CpuInfo::Task::STATUS_S);
+ task3->set_virt("0");
+ task3->set_res("0");
+ task3->set_pcy(CpuInfo::Task::POLICY_bg);
+ task3->set_cmd("rcuc/3");
+ task3->set_name("[rcuc/3]");
+
+ CpuInfo::Task* task4 = expected.add_tasks();
+ task4->set_pid(27);
+ task4->set_tid(27);
+ task4->set_user("root");
+ task4->set_pr("RT");
+ task4->set_ni(0);
+ task4->set_cpu(1.4);
+ task4->set_s(CpuInfo::Task::STATUS_S);
+ task4->set_virt("0");
+ task4->set_res("0");
+ task4->set_pcy(CpuInfo::Task::POLICY_ta);
+ task4->set_cmd("migration/3");
+ task4->set_name("[migration/3]");
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+ close(fd);
+}
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
index 3cef6b378ee1..5740b330d949 100644
--- a/cmds/incident_helper/tests/ih_util_test.cpp
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -62,6 +62,59 @@ TEST(IhUtilTest, ParseRecord) {
EXPECT_EQ(expected, result);
}
+TEST(IhUtilTest, ParseRecordByColumns) {
+ record_t result, expected;
+ std::vector<int> indices = { 3, 10 };
+
+ result = parseRecordByColumns("12345", indices);
+ expected = {};
+ EXPECT_EQ(expected, result);
+
+ result = parseRecordByColumns("abc \t2345 6789 ", indices);
+ expected = { "abc", "2345", "6789" };
+ EXPECT_EQ(expected, result);
+
+ result = parseRecordByColumns("abc \t23456789 bob", indices);
+ expected = { "abc", "23456789", "bob" };
+ EXPECT_EQ(expected, result);
+}
+
+TEST(IhUtilTest, stripPrefix) {
+ string data1 = "Swap: abc ";
+ EXPECT_TRUE(stripPrefix(&data1, "Swap:"));
+ EXPECT_THAT(data1, StrEq("abc"));
+
+ string data2 = "Swap: abc ";
+ EXPECT_FALSE(stripPrefix(&data2, "Total:"));
+ EXPECT_THAT(data2, StrEq("Swap: abc "));
+
+ string data3 = "Swap: abc ";
+ EXPECT_TRUE(stripPrefix(&data3, "Swa"));
+ EXPECT_THAT(data3, StrEq("p: abc"));
+
+ string data4 = "Swap: abc ";
+ EXPECT_FALSE(stripPrefix(&data4, "Swa", true));
+ EXPECT_THAT(data4, StrEq("Swap: abc "));
+}
+
+TEST(IhUtilTest, stripSuffix) {
+ string data1 = " 243%abc";
+ EXPECT_TRUE(stripSuffix(&data1, "abc"));
+ EXPECT_THAT(data1, StrEq("243%"));
+
+ string data2 = " 243%abc";
+ EXPECT_FALSE(stripSuffix(&data2, "Not right"));
+ EXPECT_THAT(data2, StrEq(" 243%abc"));
+
+ string data3 = " 243%abc";
+ EXPECT_TRUE(stripSuffix(&data3, "bc"));
+ EXPECT_THAT(data3, StrEq("243%a"));
+
+ string data4 = " 243%abc";
+ EXPECT_FALSE(stripSuffix(&data4, "bc", true));
+ EXPECT_THAT(data4, StrEq(" 243%abc"));
+}
+
TEST(IhUtilTest, Reader) {
TemporaryFile tf;
ASSERT_NE(tf.fd, -1);
@@ -79,23 +132,6 @@ TEST(IhUtilTest, Reader) {
ASSERT_TRUE(r.ok(&line));
}
-TEST(IhUtilTest, ReaderSmallBufSize) {
- TemporaryFile tf;
- ASSERT_NE(tf.fd, -1);
- ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooiecccojreo", tf.path));
-
- 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);
@@ -103,9 +139,8 @@ TEST(IhUtilTest, ReaderEmpty) {
Reader r(tf.fd);
string line;
- ASSERT_TRUE(r.readLine(&line));
- EXPECT_THAT(line, StrEq(""));
ASSERT_FALSE(r.readLine(&line));
+ EXPECT_THAT(line, StrEq(""));
ASSERT_TRUE(r.ok(&line));
}
@@ -130,15 +165,7 @@ TEST(IhUtilTest, ReaderFailedNegativeFd) {
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"));
+ EXPECT_THAT(line, StrEq("Invalid fd -123"));
}
TEST(IhUtilTest, ReaderFailedBadFd) {
@@ -146,5 +173,5 @@ TEST(IhUtilTest, ReaderFailedBadFd) {
string line;
EXPECT_FALSE(r.readLine(&line));
EXPECT_FALSE(r.ok(&line));
- EXPECT_THAT(line, StrEq("Fail to read from fd"));
+ EXPECT_THAT(line, StrEq("Invalid fd 1231432"));
}