diff options
author | Yi Jin <jinyithu@google.com> | 2017-11-01 17:08:27 -0700 |
---|---|---|
committer | Yi Jin <jinyithu@google.com> | 2017-11-10 17:34:07 -0800 |
commit | e2f7f79d023f0b3ba2fee374492dde61f525ece6 (patch) | |
tree | 8ea7efe3b552f1cb23c2f4c2ef379be6fb02f19b | |
parent | 9a753af26b2ce27c10ad215aa70cf1bcd44d7915 (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
21 files changed, 813 insertions, 152 deletions
diff --git a/Android.bp b/Android.bp index 5c1ccb7cc639..2c4963c91583 100644 --- a/Android.bp +++ b/Android.bp @@ -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", 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")); } diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto new file mode 100644 index 000000000000..a95fa57ac2b2 --- /dev/null +++ b/core/proto/android/os/cpuinfo.proto @@ -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; +} diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index e998b09d18ee..55ea28527dc1 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -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 [ diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto index d032a452c14e..eaad37af2ebc 100644 --- a/core/proto/android/os/kernelwake.proto +++ b/core/proto/android/os/kernelwake.proto @@ -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; diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto index 22b3d730f1a1..b86ee01f31b7 100644 --- a/core/proto/android/os/pagetypeinfo.proto +++ b/core/proto/android/os/pagetypeinfo.proto @@ -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; diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto index 4d62a60c8345..9945f2e792a9 100644 --- a/core/proto/android/os/procrank.proto +++ b/core/proto/android/os/procrank.proto @@ -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; diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp index 481698432711..9aef56270ee2 100644 --- a/tools/streaming_proto/cpp/main.cpp +++ b/tools/streaming_proto/cpp/main.cpp @@ -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 diff --git a/tools/streaming_proto/stream.proto b/tools/streaming_proto/stream.proto index 123506c03cfd..c08120986cc7 100644 --- a/tools/streaming_proto/stream.proto +++ b/tools/streaming_proto/stream.proto @@ -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; } diff --git a/tools/streaming_proto/string_utils.cpp b/tools/streaming_proto/string_utils.cpp index bd34ab7aa44d..607d820033ff 100644 --- a/tools/streaming_proto/string_utils.cpp +++ b/tools/streaming_proto/string_utils.cpp @@ -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 diff --git a/tools/streaming_proto/string_utils.h b/tools/streaming_proto/string_utils.h index d6f195f67188..315b27531afd 100644 --- a/tools/streaming_proto/string_utils.h +++ b/tools/streaming_proto/string_utils.h @@ -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 |