| /* |
| * 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/util/profile_builder.h" |
| #include <algorithm> |
| #include <cstdint> |
| #include <deque> |
| #include <iostream> |
| #include <iterator> |
| #include <vector> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/ext/base/optional.h" |
| #include "perfetto/ext/base/string_utils.h" |
| #include "perfetto/ext/base/string_view.h" |
| #include "perfetto/ext/trace_processor/demangle.h" |
| #include "src/trace_processor/containers/string_pool.h" |
| #include "src/trace_processor/storage/trace_storage.h" |
| #include "src/trace_processor/types/trace_processor_context.h" |
| #include "src/trace_processor/util/annotated_callsites.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| namespace { |
| |
| using protos::pbzero::Stack; |
| |
| base::StringView ToString(CallsiteAnnotation annotation) { |
| switch (annotation) { |
| case CallsiteAnnotation::kNone: |
| return ""; |
| case CallsiteAnnotation::kArtAot: |
| return "aot"; |
| case CallsiteAnnotation::kArtInterpreted: |
| return "interp"; |
| case CallsiteAnnotation::kArtJit: |
| return "jit"; |
| case CallsiteAnnotation::kCommonFrame: |
| return "common-frame"; |
| case CallsiteAnnotation::kCommonFrameInterp: |
| return "common-frame-interp"; |
| } |
| PERFETTO_FATAL("For GCC"); |
| } |
| |
| } // namespace |
| |
| GProfileBuilder::StringTable::StringTable( |
| protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>* |
| result, |
| const StringPool* string_pool) |
| : string_pool_(*string_pool), result_(*result) { |
| // String at index 0 of the string table must be the empty string (see |
| // profile.proto) |
| int64_t empty_index = WriteString(""); |
| PERFETTO_CHECK(empty_index == kEmptyStringIndex); |
| } |
| |
| int64_t GProfileBuilder::StringTable::InternString(base::StringView str) { |
| if (str.empty()) { |
| return kEmptyStringIndex; |
| } |
| auto hash = str.Hash(); |
| auto it = seen_strings_.find(hash); |
| if (it != seen_strings_.end()) { |
| return it->second; |
| } |
| |
| auto pool_id = string_pool_.GetId(str); |
| int64_t index = pool_id ? InternString(*pool_id) : WriteString(str); |
| |
| seen_strings_.insert({hash, index}); |
| return index; |
| } |
| |
| int64_t GProfileBuilder::StringTable::InternString( |
| StringPool::Id string_pool_id) { |
| auto it = seen_string_pool_ids_.find(string_pool_id); |
| if (it != seen_string_pool_ids_.end()) { |
| return it->second; |
| } |
| |
| NullTermStringView str = string_pool_.Get(string_pool_id); |
| |
| int64_t index = str.empty() ? kEmptyStringIndex : WriteString(str); |
| seen_string_pool_ids_.insert({string_pool_id, index}); |
| return index; |
| } |
| |
| int64_t GProfileBuilder::StringTable::GetAnnotatedString( |
| StringPool::Id str, |
| CallsiteAnnotation annotation) { |
| if (str.is_null() || annotation == CallsiteAnnotation::kNone) { |
| return InternString(str); |
| } |
| return GetAnnotatedString(string_pool_.Get(str), annotation); |
| } |
| |
| int64_t GProfileBuilder::StringTable::GetAnnotatedString( |
| base::StringView str, |
| CallsiteAnnotation annotation) { |
| if (str.empty() || annotation == CallsiteAnnotation::kNone) { |
| return InternString(str); |
| } |
| return InternString(base::StringView( |
| str.ToStdString() + " [" + ToString(annotation).ToStdString() + "]")); |
| } |
| |
| int64_t GProfileBuilder::StringTable::WriteString(base::StringView str) { |
| result_->add_string_table(str.data(), str.size()); |
| return next_index_++; |
| } |
| |
| GProfileBuilder::MappingKey::MappingKey( |
| const tables::StackProfileMappingTable::ConstRowReference& mapping, |
| StringTable& string_table) { |
| size = static_cast<uint64_t>(mapping.end() - mapping.start()); |
| file_offset = static_cast<uint64_t>(mapping.exact_offset()); |
| build_id_or_filename = string_table.InternString(mapping.build_id()); |
| if (build_id_or_filename == kEmptyStringIndex) { |
| build_id_or_filename = string_table.InternString(mapping.name()); |
| } |
| } |
| |
| GProfileBuilder::Mapping::Mapping( |
| const tables::StackProfileMappingTable::ConstRowReference& mapping, |
| const StringPool& string_pool, |
| StringTable& string_table) |
| : memory_start(static_cast<uint64_t>(mapping.start())), |
| memory_limit(static_cast<uint64_t>(mapping.end())), |
| file_offset(static_cast<uint64_t>(mapping.exact_offset())), |
| filename(string_table.InternString(mapping.name())), |
| build_id(string_table.InternString(mapping.build_id())), |
| filename_str(string_pool.Get(mapping.name()).ToStdString()) {} |
| |
| // Do some very basic scoring. |
| int64_t GProfileBuilder::Mapping::ComputeMainBinaryScore() const { |
| constexpr const char* kBadSuffixes[] = {".so"}; |
| constexpr const char* kBadPrefixes[] = {"/apex", "/system", "/[", "["}; |
| |
| int64_t score = 0; |
| if (build_id != kEmptyStringIndex) { |
| score += 10; |
| } |
| |
| if (filename != kEmptyStringIndex) { |
| score += 10; |
| } |
| |
| if (debug_info.has_functions) { |
| score += 10; |
| } |
| if (debug_info.has_filenames) { |
| score += 10; |
| } |
| if (debug_info.has_line_numbers) { |
| score += 10; |
| } |
| if (debug_info.has_inline_frames) { |
| score += 10; |
| } |
| |
| if (memory_limit == memory_start) { |
| score -= 1000; |
| } |
| |
| for (const char* suffix : kBadSuffixes) { |
| if (base::EndsWith(filename_str, suffix)) { |
| score -= 1000; |
| break; |
| } |
| } |
| |
| for (const char* prefix : kBadPrefixes) { |
| if (base::StartsWith(filename_str, prefix)) { |
| score -= 1000; |
| break; |
| } |
| } |
| |
| return score; |
| } |
| |
| GProfileBuilder::GProfileBuilder(const TraceProcessorContext* context, |
| const std::vector<ValueType>& sample_types, |
| bool annotated) |
| : context_(*context), |
| string_table_(&result_, &context->storage->string_pool()) { |
| if (annotated) { |
| annotations_.emplace(context); |
| } |
| WriteSampleTypes(sample_types); |
| } |
| |
| GProfileBuilder::~GProfileBuilder() = default; |
| |
| void GProfileBuilder::WriteSampleTypes( |
| const std::vector<ValueType>& sample_types) { |
| for (const auto& value_type : sample_types) { |
| // Write strings first |
| int64_t type = |
| string_table_.InternString(base::StringView(value_type.type)); |
| int64_t unit = |
| string_table_.InternString(base::StringView(value_type.unit)); |
| // Add message later, remember protozero does not allow you to interleave |
| // these write calls. |
| auto* sample_type = result_->add_sample_type(); |
| sample_type->set_type(type); |
| sample_type->set_unit(unit); |
| } |
| } |
| |
| bool GProfileBuilder::AddSample(const protozero::PackedVarInt& location_ids, |
| const protozero::PackedVarInt& values) { |
| PERFETTO_CHECK(!finalized_); |
| if (location_ids.size() == 0) { |
| return false; |
| } |
| auto* sample = result_->add_sample(); |
| sample->set_value(values); |
| sample->set_location_id(location_ids); |
| return true; |
| } |
| |
| bool GProfileBuilder::AddSample(const Stack::Decoder& stack, |
| const protozero::PackedVarInt& values) { |
| PERFETTO_CHECK(!finalized_); |
| |
| auto it = stack.entries(); |
| if (!it) { |
| return true; |
| } |
| |
| auto next = it; |
| ++next; |
| if (!next) { |
| Stack::Entry::Decoder entry(it->as_bytes()); |
| if (entry.has_callsite_id()) { |
| return AddSample( |
| GetLocationIdsForCallsite(CallsiteId(entry.callsite_id())), values); |
| } |
| } |
| |
| // Note pprof orders the stacks leafs first. That is also the ordering |
| // StackBlob uses for entries |
| protozero::PackedVarInt location_ids; |
| for (; it; ++it) { |
| Stack::Entry::Decoder entry(it->as_bytes()); |
| if (entry.has_name()) { |
| location_ids.Append( |
| WriteFakeLocationIfNeeded(entry.name().ToStdString())); |
| } else if (entry.has_callsite_id()) { |
| const protozero::PackedVarInt& ids = |
| GetLocationIdsForCallsite(CallsiteId(entry.callsite_id())); |
| for (auto* p = ids.data(); p < ids.data() + ids.size();) { |
| uint64_t location_id; |
| p = protozero::proto_utils::ParseVarInt(p, ids.data() + ids.size(), |
| &location_id); |
| location_ids.Append(location_id); |
| } |
| } else if (entry.has_frame_id()) { |
| location_ids.Append(WriteLocationIfNeeded(FrameId(entry.frame_id()), |
| CallsiteAnnotation::kNone)); |
| } |
| } |
| return AddSample(location_ids, values); |
| } |
| |
| void GProfileBuilder::Finalize() { |
| if (finalized_) { |
| return; |
| } |
| WriteMappings(); |
| WriteFunctions(); |
| WriteLocations(); |
| finalized_ = true; |
| } |
| |
| std::string GProfileBuilder::Build() { |
| Finalize(); |
| return result_.SerializeAsString(); |
| } |
| |
| CallsiteAnnotation GProfileBuilder::GetAnnotation( |
| const tables::StackProfileCallsiteTable::ConstRowReference& callsite) { |
| if (!annotations_) { |
| return CallsiteAnnotation::kNone; |
| } |
| |
| return annotations_->GetAnnotation(callsite); |
| } |
| |
| const protozero::PackedVarInt& GProfileBuilder::GetLocationIdsForCallsite( |
| const CallsiteId& callsite_id) { |
| auto it = cached_location_ids_.find(callsite_id); |
| if (it != cached_location_ids_.end()) { |
| return it->second; |
| } |
| |
| protozero::PackedVarInt& location_ids = cached_location_ids_[callsite_id]; |
| |
| const auto& cs_table = context_.storage->stack_profile_callsite_table(); |
| |
| base::Optional<tables::StackProfileCallsiteTable::ConstRowReference> |
| start_ref = cs_table.FindById(callsite_id); |
| if (!start_ref) { |
| return location_ids; |
| } |
| |
| location_ids.Append(WriteLocationIfNeeded(*start_ref)); |
| |
| base::Optional<CallsiteId> parent_id = start_ref->parent_id(); |
| while (parent_id) { |
| auto parent_ref = cs_table.FindById(*parent_id); |
| location_ids.Append(WriteLocationIfNeeded(*parent_ref)); |
| parent_id = parent_ref->parent_id(); |
| } |
| |
| return location_ids; |
| } |
| |
| uint64_t GProfileBuilder::WriteLocationIfNeeded(FrameId frame_id, |
| CallsiteAnnotation annotation) { |
| AnnotatedFrameId key{frame_id, annotation}; |
| auto it = seen_locations_.find(key); |
| if (it != seen_locations_.end()) { |
| return it->second; |
| } |
| |
| auto& frames = context_.storage->stack_profile_frame_table(); |
| auto frame = *frames.FindById(key.frame_id); |
| |
| const auto& mappings = context_.storage->stack_profile_mapping_table(); |
| auto mapping = *mappings.FindById(frame.mapping()); |
| uint64_t mapping_id = WriteMappingIfNeeded(mapping); |
| |
| uint64_t& id = |
| locations_[Location{mapping_id, static_cast<uint64_t>(frame.rel_pc()), |
| GetLines(frame, key.annotation, mapping_id)}]; |
| |
| if (id == 0) { |
| id = locations_.size(); |
| } |
| |
| seen_locations_.insert({key, id}); |
| |
| return id; |
| } |
| |
| uint64_t GProfileBuilder::WriteFakeLocationIfNeeded(const std::string& name) { |
| int64_t name_id = string_table_.InternString(base::StringView(name)); |
| auto it = seen_fake_locations_.find(name_id); |
| if (it != seen_fake_locations_.end()) { |
| return it->second; |
| } |
| |
| uint64_t& id = |
| locations_[Location{0, 0, {{WriteFakeFunctionIfNeeded(name_id), 0}}}]; |
| |
| if (id == 0) { |
| id = locations_.size(); |
| } |
| |
| seen_fake_locations_.insert({name_id, id}); |
| |
| return id; |
| } |
| |
| void GProfileBuilder::WriteLocations() { |
| for (const auto& entry : locations_) { |
| auto* location = result_->add_location(); |
| location->set_id(entry.second); |
| location->set_mapping_id(entry.first.mapping_id); |
| if (entry.first.mapping_id != 0) { |
| location->set_address(entry.first.rel_pc + |
| GetMapping(entry.first.mapping_id).memory_start); |
| } |
| for (const Line& line : entry.first.lines) { |
| auto* l = location->add_line(); |
| l->set_function_id(line.function_id); |
| if (line.line != 0) { |
| l->set_line(line.line); |
| } |
| } |
| } |
| } |
| |
| std::vector<GProfileBuilder::Line> GProfileBuilder::GetLines( |
| const tables::StackProfileFrameTable::ConstRowReference& frame, |
| CallsiteAnnotation annotation, |
| uint64_t mapping_id) { |
| std::vector<Line> lines = |
| GetLinesForSymbolSetId(frame.symbol_set_id(), annotation, mapping_id); |
| if (lines.empty()) { |
| uint64_t function_id = WriteFunctionIfNeeded(frame, annotation, mapping_id); |
| lines.push_back({function_id, 0}); |
| } |
| |
| return lines; |
| } |
| |
| std::vector<GProfileBuilder::Line> GProfileBuilder::GetLinesForSymbolSetId( |
| base::Optional<uint32_t> symbol_set_id, |
| CallsiteAnnotation annotation, |
| uint64_t mapping_id) { |
| if (!symbol_set_id) { |
| return {}; |
| } |
| |
| auto& symbols = context_.storage->symbol_table(); |
| |
| using RowRef = |
| perfetto::trace_processor::tables::SymbolTable::ConstRowReference; |
| std::vector<RowRef> symbol_set; |
| for (auto it = symbols.FilterToIterator( |
| {symbols.symbol_set_id().eq(*symbol_set_id)}); |
| it; ++it) { |
| symbol_set.push_back(it.row_reference()); |
| } |
| |
| std::sort(symbol_set.begin(), symbol_set.end(), |
| [](const RowRef& a, const RowRef& b) { return a.id() < b.id(); }); |
| |
| std::vector<GProfileBuilder::Line> lines; |
| for (const RowRef& symbol : symbol_set) { |
| lines.push_back({WriteFunctionIfNeeded(symbol, annotation, mapping_id), |
| symbol.line_number()}); |
| } |
| |
| GetMapping(mapping_id).debug_info.has_inline_frames = true; |
| GetMapping(mapping_id).debug_info.has_line_numbers = true; |
| |
| return lines; |
| } |
| |
| uint64_t GProfileBuilder::WriteFakeFunctionIfNeeded(int64_t name_id) { |
| auto ins = functions_.insert( |
| {Function{name_id, kEmptyStringIndex, kEmptyStringIndex}, |
| functions_.size() + 1}); |
| return ins.first->second; |
| } |
| |
| uint64_t GProfileBuilder::WriteFunctionIfNeeded( |
| const tables::SymbolTable::ConstRowReference& symbol, |
| CallsiteAnnotation annotation, |
| uint64_t mapping_id) { |
| int64_t name = string_table_.GetAnnotatedString(symbol.name(), annotation); |
| int64_t filename = string_table_.InternString(symbol.source_file()); |
| |
| auto ins = functions_.insert( |
| {Function{name, kEmptyStringIndex, filename}, functions_.size() + 1}); |
| uint64_t id = ins.first->second; |
| |
| if (ins.second) { |
| if (name != kEmptyStringIndex) { |
| GetMapping(mapping_id).debug_info.has_functions = true; |
| } |
| if (filename != kEmptyStringIndex) { |
| GetMapping(mapping_id).debug_info.has_filenames = true; |
| } |
| } |
| |
| return id; |
| } |
| |
| uint64_t GProfileBuilder::WriteFunctionIfNeeded( |
| const tables::StackProfileFrameTable::ConstRowReference& frame, |
| CallsiteAnnotation annotation, |
| uint64_t mapping_id) { |
| AnnotatedFrameId key{frame.id(), annotation}; |
| auto it = seen_functions_.find(key); |
| if (it != seen_functions_.end()) { |
| return it->second; |
| } |
| |
| int64_t system_name = string_table_.InternString(frame.name()); |
| int64_t name = kEmptyStringIndex; |
| |
| if (frame.deobfuscated_name()) { |
| name = string_table_.GetAnnotatedString(*frame.deobfuscated_name(), |
| annotation); |
| } else if (system_name != kEmptyStringIndex) { |
| std::unique_ptr<char, base::FreeDeleter> demangled = |
| demangle::Demangle(context_.storage->GetString(frame.name()).c_str()); |
| if (demangled) { |
| name = string_table_.GetAnnotatedString(demangled.get(), annotation); |
| } else { |
| // demangling failed, expected if the name wasn't mangled. In any case |
| // reuse the system_name as this is what UI will usually display. |
| name = string_table_.GetAnnotatedString(frame.name(), annotation); |
| } |
| } |
| |
| auto ins = functions_.insert( |
| {Function{name, system_name, kEmptyStringIndex}, functions_.size() + 1}); |
| uint64_t id = ins.first->second; |
| seen_functions_.insert({key, id}); |
| |
| if (ins.second && |
| (name != kEmptyStringIndex || system_name != kEmptyStringIndex)) { |
| GetMapping(mapping_id).debug_info.has_functions = true; |
| } |
| |
| return id; |
| } |
| |
| void GProfileBuilder::WriteFunctions() { |
| for (const auto& entry : functions_) { |
| auto* func = result_->add_function(); |
| func->set_id(entry.second); |
| if (entry.first.name != 0) { |
| func->set_name(entry.first.name); |
| } |
| if (entry.first.system_name != 0) { |
| func->set_system_name(entry.first.system_name); |
| } |
| if (entry.first.filename != 0) { |
| func->set_filename(entry.first.filename); |
| } |
| } |
| } |
| |
| uint64_t GProfileBuilder::WriteMappingIfNeeded( |
| const tables::StackProfileMappingTable::ConstRowReference& mapping_ref) { |
| auto it = seen_mappings_.find(mapping_ref.id()); |
| if (it != seen_mappings_.end()) { |
| return it->second; |
| } |
| |
| auto ins = mapping_keys_.insert( |
| {MappingKey(mapping_ref, string_table_), mapping_keys_.size() + 1}); |
| |
| if (ins.second) { |
| mappings_.push_back( |
| Mapping(mapping_ref, context_.storage->string_pool(), string_table_)); |
| } |
| |
| return ins.first->second; |
| } |
| |
| void GProfileBuilder::WriteMapping(uint64_t mapping_id) { |
| const Mapping& mapping = GetMapping(mapping_id); |
| auto m = result_->add_mapping(); |
| m->set_id(mapping_id); |
| m->set_memory_start(mapping.memory_start); |
| m->set_memory_limit(mapping.memory_limit); |
| m->set_file_offset(mapping.file_offset); |
| m->set_filename(mapping.filename); |
| m->set_build_id(mapping.build_id); |
| m->set_has_functions(mapping.debug_info.has_functions); |
| m->set_has_filenames(mapping.debug_info.has_filenames); |
| m->set_has_line_numbers(mapping.debug_info.has_line_numbers); |
| m->set_has_inline_frames(mapping.debug_info.has_inline_frames); |
| } |
| |
| void GProfileBuilder::WriteMappings() { |
| // The convention in pprof files is to write the mapping for the main binary |
| // first. So lets do just that. |
| base::Optional<uint64_t> main_mapping_id = GuessMainBinary(); |
| if (main_mapping_id) { |
| WriteMapping(*main_mapping_id); |
| } |
| |
| for (size_t i = 0; i < mappings_.size(); ++i) { |
| uint64_t mapping_id = i + 1; |
| if (main_mapping_id && *main_mapping_id == mapping_id) { |
| continue; |
| } |
| WriteMapping(mapping_id); |
| } |
| } |
| |
| base::Optional<uint64_t> GProfileBuilder::GuessMainBinary() const { |
| std::vector<int64_t> mapping_scores; |
| |
| for (const auto& mapping : mappings_) { |
| mapping_scores.push_back(mapping.ComputeMainBinaryScore()); |
| } |
| |
| auto it = std::max_element(mapping_scores.begin(), mapping_scores.end()); |
| |
| if (it == mapping_scores.end()) { |
| return base::nullopt; |
| } |
| |
| return static_cast<uint64_t>(std::distance(mapping_scores.begin(), it) + 1); |
| } |
| |
| } // namespace trace_processor |
| } // namespace perfetto |