Loading Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -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", Loading @@ -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", Loading cmds/incident_helper/src/ih_util.cpp +146 −76 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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) { Loading @@ -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() { } Loading @@ -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; Loading cmds/incident_helper/src/ih_util.h +45 −17 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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 Loading @@ -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. Loading @@ -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; }; /** Loading @@ -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 cmds/incident_helper/src/main.cpp +3 −0 Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -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; } Loading cmds/incident_helper/src/parsers/CpuInfoParser.cpp 0 → 100644 +161 −0 Original line number Diff line number Diff line /* * 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; } Loading
Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -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", Loading @@ -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", Loading
cmds/incident_helper/src/ih_util.cpp +146 −76 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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) { Loading @@ -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() { } Loading @@ -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; Loading
cmds/incident_helper/src/ih_util.h +45 −17 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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 Loading @@ -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. Loading @@ -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; }; /** Loading @@ -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
cmds/incident_helper/src/main.cpp +3 −0 Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -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; } Loading
cmds/incident_helper/src/parsers/CpuInfoParser.cpp 0 → 100644 +161 −0 Original line number Diff line number Diff line /* * 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; }