diff options
Diffstat (limited to 'cmds/incident_helper/src')
23 files changed, 2248 insertions, 0 deletions
diff --git a/cmds/incident_helper/src/TextParserBase.cpp b/cmds/incident_helper/src/TextParserBase.cpp new file mode 100644 index 000000000000..a8f9968ee8f6 --- /dev/null +++ b/cmds/incident_helper/src/TextParserBase.cpp @@ -0,0 +1,56 @@ +/* + * 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 "TextParserBase.h" + +#include <android-base/file.h> + +using namespace android::base; +using namespace std; + +// ================================================================================ +status_t NoopParser::Parse(const int in, const int out) const +{ + string content; + if (!ReadFdToString(in, &content)) { + fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string()); + return -1; + } + if (!WriteStringToFd(content, out)) { + fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string()); + return -1; + } + return NO_ERROR; +} + +// ================================================================================ +status_t ReverseParser::Parse(const int in, const int out) const +{ + string content; + if (!ReadFdToString(in, &content)) { + fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string()); + return -1; + } + // reverse the content + reverse(content.begin(), content.end()); + if (!WriteStringToFd(content, out)) { + fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string()); + return -1; + } + return NO_ERROR; +}
\ No newline at end of file diff --git a/cmds/incident_helper/src/TextParserBase.h b/cmds/incident_helper/src/TextParserBase.h new file mode 100644 index 000000000000..166796673e25 --- /dev/null +++ b/cmds/incident_helper/src/TextParserBase.h @@ -0,0 +1,71 @@ +/* + * 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 TEXT_PARSER_BASE_H +#define TEXT_PARSER_BASE_H + +#include <utils/Errors.h> +#include <utils/String8.h> + +using namespace android; + +/** + * Base class for text parser + */ +class TextParserBase { +public: + String8 name; + + TextParserBase(String8 name) : name(name) {}; + virtual ~TextParserBase() {}; + + virtual status_t Parse(const int in, const int out) const = 0; +}; + +/** + * No op parser returns what it reads + */ +class NoopParser : public TextParserBase { +public: + NoopParser() : TextParserBase(String8("NoopParser")) {}; + ~NoopParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +/** + * This parser is used for testing only, results in timeout. + */ +class TimeoutParser : public TextParserBase { +public: + TimeoutParser() : TextParserBase(String8("TimeoutParser")) {}; + ~TimeoutParser() {}; + + virtual status_t Parse(const int /** in */, const int /** out */) const { while (true); }; +}; + +/** + * This parser is used for testing only, results in reversed input text. + */ +class ReverseParser : public TextParserBase { +public: + ReverseParser() : TextParserBase(String8("ReverseParser")) {}; + ~ReverseParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // TEXT_PARSER_BASE_H diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp new file mode 100644 index 000000000000..847b26a39ffe --- /dev/null +++ b/cmds/incident_helper/src/ih_util.cpp @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incident_helper" + +#include "ih_util.h" + +#include <algorithm> +#include <sstream> +#include <unistd.h> + +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)'_'); +} + +std::string trim(const std::string& s, const std::string& charset) { + const auto head = s.find_first_not_of(charset); + if (head == std::string::npos) return ""; + + const auto tail = s.find_last_not_of(charset); + return s.substr(head, tail - head + 1); +} + +static inline std::string toLowerStr(const std::string& s) { + std::string res(s); + std::transform(res.begin(), res.end(), res.begin(), ::tolower); + return res; +} + +static inline std::string trimDefault(const std::string& s) { + return trim(s, DEFAULT_WHITESPACE); +} + +static inline std::string trimHeader(const std::string& s) { + return toLowerStr(trimDefault(s)); +} + +static inline bool isNumber(const std::string& s) { + std::string::const_iterator it = s.begin(); + while (it != s.end() && std::isdigit(*it)) ++it; + return !s.empty() && it == s.end(); +} + +// This is similiar to Split in android-base/file.h, but it won't add empty string +static void split(const std::string& line, std::vector<std::string>& words, + const trans_func& func, const std::string& delimiters) { + words.clear(); // clear the buffer before split + + size_t base = 0; + size_t found; + while (true) { + found = line.find_first_of(delimiters, base); + if (found != base) { + std::string word = (*func) (line.substr(base, found - base)); + if (!word.empty()) { + words.push_back(word); + } + } + if (found == line.npos) break; + base = found + 1; + } +} + +header_t parseHeader(const std::string& line, const std::string& delimiters) { + header_t header; + trans_func f = &trimHeader; + split(line, header, f, delimiters); + return header; +} + +record_t parseRecord(const std::string& line, const std::string& delimiters) { + record_t record; + trans_func f = &trimDefault; + split(line, record, f, delimiters); + return record; +} + +bool getColumnIndices(std::vector<int>& indices, const char** headerNames, const std::string& line) { + indices.clear(); + + size_t lastIndex = 0; + int i = 0; + while (headerNames[i] != NULL) { + string s = headerNames[i]; + lastIndex = line.find(s, lastIndex); + if (lastIndex == string::npos) { + fprintf(stderr, "Bad Task Header: %s\n", line.c_str()); + return false; + } + lastIndex += s.length(); + indices.push_back(lastIndex); + i++; + } + + return true; +} + +record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) { + record_t record; + int lastIndex = 0; + int lastBeginning = 0; + int lineSize = (int)line.size(); + for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) { + int idx = *it; + if (idx <= lastIndex) { + // We saved up until lastIndex last time, so we should start at + // lastIndex + 1 this time. + idx = lastIndex + 1; + } + if (idx > lineSize) { + if (lastIndex < idx && lastIndex < lineSize) { + // There's a little bit more for us to save, which we'll do + // outside of the loop. + break; + } + // If we're past the end of the line AND we've already saved everything up to the end. + fprintf(stderr, "index wrong: lastIndex: %d, idx: %d, lineSize: %d\n", lastIndex, idx, lineSize); + record.clear(); // The indices are wrong, return empty. + return record; + } + while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos); + record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex))); + lastBeginning = lastIndex; + lastIndex = idx; + } + if (lineSize - lastIndex > 0) { + int beginning = lastIndex; + if (record.size() == indices.size()) { + // We've already encountered all of the columns...put whatever is + // left in the last column. + record.pop_back(); + beginning = lastBeginning; + } + record.push_back(trimDefault(line.substr(beginning, lineSize - beginning))); + } + return record; +} + +void printRecord(const record_t& record) { + fprintf(stderr, "Record: { "); + if (record.size() == 0) { + fprintf(stderr, "}\n"); + return; + } + for(size_t i = 0; i < record.size(); ++i) { + if(i != 0) fprintf(stderr, "\", "); + fprintf(stderr, "\"%s", record[i].c_str()); + } + fprintf(stderr, "\" }\n"); +} + +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; + int len = (int)line->length(); + int i = 0; + int j = head; + while (key[i] != '\0') { + 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; + } + } + + 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; +} + +std::string behead(std::string* line, const char cut) { + auto found = line->find_first_of(cut); + if (found == std::string::npos) { + std::string head = line->substr(0); + line->assign(""); + return head; + } + std::string head = line->substr(0, found); + while(line->at(found) == cut) found++; // trim more cut of the rest + line->assign(line->substr(found)); + return head; +} + +int toInt(const std::string& s) { + return atoi(s.c_str()); +} + +long long toLongLong(const std::string& s) { + return atoll(s.c_str()); +} + +double toDouble(const std::string& s) { + return atof(s.c_str()); +} + +// ============================================================================== +Reader::Reader(const int fd) +{ + mFile = fdopen(fd, "r"); + mStatus = mFile == NULL ? "Invalid fd " + std::to_string(fd) : ""; +} + +Reader::~Reader() +{ + if (mFile != NULL) fclose(mFile); +} + +bool Reader::readLine(std::string* line) { + if (mFile == NULL) return false; + + 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"; + } + free(buf); + return read != -1; +} + +bool Reader::ok(std::string* error) { + error->assign(mStatus); + return mStatus.empty(); +} + +// ============================================================================== +Table::Table(const char* names[], const uint64_t ids[], const int count) + :mEnums(), + mEnumValuesByName() +{ + map<std::string, uint64_t> fields; + for (int i = 0; i < count; i++) { + fields[names[i]] = ids[i]; + } + mFields = fields; +} + +Table::~Table() +{ +} + +void +Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize) +{ + if (mFields.find(field) == mFields.end()) { + fprintf(stderr, "Field '%s' not found", string(field).c_str()); + return; + } + + map<std::string, int> enu; + for (int i = 0; i < enumSize; i++) { + enu[enumNames[i]] = enumValues[i]; + } + mEnums[field] = enu; +} + +void +Table::addEnumNameToValue(const char* enumName, const int enumValue) +{ + mEnumValuesByName[enumName] = enumValue; +} + +bool +Table::insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value) +{ + if (mFields.find(name) == mFields.end()) return false; + + uint64_t found = mFields[name]; + record_t repeats; // used for repeated fields + switch ((found & FIELD_COUNT_MASK) | (found & FIELD_TYPE_MASK)) { + case FIELD_COUNT_SINGLE | FIELD_TYPE_DOUBLE: + case FIELD_COUNT_SINGLE | FIELD_TYPE_FLOAT: + proto->write(found, toDouble(value)); + break; + case FIELD_COUNT_SINGLE | FIELD_TYPE_STRING: + case FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES: + proto->write(found, value); + break; + case FIELD_COUNT_SINGLE | FIELD_TYPE_INT64: + case FIELD_COUNT_SINGLE | FIELD_TYPE_SINT64: + case FIELD_COUNT_SINGLE | FIELD_TYPE_UINT64: + case FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED64: + case FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED64: + proto->write(found, toLongLong(value)); + break; + case FIELD_COUNT_SINGLE | FIELD_TYPE_BOOL: + if (strcmp(toLowerStr(value).c_str(), "true") == 0 || strcmp(value.c_str(), "1") == 0) { + proto->write(found, true); + break; + } + if (strcmp(toLowerStr(value).c_str(), "false") == 0 || strcmp(value.c_str(), "0") == 0) { + proto->write(found, false); + break; + } + return false; + case FIELD_COUNT_SINGLE | FIELD_TYPE_ENUM: + // if the field has its own enum mapping, use this, otherwise use general name to value mapping. + if (mEnums.find(name) != mEnums.end()) { + if (mEnums[name].find(value) != mEnums[name].end()) { + proto->write(found, mEnums[name][value]); + } else { + proto->write(found, 0); // TODO: should get the default enum value (Unknown) + } + } else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) { + proto->write(found, mEnumValuesByName[value]); + } else if (isNumber(value)) { + proto->write(found, toInt(value)); + } else { + return false; + } + break; + case FIELD_COUNT_SINGLE | FIELD_TYPE_INT32: + case FIELD_COUNT_SINGLE | FIELD_TYPE_SINT32: + case FIELD_COUNT_SINGLE | FIELD_TYPE_UINT32: + case FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED32: + case FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED32: + proto->write(found, toInt(value)); + break; + // REPEATED TYPE below: + case FIELD_COUNT_REPEATED | FIELD_TYPE_INT32: + repeats = parseRecord(value, COMMA_DELIMITER); + for (size_t i=0; i<repeats.size(); i++) { + proto->write(found, toInt(repeats[i])); + } + break; + case FIELD_COUNT_REPEATED | FIELD_TYPE_STRING: + repeats = parseRecord(value, COMMA_DELIMITER); + for (size_t i=0; i<repeats.size(); i++) { + proto->write(found, repeats[i]); + } + break; + default: + return false; + } + return true; +} + +// ================================================================================ +Message::Message(Table* table) + :mTable(table), + mPreviousField(""), + mTokens(), + mSubMessages() +{ +} + +Message::~Message() +{ +} + +void +Message::addSubMessage(uint64_t fieldId, Message* fieldMsg) +{ + for (auto iter = mTable->mFields.begin(); iter != mTable->mFields.end(); iter++) { + if (iter->second == fieldId) { + mSubMessages[iter->first] = fieldMsg; + return; + } + } +} + +bool +Message::insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value) +{ + // If the field name can be found, it means the name is a primitive field. + if (mTable->mFields.find(name) != mTable->mFields.end()) { + endSession(proto); + // The only edge case is for example ro.hardware itself is a message, so a field called "value" + // would be defined in proto Ro::Hardware and it must be the first field. + if (mSubMessages.find(name) != mSubMessages.end()) { + startSession(proto, name); + return mSubMessages[name]->insertField(proto, "value", value); + } else { + return mTable->insertField(proto, name, value); + } + } + + // Try to find the message field which is the prefix of name, so the value would be inserted + // recursively into the submessage. + string mutableName = name; + for (auto iter = mSubMessages.begin(); iter != mSubMessages.end(); iter++) { + string fieldName = iter->first; + string prefix = fieldName + "_"; // underscore is the delimiter in the name + if (stripPrefix(&mutableName, prefix.c_str())) { + if (mPreviousField != fieldName) { + endSession(proto); + startSession(proto, fieldName); + } + return mSubMessages[fieldName]->insertField(proto, mutableName, value); + } + } + // Can't find the name in proto definition, handle it separately. + return false; +} + +void +Message::startSession(ProtoOutputStream* proto, const string& name) +{ + uint64_t fieldId = mTable->mFields[name]; + long long token = proto->start(fieldId); + mPreviousField = name; + mTokens.push(token); +} + +void +Message::endSession(ProtoOutputStream* proto) +{ + if (mPreviousField == "") return; + if (mSubMessages.find(mPreviousField) != mSubMessages.end()) { + mSubMessages[mPreviousField]->endSession(proto); + } + proto->end(mTokens.top()); + mTokens.pop(); + mPreviousField = ""; +} diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h new file mode 100644 index 000000000000..53f443873e4d --- /dev/null +++ b/cmds/incident_helper/src/ih_util.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCIDENT_HELPER_UTIL_H +#define INCIDENT_HELPER_UTIL_H + +#include <map> +#include <stack> +#include <string> +#include <vector> + +#include <android/util/ProtoOutputStream.h> + +using namespace android::util; + +typedef std::vector<std::string> header_t; +typedef std::vector<std::string> record_t; +typedef std::string (*trans_func) (const std::string&); + +const std::string DEFAULT_WHITESPACE = " \t"; +const std::string DEFAULT_NEWLINE = "\r\n"; +const std::string TAB_DELIMITER = "\t"; +const std::string COMMA_DELIMITER = ","; +const std::string PIPE_DELIMITER = "|"; +const std::string PARENTHESES_DELIMITER = "()"; + +// returns true if c is a-zA-Z0-9 or underscore +bool isValidChar(char c); + +// trim the string with the given charset +std::string trim(const std::string& s, const std::string& charset); + +/** + * When a text has a table format like this + * line 1: HeadA HeadB HeadC + * line 2: v1 v2 v3 + * line 3: v11 v12 v13 + * + * We want to parse the line in structure given the delimiter. + * parseHeader is used to parse the firse line of the table and returns a list of strings in lower case + * parseRecord is used to parse other lines and returns a list of strings + * empty strings are skipped + */ +header_t parseHeader(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE); +record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE); + +/** + * Gets the list of end indices of each word in the line and places it in the given vector, + * clearing out the vector beforehand. These indices can be used with parseRecordByColumns. + * Will return false if there was a problem getting the indices. headerNames + * must be NULL terminated. + */ +bool getColumnIndices(std::vector<int>& indices, const char* headerNames[], const std::string& line); + +/** + * 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); + +/** Prints record_t to stderr */ +void printRecord(const record_t& record); + +/** + * 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 stripPrefix(std::string* line, const char* key, bool endAtDelimiter = false); +bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter = false); + +/** + * behead the given line by the cut, return the head and reassign the line to be the rest. + */ +std::string behead(std::string* line, const char cut); + +/** + * 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. + * The buffer size is controlled by capacity parameter. + */ +class Reader +{ +public: + Reader(const int fd); + ~Reader(); + + bool readLine(std::string* line); + bool ok(std::string* error); + +private: + FILE* mFile; + std::string mStatus; +}; + +/** + * The Table class is constructed from two arrays generated by the given message with + * option (stream_proto.stream_msg).enable_fields_mapping = true. + * The names are each field's names in the message and must corresponding to the header/name of + * the text to be parsed, and the ids are the streaming proto encoded field ids. + * + * This class then allows users to insert the table values to proto based on its header. + * + * Advance feature: if some fields in the message are enums, user must explicitly add the + * mapping from enum name string to its enum values. + */ +class Message; +class Table +{ +friend class Message; +public: + Table(const char* names[], const uint64_t ids[], const int count); + ~Table(); + + // Add enum names to values for parsing purpose. + void addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize); + + // Manually add enum names to values mapping, useful when an Enum type is used by + // a number of fields, there must not be any enum name conflicts. + void addEnumNameToValue(const char* enumName, const int enumValue); + + // Based on given name, find the right field id, parse the text value and insert to proto. + // Return false if the given name can't be found. + bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value); +private: + map<std::string, uint64_t> mFields; + map<std::string, map<std::string, int>> mEnums; + map<std::string, int> mEnumValuesByName; +}; + +/** + * Reconstructs a typical proto message given its message Table, adds submessage fields explicitly. + * It allows user to insert nested proto values purely by the names. See insertField for detail. + */ +class Message +{ +public: + Message(Table* table); + ~Message(); + + // Reconstructs the typical proto message by adding its message fields. + void addSubMessage(uint64_t fieldId, Message* fieldMsg); + + // Inserts value if the given name has the corresponding field in its message and return true. + // It will recursively search the name in submessages and find the correct field to insert. + // For example, when the name is dalvik_vm_heapsize, and the message's corresponding proto is: + // message Properties { + // message DalvikVm { + // int32 heapsize = 1; + // bool usejit = 2; + // } + // DalvikVm dalvik_vm = 1; + // string hack_in = 2; + // } + // The value will be inserted into field heapsize in dalvik_vm submessage. + // + // Also value belongs to same submessage MUST be inserted contiguously. + // For example, dalvik_vm_usejit must be inserted directly after dalvik_vm_heapsize, otherwise + // if hack_in attempts to be inserted before dalvik_vm_usejit, value of usejit isn't added as expected. + bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value); + + // Starts a new message field proto session. + void startSession(ProtoOutputStream* proto, const string& name); + + // Ends the previous message field proto session. + void endSession(ProtoOutputStream* proto); +private: + Table* mTable; + std::string mPreviousField; + stack<long long> mTokens; + map<std::string, Message*> mSubMessages; +}; + +#endif // INCIDENT_HELPER_UTIL_H diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp new file mode 100644 index 000000000000..418dc3fad761 --- /dev/null +++ b/cmds/incident_helper/src/main.cpp @@ -0,0 +1,113 @@ +/* + * 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 "parsers/BatteryTypeParser.h" +#include "parsers/CpuFreqParser.h" +#include "parsers/CpuInfoParser.h" +#include "parsers/EventLogTagsParser.h" +#include "parsers/KernelWakesParser.h" +#include "parsers/PageTypeInfoParser.h" +#include "parsers/ProcrankParser.h" +#include "parsers/PsParser.h" +#include "parsers/SystemPropertiesParser.h" + +#include <android-base/file.h> +#include <getopt.h> +#include <stdlib.h> +#include <unistd.h> + +using namespace android::base; +using namespace std; + +static void usage(FILE* out) { + fprintf(out, "incident_helper is not designed to run manually,"); + fprintf(out, "it reads from stdin and writes to stdout, see README.md for details.\n"); + fprintf(out, "usage: incident_helper -s SECTION\n"); + fprintf(out, "REQUIRED:\n"); + fprintf(out, " -s section id, must be positive\n"); +} + +//============================================================================= +static TextParserBase* selectParser(int section) { + switch (section) { + // IDs smaller than or equal to 0 are reserved for testing + case -1: + return new TimeoutParser(); + case 0: + return new NoopParser(); + case 1: // 1 is reserved for incident header so it won't be section id + return new ReverseParser(); +/* ========================================================================= */ + // IDs larger than 1 are section ids reserved in incident.proto + case 1000: + return new SystemPropertiesParser(); + case 1100: + return new EventLogTagsParser(); + case 2000: + return new ProcrankParser(); + case 2001: + return new PageTypeInfoParser(); + case 2002: + return new KernelWakesParser(); + case 2003: + return new CpuInfoParser(); + case 2004: + return new CpuFreqParser(); + case 2005: + return new PsParser(); + case 2006: + return new BatteryTypeParser(); + default: + return NULL; + } +} + +//============================================================================= +int main(int argc, char** argv) { + fprintf(stderr, "Start incident_helper...\n"); + + // Parse the args + int opt; + int sectionID = 0; + while ((opt = getopt(argc, argv, "hs:")) != -1) { + switch (opt) { + case 'h': + usage(stdout); + return 0; + case 's': + sectionID = atoi(optarg); + break; + } + } + + fprintf(stderr, "Pasring section %d...\n", sectionID); + TextParserBase* parser = selectParser(sectionID); + if (parser != NULL) { + fprintf(stderr, "Running parser: %s\n", parser->name.string()); + status_t err = parser->Parse(STDIN_FILENO, STDOUT_FILENO); + if (err != NO_ERROR) { + fprintf(stderr, "Parse error in section %d: %s\n", sectionID, strerror(-err)); + return -1; + } + + delete parser; + } + fprintf(stderr, "Finish section %d, exiting...\n", sectionID); + + return 0; +} diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp new file mode 100644 index 000000000000..ced6cf807e0d --- /dev/null +++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp @@ -0,0 +1,60 @@ +/* + * 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/batterytype.proto.h" +#include "ih_util.h" +#include "BatteryTypeParser.h" + +using namespace android::os; + +status_t +BatteryTypeParser::Parse(const int in, const int out) const +{ + Reader reader(in); + string line; + bool readLine = false; + + ProtoOutputStream proto; + + // parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + + if (readLine) { + fprintf(stderr, "Multiple lines in file. Unsure what to do.\n"); + break; + } + + proto.write(BatteryTypeProto::TYPE, line); + + readLine = true; + } + + 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/BatteryTypeParser.h b/cmds/incident_helper/src/parsers/BatteryTypeParser.h new file mode 100644 index 000000000000..ac0c098965d3 --- /dev/null +++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.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 BATTERY_TYPE_PARSER_H +#define BATTERY_TYPE_PARSER_H + +#include "TextParserBase.h" + +using namespace android; + +/** + * Battery type parser, parses text in file + * /sys/class/power_supply/bms/battery_type. + */ +class BatteryTypeParser : public TextParserBase { +public: + BatteryTypeParser() : TextParserBase(String8("BatteryTypeParser")) {}; + ~BatteryTypeParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // BATTERY_TYPE_PARSER_H diff --git a/cmds/incident_helper/src/parsers/CpuFreqParser.cpp b/cmds/incident_helper/src/parsers/CpuFreqParser.cpp new file mode 100644 index 000000000000..02f1ce7cc0fc --- /dev/null +++ b/cmds/incident_helper/src/parsers/CpuFreqParser.cpp @@ -0,0 +1,90 @@ +/* + * 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 <unistd.h> + +#include "frameworks/base/core/proto/android/os/cpufreq.proto.h" +#include "ih_util.h" +#include "CpuFreqParser.h" + +using namespace android::os; + +status_t +CpuFreqParser::Parse(const int in, const int out) const +{ + Reader reader(in); + string line; + + // parse header + reader.readLine(&line); + header_t header = parseHeader(line, TAB_DELIMITER); + if (header.size() < 1) { + fprintf(stderr, "Bad header: %s\n", line.c_str()); + return BAD_VALUE; + } + const int numCpus = (int)header.size() - 1; + vector<pair<int, long long>> cpucores[numCpus]; + + // parse freq and time + while (reader.readLine(&line)) { + if (line.empty()) continue; + + record_t record = parseRecord(line, TAB_DELIMITER); + if (record.size() != header.size()) { + fprintf(stderr, "Bad line: %s\n", line.c_str()); + continue; + } + + int freq = toInt(record[0]); + for (int i=0; i<numCpus; i++) { + if (strcmp(record[i+1].c_str(), "N/A") == 0) { + continue; + } + cpucores[i].push_back(make_pair(freq, toLongLong(record[i+1]))); + } + } + + ProtoOutputStream proto; + + long jiffyHz = sysconf(_SC_CLK_TCK); + proto.write(CpuFreq::JIFFY_HZ, (int)jiffyHz); + + for (int i=0; i<numCpus; i++) { + long long token = proto.start(CpuFreq::CPU_FREQS); + proto.write(CpuFreqStats::CPU_NAME, header[i+1]); + for (vector<pair<int, long long>>::iterator it = cpucores[i].begin(); it != cpucores[i].end(); it++) { + long long stateToken = proto.start(CpuFreqStats::TIMES); + proto.write(CpuFreqStats::TimeInState::STATE_KHZ, it->first); + proto.write(CpuFreqStats::TimeInState::TIME_JIFFY, it->second); + proto.end(stateToken); + } + 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/CpuFreqParser.h b/cmds/incident_helper/src/parsers/CpuFreqParser.h new file mode 100644 index 000000000000..470d56834781 --- /dev/null +++ b/cmds/incident_helper/src/parsers/CpuFreqParser.h @@ -0,0 +1,35 @@ +/* + * 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_FREQ_PARSER_H +#define CPU_FREQ_PARSER_H + +#include "TextParserBase.h" + +using namespace android; + +/** + * Cpu frequency parser, parses text in /sys/devices/system/cpu/cpufreq/all_time_in_state + */ +class CpuFreqParser : public TextParserBase { +public: + CpuFreqParser() : TextParserBase(String8("CpuFreqParser")) {}; + ~CpuFreqParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // CPU_FREQ_PARSER_H diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp new file mode 100644 index 000000000000..d73de54d8c5d --- /dev/null +++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp @@ -0,0 +1,162 @@ +/* + * 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; + int diff = 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 we need to modify the end of the CMD index. + const char* headerNames[] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD", NULL }; + if (!getColumnIndices(columnIndices, headerNames, line)) { + return -1; + } + // 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); + // Add NAME index to complete the column list. + columnIndices.push_back(columnIndices.back() + 4); + continue; + } + + record = parseRecordByColumns(line, columnIndices); + diff = record.size() - header.size(); + if (diff < 0) { + fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str()); + printRecord(record); + continue; + } else if (diff > 0) { + fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str()); + printRecord(record); + 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/EventLogTagsParser.cpp b/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp new file mode 100644 index 000000000000..73e37bd166cd --- /dev/null +++ b/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp @@ -0,0 +1,84 @@ +/* + * 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/util/event_log_tags.proto.h" +#include "ih_util.h" +#include "EventLogTagsParser.h" + +status_t +EventLogTagsParser::Parse(const int in, const int out) const +{ + Reader reader(in); + string line; + + ProtoOutputStream proto; + + // parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + string debug = line; + string tagNumber = behead(&line, ' '); + string tagName = behead(&line, ' '); + if (tagNumber == "" || tagName == "") { + fprintf(stderr, "Bad line, expect at least two parts: %s[%s, %s]\n", + debug.c_str(), tagNumber.c_str(), tagName.c_str()); + continue; + } + + long long token = proto.start(EventLogTagMapProto::EVENT_LOG_TAGS); + proto.write(EventLogTag::TAG_NUMBER, toInt(tagNumber)); + proto.write(EventLogTag::TAG_NAME, tagName); + + record_t valueDescriptors = parseRecord(line, PARENTHESES_DELIMITER); + for (size_t i = 0; i < valueDescriptors.size(); i++) { + record_t valueDescriptor = parseRecord(valueDescriptors[i], PIPE_DELIMITER); + if (valueDescriptor.size() != 2 && valueDescriptor.size() != 3) { + // If the parts doesn't contains pipe, then skips it. + continue; + } + long long descriptorToken = proto.start(EventLogTag::VALUE_DESCRIPTORS); + proto.write(EventLogTag::ValueDescriptor::NAME, valueDescriptor[0]); + proto.write(EventLogTag::ValueDescriptor::TYPE, toInt(valueDescriptor[1])); + if (valueDescriptor.size() == 3) { + char c = valueDescriptor[2][0]; + int unit = 0; + if (c < '0' || c > '9') { + unit = (int) c; + } else { + unit = toInt(valueDescriptor[2]); + } + proto.write(EventLogTag::ValueDescriptor::UNIT, unit); + } + proto.end(descriptorToken); + } + 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/EventLogTagsParser.h b/cmds/incident_helper/src/parsers/EventLogTagsParser.h new file mode 100644 index 000000000000..79057ce0b3ca --- /dev/null +++ b/cmds/incident_helper/src/parsers/EventLogTagsParser.h @@ -0,0 +1,35 @@ +/* + * 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 EVENT_LOG_TAGS_PARSER_H +#define EVENT_LOG_TAGS_PARSER_H + +#include "TextParserBase.h" + +using namespace android; + +/** + * event.logtags parser, parse file in /system/etc/event-log-tags + */ +class EventLogTagsParser : public TextParserBase { +public: + EventLogTagsParser() : TextParserBase(String8("EventLogTagsParser")) {}; + ~EventLogTagsParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // EVENT_LOG_TAGS_PARSER_H diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp new file mode 100644 index 000000000000..cae51abbe57f --- /dev/null +++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp @@ -0,0 +1,81 @@ +/* + * 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/kernelwake.proto.h" +#include "ih_util.h" +#include "KernelWakesParser.h" + +using namespace android::os; + +status_t +KernelWakesParser::Parse(const int in, const int out) const +{ + Reader reader(in); + string line; + header_t header; // the header of /d/wakeup_sources + record_t record; // retain each record + int nline = 0; + + ProtoOutputStream proto; + Table table(WakeupSourceProto::_FIELD_NAMES, WakeupSourceProto::_FIELD_IDS, WakeupSourceProto::_FIELD_COUNT); + + // parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + // parse head line + if (nline++ == 0) { + header = parseHeader(line, TAB_DELIMITER); + continue; + } + + // parse for each record, the line delimiter is \t only! + record = parseRecord(line, TAB_DELIMITER); + + if (record.size() < header.size()) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str()); + continue; + } else if (record.size() > header.size()) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.string(), nline, line.c_str()); + continue; + } + + 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])) { + fprintf(stderr, "[%s]Line %d has bad value %s of %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/KernelWakesParser.h b/cmds/incident_helper/src/parsers/KernelWakesParser.h new file mode 100644 index 000000000000..aabab7c64a4f --- /dev/null +++ b/cmds/incident_helper/src/parsers/KernelWakesParser.h @@ -0,0 +1,33 @@ +/* + * 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 KERNEL_WAKES_PARSER_H +#define KERNEL_WAKES_PARSER_H + +#include "TextParserBase.h" + +/** + * Kernel wakeup sources parser, parses text to protobuf in /d/wakeup_sources + */ +class KernelWakesParser : public TextParserBase { +public: + KernelWakesParser() : TextParserBase(String8("KernelWakeSources")) {}; + ~KernelWakesParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // KERNEL_WAKES_PARSER_H diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp new file mode 100644 index 000000000000..f1b93ff9ec41 --- /dev/null +++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp @@ -0,0 +1,125 @@ +/* + * 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/pagetypeinfo.proto.h" +#include "ih_util.h" +#include "PageTypeInfoParser.h" + +using namespace android::os; + +status_t +PageTypeInfoParser::Parse(const int in, const int out) const +{ + Reader reader(in); + string line; + bool migrateTypeSession = false; + int pageBlockOrder; + header_t blockHeader; + + ProtoOutputStream proto; + Table table(BlockProto::_FIELD_NAMES, BlockProto::_FIELD_IDS, BlockProto::_FIELD_COUNT); + + while (reader.readLine(&line)) { + if (line.empty()) { + migrateTypeSession = false; + blockHeader.clear(); + continue; + } + + if (stripPrefix(&line, "Page block order:")) { + pageBlockOrder = toInt(line); + proto.write(PageTypeInfo::PAGE_BLOCK_ORDER, pageBlockOrder); + continue; + } + if (stripPrefix(&line, "Pages per block:")) { + proto.write(PageTypeInfo::PAGES_PER_BLOCK, toInt(line)); + continue; + } + if (stripPrefix(&line, "Free pages count per migrate type at order")) { + migrateTypeSession = true; + continue; + } + if (stripPrefix(&line, "Number of blocks type")) { + blockHeader = parseHeader(line); + continue; + } + + 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 (stripPrefix(&record[0], "Node")) { + proto.write(MigrateTypeProto::NODE, toInt(record[0])); + } else return BAD_VALUE; + // expect part 1 starts with "zone" + if (stripPrefix(&record[1], "zone")) { + proto.write(MigrateTypeProto::ZONE, record[1]); + } else return BAD_VALUE; + // expect part 2 starts with "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 + // record line: Unmovable 426 279 226 1 1 1 0 0 2 2 0 + // The pageBlockOrder = 10 and it's zero-indexed. so total parts + // are 10 + 1(zero-indexed) + 1(the type part) = 12. + record_t pageCounts = parseRecord(record[2]); + int pageCountsSize = pageBlockOrder + 2; + if ((int)pageCounts.size() != pageCountsSize) return BAD_VALUE; + + proto.write(MigrateTypeProto::TYPE, pageCounts[0]); + for (auto i=1; i<pageCountsSize; i++) { + proto.write(MigrateTypeProto::FREE_PAGES_COUNT, toInt(pageCounts[i])); + } + } else return BAD_VALUE; + + proto.end(token); + } else if (!blockHeader.empty() && record.size() == 2) { + long long token = proto.start(PageTypeInfo::BLOCKS); + if (stripPrefix(&record[0], "Node")) { + proto.write(BlockProto::NODE, toInt(record[0])); + } else return BAD_VALUE; + + 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])) { + return BAD_VALUE; + } + } + } else return BAD_VALUE; + 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; +}
\ No newline at end of file diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.h b/cmds/incident_helper/src/parsers/PageTypeInfoParser.h new file mode 100644 index 000000000000..fb84d912a5f2 --- /dev/null +++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.h @@ -0,0 +1,35 @@ +/* + * 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 PAGE_TYPE_INFO_PARSER_H +#define PAGE_TYPE_INFO_PARSER_H + +#include "TextParserBase.h" + +using namespace android; + +/** + * PageTypeInfo parser, parses text to protobuf in /proc/pageinfotype + */ +class PageTypeInfoParser : public TextParserBase { +public: + PageTypeInfoParser() : TextParserBase(String8("PageTypeInfo")) {}; + ~PageTypeInfoParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // PAGE_TYPE_INFO_PARSER_H diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.cpp b/cmds/incident_helper/src/parsers/ProcrankParser.cpp new file mode 100644 index 000000000000..a4eb0fdfd988 --- /dev/null +++ b/cmds/incident_helper/src/parsers/ProcrankParser.cpp @@ -0,0 +1,112 @@ +/* + * 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/procrank.proto.h" +#include "ih_util.h" +#include "ProcrankParser.h" + +using namespace android::os; + +status_t +ProcrankParser::Parse(const int in, const int out) const +{ + Reader reader(in); + string line; + header_t header; // the header of /d/wakeup_sources + record_t record; // retain each record + int nline = 0; + + ProtoOutputStream proto; + Table table(ProcessProto::_FIELD_NAMES, ProcessProto::_FIELD_IDS, ProcessProto::_FIELD_COUNT); + string zram, ram, total; + + // parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + + // parse head line + if (nline++ == 0) { + header = parseHeader(line); + continue; + } + + if (stripPrefix(&line, "ZRAM:")) { + zram = line; + continue; + } + if (stripPrefix(&line, "RAM:")) { + ram = line; + continue; + } + + record = parseRecord(line); + if (record.size() != header.size()) { + if (record[record.size() - 1] == "TOTAL") { // TOTAL record + total = line; + } else { + fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, + line.c_str()); + } + continue; + } + + long long token = proto.start(Procrank::PROCESSES); + for (int i=0; i<(int)record.size(); 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()); + } + } + proto.end(token); + } + + // add summary + long long token = proto.start(Procrank::SUMMARY); + if (!total.empty()) { + 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()); + } + proto.end(token); + } + if (!zram.empty()) { + long long token = proto.start(SummaryProto::ZRAM); + proto.write(ZramProto::RAW_TEXT, zram); + proto.end(token); + } + if (!ram.empty()) { + long long token = proto.start(SummaryProto::RAM); + proto.write(RamProto::RAW_TEXT, ram); + proto.end(token); + } + 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/ProcrankParser.h b/cmds/incident_helper/src/parsers/ProcrankParser.h new file mode 100644 index 000000000000..5d0ee48aa5b1 --- /dev/null +++ b/cmds/incident_helper/src/parsers/ProcrankParser.h @@ -0,0 +1,35 @@ +/* + * 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 PROCRANK_PARSER_H +#define PROCRANK_PARSER_H + +#include "TextParserBase.h" + +using namespace android; + +/** + * Procrank parser, parses text produced by command procrank + */ +class ProcrankParser : public TextParserBase { +public: + ProcrankParser() : TextParserBase(String8("ProcrankParser")) {}; + ~ProcrankParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // PROCRANK_PARSER_H diff --git a/cmds/incident_helper/src/parsers/PsParser.cpp b/cmds/incident_helper/src/parsers/PsParser.cpp new file mode 100644 index 000000000000..e9014cacfa0b --- /dev/null +++ b/cmds/incident_helper/src/parsers/PsParser.cpp @@ -0,0 +1,95 @@ +/* + * 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/ps.proto.h" +#include "ih_util.h" +#include "PsParser.h" + +using namespace android::os; + +status_t PsParser::Parse(const int in, const int out) const { + Reader reader(in); + string line; + header_t header; // the header of /d/wakeup_sources + vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions. + record_t record; // retain each record + int nline = 0; + int diff = 0; + + ProtoOutputStream proto; + Table table(PsDumpProto::Process::_FIELD_NAMES, PsDumpProto::Process::_FIELD_IDS, PsDumpProto::Process::_FIELD_COUNT); + const char* pcyNames[] = { "fg", "bg", "ta" }; + const int pcyValues[] = {PsDumpProto::Process::POLICY_FG, PsDumpProto::Process::POLICY_BG, PsDumpProto::Process::POLICY_TA}; + table.addEnumTypeMap("pcy", pcyNames, pcyValues, 3); + const char* sNames[] = { "D", "R", "S", "T", "t", "X", "Z" }; + const int sValues[] = {PsDumpProto::Process::STATE_D, PsDumpProto::Process::STATE_R, PsDumpProto::Process::STATE_S, PsDumpProto::Process::STATE_T, PsDumpProto::Process::STATE_TRACING, PsDumpProto::Process::STATE_X, PsDumpProto::Process::STATE_Z}; + table.addEnumTypeMap("s", sNames, sValues, 7); + + // Parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + + if (nline++ == 0) { + header = parseHeader(line, DEFAULT_WHITESPACE); + + const char* headerNames[] = { "LABEL", "USER", "PID", "TID", "PPID", "VSZ", "RSS", "WCHAN", "ADDR", "S", "PRI", "NI", "RTPRIO", "SCH", "PCY", "TIME", "CMD", NULL }; + if (!getColumnIndices(columnIndices, headerNames, line)) { + return -1; + } + + continue; + } + + record = parseRecordByColumns(line, columnIndices); + + diff = record.size() - header.size(); + if (diff < 0) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str()); + printRecord(record); + continue; + } else if (diff > 0) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str()); + printRecord(record); + continue; + } + + long long token = proto.start(PsDumpProto::PROCESSES); + for (int i=0; i<(int)record.size(); 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()); + } + } + 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/PsParser.h b/cmds/incident_helper/src/parsers/PsParser.h new file mode 100644 index 000000000000..9488e40e88fe --- /dev/null +++ b/cmds/incident_helper/src/parsers/PsParser.h @@ -0,0 +1,33 @@ +/* + * 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 PS_PARSER_H +#define PS_PARSER_H + +#include "TextParserBase.h" + +/** + * PS parser, parses output of 'ps' command to protobuf. + */ +class PsParser : public TextParserBase { +public: + PsParser() : TextParserBase(String8("Ps")) {}; + ~PsParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // PS_PARSER_H diff --git a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp new file mode 100644 index 000000000000..7b0ac0b8452b --- /dev/null +++ b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp @@ -0,0 +1,227 @@ +/* + * 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/system_properties.proto.h" +#include "ih_util.h" +#include "SystemPropertiesParser.h" + +using namespace android::os; + +const string LINE_DELIMITER = "]: ["; + +// system properties' names sometimes are not valid proto field names, make the names valid. +static string convertToFieldName(const string& name) { + int len = (int)name.length(); + char cstr[len + 1]; + strcpy(cstr, name.c_str()); + for (int i = 0; i < len; i++) { + if (!isValidChar(cstr[i])) { + cstr[i] = '_'; + } + } + return string(cstr); +} + +status_t +SystemPropertiesParser::Parse(const int in, const int out) const +{ + Reader reader(in); + string line; + string name; // the name of the property + string value; // the string value of the property + ProtoOutputStream proto; + vector<pair<string, string>> extras; + + Table sysPropTable(SystemPropertiesProto::_FIELD_NAMES, + SystemPropertiesProto::_FIELD_IDS, + SystemPropertiesProto::_FIELD_COUNT); + Message sysProp(&sysPropTable); + + Table aacDrcTable(SystemPropertiesProto::AacDrc::_FIELD_NAMES, + SystemPropertiesProto::AacDrc::_FIELD_IDS, + SystemPropertiesProto::AacDrc::_FIELD_COUNT); + Message aacDrc(&aacDrcTable); + sysProp.addSubMessage(SystemPropertiesProto::AAC_DRC, &aacDrc); + + Table aaudioTable(SystemPropertiesProto::Aaudio::_FIELD_NAMES, + SystemPropertiesProto::Aaudio::_FIELD_IDS, + SystemPropertiesProto::Aaudio::_FIELD_COUNT); + Message aaudio(&aaudioTable); + sysProp.addSubMessage(SystemPropertiesProto::AAUDIO, &aaudio); + + Table cameraTable(SystemPropertiesProto::Camera::_FIELD_NAMES, + SystemPropertiesProto::Camera::_FIELD_IDS, + SystemPropertiesProto::Camera::_FIELD_COUNT); + Message camera(&cameraTable); + sysProp.addSubMessage(SystemPropertiesProto::CAMERA, &camera); + + Table dalvikVmTable(SystemPropertiesProto::DalvikVm::_FIELD_NAMES, + SystemPropertiesProto::DalvikVm::_FIELD_IDS, + SystemPropertiesProto::DalvikVm::_FIELD_COUNT); + Message dalvikVm(&dalvikVmTable); + sysProp.addSubMessage(SystemPropertiesProto::DALVIK_VM, &dalvikVm); + + Table initSvcTable(SystemPropertiesProto::InitSvc::_FIELD_NAMES, + SystemPropertiesProto::InitSvc::_FIELD_IDS, + SystemPropertiesProto::InitSvc::_FIELD_COUNT); + initSvcTable.addEnumNameToValue("running", SystemPropertiesProto::InitSvc::STATUS_RUNNING); + initSvcTable.addEnumNameToValue("stopped", SystemPropertiesProto::InitSvc::STATUS_STOPPED); + Message initSvc(&initSvcTable); + sysProp.addSubMessage(SystemPropertiesProto::INIT_SVC, &initSvc); + + Table logTable(SystemPropertiesProto::Log::_FIELD_NAMES, + SystemPropertiesProto::Log::_FIELD_IDS, + SystemPropertiesProto::Log::_FIELD_COUNT); + Message logMsg(&logTable); + sysProp.addSubMessage(SystemPropertiesProto::LOG, &logMsg); + + Table persistTable(SystemPropertiesProto::Persist::_FIELD_NAMES, + SystemPropertiesProto::Persist::_FIELD_IDS, + SystemPropertiesProto::Persist::_FIELD_COUNT); + Message persist(&persistTable); + sysProp.addSubMessage(SystemPropertiesProto::PERSIST, &persist); + + Table pmDexoptTable(SystemPropertiesProto::PmDexopt::_FIELD_NAMES, + SystemPropertiesProto::PmDexopt::_FIELD_IDS, + SystemPropertiesProto::PmDexopt::_FIELD_COUNT); + Message pmDexopt(&pmDexoptTable); + sysProp.addSubMessage(SystemPropertiesProto::PM_DEXOPT, &pmDexopt); + + Table roTable(SystemPropertiesProto::Ro::_FIELD_NAMES, + SystemPropertiesProto::Ro::_FIELD_IDS, + SystemPropertiesProto::Ro::_FIELD_COUNT); + Message ro(&roTable); + + Table bootTable(SystemPropertiesProto::Ro::Boot::_FIELD_NAMES, + SystemPropertiesProto::Ro::Boot::_FIELD_IDS, + SystemPropertiesProto::Ro::Boot::_FIELD_COUNT); + Message boot(&bootTable); + ro.addSubMessage(SystemPropertiesProto::Ro::BOOT, &boot); + + Table bootimageTable(SystemPropertiesProto::Ro::BootImage::_FIELD_NAMES, + SystemPropertiesProto::Ro::BootImage::_FIELD_IDS, + SystemPropertiesProto::Ro::BootImage::_FIELD_COUNT); + Message bootimage(&bootimageTable); + ro.addSubMessage(SystemPropertiesProto::Ro::BOOTIMAGE, &bootimage); + + Table buildTable(SystemPropertiesProto::Ro::Build::_FIELD_NAMES, + SystemPropertiesProto::Ro::Build::_FIELD_IDS, + SystemPropertiesProto::Ro::Build::_FIELD_COUNT); + Message build(&buildTable); + + Table versionTable(SystemPropertiesProto::Ro::Build::Version::_FIELD_NAMES, + SystemPropertiesProto::Ro::Build::Version::_FIELD_IDS, + SystemPropertiesProto::Ro::Build::Version::_FIELD_COUNT); + Message version(&versionTable); + build.addSubMessage(SystemPropertiesProto::Ro::Build::VERSION, &version); + ro.addSubMessage(SystemPropertiesProto::Ro::BUILD, &build); + + Table configTable(SystemPropertiesProto::Ro::Config::_FIELD_NAMES, + SystemPropertiesProto::Ro::Config::_FIELD_IDS, + SystemPropertiesProto::Ro::Config::_FIELD_COUNT); + Message config(&configTable); + ro.addSubMessage(SystemPropertiesProto::Ro::CONFIG, &config); + + Table hardwareTable(SystemPropertiesProto::Ro::Hardware::_FIELD_NAMES, + SystemPropertiesProto::Ro::Hardware::_FIELD_IDS, + SystemPropertiesProto::Ro::Hardware::_FIELD_COUNT); + Message hardware(&hardwareTable); + ro.addSubMessage(SystemPropertiesProto::Ro::HARDWARE, &hardware); + + Table productTable(SystemPropertiesProto::Ro::Product::_FIELD_NAMES, + SystemPropertiesProto::Ro::Product::_FIELD_IDS, + SystemPropertiesProto::Ro::Product::_FIELD_COUNT); + Message product(&productTable); + + Table pVendorTable(SystemPropertiesProto::Ro::Product::Vendor::_FIELD_NAMES, + SystemPropertiesProto::Ro::Product::Vendor::_FIELD_IDS, + SystemPropertiesProto::Ro::Product::Vendor::_FIELD_COUNT); + Message pVendor(&pVendorTable); + product.addSubMessage(SystemPropertiesProto::Ro::Product::VENDOR, &pVendor); + ro.addSubMessage(SystemPropertiesProto::Ro::PRODUCT, &product); + + Table telephonyTable(SystemPropertiesProto::Ro::Telephony::_FIELD_NAMES, + SystemPropertiesProto::Ro::Telephony::_FIELD_IDS, + SystemPropertiesProto::Ro::Telephony::_FIELD_COUNT); + Message telephony(&telephonyTable); + ro.addSubMessage(SystemPropertiesProto::Ro::TELEPHONY, &telephony); + + Table vendorTable(SystemPropertiesProto::Ro::Vendor::_FIELD_NAMES, + SystemPropertiesProto::Ro::Vendor::_FIELD_IDS, + SystemPropertiesProto::Ro::Vendor::_FIELD_COUNT); + Message vendor(&vendorTable); + ro.addSubMessage(SystemPropertiesProto::Ro::VENDOR, &vendor); + + sysProp.addSubMessage(SystemPropertiesProto::RO, &ro); + + Table sysTable(SystemPropertiesProto::Sys::_FIELD_NAMES, + SystemPropertiesProto::Sys::_FIELD_IDS, + SystemPropertiesProto::Sys::_FIELD_COUNT); + Message sys(&sysTable); + + Table usbTable(SystemPropertiesProto::Sys::Usb::_FIELD_NAMES, + SystemPropertiesProto::Sys::Usb::_FIELD_IDS, + SystemPropertiesProto::Sys::Usb::_FIELD_COUNT); + Message usb(&usbTable); + sys.addSubMessage(SystemPropertiesProto::Sys::USB, &usb); + + sysProp.addSubMessage(SystemPropertiesProto::SYS, &sys); + + // parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + + line = line.substr(1, line.size() - 2); // trim [] + size_t index = line.find(LINE_DELIMITER); // split by "]: [" + if (index == string::npos) { + fprintf(stderr, "Bad Line %s\n", line.c_str()); + continue; + } + name = line.substr(0, index); + value = trim(line.substr(index + 4), DEFAULT_WHITESPACE); + if (value.empty()) continue; + + // if the property name couldn't be found in proto definition or the value has mistype, + // add to extra properties with its name and value + if (!sysProp.insertField(&proto, convertToFieldName(name), value)) { + extras.push_back(make_pair(name, value)); + } + } + // end session for the last write. + sysProp.endSession(&proto); + + for (auto it = extras.begin(); it != extras.end(); it++) { + long long token = proto.start(SystemPropertiesProto::EXTRA_PROPERTIES); + proto.write(SystemPropertiesProto::Property::NAME, it->first); + proto.write(SystemPropertiesProto::Property::VALUE, it->second); + 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/SystemPropertiesParser.h b/cmds/incident_helper/src/parsers/SystemPropertiesParser.h new file mode 100644 index 000000000000..c4016006a48a --- /dev/null +++ b/cmds/incident_helper/src/parsers/SystemPropertiesParser.h @@ -0,0 +1,35 @@ +/* + * 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 SYSTEM_PROPERTIES_PARSER_H +#define SYSTEM_PROPERTIES_PARSER_H + +#include "TextParserBase.h" + +using namespace android; + +/** + * SystemProperties parser, parses text produced by command getprop. + */ +class SystemPropertiesParser : public TextParserBase { +public: + SystemPropertiesParser() : TextParserBase(String8("SystemPropertiesParser")) {}; + ~SystemPropertiesParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // SYSTEM_PROPERTIES_PARSER_H |