1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h"
18
19 #include <algorithm>
20 #include <cstddef>
21 #include <optional>
22 #include <string>
23 #include <vector>
24
25 #include "perfetto/base/logging.h"
26 #include "perfetto/base/status.h"
27 #include "perfetto/ext/base/string_utils.h"
28 #include "protos/perfetto/common/builtin_clock.pbzero.h"
29 #include "src/trace_processor/importers/android_bugreport/android_log_parser.h"
30 #include "src/trace_processor/importers/common/clock_tracker.h"
31 #include "src/trace_processor/importers/common/process_tracker.h"
32 #include "src/trace_processor/types/trace_processor_context.h"
33 #include "src/trace_processor/util/zip_reader.h"
34
35 namespace perfetto {
36 namespace trace_processor {
37 namespace {
FindBugReportFile(const std::vector<util::ZipFile> & zip_file_entries)38 const util::ZipFile* FindBugReportFile(
39 const std::vector<util::ZipFile>& zip_file_entries) {
40 for (const auto& zf : zip_file_entries) {
41 if (base::StartsWith(zf.name(), "bugreport-") &&
42 base::EndsWith(zf.name(), ".txt")) {
43 return &zf;
44 }
45 }
46 return nullptr;
47 }
48
ExtractYearFromBugReportFilename(const std::string & filename)49 std::optional<int32_t> ExtractYearFromBugReportFilename(
50 const std::string& filename) {
51 // Typical name: "bugreport-product-TP1A.220623.001-2022-06-24-16-24-37.txt".
52 auto year_str =
53 filename.substr(filename.size() - strlen("2022-12-31-23-59-00.txt"), 4);
54 return base::StringToInt32(year_str);
55 }
56
57 } // namespace
58
59 // static
IsAndroidBugReport(const std::vector<util::ZipFile> & zip_file_entries)60 bool AndroidBugreportParser::IsAndroidBugReport(
61 const std::vector<util::ZipFile>& zip_file_entries) {
62 if (const util::ZipFile* file = FindBugReportFile(zip_file_entries);
63 file != nullptr) {
64 return ExtractYearFromBugReportFilename(file->name()).has_value();
65 }
66
67 return false;
68 }
69
70 // static
Parse(TraceProcessorContext * context,std::vector<util::ZipFile> zip_file_entries)71 util::Status AndroidBugreportParser::Parse(
72 TraceProcessorContext* context,
73 std::vector<util::ZipFile> zip_file_entries) {
74 return AndroidBugreportParser(context, std::move(zip_file_entries))
75 .ParseImpl();
76 }
77
AndroidBugreportParser(TraceProcessorContext * context,std::vector<util::ZipFile> zip_file_entries)78 AndroidBugreportParser::AndroidBugreportParser(
79 TraceProcessorContext* context,
80 std::vector<util::ZipFile> zip_file_entries)
81 : context_(context), zip_file_entries_(std::move(zip_file_entries)) {}
82
83 AndroidBugreportParser::~AndroidBugreportParser() = default;
84
ParseImpl()85 util::Status AndroidBugreportParser::ParseImpl() {
86 // All logs in Android bugreports use wall time (which creates problems
87 // in case of early boot events before NTP kicks in, which get emitted as
88 // 1970), but that is the state of affairs.
89 context_->clock_tracker->SetTraceTimeClock(
90 protos::pbzero::BUILTIN_CLOCK_REALTIME);
91 if (!DetectYearAndBrFilename()) {
92 context_->storage->IncrementStats(stats::android_br_parse_errors);
93 return base::ErrStatus("Zip file does not contain bugreport file.");
94 }
95
96 ParsePersistentLogcat();
97 ParseDumpstateTxt();
98 SortAndStoreLogcat();
99 return base::OkStatus();
100 }
101
ParseDumpstateTxt()102 void AndroidBugreportParser::ParseDumpstateTxt() {
103 PERFETTO_CHECK(dumpstate_file_);
104 // Dumpstate is organized in a two level hierarchy, beautifully flattened into
105 // one text file with load bearing ----- markers:
106 // 1. Various dumpstate sections, examples:
107 // ```
108 // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------
109 // ...
110 // ------ SYSTEM LOG (logcat -v threadtime -v printable -v uid) ------
111 // ...
112 // ------ IPTABLES (iptables -L -nvx) ------
113 // ...
114 // ------ DUMPSYS HIGH (/system/bin/dumpsys) ------
115 // ...
116 // ------ DUMPSYS (/system/bin/dumpsys) ------
117 // ```
118 //
119 // 2. Within the "------ DUMPSYS" section (note dumpsys != dumpstate), there
120 // are multiple services. Note that there are at least 3 DUMPSYS sections
121 // (CRITICAL, HIGH and default), with multiple services in each:
122 // ```
123 // ------ DUMPSYS (/system/bin/dumpsys) ------
124 // DUMP OF SERVICE activity:
125 // ...
126 // ---------------------------------------------------------------------------
127 // DUMP OF SERVICE input_method:
128 // ...
129 // ---------------------------------------------------------------------------
130 // ```
131 // Here we put each line in a dedicated table, android_dumpstate, keeping
132 // track of the dumpstate `section` and dumpsys `service`.
133 AndroidLogParser log_parser(br_year_, context_->storage.get());
134 StringId section_id = StringId::Null(); // The current dumpstate section.
135 StringId service_id = StringId::Null(); // The current dumpsys service.
136 static constexpr size_t npos = base::StringView::npos;
137 enum { OTHER = 0, DUMPSYS, LOG } cur_sect = OTHER;
138 dumpstate_file_->DecompressLines(
139 [&](const std::vector<base::StringView>& lines) {
140 // Optimization for ParseLogLines() below. Avoids ctor/dtor-ing a new
141 // vector on every line.
142 std::vector<base::StringView> log_line(1);
143 for (const base::StringView& line : lines) {
144 if (line.StartsWith("------ ") && line.EndsWith(" ------")) {
145 // These lines mark the beginning and end of dumpstate sections:
146 // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------
147 // ------ 0.356s was the duration of 'DUMPSYS CRITICAL' ------
148 base::StringView section = line.substr(7);
149 section = section.substr(0, section.size() - 7);
150 bool end_marker = section.find("was the duration of") != npos;
151 service_id = StringId::Null();
152 if (end_marker) {
153 section_id = StringId::Null();
154 } else {
155 section_id = context_->storage->InternString(section);
156 cur_sect = OTHER;
157 if (section.StartsWith("DUMPSYS")) {
158 cur_sect = DUMPSYS;
159 } else if (section.StartsWith("SYSTEM LOG") ||
160 section.StartsWith("EVENT LOG") ||
161 section.StartsWith("RADIO LOG")) {
162 // KERNEL LOG is deliberately omitted because SYSTEM LOG is a
163 // superset. KERNEL LOG contains all dupes.
164 cur_sect = LOG;
165 } else if (section.StartsWith("BLOCK STAT")) {
166 // Coalesce all the block stats into one section. Otherwise they
167 // pollute the table with one section per block device.
168 section_id = context_->storage->InternString("BLOCK STAT");
169 }
170 }
171 continue;
172 }
173 // Skip end marker lines for dumpsys sections.
174 if (cur_sect == DUMPSYS && line.StartsWith("--------- ") &&
175 line.find("was the duration of dumpsys") != npos) {
176 service_id = StringId::Null();
177 continue;
178 }
179 if (cur_sect == DUMPSYS && service_id.is_null() &&
180 line.StartsWith(
181 "----------------------------------------------")) {
182 continue;
183 }
184 if (cur_sect == DUMPSYS && line.StartsWith("DUMP OF SERVICE")) {
185 // DUMP OF SERVICE [CRITICAL|HIGH] ServiceName:
186 base::StringView svc = line.substr(line.rfind(' ') + 1);
187 svc = svc.substr(0, svc.size() - 1);
188 service_id = context_->storage->InternString(svc);
189 } else if (cur_sect == LOG) {
190 // Parse the non-persistent logcat and append to `log_events_`,
191 // together with the persistent one previously parsed by
192 // ParsePersistentLogcat(). Skips entries that are already seen in
193 // the persistent logcat, handling us vs ms truncation.
194 PERFETTO_DCHECK(log_line.size() == 1);
195 log_line[0] = line;
196 log_parser.ParseLogLines(log_line, &log_events_,
197 log_events_last_sorted_idx_);
198 }
199
200 if (build_fpr_.empty() && line.StartsWith("Build fingerprint:")) {
201 build_fpr_ = line.substr(20, line.size() - 20).ToStdString();
202 }
203
204 // Append the line to the android_dumpstate table.
205 context_->storage->mutable_android_dumpstate_table()->Insert(
206 {section_id, service_id, context_->storage->InternString(line)});
207 }
208 });
209 }
210
ParsePersistentLogcat()211 void AndroidBugreportParser::ParsePersistentLogcat() {
212 // 1. List logcat files in reverse timestmap order (old to most recent).
213 // 2. Decode events from log lines into a vector. Dedupe and intern strings.
214 // 3. Globally sort all extracted events.
215 // 4. Insert into the android_logs table.
216
217 AndroidLogParser log_parser(br_year_, context_->storage.get());
218
219 // Sort files to ease the job of the subsequent line-based sort. Unfortunately
220 // lines within each file are not 100% timestamp-ordered, due to things like
221 // kernel messages where log time != event time.
222 std::vector<std::pair<uint64_t, const util::ZipFile*>> log_files;
223 for (const util::ZipFile& zf : zip_file_entries_) {
224 if (base::StartsWith(zf.name(), "FS/data/misc/logd/logcat") &&
225 !base::EndsWith(zf.name(), "logcat.id")) {
226 log_files.push_back(std::make_pair(zf.GetDatetime(), &zf));
227 }
228 }
229
230 std::sort(log_files.begin(), log_files.end());
231
232 // Push all events into the AndroidLogParser. It will take care of string
233 // interning into the pool. Appends entries into `log_events`.
234 for (const auto& log_file : log_files) {
235 log_file.second->DecompressLines(
236 [&](const std::vector<base::StringView>& lines) {
237 log_parser.ParseLogLines(lines, &log_events_);
238 });
239 }
240
241 // Do an initial sorting pass. This is not the final sorting because we
242 // haven't ingested the latest logs from dumpstate yet. But we need this sort
243 // to be able to de-dupe the same lines showing both in dumpstate and in the
244 // persistent log.
245 SortLogEvents();
246 }
247
SortAndStoreLogcat()248 void AndroidBugreportParser::SortAndStoreLogcat() {
249 // Sort the union of all log events parsed from both /data/misc/logd
250 // (persistent logcat on disk) and the dumpstate file (last in-memory logcat).
251 // Before the std::stable_sort, entries in `log_events_` are already "mostly"
252 // sorted, because we processed files in order (see notes above about kernel
253 // logs on why we need a final sort here).
254 // We need stable-sort to preserve FIFO-ness of events emitted at the same
255 // time, logcat is not granular enough (us for persistent, ms for dumpstate).
256 SortLogEvents();
257
258 // Insert the globally sorted events into the android_logs table.
259 for (const auto& e : log_events_) {
260 UniquePid utid = context_->process_tracker->UpdateThread(e.tid, e.pid);
261 context_->storage->mutable_android_log_table()->Insert(
262 {e.ts, utid, e.prio, e.tag, e.msg});
263 }
264 }
265
266 // Populates the `year_` field from the bugreport-xxx.txt file name.
267 // This is because logcat events have only the month and day.
268 // This is obviously bugged for cases of bugreports collected across new year
269 // but we'll live with that.
DetectYearAndBrFilename()270 bool AndroidBugreportParser::DetectYearAndBrFilename() {
271 const util::ZipFile* br_file = FindBugReportFile(zip_file_entries_);
272 if (!br_file) {
273 PERFETTO_ELOG("Could not find bugreport-*.txt in the zip file");
274 return false;
275 }
276
277 std::optional<int32_t> year =
278 ExtractYearFromBugReportFilename(br_file->name());
279 if (!year.has_value()) {
280 PERFETTO_ELOG("Could not parse the year from %s", br_file->name().c_str());
281 return false;
282 }
283 br_year_ = *year;
284 dumpstate_file_ = br_file;
285 return true;
286 }
287
SortLogEvents()288 void AndroidBugreportParser::SortLogEvents() {
289 std::stable_sort(log_events_.begin(), log_events_.end());
290 log_events_last_sorted_idx_ = log_events_.size();
291 }
292
293 } // namespace trace_processor
294 } // namespace perfetto
295