diff --git a/Android.bp b/Android.bp index 69ee848cadad..03a79f61def7 100644 --- a/Android.bp +++ b/Android.bp @@ -730,10 +730,18 @@ gensrcs { "core/proto/android/os/procrank.proto", "core/proto/android/os/ps.proto", "core/proto/android/os/system_properties.proto", + "core/proto/android/util/event_log_tags.proto", ], // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool - cmd: "PATH=$$PATH:$$(dirname $(location protoc-gen-cppstream)) $(location aprotoc) --plugin=protoc-gen-cpp-stream=$(location protoc-gen-cppstream) --dependency_out=$(depfile) --cppstream_out=$(genDir)/ -Iexternal/protobuf/src -I . $(in)", + cmd: "mkdir -p $(genDir) " + + "&& $(location aprotoc) " + + " --plugin=$(location protoc-gen-cppstream) " + + " --dependency_out=$(depfile) " + + " --cppstream_out=$(genDir) " + + " -Iexternal/protobuf/src " + + " -I . " + + " $(in)", output_extension = "proto.h", } diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp index e23e80ae21e8..847b26a39ffe 100644 --- a/cmds/incident_helper/src/ih_util.cpp +++ b/cmds/incident_helper/src/ih_util.cpp @@ -208,6 +208,19 @@ bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter) { 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()); } diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h index b063b2fe0bba..53f443873e4d 100644 --- a/cmds/incident_helper/src/ih_util.h +++ b/cmds/incident_helper/src/ih_util.h @@ -34,6 +34,8 @@ 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); @@ -88,6 +90,11 @@ void printRecord(const record_t& record); 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 */ diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp index 8c6cd78d3bf2..418dc3fad761 100644 --- a/cmds/incident_helper/src/main.cpp +++ b/cmds/incident_helper/src/main.cpp @@ -19,6 +19,7 @@ #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" @@ -55,6 +56,8 @@ static TextParserBase* selectParser(int section) { // 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: 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 + +#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/testdata/event-log-tags.txt b/cmds/incident_helper/testdata/event-log-tags.txt new file mode 100644 index 000000000000..35396bfb4250 --- /dev/null +++ b/cmds/incident_helper/testdata/event-log-tags.txt @@ -0,0 +1,6 @@ +42 answer (to life the universe etc|3) +314 pi +1004 chatty (dropped|3) +1005 tag_def (tag|1),(name|3),(format|3) +2747 contacts_aggregation (aggregation time|2|3), (count|1|1) +1397638484 snet_event_log (subtag|3) (uid|1) (message|3|s) \ No newline at end of file diff --git a/cmds/incident_helper/tests/EventLogTagsParser_test.cpp b/cmds/incident_helper/tests/EventLogTagsParser_test.cpp new file mode 100644 index 000000000000..d0d1f1e023a8 --- /dev/null +++ b/cmds/incident_helper/tests/EventLogTagsParser_test.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. + */ + +#include "EventLogTagsParser.h" + +#include "frameworks/base/core/proto/android/util/event_log_tags.pb.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace android::base; +using namespace android::util; +using namespace std; +using ::testing::StrEq; +using ::testing::Test; +using ::testing::internal::CaptureStderr; +using ::testing::internal::CaptureStdout; +using ::testing::internal::GetCapturedStderr; +using ::testing::internal::GetCapturedStdout; + +class EventLogTagsParserTest : public Test { +public: + virtual void SetUp() override { + ASSERT_TRUE(tf.fd != -1); + } + +protected: + TemporaryFile tf; + + const string kTestPath = GetExecutableDirectory(); + const string kTestDataPath = kTestPath + "/testdata/"; +}; + +TEST_F(EventLogTagsParserTest, Success) { + const string testFile = kTestDataPath + "event-log-tags.txt"; + + EventLogTagsParser parser; + EventLogTagMapProto expected; + + EventLogTag* eventLogTag; + EventLogTag::ValueDescriptor* desp; + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(42); + eventLogTag->set_tag_name("answer"); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("to life the universe etc"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(314); + eventLogTag->set_tag_name("pi"); + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(1004); + eventLogTag->set_tag_name("chatty"); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("dropped"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(1005); + eventLogTag->set_tag_name("tag_def"); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("tag"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_INT); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("name"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("format"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(2747); + eventLogTag->set_tag_name("contacts_aggregation"); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("aggregation time"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_LONG); + desp->set_unit(EventLogTag_ValueDescriptor_DataUnit_MILLISECONDS); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("count"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_INT); + desp->set_unit(EventLogTag_ValueDescriptor_DataUnit_OBJECTS); + + eventLogTag = expected.add_event_log_tags(); + eventLogTag->set_tag_number(1397638484); + eventLogTag->set_tag_name("snet_event_log"); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("subtag"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("uid"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_INT); + desp = eventLogTag->add_value_descriptors(); + desp->set_name("message"); + desp->set_type(EventLogTag_ValueDescriptor_DataType_STRING); + desp->set_unit(EventLogTag_ValueDescriptor_DataUnit_SECONDS); + + int fd = open(testFile.c_str(), O_RDONLY); + ASSERT_TRUE(fd != -1); + + CaptureStdout(); + ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO)); + EXPECT_EQ(GetCapturedStdout(), expected.SerializeAsString()); + close(fd); +} diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp index 7b8cf52c8bee..efe714d98b18 100644 --- a/cmds/incident_helper/tests/ih_util_test.cpp +++ b/cmds/incident_helper/tests/ih_util_test.cpp @@ -60,6 +60,9 @@ TEST(IhUtilTest, ParseRecord) { result = parseRecord("123,456,78_9", ","); expected = { "123", "456", "78_9" }; EXPECT_EQ(expected, result); + + result = parseRecord("", " "); + EXPECT_TRUE(result.empty()); } TEST(IhUtilTest, ParseRecordByColumns) { @@ -133,6 +136,22 @@ TEST(IhUtilTest, stripSuffix) { EXPECT_THAT(data4, StrEq(" 243%abc")); } +TEST(IhUtilTest, behead) { + string testcase1 = "81002 dropbox_file_copy (a)(b)"; + EXPECT_THAT(behead(&testcase1, ' '), StrEq("81002")); + EXPECT_THAT(behead(&testcase1, ' '), StrEq("dropbox_file_copy")); + EXPECT_THAT(testcase1, "(a)(b)"); + + string testcase2 = "adbce,erwqr"; + EXPECT_THAT(behead(&testcase2, ' '), StrEq("adbce,erwqr")); + EXPECT_THAT(testcase2, ""); + + string testcase3 = "first second"; + EXPECT_THAT(behead(&testcase3, ' '), StrEq("first")); + EXPECT_THAT(behead(&testcase3, ' '), StrEq("second")); + EXPECT_THAT(testcase3, ""); +} + TEST(IhUtilTest, Reader) { TemporaryFile tf; ASSERT_NE(tf.fd, -1); diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk index 11d3e4911761..1cf02d35636c 100644 --- a/cmds/incidentd/Android.mk +++ b/cmds/incidentd/Android.mk @@ -14,6 +14,9 @@ LOCAL_PATH:= $(call my-dir) +# proto files used in incidentd to generate cppstream proto headers. +PROTO_FILES:= frameworks/base/core/proto/android/util/log.proto + # ========= # # incidentd # # ========= # @@ -59,18 +62,34 @@ LOCAL_SHARED_LIBRARIES := \ libutils LOCAL_MODULE_CLASS := EXECUTABLES + gen_src_dir := $(local-generated-sources-dir) -GEN := $(gen_src_dir)/src/section_list.cpp -$(GEN): $(HOST_OUT_EXECUTABLES)/incident-section-gen -$(GEN): PRIVATE_CUSTOM_TOOL = \ +# generate section_list.cpp +GEN_LIST := $(gen_src_dir)/src/section_list.cpp +$(GEN_LIST): $(HOST_OUT_EXECUTABLES)/incident-section-gen +$(GEN_LIST): PRIVATE_CUSTOM_TOOL = \ $(HOST_OUT_EXECUTABLES)/incident-section-gen incidentd > $@ -$(GEN): $(HOST_OUT_EXECUTABLES)/incident-section-gen +$(GEN_LIST): $(HOST_OUT_EXECUTABLES)/incident-section-gen $(transform-generated-source) -LOCAL_GENERATED_SOURCES += $(GEN) +LOCAL_GENERATED_SOURCES += $(GEN_LIST) +GEN_LIST:= + +# generate cppstream proto, add proto files to PROTO_FILES +GEN_PROTO := $(gen_src_dir)/proto.timestamp +$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES) +$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir) +$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \ + $(HOST_OUT_EXECUTABLES)/aprotoc --plugin=protoc-gen-cppstream=$(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream \ + --cppstream_out=$(PRIVATE_GEN_SRC_DIR) -Iexternal/protobuf/src -I . \ + $(PROTO_FILES) \ + && touch $@ +$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(GEN_PROTO) +GEN_PROTO:= gen_src_dir:= -GEN:= LOCAL_INIT_RC := incidentd.rc @@ -120,4 +139,22 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_TEST_DATA := $(call find-test-data-in-subdirs, $(LOCAL_PATH), *, testdata) +LOCAL_MODULE_CLASS := NATIVE_TESTS +gen_src_dir := $(local-generated-sources-dir) +# generate cppstream proto for testing +GEN_PROTO := $(gen_src_dir)/log.proto.timestamp +$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES) +$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir) +$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \ + $(HOST_OUT_EXECUTABLES)/aprotoc --plugin=protoc-gen-cppstream=$(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream \ + --cppstream_out=$(PRIVATE_GEN_SRC_DIR) -Iexternal/protobuf/src -I . \ + $(PROTO_FILES) \ + && touch $@ +$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(GEN_PROTO) +GEN_PROTO:= + +gen_src_dir:= + include $(BUILD_NATIVE_TEST) diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 22053ef3c53a..61d16f815e65 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -16,21 +16,29 @@ #define LOG_TAG "incidentd" -#include "FdBuffer.h" -#include "Privacy.h" -#include "PrivacyBuffer.h" #include "Section.h" -#include "io_util.h" -#include "section_list.h" +#include +#include +#include + +#include +#include #include -#include #include -#include -#include -#include -#include +#include +#include +#include +#include // for AID_NOBODY +#include + +#include "FdBuffer.h" +#include "frameworks/base/core/proto/android/util/log.proto.h" +#include "io_util.h" +#include "Privacy.h" +#include "PrivacyBuffer.h" +#include "section_list.h" using namespace android::util; using namespace std; @@ -41,7 +49,7 @@ const int FIELD_ID_INCIDENT_HEADER = 1; // incident section parameters const int WAIT_MAX = 5; const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000}; -const char* INCIDENT_HELPER = "/system/bin/incident_helper"; +const char INCIDENT_HELPER[] = "/system/bin/incident_helper"; static pid_t fork_execute_incident_helper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe) @@ -609,3 +617,160 @@ DumpsysSection::BlockingCall(int pipeWriteFd) const return NO_ERROR; } + +// ================================================================================ +// initialization only once in Section.cpp. +map LogSection::gLastLogsRetrieved; + +LogSection::LogSection(int id, log_id_t logID) + :WorkerThreadSection(id), + mLogID(logID) +{ + name += "logcat "; + name += android_log_id_to_name(logID); + switch (logID) { + case LOG_ID_EVENTS: + case LOG_ID_STATS: + case LOG_ID_SECURITY: + mBinary = true; + break; + default: + mBinary = false; + } +} + +LogSection::~LogSection() +{ +} + +static size_t +trimTail(char const* buf, size_t len) +{ + while (len > 0) { + char c = buf[len - 1]; + if (c == '\0' || c == ' ' || c == '\n' || c == '\r' || c == ':') { + len--; + } else { + break; + } + } + return len; +} + +static inline int32_t get4LE(uint8_t const* src) { + return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); +} + +status_t +LogSection::BlockingCall(int pipeWriteFd) const +{ + status_t err = NO_ERROR; + // Open log buffer and getting logs since last retrieved time if any. + unique_ptr loggers( + gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end() ? + android_logger_list_alloc(ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, 0) : + android_logger_list_alloc_time(ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, + gLastLogsRetrieved[mLogID], 0), + android_logger_list_free); + + if (android_logger_open(loggers.get(), mLogID) == NULL) { + ALOGW("LogSection %s: Can't get logger.", this->name.string()); + return err; + } + + log_msg msg; + log_time lastTimestamp(0); + + ProtoOutputStream proto; + while (true) { // keeps reading until logd buffer is fully read. + status_t err = android_logger_list_read(loggers.get(), &msg); + // err = 0 - no content, unexpected connection drop or EOF. + // err = +ive number - size of retrieved data from logger + // err = -ive number, OS supplied error _except_ for -EAGAIN + // err = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end of data. + if (err <= 0) { + if (err != -EAGAIN) { + ALOGE("LogSection %s: fails to read a log_msg.\n", this->name.string()); + } + break; + } + if (mBinary) { + // remove the first uint32 which is tag's index in event log tags + android_log_context context = create_android_log_parser(msg.msg() + sizeof(uint32_t), + msg.len() - sizeof(uint32_t));; + android_log_list_element elem; + + lastTimestamp.tv_sec = msg.entry_v1.sec; + lastTimestamp.tv_nsec = msg.entry_v1.nsec; + + // format a BinaryLogEntry + long long token = proto.start(LogProto::BINARY_LOGS); + proto.write(BinaryLogEntry::SEC, msg.entry_v1.sec); + proto.write(BinaryLogEntry::NANOSEC, msg.entry_v1.nsec); + proto.write(BinaryLogEntry::UID, (int) msg.entry_v4.uid); + proto.write(BinaryLogEntry::PID, msg.entry_v1.pid); + proto.write(BinaryLogEntry::TID, msg.entry_v1.tid); + proto.write(BinaryLogEntry::TAG_INDEX, get4LE(reinterpret_cast(msg.msg()))); + do { + elem = android_log_read_next(context); + long long elemToken = proto.start(BinaryLogEntry::ELEMS); + switch (elem.type) { + case EVENT_TYPE_INT: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_INT); + proto.write(BinaryLogEntry::Elem::VAL_INT32, (int) elem.data.int32); + break; + case EVENT_TYPE_LONG: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LONG); + proto.write(BinaryLogEntry::Elem::VAL_INT64, (long long) elem.data.int64); + break; + case EVENT_TYPE_STRING: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_STRING); + proto.write(BinaryLogEntry::Elem::VAL_STRING, elem.data.string, elem.len); + break; + case EVENT_TYPE_FLOAT: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_FLOAT); + proto.write(BinaryLogEntry::Elem::VAL_FLOAT, elem.data.float32); + break; + case EVENT_TYPE_LIST: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LIST); + break; + case EVENT_TYPE_LIST_STOP: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LIST_STOP); + break; + case EVENT_TYPE_UNKNOWN: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_UNKNOWN); + break; + } + proto.end(elemToken); + } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete); + proto.end(token); + if (context) { + android_log_destroy(&context); + } + } else { + AndroidLogEntry entry; + err = android_log_processLogBuffer(&msg.entry_v1, &entry); + if (err != NO_ERROR) { + ALOGE("LogSection %s: fails to process to an entry.\n", this->name.string()); + break; + } + lastTimestamp.tv_sec = entry.tv_sec; + lastTimestamp.tv_nsec = entry.tv_nsec; + + // format a TextLogEntry + long long token = proto.start(LogProto::TEXT_LOGS); + proto.write(TextLogEntry::SEC, (long long)entry.tv_sec); + proto.write(TextLogEntry::NANOSEC, (long long)entry.tv_nsec); + proto.write(TextLogEntry::PRIORITY, (int)entry.priority); + proto.write(TextLogEntry::UID, entry.uid); + proto.write(TextLogEntry::PID, entry.pid); + proto.write(TextLogEntry::TID, entry.tid); + proto.write(TextLogEntry::TAG, entry.tag, trimTail(entry.tag, entry.tagLen)); + proto.write(TextLogEntry::LOG, entry.message, trimTail(entry.message, entry.messageLen)); + proto.end(token); + } + } + gLastLogsRetrieved[mLogID] = lastTimestamp; + proto.flush(pipeWriteFd); + return err; +} diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h index 64558a6b732b..d440ee92601c 100644 --- a/cmds/incidentd/src/Section.h +++ b/cmds/incidentd/src/Section.h @@ -19,7 +19,9 @@ #include "Reporter.h" +#include #include + #include #include #include @@ -122,5 +124,24 @@ private: Vector mArgs; }; +/** + * Section that reads from logd. + */ +class LogSection : public WorkerThreadSection +{ + // global last log retrieved timestamp for each log_id_t. + static map gLastLogsRetrieved; + +public: + LogSection(int id, log_id_t logID); + virtual ~LogSection(); + + virtual status_t BlockingCall(int pipeWriteFd) const; + +private: + log_id_t mLogID; + bool mBinary; +}; + #endif // SECTIONS_H diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h index dfd2312df668..ddc0505df331 100644 --- a/cmds/incidentd/src/section_list.h +++ b/cmds/incidentd/src/section_list.h @@ -17,6 +17,8 @@ #ifndef SECTION_LIST_H #define SECTION_LIST_H +#include // include log_id_t enums. + #include "Privacy.h" #include "Section.h" diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp index 0c7876c1ecfb..cbfb89685de0 100644 --- a/cmds/incidentd/tests/Section_test.cpp +++ b/cmds/incidentd/tests/Section_test.cpp @@ -155,7 +155,7 @@ TEST(SectionTest, CommandSectionIncidentHelperTimeout) { } TEST(SectionTest, CommandSectionBadCommand) { - CommandSection cs(NOOP_PARSER, "echo", "about", NULL); + CommandSection cs(NOOP_PARSER, "echoo", "about", NULL); ReportRequestSet requests; ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests)); } @@ -167,6 +167,26 @@ TEST(SectionTest, CommandSectionBadCommandAndTimeout) { ASSERT_EQ(NO_ERROR, cs.Execute(&requests)); } +TEST(SectionTest, LogSectionBinary) { + LogSection ls(1, LOG_ID_EVENTS); + ReportRequestSet requests; + requests.setMainFd(STDOUT_FILENO); + CaptureStdout(); + ASSERT_EQ(NO_ERROR, ls.Execute(&requests)); + string results = GetCapturedStdout(); + EXPECT_FALSE(results.empty()); +} + +TEST(SectionTest, LogSectionSystem) { + LogSection ls(1, LOG_ID_SYSTEM); + ReportRequestSet requests; + requests.setMainFd(STDOUT_FILENO); + CaptureStdout(); + ASSERT_EQ(NO_ERROR, ls.Execute(&requests)); + string results = GetCapturedStdout(); + EXPECT_FALSE(results.empty()); +} + TEST(SectionTest, TestFilterPiiTaggedFields) { TemporaryFile tf; FileSection fs(NOOP_PARSER, tf.path); diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index a6db31f67b33..2f856ab68768 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -43,6 +43,8 @@ import "frameworks/base/core/proto/android/service/notification.proto"; import "frameworks/base/core/proto/android/service/package.proto"; import "frameworks/base/core/proto/android/service/print.proto"; import "frameworks/base/core/proto/android/service/procstats.proto"; +import "frameworks/base/core/proto/android/util/event_log_tags.proto"; +import "frameworks/base/core/proto/android/util/log.proto"; import "frameworks/base/libs/incident/proto/android/privacy.proto"; import "frameworks/base/libs/incident/proto/android/section.proto"; @@ -59,6 +61,8 @@ message IncidentMetadata { // the sections are able to be controlled and configured by section ids. // Instead privacy field options need to be configured in each section proto message. message IncidentProto { + reserved 1001; + // Incident header from callers repeated IncidentHeaderProto header = 1; // Internal metadata of incidentd @@ -70,6 +74,52 @@ message IncidentProto { (section).args = "getprop" ]; + // Device Logs + optional android.util.EventLogTagMapProto event_log_tag_map = 1100 [ + (section).type = SECTION_FILE, + (section).args = "/system/etc/event-log-tags" + ]; + + optional android.util.LogProto main_logs = 1101 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_MAIN" + ]; + + optional android.util.LogProto radio_logs = 1102 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_RADIO" + ]; + + optional android.util.LogProto events_logs = 1103 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_EVENTS" + ]; + + optional android.util.LogProto system_logs = 1104 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_SYSTEM" + ]; + + optional android.util.LogProto crash_logs = 1105 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_CRASH" + ]; + + optional android.util.LogProto stats_logs = 1106 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_STATS" + ]; + + optional android.util.LogProto security_logs = 1107 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_SECURITY" + ]; + + optional android.util.LogProto kernel_logs = 1108 [ + (section).type = SECTION_LOG, + (section).args = "LOG_ID_KERNEL" + ]; + // Linux services optional Procrank procrank = 2000 [ (section).type = SECTION_COMMAND, @@ -196,4 +246,5 @@ message IncidentProto { (section).type = SECTION_DUMPSYS, (section).args = "graphicsstats --proto" ]; + } diff --git a/core/proto/android/util/event_log_tags.proto b/core/proto/android/util/event_log_tags.proto new file mode 100644 index 000000000000..cb039be55b85 --- /dev/null +++ b/core/proto/android/util/event_log_tags.proto @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.util; + +option java_multiple_files = true; + +// Proto representation of event.logtags. +// Usually sit in /system/etc/event-log-tags. +message EventLogTagMapProto { + repeated EventLogTag event_log_tags = 1; +} + +message EventLogTag { + optional uint32 tag_number = 1; // keyed by tag number. + optional string tag_name = 2; + + message ValueDescriptor { + optional string name = 1; + + enum DataType { + UNKNOWN = 0; + INT = 1; + LONG = 2; + STRING = 3; + LIST = 4; + FLOAT = 5; + } + optional DataType type = 2; + + enum DataUnit { + UNSET = 0; // this field is optional, so default is unset + OBJECTS = 1; // Number of objects + BYTES = 2; // Number of bytes (default for type of int/long) + MILLISECONDS = 3; // Number of milliseconds + ALLOCATIONS = 4; // Number of allocations + ID = 5; // Id + PERCENT = 6; // Percent + SECONDS = 115; // 's', Number of seconds (monotonic time) + } + optional DataUnit unit = 3; + } + repeated ValueDescriptor value_descriptors = 3; +} \ No newline at end of file diff --git a/core/proto/android/util/log.proto b/core/proto/android/util/log.proto new file mode 100644 index 000000000000..30ff41242bcc --- /dev/null +++ b/core/proto/android/util/log.proto @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.util; + +option java_multiple_files = true; + +// Represents a Text Log in logd +// Next Tag: 9 +message TextLogEntry { + optional uint64 sec = 1; + optional uint64 nanosec = 2; + + enum LogPriority { + LOG_UNKNOWN = 0; + LOG_DEFAULT = 1; + LOG_VERBOSE = 2; + LOG_DEBUG = 3; + LOG_INFO = 4; + LOG_WARN = 5; + LOG_ERROR = 6; + LOG_FATAL = 7; + LOG_SILENT = 8; + } + optional LogPriority priority = 3; + optional int32 uid = 4; + optional int32 pid = 5; + optional int32 tid = 6; + optional string tag = 7; + optional string log = 8; +} + +// Represents a Binary Log in logd, need to look event-log-tags for more info. +// Next Tag: 8 +message BinaryLogEntry { + optional uint64 sec = 1; + optional uint64 nanosec = 2; + optional int32 uid = 3; + optional int32 pid = 4; + optional int32 tid = 5; + + // Index of the event tag, can look up in event-log-tags file + optional uint32 tag_index = 6; + + message Elem { + // must be sync with liblog log/log.h + enum Type { + EVENT_TYPE_LIST_STOP = 10; // '\n' + EVENT_TYPE_UNKNOWN = 63; // '?' + + EVENT_TYPE_INT = 0; + EVENT_TYPE_LONG = 1; + EVENT_TYPE_STRING = 2; + EVENT_TYPE_LIST = 3; + EVENT_TYPE_FLOAT = 4; + } + optional Type type = 1 [default=EVENT_TYPE_UNKNOWN]; + + oneof value { + int32 val_int32 = 2; + int64 val_int64 = 3; + string val_string = 4; + float val_float = 5; + } + } + repeated Elem elems = 7; +} + +message LogProto { + repeated TextLogEntry text_logs = 1; + + repeated BinaryLogEntry binary_logs = 2; +} + diff --git a/libs/incident/proto/android/section.proto b/libs/incident/proto/android/section.proto index d268cf4fc09a..49bfe1e8a598 100644 --- a/libs/incident/proto/android/section.proto +++ b/libs/incident/proto/android/section.proto @@ -37,6 +37,9 @@ enum SectionType { // incidentd calls dumpsys for annotated field SECTION_DUMPSYS = 3; + + // incidentd calls logs for annotated field + SECTION_LOG = 4; } message SectionFlags { diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp index 102bbf9d07d7..e7b269aaa9a5 100644 --- a/tools/incident_section_gen/main.cpp +++ b/tools/incident_section_gen/main.cpp @@ -408,6 +408,9 @@ static bool generateSectionListCpp(Descriptor const* descriptor) { splitAndPrint(s.args()); printf(" NULL),\n"); break; + case SECTION_LOG: + printf(" new LogSection(%d, %s),\n", field->number(), s.args().c_str()); + break; } } printf(" NULL };\n");