| /* |
| * Copyright (C) 2022 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 "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h" |
| |
| #include <algorithm> |
| #include <optional> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/ext/base/string_utils.h" |
| #include "perfetto/trace_processor/trace_blob.h" |
| #include "perfetto/trace_processor/trace_blob_view.h" |
| #include "src/trace_processor/importers/android_bugreport/android_log_parser.h" |
| #include "src/trace_processor/importers/common/clock_tracker.h" |
| #include "src/trace_processor/importers/common/process_tracker.h" |
| #include "src/trace_processor/importers/common/track_tracker.h" |
| #include "src/trace_processor/types/trace_processor_context.h" |
| #include "src/trace_processor/util/zip_reader.h" |
| |
| #include "protos/perfetto/common/builtin_clock.pbzero.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| |
| AndroidBugreportParser::AndroidBugreportParser(TraceProcessorContext* ctx) |
| : context_(ctx), zip_reader_(new util::ZipReader()) {} |
| |
| AndroidBugreportParser::~AndroidBugreportParser() = default; |
| |
| util::Status AndroidBugreportParser::Parse(TraceBlobView tbv) { |
| if (!first_chunk_seen_) { |
| first_chunk_seen_ = true; |
| // All logs in Android bugreports use wall time (which creates problems |
| // in case of early boot events before NTP kicks in, which get emitted as |
| // 1970), but that is the state of affairs. |
| context_->clock_tracker->SetTraceTimeClock( |
| protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| } |
| |
| return zip_reader_->Parse(tbv.data(), tbv.size()); |
| } |
| |
| void AndroidBugreportParser::NotifyEndOfFile() { |
| if (!DetectYearAndBrFilename()) { |
| context_->storage->IncrementStats(stats::android_br_parse_errors); |
| return; |
| } |
| |
| ParsePersistentLogcat(); |
| ParseDumpstateTxt(); |
| SortAndStoreLogcat(); |
| } |
| |
| void AndroidBugreportParser::ParseDumpstateTxt() { |
| // Dumpstate is organized in a two level hierarchy, beautifully flattened into |
| // one text file with load bearing ----- markers: |
| // 1. Various dumpstate sections, examples: |
| // ``` |
| // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------ |
| // ... |
| // ------ SYSTEM LOG (logcat -v threadtime -v printable -v uid) ------ |
| // ... |
| // ------ IPTABLES (iptables -L -nvx) ------ |
| // ... |
| // ------ DUMPSYS HIGH (/system/bin/dumpsys) ------ |
| // ... |
| // ------ DUMPSYS (/system/bin/dumpsys) ------ |
| // ``` |
| // |
| // 2. Within the "------ DUMPSYS" section (note dumpsys != dumpstate), there |
| // are multiple services. Note that there are at least 3 DUMPSYS sections |
| // (CRITICAL, HIGH and default), with multiple services in each: |
| // ``` |
| // ------ DUMPSYS (/system/bin/dumpsys) ------ |
| // DUMP OF SERVICE activity: |
| // ... |
| // --------------------------------------------------------------------------- |
| // DUMP OF SERVICE input_method: |
| // ... |
| // --------------------------------------------------------------------------- |
| // ``` |
| // Here we put each line in a dedicated table, android_dumpstate, keeping |
| // track of the dumpstate `section` and dumpsys `service`. |
| AndroidLogParser log_parser(br_year_, context_->storage.get()); |
| util::ZipFile* zf = zip_reader_->Find(dumpstate_fname_); |
| StringId section_id = StringId::Null(); // The current dumpstate section. |
| StringId service_id = StringId::Null(); // The current dumpsys service. |
| static constexpr size_t npos = base::StringView::npos; |
| enum { OTHER = 0, DUMPSYS, LOG } cur_sect = OTHER; |
| zf->DecompressLines([&](const std::vector<base::StringView>& lines) { |
| // Optimization for ParseLogLines() below. Avoids ctor/dtor-ing a new vector |
| // on every line. |
| std::vector<base::StringView> log_line(1); |
| for (const base::StringView& line : lines) { |
| if (line.StartsWith("------ ") && line.EndsWith(" ------")) { |
| // These lines mark the beginning and end of dumpstate sections: |
| // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------ |
| // ------ 0.356s was the duration of 'DUMPSYS CRITICAL' ------ |
| base::StringView section = line.substr(7); |
| section = section.substr(0, section.size() - 7); |
| bool end_marker = section.find("was the duration of") != npos; |
| service_id = StringId::Null(); |
| if (end_marker) { |
| section_id = StringId::Null(); |
| } else { |
| section_id = context_->storage->InternString(section); |
| cur_sect = OTHER; |
| if (section.StartsWith("DUMPSYS")) { |
| cur_sect = DUMPSYS; |
| } else if (section.StartsWith("SYSTEM LOG") || |
| section.StartsWith("EVENT LOG") || |
| section.StartsWith("RADIO LOG")) { |
| // KERNEL LOG is deliberately omitted because SYSTEM LOG is a |
| // superset. KERNEL LOG contains all dupes. |
| cur_sect = LOG; |
| } else if (section.StartsWith("BLOCK STAT")) { |
| // Coalesce all the block stats into one section. Otherwise they |
| // pollute the table with one section per block device. |
| section_id = context_->storage->InternString("BLOCK STAT"); |
| } |
| } |
| continue; |
| } |
| // Skip end marker lines for dumpsys sections. |
| if (cur_sect == DUMPSYS && line.StartsWith("--------- ") && |
| line.find("was the duration of dumpsys") != npos) { |
| service_id = StringId::Null(); |
| continue; |
| } |
| if (cur_sect == DUMPSYS && service_id.is_null() && |
| line.StartsWith("----------------------------------------------")) { |
| continue; |
| } |
| if (cur_sect == DUMPSYS && line.StartsWith("DUMP OF SERVICE")) { |
| // DUMP OF SERVICE [CRITICAL|HIGH] ServiceName: |
| base::StringView svc = line.substr(line.rfind(' ') + 1); |
| svc = svc.substr(0, svc.size() - 1); |
| service_id = context_->storage->InternString(svc); |
| } else if (cur_sect == LOG) { |
| // Parse the non-persistent logcat and append to `log_events_`, together |
| // with the persistent one previously parsed by ParsePersistentLogcat(). |
| // Skips entries that are already seen in the persistent logcat, |
| // handling us vs ms truncation. |
| PERFETTO_DCHECK(log_line.size() == 1); |
| log_line[0] = line; |
| log_parser.ParseLogLines(log_line, &log_events_, |
| log_events_last_sorted_idx_); |
| } |
| |
| if (build_fpr_.empty() && line.StartsWith("Build fingerprint:")) { |
| build_fpr_ = line.substr(20, line.size() - 20).ToStdString(); |
| } |
| |
| // Append the line to the android_dumpstate table. |
| context_->storage->mutable_android_dumpstate_table()->Insert( |
| {section_id, service_id, context_->storage->InternString(line)}); |
| } |
| }); |
| } |
| |
| void AndroidBugreportParser::ParsePersistentLogcat() { |
| // 1. List logcat files in reverse timestmap order (old to most recent). |
| // 2. Decode events from log lines into a vector. Dedupe and intern strings. |
| // 3. Globally sort all extracted events. |
| // 4. Insert into the android_logs table. |
| |
| AndroidLogParser log_parser(br_year_, context_->storage.get()); |
| |
| // Sort files to ease the job of the subsequent line-based sort. Unfortunately |
| // lines within each file are not 100% timestamp-ordered, due to things like |
| // kernel messages where log time != event time. |
| std::vector<std::pair<uint64_t, std::string>> log_paths; |
| for (const util::ZipFile& zf : zip_reader_->files()) { |
| if (base::StartsWith(zf.name(), "FS/data/misc/logd/logcat") && |
| !base::EndsWith(zf.name(), "logcat.id")) { |
| log_paths.emplace_back(std::make_pair(zf.GetDatetime(), zf.name())); |
| } |
| } |
| std::sort(log_paths.begin(), log_paths.end()); |
| |
| // Push all events into the AndroidLogParser. It will take care of string |
| // interning into the pool. Appends entries into `log_events`. |
| for (const auto& kv : log_paths) { |
| util::ZipFile* zf = zip_reader_->Find(kv.second); |
| zf->DecompressLines([&](const std::vector<base::StringView>& lines) { |
| log_parser.ParseLogLines(lines, &log_events_); |
| }); |
| } |
| |
| // Do an initial sorting pass. This is not the final sorting because we |
| // haven't ingested the latest logs from dumpstate yet. But we need this sort |
| // to be able to de-dupe the same lines showing both in dumpstate and in the |
| // persistent log. |
| SortLogEvents(); |
| } |
| |
| void AndroidBugreportParser::SortAndStoreLogcat() { |
| // Sort the union of all log events parsed from both /data/misc/logd |
| // (persistent logcat on disk) and the dumpstate file (last in-memory logcat). |
| // Before the std::stable_sort, entries in `log_events_` are already "mostly" |
| // sorted, because we processed files in order (see notes above about kernel |
| // logs on why we need a final sort here). |
| // We need stable-sort to preserve FIFO-ness of events emitted at the same |
| // time, logcat is not granular enough (us for persistent, ms for dumpstate). |
| SortLogEvents(); |
| |
| // Insert the globally sorted events into the android_logs table. |
| for (const auto& e : log_events_) { |
| UniquePid utid = context_->process_tracker->UpdateThread(e.tid, e.pid); |
| context_->storage->mutable_android_log_table()->Insert( |
| {e.ts, utid, e.prio, e.tag, e.msg}); |
| } |
| } |
| |
| // Populates the `year_` field from the bugreport-xxx.txt file name. |
| // This is because logcat events have only the month and day. |
| // This is obviously bugged for cases of bugreports collected across new year |
| // but we'll live with that. |
| bool AndroidBugreportParser::DetectYearAndBrFilename() { |
| const util::ZipFile* br_file = nullptr; |
| for (const auto& zf : zip_reader_->files()) { |
| if (base::StartsWith(zf.name(), "bugreport-") && |
| base::EndsWith(zf.name(), ".txt")) { |
| br_file = &zf; |
| break; |
| } |
| } |
| |
| if (!br_file) { |
| PERFETTO_ELOG("Could not find bugreport-*.txt in the zip file"); |
| return false; |
| } |
| |
| // Typical name: "bugreport-product-TP1A.220623.001-2022-06-24-16-24-37.txt". |
| auto year_str = br_file->name().substr( |
| br_file->name().size() - strlen("2022-12-31-23-59-00.txt"), 4); |
| std::optional<int32_t> year = base::StringToInt32(year_str); |
| if (!year.has_value()) { |
| PERFETTO_ELOG("Could not parse the year from %s", br_file->name().c_str()); |
| return false; |
| } |
| br_year_ = *year; |
| dumpstate_fname_ = br_file->name(); |
| return true; |
| } |
| |
| void AndroidBugreportParser::SortLogEvents() { |
| std::stable_sort(log_events_.begin(), log_events_.end()); |
| log_events_last_sorted_idx_ = log_events_.size(); |
| } |
| |
| } // namespace trace_processor |
| } // namespace perfetto |