blob: fa1969b6e791f9bd266fcac5e0eb03a4d86de26e [file] [log] [blame]
/*
* Copyright (C) 2019 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/proto/stack_profile_tracker.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/importers/proto/profiler_util.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/stack_traces_util.h"
namespace perfetto {
namespace trace_processor {
SequenceStackProfileTracker::InternLookup::~InternLookup() = default;
SequenceStackProfileTracker::SequenceStackProfileTracker(
TraceProcessorContext* context)
: context_(context), empty_(kNullStringId) {}
SequenceStackProfileTracker::~SequenceStackProfileTracker() = default;
StringId SequenceStackProfileTracker::GetEmptyStringId() {
if (empty_ == kNullStringId) {
empty_ = context_->storage->InternString({"", 0});
}
return empty_;
}
void SequenceStackProfileTracker::AddString(SourceStringId id,
base::StringView str) {
string_map_.emplace(id, str.ToStdString());
}
std::optional<MappingId> SequenceStackProfileTracker::AddMapping(
SourceMappingId id,
const SourceMapping& mapping,
const InternLookup* intern_lookup) {
std::string path;
for (SourceStringId str_id : mapping.name_ids) {
auto opt_str = FindOrInsertString(str_id, intern_lookup,
InternedStringType::kMappingPath);
if (!opt_str)
break;
path += "/" + *opt_str;
}
// When path strings just have single full path(like Chrome does), the mapping
// path gets an extra '/' prepended, strip the extra '/'.
if(base::StartsWith(path, "//")) {
path = path.substr(1);
}
auto opt_build_id = FindAndInternString(mapping.build_id, intern_lookup,
InternedStringType::kBuildId);
if (!opt_build_id) {
context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
PERFETTO_DLOG("Invalid string.");
return std::nullopt;
}
const StringId raw_build_id = opt_build_id.value();
NullTermStringView raw_build_id_str =
context_->storage->GetString(raw_build_id);
StringId build_id = GetEmptyStringId();
if (!raw_build_id_str.empty()) {
// If the build_id is 33 characters long, we assume it's a Breakpad debug
// identifier which is already in Hex and doesn't need conversion.
// TODO(b/148109467): Remove workaround once all active Chrome versions
// write raw bytes instead of a string as build_id.
if (util::IsHexModuleId(raw_build_id_str)) {
build_id = raw_build_id;
} else {
std::string hex_build_id =
base::ToHex(raw_build_id_str.c_str(), raw_build_id_str.size());
build_id =
context_->storage->InternString(base::StringView(hex_build_id));
}
}
tables::StackProfileMappingTable::Row row{
build_id,
static_cast<int64_t>(mapping.exact_offset),
static_cast<int64_t>(mapping.start_offset),
static_cast<int64_t>(mapping.start),
static_cast<int64_t>(mapping.end),
static_cast<int64_t>(mapping.load_bias),
context_->storage->InternString(base::StringView(path))};
tables::StackProfileMappingTable* mappings =
context_->storage->mutable_stack_profile_mapping_table();
std::optional<MappingId> cur_id;
auto it = mapping_idx_.find(row);
if (it != mapping_idx_.end()) {
cur_id = it->second;
} else {
std::vector<MappingId> db_mappings =
context_->global_stack_profile_tracker->FindMappingRow(row.name,
row.build_id);
for (const MappingId preexisting_mapping : db_mappings) {
uint32_t preexisting_row = *mappings->id().IndexOf(preexisting_mapping);
tables::StackProfileMappingTable::Row preexisting_data{
mappings->build_id()[preexisting_row],
mappings->exact_offset()[preexisting_row],
mappings->start_offset()[preexisting_row],
mappings->start()[preexisting_row],
mappings->end()[preexisting_row],
mappings->load_bias()[preexisting_row],
mappings->name()[preexisting_row]};
if (row == preexisting_data) {
cur_id = preexisting_mapping;
}
}
if (!cur_id) {
MappingId mapping_id = mappings->Insert(row).id;
context_->global_stack_profile_tracker->InsertMappingId(
row.name, row.build_id, mapping_id);
cur_id = mapping_id;
}
mapping_idx_.emplace(row, *cur_id);
}
mapping_ids_.emplace(id, *cur_id);
return cur_id;
}
std::optional<FrameId> SequenceStackProfileTracker::AddFrame(
SourceFrameId id,
const SourceFrame& frame,
const InternLookup* intern_lookup) {
std::optional<std::string> opt_name = FindOrInsertString(
frame.name_id, intern_lookup, InternedStringType::kFunctionName);
if (!opt_name) {
context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
PERFETTO_DLOG("Invalid string.");
return std::nullopt;
}
const std::string& name = *opt_name;
const StringId str_id =
context_->storage->InternString(base::StringView(name));
auto opt_mapping = FindOrInsertMapping(frame.mapping_id, intern_lookup);
if (!opt_mapping) {
context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
return std::nullopt;
}
MappingId mapping_id = *opt_mapping;
const auto& mappings = context_->storage->stack_profile_mapping_table();
StringId mapping_name_id =
mappings.name()[*mappings.id().IndexOf(mapping_id)];
auto mapping_name = context_->storage->GetString(mapping_name_id);
tables::StackProfileFrameTable::Row row{str_id, mapping_id,
static_cast<int64_t>(frame.rel_pc)};
auto* frames = context_->storage->mutable_stack_profile_frame_table();
std::optional<FrameId> cur_id;
auto it = frame_idx_.find(row);
if (it != frame_idx_.end()) {
cur_id = it->second;
} else {
std::vector<FrameId> db_frames =
context_->global_stack_profile_tracker->FindFrameIds(mapping_id,
frame.rel_pc);
for (const FrameId preexisting_frame : db_frames) {
uint32_t preexisting_row_id = *frames->id().IndexOf(preexisting_frame);
tables::StackProfileFrameTable::Row preexisting_row{
frames->name()[preexisting_row_id],
frames->mapping()[preexisting_row_id],
frames->rel_pc()[preexisting_row_id]};
if (row == preexisting_row) {
cur_id = preexisting_frame;
}
}
if (!cur_id) {
cur_id = frames->Insert(row).id;
context_->global_stack_profile_tracker->InsertFrameRow(
mapping_id, static_cast<uint64_t>(row.rel_pc), *cur_id);
if (base::Contains(name, '.')) {
// Java frames always contain a '.'
std::optional<std::string> package =
PackageFromLocation(context_->storage.get(), mapping_name);
if (package) {
NameInPackage nip{str_id, context_->storage->InternString(
base::StringView(*package))};
context_->global_stack_profile_tracker->InsertJavaFrameForName(
nip, *cur_id);
} else if (mapping_name.find("/memfd:") == 0) {
NameInPackage nip{str_id, context_->storage->InternString("memfd")};
context_->global_stack_profile_tracker->InsertJavaFrameForName(
nip, *cur_id);
}
}
}
frame_idx_.emplace(row, *cur_id);
}
frame_ids_.emplace(id, *cur_id);
return cur_id;
}
std::optional<CallsiteId> SequenceStackProfileTracker::AddCallstack(
SourceCallstackId id,
const SourceCallstack& frame_ids,
const InternLookup* intern_lookup) {
if (frame_ids.empty())
return std::nullopt;
std::optional<CallsiteId> parent_id;
for (uint32_t depth = 0; depth < frame_ids.size(); ++depth) {
auto opt_frame_id = FindOrInsertFrame(frame_ids[depth], intern_lookup);
if (!opt_frame_id) {
context_->storage->IncrementStats(stats::stackprofile_invalid_frame_id);
return std::nullopt;
}
FrameId frame_id = *opt_frame_id;
tables::StackProfileCallsiteTable::Row row{depth, parent_id, frame_id};
CallsiteId self_id;
auto callsite_it = callsite_idx_.find(row);
if (callsite_it != callsite_idx_.end()) {
self_id = callsite_it->second;
} else {
auto* callsite =
context_->storage->mutable_stack_profile_callsite_table();
self_id = callsite->Insert(row).id;
callsite_idx_.emplace(row, self_id);
}
parent_id = self_id;
}
PERFETTO_DCHECK(parent_id); // The loop ran at least once.
callstack_ids_.emplace(id, *parent_id);
return parent_id;
}
FrameId SequenceStackProfileTracker::GetDatabaseFrameIdForTesting(
SourceFrameId frame_id) {
auto it = frame_ids_.find(frame_id);
if (it == frame_ids_.end()) {
PERFETTO_DLOG("Invalid frame.");
return {};
}
return it->second;
}
std::optional<StringId> SequenceStackProfileTracker::FindAndInternString(
SourceStringId id,
const InternLookup* intern_lookup,
SequenceStackProfileTracker::InternedStringType type) {
if (id == 0)
return GetEmptyStringId();
auto opt_str = FindOrInsertString(id, intern_lookup, type);
if (!opt_str)
return GetEmptyStringId();
return context_->storage->InternString(base::StringView(*opt_str));
}
std::optional<std::string> SequenceStackProfileTracker::FindOrInsertString(
SourceStringId id,
const InternLookup* intern_lookup,
SequenceStackProfileTracker::InternedStringType type) {
if (id == 0)
return "";
auto it = string_map_.find(id);
if (it == string_map_.end()) {
if (intern_lookup) {
auto str = intern_lookup->GetString(id, type);
if (!str) {
context_->storage->IncrementStats(
stats::stackprofile_invalid_string_id);
PERFETTO_DLOG("Invalid string.");
return std::nullopt;
}
return str->ToStdString();
}
return std::nullopt;
}
return it->second;
}
std::optional<MappingId> SequenceStackProfileTracker::FindOrInsertMapping(
SourceMappingId mapping_id,
const InternLookup* intern_lookup) {
std::optional<MappingId> res;
auto it = mapping_ids_.find(mapping_id);
if (it == mapping_ids_.end()) {
if (intern_lookup) {
auto interned_mapping = intern_lookup->GetMapping(mapping_id);
if (interned_mapping) {
res = AddMapping(mapping_id, *interned_mapping, intern_lookup);
return res;
}
}
context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
return res;
}
res = it->second;
return res;
}
std::optional<FrameId> SequenceStackProfileTracker::FindOrInsertFrame(
SourceFrameId frame_id,
const InternLookup* intern_lookup) {
std::optional<FrameId> res;
auto it = frame_ids_.find(frame_id);
if (it == frame_ids_.end()) {
if (intern_lookup) {
auto interned_frame = intern_lookup->GetFrame(frame_id);
if (interned_frame) {
res = AddFrame(frame_id, *interned_frame, intern_lookup);
return res;
}
}
context_->storage->IncrementStats(stats::stackprofile_invalid_frame_id);
PERFETTO_DLOG("Unknown frame %" PRIu64 " : %zu", frame_id,
frame_ids_.size());
return res;
}
res = it->second;
return res;
}
std::optional<CallsiteId> SequenceStackProfileTracker::FindOrInsertCallstack(
SourceCallstackId callstack_id,
const InternLookup* intern_lookup) {
std::optional<CallsiteId> res;
auto it = callstack_ids_.find(callstack_id);
if (it == callstack_ids_.end()) {
auto interned_callstack = intern_lookup->GetCallstack(callstack_id);
if (interned_callstack) {
res = AddCallstack(callstack_id, *interned_callstack, intern_lookup);
return res;
}
context_->storage->IncrementStats(stats::stackprofile_invalid_callstack_id);
PERFETTO_DLOG("Unknown callstack %" PRIu64 " : %zu", callstack_id,
callstack_ids_.size());
return res;
}
res = it->second;
return res;
}
void SequenceStackProfileTracker::ClearIndices() {
string_map_.clear();
mapping_ids_.clear();
callstack_ids_.clear();
frame_ids_.clear();
}
} // namespace trace_processor
} // namespace perfetto