blob: b8258621c9f5e11137636e724d9e45a2ca498e04 [file] [log] [blame]
/*
* 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