| /* |
| * 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_log_parser.h" |
| |
| #include <string.h> |
| #include <optional> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/base/time.h" |
| #include "perfetto/ext/base/string_utils.h" |
| #include "src/trace_processor/types/trace_processor_context.h" |
| |
| #include "protos/perfetto/common/android_log_constants.pbzero.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| |
| namespace { |
| |
| // Reads a base-10 number and advances the passed StringView beyond the *last* |
| // instance of `sep`. Example: |
| // Input: it="1234 bar". |
| // Output: it="bar", ret=1234. |
| // |
| // `decimal_scale` is used to parse decimals and defines the output resolution. |
| // E.g. input="1", decimal_scale=1000 -> res=100 |
| // input="12", decimal_scale=1000 -> res=120 |
| // input="123", decimal_scale=1000 -> res=123 |
| // input="1234", decimal_scale=1000 -> res=123 |
| // input="1234", decimal_scale=1000000 -> res=123400 |
| std::optional<int> ReadNumAndAdvance(base::StringView* it, |
| char sep, |
| int decimal_scale = 0) { |
| int num = 0; |
| bool sep_found = false; |
| size_t next_it = 0; |
| bool invalid_chars_found = false; |
| for (size_t i = 0; i < it->size(); i++) { |
| char c = it->at(i); |
| if (c == sep) { |
| next_it = i + 1; |
| sep_found = true; |
| continue; |
| } |
| if (sep_found) |
| break; |
| if (c >= '0' && c <= '9') { |
| int digit = static_cast<int>(c - '0'); |
| if (!decimal_scale) { |
| num = num * 10 + digit; |
| } else { |
| decimal_scale /= 10; |
| num += digit * decimal_scale; |
| } |
| continue; |
| } |
| // We found something that is not a digit. Keep looking for the next `sep` |
| // but flag the current token as invalid. |
| invalid_chars_found = true; |
| } |
| if (!sep_found) |
| return std::nullopt; |
| // If we find non-digit characters, we want to still skip the token but return |
| // std::nullopt. The parser below relies on token skipping to deal with cases |
| // where the uid (which we don't care about) is literal ("root" rather than |
| // 0). |
| *it = it->substr(next_it); |
| if (invalid_chars_found) |
| return std::nullopt; |
| return num; |
| } |
| |
| enum class LogcatFormat { |
| kUnknown = 0, |
| |
| // 01-02 03:04:05.678901 1000 2000 V Tag: Message |
| kPersistentLog, |
| |
| // 06-24 15:57:11.346 1000 1493 1918 D Tag: Message |
| // or also |
| // 07-28 14:25:22.181 root 0 0 I Tag : Message |
| kBugreport |
| }; |
| |
| LogcatFormat DetectFormat(base::StringView line) { |
| auto p = base::SplitString(line.ToStdString(), " "); |
| if (p.size() < 5) |
| return LogcatFormat::kUnknown; |
| |
| if (p[0].size() != 5 || p[0][2] != '-') |
| return LogcatFormat::kUnknown; |
| |
| if (p[1].size() < 10 || p[1][2] != ':' || p[1][5] != ':' || p[1][8] != '.') |
| return LogcatFormat::kUnknown; |
| |
| if (p[4].size() == 1 && p[4][0] >= 'A' && p[4][0] <= 'Z') |
| return LogcatFormat::kPersistentLog; |
| |
| if (p[5].size() == 1 && p[5][0] >= 'A' && p[5][0] <= 'Z') |
| return LogcatFormat::kBugreport; |
| |
| return LogcatFormat::kUnknown; |
| } |
| |
| } // namespace |
| |
| // Parses a bunch of logcat lines and appends broken down events into |
| // `log_events`. |
| void AndroidLogParser::ParseLogLines(std::vector<base::StringView> lines, |
| std::vector<AndroidLogEvent>* log_events, |
| size_t dedupe_idx) { |
| int parse_failures = 0; |
| LogcatFormat fmt = LogcatFormat::kUnknown; |
| for (auto line : lines) { |
| if (line.size() < 30 || |
| (line.at(0) == '-' && line.at(1) == '-' && line.at(2) == '-')) { |
| // These are markers like "--------- switch to radio" which we ignore. |
| // The smallest valid logcat line has around 30 chars, as follows: |
| // "06-24 23:10:00.123 1 1 D : ..." |
| continue; |
| } |
| if (fmt == LogcatFormat::kUnknown) { |
| fmt = DetectFormat(line); |
| if (fmt == LogcatFormat::kUnknown) { |
| PERFETTO_DLOG("Could not detect logcat format for: |%s|", |
| line.ToStdString().c_str()); |
| storage_->IncrementStats(stats::android_log_format_invalid); |
| return; |
| } |
| } |
| |
| base::StringView it = line; |
| // 06-24 16:24:23.441532 23153 23153 I wm_on_stop_called: message ... |
| // 07-28 14:25:13.506 root 0 0 I x86/fpu : Supporting XSAVE feature |
| // 0x002: 'SSE registers' |
| std::optional<int> month = ReadNumAndAdvance(&it, '-'); |
| std::optional<int> day = ReadNumAndAdvance(&it, ' '); |
| std::optional<int> hour = ReadNumAndAdvance(&it, ':'); |
| std::optional<int> minute = ReadNumAndAdvance(&it, ':'); |
| std::optional<int> sec = ReadNumAndAdvance(&it, '.'); |
| std::optional<int> ns = ReadNumAndAdvance(&it, ' ', 1000 * 1000 * 1000); |
| |
| if (fmt == LogcatFormat::kBugreport) |
| ReadNumAndAdvance(&it, ' '); // Skip the UID column. |
| |
| std::optional<int> pid = ReadNumAndAdvance(&it, ' '); |
| std::optional<int> tid = ReadNumAndAdvance(&it, ' '); |
| |
| if (!month || !day || !hour || !minute || !sec || !ns || !pid || !tid) { |
| ++parse_failures; |
| continue; |
| } |
| |
| if (it.size() < 4 || it.at(1) != ' ') { |
| ++parse_failures; |
| continue; |
| } |
| |
| char prio_str = it.at(0); |
| int prio = protos::pbzero::AndroidLogPriority::PRIO_UNSPECIFIED; |
| if ('V' == prio_str) { |
| prio = protos::pbzero::AndroidLogPriority::PRIO_VERBOSE; |
| } else if ('D' == prio_str) { |
| prio = protos::pbzero::AndroidLogPriority::PRIO_DEBUG; |
| } else if ('I' == prio_str) { |
| prio = protos::pbzero::AndroidLogPriority::PRIO_INFO; |
| } else if ('W' == prio_str) { |
| prio = protos::pbzero::AndroidLogPriority::PRIO_WARN; |
| } else if ('E' == prio_str) { |
| prio = protos::pbzero::AndroidLogPriority::PRIO_ERROR; |
| } else if ('F' == prio_str) { |
| prio = protos::pbzero::AndroidLogPriority::PRIO_FATAL; |
| } |
| |
| it = it.substr(2); |
| |
| // Find the ': ' that defines the boundary between the tag and message. |
| // We can't just look for ':' because various HALs emit tags with a ':'. |
| base::StringView cat; |
| for (size_t i = 0; i < it.size() - 1; ++i) { |
| if (it.at(i) == ':' && it.at(i + 1) == ' ') { |
| cat = it.substr(0, i); |
| it = it.substr(i + 2); |
| break; |
| } |
| } |
| // Trim trailing spaces, happens in kernel events (e.g. "init :"). |
| while (!cat.empty() && cat.at(cat.size() - 1) == ' ') |
| cat = cat.substr(0, cat.size() - 1); |
| |
| base::StringView msg = it; // The rest is the log message. |
| |
| int64_t secs = base::MkTime(year_, *month, *day, *hour, *minute, *sec); |
| int64_t ts = secs * 1000000000ll + *ns; |
| |
| AndroidLogEvent evt{ts, |
| static_cast<uint32_t>(*pid), |
| static_cast<uint32_t>(*tid), |
| static_cast<uint32_t>(prio), |
| storage_->InternString(cat), |
| storage_->InternString(msg)}; |
| |
| if (dedupe_idx > 0) { |
| // Search for dupes before inserting. |
| // Events in the [0, dedupe_idx] range are sorted by timestamp with ns |
| // resolution. Here we search for dupes within the same millisecond of |
| // the event we are trying to insert. The /1000000*1000000 is to deal with |
| // the fact that events coming from the persistent log have us resolution, |
| // while events from dumpstate (which are often dupes of persistent ones) |
| // have only ms resolution. Here we consider an event a dupe if it has |
| // the same ms-truncated solution, same pid, tid and message. |
| AndroidLogEvent etrunc = evt; |
| etrunc.ts = etrunc.ts / 1000000 * 1000000; |
| auto begin = log_events->begin(); |
| auto end = log_events->begin() + static_cast<ssize_t>(dedupe_idx); |
| bool dupe_found = false; |
| for (auto eit = std::lower_bound(begin, end, etrunc); eit < end; ++eit) { |
| if (eit->ts / 1000000 * 1000000 != etrunc.ts) |
| break; |
| if (eit->msg == evt.msg && eit->tag == evt.tag && eit->tid == evt.tid && |
| eit->pid == evt.pid) { |
| dupe_found = true; |
| break; |
| } |
| } |
| if (dupe_found) { |
| continue; // Skip the current line. |
| } |
| } // if (dedupe_idx) |
| |
| log_events->emplace_back(std::move(evt)); |
| } // for (line : lines) |
| storage_->IncrementStats(stats::android_log_num_failed, parse_failures); |
| } |
| |
| } // namespace trace_processor |
| } // namespace perfetto |