• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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