Merge "Implement Cpu Info Section"
This commit is contained in:
committed by
Android (Google) Code Review
commit
2888533e3c
@@ -58,6 +58,7 @@ cc_library {
|
||||
// runtime, as well as the only protos that are actually
|
||||
// needed by the device.
|
||||
srcs: [
|
||||
"core/proto/android/os/cpuinfo.proto",
|
||||
"core/proto/android/os/kernelwake.proto",
|
||||
"core/proto/android/os/pagetypeinfo.proto",
|
||||
"core/proto/android/os/procrank.proto",
|
||||
@@ -81,6 +82,7 @@ gensrcs {
|
||||
],
|
||||
|
||||
srcs: [
|
||||
"core/proto/android/os/cpuinfo.proto",
|
||||
"core/proto/android/os/kernelwake.proto",
|
||||
"core/proto/android/os/pagetypeinfo.proto",
|
||||
"core/proto/android/os/procrank.proto",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
line->assign(trim(line->substr(j)));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (mFile != NULL) fclose(mFile);
|
||||
}
|
||||
|
||||
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 Reader::readLine(std::string* line) {
|
||||
if (mFile == NULL) return false;
|
||||
|
||||
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;
|
||||
}
|
||||
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()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
Table::insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value)
|
||||
void
|
||||
Table::addEnumTypeMap(const char* field, const char* enumNames[], const uint32_t enumValues[], const int enumSize)
|
||||
{
|
||||
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, 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)
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
161
cmds/incident_helper/src/parsers/CpuInfoParser.cpp
Normal file
161
cmds/incident_helper/src/parsers/CpuInfoParser.cpp
Normal file
@@ -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;
|
||||
}
|
||||
36
cmds/incident_helper/src/parsers/CpuInfoParser.h
Normal file
36
cmds/incident_helper/src/parsers/CpuInfoParser.h
Normal file
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
15
cmds/incident_helper/testdata/cpuinfo.txt
vendored
Normal file
15
cmds/incident_helper/testdata/cpuinfo.txt
vendored
Normal file
@@ -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]
|
||||
158
cmds/incident_helper/tests/CpuInfoParser_test.cpp
Normal file
158
cmds/incident_helper/tests/CpuInfoParser_test.cpp
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
110
core/proto/android/os/cpuinfo.proto
Normal file
110
core/proto/android/os/cpuinfo.proto
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
syntax = "proto2";
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_outer_classname = "CpuInfoProto";
|
||||
|
||||
import "frameworks/base/tools/streaming_proto/stream.proto";
|
||||
|
||||
package android.os;
|
||||
|
||||
/**
|
||||
* Data structure of the linux command
|
||||
* 'top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name'
|
||||
*
|
||||
* Next Tag: 6
|
||||
*/
|
||||
message CpuInfo {
|
||||
|
||||
message TaskStats {
|
||||
option (stream_proto.stream_msg).enable_fields_mapping = true;
|
||||
|
||||
optional int32 total = 1; // total number of cpu tasks
|
||||
optional int32 running = 2; // number of running tasks
|
||||
optional int32 sleeping = 3; // number of sleeping tasks
|
||||
optional int32 stopped = 4; // number of stopped tasks
|
||||
optional int32 zombie = 5; // number of zombie tasks
|
||||
}
|
||||
optional TaskStats task_stats = 1;
|
||||
|
||||
message MemStats { // unit in kB
|
||||
option (stream_proto.stream_msg).enable_fields_mapping = true;
|
||||
|
||||
optional int32 total = 1;
|
||||
optional int32 used = 2;
|
||||
optional int32 free = 3;
|
||||
optional int32 buffers = 4;
|
||||
optional int32 cached = 5;
|
||||
}
|
||||
optional MemStats mem = 2;
|
||||
optional MemStats swap = 3;
|
||||
|
||||
message CpuUsage { // unit is percentage %
|
||||
option (stream_proto.stream_msg).enable_fields_mapping = true;
|
||||
|
||||
optional int32 cpu = 1; // 400% cpu indicates 4 cores
|
||||
optional int32 user = 2;
|
||||
optional int32 nice = 3;
|
||||
optional int32 sys = 4;
|
||||
optional int32 idle = 5;
|
||||
optional int32 iow = 6;
|
||||
optional int32 irq = 7;
|
||||
optional int32 sirq = 8;
|
||||
optional int32 host = 9;
|
||||
}
|
||||
optional CpuUsage cpu_usage = 4;
|
||||
|
||||
// Next Tag: 13
|
||||
message Task {
|
||||
option (stream_proto.stream_msg).enable_fields_mapping = true;
|
||||
|
||||
optional int32 pid = 1;
|
||||
optional int32 tid = 2;
|
||||
optional string user = 3;
|
||||
optional string pr = 4; // priority of each task, using string type is because special value RT (real time)
|
||||
optional sint32 ni = 5; // niceness value
|
||||
optional float cpu = 6; // precentage of cpu usage of the task
|
||||
|
||||
enum Status {
|
||||
option (stream_proto.stream_enum).enable_enums_mapping = true;
|
||||
|
||||
STATUS_UNKNOWN = 0;
|
||||
STATUS_D = 1; // uninterruptible sleep
|
||||
STATUS_R = 2; // running
|
||||
STATUS_S = 3; // sleeping
|
||||
STATUS_T = 4; // traced or stopped
|
||||
STATUS_Z = 5; // zombie
|
||||
}
|
||||
optional Status s = 7; // process status
|
||||
optional string virt = 8; // virtual memory size, i.e. 14.0G, 13.5M
|
||||
optional string res = 9; // Resident size, i.e. 0, 3.1G
|
||||
|
||||
// How Android memory manager will treat the task
|
||||
enum Policy {
|
||||
option (stream_proto.stream_enum).enable_enums_mapping = true;
|
||||
|
||||
POLICY_UNKNOWN = 0;
|
||||
POLICY_fg = 1; // foreground, the name is lower case for parsing the value
|
||||
POLICY_bg = 2; // background, the name is lower case for parsing the value
|
||||
POLICY_ta = 3; // TODO: figure out what is this value
|
||||
}
|
||||
optional Policy pcy = 10; // Policy of the task
|
||||
optional string cmd = 11; // thread name
|
||||
optional string name = 12; // program name
|
||||
}
|
||||
repeated Task tasks = 5;
|
||||
}
|
||||
@@ -21,6 +21,7 @@ option java_outer_classname = "IncidentProtoMetadata";
|
||||
import "frameworks/base/libs/incident/proto/android/privacy.proto";
|
||||
import "frameworks/base/libs/incident/proto/android/section.proto";
|
||||
import "frameworks/base/core/proto/android/providers/settings.proto";
|
||||
import "frameworks/base/core/proto/android/os/cpuinfo.proto";
|
||||
import "frameworks/base/core/proto/android/os/incidentheader.proto";
|
||||
import "frameworks/base/core/proto/android/os/kernelwake.proto";
|
||||
import "frameworks/base/core/proto/android/os/pagetypeinfo.proto";
|
||||
@@ -68,6 +69,11 @@ message IncidentProto {
|
||||
(section).args = "/d/wakeup_sources"
|
||||
];
|
||||
|
||||
optional CpuInfo cpu_info = 2003 [
|
||||
(section).type = SECTION_COMMAND,
|
||||
(section).args = "/system/bin/top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
|
||||
];
|
||||
|
||||
|
||||
// System Services
|
||||
optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
|
||||
|
||||
@@ -29,7 +29,7 @@ message KernelWakeSources {
|
||||
|
||||
// Next Tag: 11
|
||||
message WakeupSourceProto {
|
||||
option (stream_proto.stream).enable_fields_mapping = true;
|
||||
option (stream_proto.stream_msg).enable_fields_mapping = true;
|
||||
|
||||
// Name of the event which triggers application processor
|
||||
optional string name = 1;
|
||||
|
||||
@@ -63,7 +63,7 @@ message MigrateTypeProto {
|
||||
|
||||
// Next tag: 9
|
||||
message BlockProto {
|
||||
option (stream_proto.stream).enable_fields_mapping = true;
|
||||
option (stream_proto.stream_msg).enable_fields_mapping = true;
|
||||
|
||||
optional int32 node = 1;
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ message Procrank {
|
||||
|
||||
// Next Tag: 11
|
||||
message ProcessProto {
|
||||
option (stream_proto.stream).enable_fields_mapping = true;
|
||||
option (stream_proto.stream_msg).enable_fields_mapping = true;
|
||||
|
||||
// ID of the process
|
||||
optional int32 pid = 1;
|
||||
|
||||
@@ -18,6 +18,12 @@ make_filename(const FileDescriptorProto& file_descriptor)
|
||||
return file_descriptor.name() + ".h";
|
||||
}
|
||||
|
||||
static inline bool
|
||||
should_generate_enums_mapping(const EnumDescriptorProto& enu)
|
||||
{
|
||||
return enu.options().GetExtension(stream_enum).enable_enums_mapping();
|
||||
}
|
||||
|
||||
static void
|
||||
write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
|
||||
{
|
||||
@@ -29,6 +35,23 @@ write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& ind
|
||||
<< make_constant_name(value.name())
|
||||
<< " = " << value.number() << ";" << endl;
|
||||
}
|
||||
|
||||
if (should_generate_enums_mapping(enu)) {
|
||||
string name = make_constant_name(enu.name());
|
||||
string prefix = name + "_";
|
||||
text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
|
||||
text << indent << "const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl;
|
||||
for (int i=0; i<N; i++) {
|
||||
text << indent << INDENT << "\"" << stripPrefix(enu.value(i).name(), prefix) << "\"," << endl;
|
||||
}
|
||||
text << indent << "};" << endl;
|
||||
text << indent << "const uint32_t _ENUM_" << name << "_VALUES[" << N << "] = {" << endl;
|
||||
for (int i=0; i<N; i++) {
|
||||
text << indent << INDENT << make_constant_name(enu.value(i).name()) << "," << endl;
|
||||
}
|
||||
text << indent << "};" << endl;
|
||||
}
|
||||
|
||||
text << endl;
|
||||
}
|
||||
|
||||
@@ -59,7 +82,7 @@ write_field(stringstream& text, const FieldDescriptorProto& field, const string&
|
||||
static inline bool
|
||||
should_generate_fields_mapping(const DescriptorProto& message)
|
||||
{
|
||||
return message.options().GetExtension(stream).enable_fields_mapping();
|
||||
return message.options().GetExtension(stream_msg).enable_fields_mapping();
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -21,12 +21,22 @@ import "google/protobuf/descriptor.proto";
|
||||
package android.stream_proto;
|
||||
|
||||
// This option tells streaming proto plugin to compile .proto files with extra features.
|
||||
message StreamFlags {
|
||||
message MessageOptions {
|
||||
// creates a mapping of field names of the message to its field ids
|
||||
optional bool enable_fields_mapping = 1;
|
||||
}
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
// Flags used by streaming proto plugins
|
||||
optional StreamFlags stream = 126856794;
|
||||
optional MessageOptions stream_msg = 126856794;
|
||||
}
|
||||
|
||||
message EnumOptions {
|
||||
// creates a mapping of enum names to its values, strip its prefix enum type for each value
|
||||
optional bool enable_enums_mapping = 1;
|
||||
}
|
||||
|
||||
extend google.protobuf.EnumOptions {
|
||||
// Flags used by streaming proto plugins
|
||||
optional EnumOptions stream_enum = 126856794;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,17 @@ split(const string& str, const char delimiter)
|
||||
return result;
|
||||
}
|
||||
|
||||
string
|
||||
stripPrefix(const string& str, const string& prefix)
|
||||
{
|
||||
if (str.size() <= prefix.size()) return str;
|
||||
size_t i = 0, len = prefix.size();
|
||||
for (; i<len; i++) {
|
||||
if (str[i] != prefix[i]) return str;
|
||||
}
|
||||
return str.substr(i);
|
||||
}
|
||||
|
||||
} // namespace stream_proto
|
||||
} // namespace android
|
||||
|
||||
|
||||
@@ -26,15 +26,20 @@ string make_constant_name(const string& str);
|
||||
string file_base_name(const string& str);
|
||||
|
||||
/**
|
||||
* Replace all occurances of 'replace' with 'with'.
|
||||
* Replaces all occurances of 'replace' with 'with'.
|
||||
*/
|
||||
string replace_string(const string& str, const char replace, const char with);
|
||||
|
||||
/**
|
||||
* Split a string to parts by delimiter.
|
||||
* Splits a string to parts by delimiter.
|
||||
*/
|
||||
vector<string> split(const string& str, const char delimiter);
|
||||
|
||||
/**
|
||||
* Returns the rest of str if it has prefix, otherwise return all.
|
||||
*/
|
||||
string stripPrefix(const string& str, const string& prefix);
|
||||
|
||||
} // namespace stream_proto
|
||||
} // namespace android
|
||||
|
||||
|
||||
Reference in New Issue
Block a user