/* * 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 "perfetto/base/build_config.h" #include "perfetto/profiling/pprof_builder.h" #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) #include #endif #include #include #include #include #include #include #include #include "tools/trace_to_text/utils.h" #include "perfetto/base/logging.h" #include "perfetto/ext/base/hash.h" #include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/base/utils.h" #include "perfetto/protozero/packed_repeated_fields.h" #include "perfetto/protozero/scattered_heap_buffer.h" #include "perfetto/trace_processor/trace_processor.h" #include "src/profiling/symbolizer/symbolize_database.h" #include "src/profiling/symbolizer/symbolizer.h" #include "src/trace_processor/containers/string_pool.h" #include "protos/perfetto/trace/trace.pbzero.h" #include "protos/perfetto/trace/trace_packet.pbzero.h" #include "protos/third_party/pprof/profile.pbzero.h" // Quick hint on navigating the file: // Conversions for both perf and heap profiles start with |TraceToPprof|. // Non-shared logic is in the |heap_profile| and |perf_profile| namespaces. // // To build one or more profiles, first the callstack information is queried // from the SQL tables, and converted into an in-memory representation by // |PreprocessLocations|. Then an instance of |GProfileBuilder| is used to // accumulate samples for that profile, and emit all additional information as a // serialized proto. Only the entities referenced by that particular // |GProfileBuilder| instance are emitted. // // See protos/third_party/pprof/profile.proto for the meaning of terms like // function/location/line. namespace { using StringId = ::perfetto::trace_processor::StringPool::Id; // In-memory representation of a Profile.Function. struct Function { StringId name_id = StringId::Null(); StringId system_name_id = StringId::Null(); StringId filename_id = StringId::Null(); Function(StringId n, StringId s, StringId f) : name_id(n), system_name_id(s), filename_id(f) {} bool operator==(const Function& other) const { return std::tie(name_id, system_name_id, filename_id) == std::tie(other.name_id, other.system_name_id, other.filename_id); } }; // In-memory representation of a Profile.Line. struct Line { int64_t function_id = 0; // LocationTracker's interned Function id int64_t line_no = 0; Line(int64_t func, int64_t line) : function_id(func), line_no(line) {} bool operator==(const Line& other) const { return function_id == other.function_id && line_no == other.line_no; } }; // In-memory representation of a Profile.Location. struct Location { int64_t mapping_id = 0; // sqlite row id // Common case: location references a single function. int64_t single_function_id = 0; // interned Function id // Alternatively: multiple inlined functions, recovered via offline // symbolisation. Leaf-first ordering. std::vector inlined_functions; Location(int64_t map, int64_t func, std::vector inlines) : mapping_id(map), single_function_id(func), inlined_functions(std::move(inlines)) {} bool operator==(const Location& other) const { return std::tie(mapping_id, single_function_id, inlined_functions) == std::tie(other.mapping_id, other.single_function_id, other.inlined_functions); } }; } // namespace template <> struct std::hash { size_t operator()(const Function& loc) const { perfetto::base::Hash hasher; hasher.Update(loc.name_id.raw_id()); hasher.Update(loc.system_name_id.raw_id()); hasher.Update(loc.filename_id.raw_id()); return static_cast(hasher.digest()); } }; template <> struct std::hash { size_t operator()(const Location& loc) const { perfetto::base::Hash hasher; hasher.Update(loc.mapping_id); hasher.Update(loc.single_function_id); for (auto line : loc.inlined_functions) { hasher.Update(line.function_id); hasher.Update(line.line_no); } return static_cast(hasher.digest()); } }; namespace perfetto { namespace trace_to_text { namespace { using ::perfetto::trace_processor::Iterator; void MaybeDemangle(std::string* name) { #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) char* data = nullptr; #else int ignored; char* data = abi::__cxa_demangle(name->c_str(), nullptr, nullptr, &ignored); #endif if (data) { *name = data; free(data); } } uint64_t ToPprofId(int64_t id) { PERFETTO_DCHECK(id >= 0); return static_cast(id) + 1; } std::string AsCsvString(std::vector vals) { std::string ret; for (size_t i = 0; i < vals.size(); i++) { if (i != 0) { ret += ","; } ret += std::to_string(vals[i]); } return ret; } base::Optional GetStatsEntry( trace_processor::TraceProcessor* tp, const std::string& name, base::Optional idx = base::nullopt) { std::string query = "select value from stats where name == '" + name + "'"; if (idx.has_value()) query += " and idx == " + std::to_string(idx.value()); auto it = tp->ExecuteQuery(query); if (!it.Next()) { if (!it.Status().ok()) { PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", it.Status().message().c_str()); return base::nullopt; } // some stats are not present unless non-zero return base::make_optional(0); } return base::make_optional(it.Get(0).AsLong()); } // Interns Locations, Lines, and Functions. Interning is done by the entity's // contents, and has no relation to the row ids in the SQL tables. // Contains all data for the trace, so can be reused when emitting multiple // profiles. // // TODO(rsavitski): consider moving mappings into here as well. For now, they're // still emitted in a single scan during profile building. Mappings should be // unique-enough already in the SQL tables, with only incremental state clearing // duplicating entries. class LocationTracker { public: int64_t InternLocation(Location loc) { auto it = locations_.find(loc); if (it == locations_.end()) { bool inserted = false; std::tie(it, inserted) = locations_.emplace( std::move(loc), static_cast(locations_.size())); PERFETTO_DCHECK(inserted); } return it->second; } int64_t InternFunction(Function func) { auto it = functions_.find(func); if (it == functions_.end()) { bool inserted = false; std::tie(it, inserted) = functions_.emplace(func, static_cast(functions_.size())); PERFETTO_DCHECK(inserted); } return it->second; } bool IsCallsiteProcessed(int64_t callstack_id) const { return callsite_to_locations_.find(callstack_id) != callsite_to_locations_.end(); } void MaybeSetCallsiteLocations(int64_t callstack_id, const std::vector& locs) { // nop if already set callsite_to_locations_.emplace(callstack_id, locs); } const std::vector& LocationsForCallstack( int64_t callstack_id) const { auto it = callsite_to_locations_.find(callstack_id); PERFETTO_CHECK(callstack_id >= 0 && it != callsite_to_locations_.end()); return it->second; } const std::unordered_map& AllLocations() const { return locations_; } const std::unordered_map& AllFunctions() const { return functions_; } private: // Root-first location ids for a given callsite id. std::unordered_map> callsite_to_locations_; std::unordered_map locations_; std::unordered_map functions_; }; struct PreprocessedInline { StringId system_name_id = StringId::Null(); StringId filename_id = StringId::Null(); int64_t line_no = 0; PreprocessedInline(StringId s, StringId f, int64_t line) : system_name_id(s), filename_id(f), line_no(line) {} }; std::unordered_map> PreprocessInliningInfo(trace_processor::TraceProcessor* tp, trace_processor::StringPool* interner) { std::unordered_map> inlines; // Most-inlined function (leaf) has the lowest id within a symbol set. Query // such that the per-set line vectors are built up leaf-first. Iterator it = tp->ExecuteQuery( "select symbol_set_id, name, source_file, line_number from " "stack_profile_symbol order by symbol_set_id asc, id asc;"); while (it.Next()) { int64_t symbol_set_id = it.Get(0).AsLong(); auto func_sysname = it.Get(1).is_null() ? "" : it.Get(1).AsString(); auto filename = it.Get(2).is_null() ? "" : it.Get(2).AsString(); int64_t line_no = it.Get(3).AsLong(); inlines[symbol_set_id].emplace_back(interner->InternString(func_sysname), interner->InternString(filename), line_no); } if (!it.Status().ok()) { PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", it.Status().message().c_str()); return {}; } return inlines; } // Extracts and interns the unique frames and locations (as defined by the proto // format) from the callstack SQL tables. // // Approach: // * for each callstack (callsite ids of the leaves): // * use experimental_annotated_callstack to build the full list of // constituent frames // * for each frame (root to leaf): // * intern the location and function(s) // * remember the mapping from callsite_id to the callstack so far (from // the root and including the frame being considered) // // Optionally mixes in the annotations as a frame name suffix (since there's no // good way to attach extra info to locations in the proto format). This relies // on the annotations (produced by experimental_annotated_callstack) to be // stable for a given callsite (equivalently: dependent only on their parents). LocationTracker PreprocessLocations(trace_processor::TraceProcessor* tp, trace_processor::StringPool* interner, bool annotate_frames) { LocationTracker tracker; // Keyed by symbol_set_id, discarded once this function converts the inlines // into Line and Function entries. std::unordered_map> inlining_info = PreprocessInliningInfo(tp, interner); // Higher callsite ids most likely correspond to the deepest stacks, so we'll // fill more of the overall callsite->location map by visiting the callsited // in decreasing id order. Since processing a callstack also fills in the data // for all parent callsites. Iterator cid_it = tp->ExecuteQuery( "select id from stack_profile_callsite order by id desc;"); while (cid_it.Next()) { int64_t query_cid = cid_it.Get(0).AsLong(); // If the leaf has been processed, the rest of the stack is already known. if (tracker.IsCallsiteProcessed(query_cid)) continue; std::string annotated_query = "select sp.id, sp.annotation, spf.mapping, " "ifnull(spf.deobfuscated_name, spf.name), spf.symbol_set_id from " "experimental_annotated_callstack(" + std::to_string(query_cid) + ") sp join stack_profile_frame spf on (sp.frame_id == spf.id) " "order by depth asc"; Iterator c_it = tp->ExecuteQuery(annotated_query); std::vector callstack_loc_ids; while (c_it.Next()) { int64_t cid = c_it.Get(0).AsLong(); int64_t mapping_id = c_it.Get(2).AsLong(); auto annotation = c_it.Get(1).is_null() ? "" : c_it.Get(1).AsString(); auto func_sysname = c_it.Get(3).is_null() ? "" : c_it.Get(3).AsString(); base::Optional symbol_set_id = c_it.Get(4).is_null() ? base::nullopt : base::make_optional(c_it.Get(4).AsLong()); Location loc(mapping_id, /*single_function_id=*/-1, {}); auto intern_function = [interner, &tracker, annotate_frames]( StringId func_sysname_id, StringId filename_id, const std::string& anno) { std::string func_name = interner->Get(func_sysname_id).ToStdString(); MaybeDemangle(&func_name); if (annotate_frames && !anno.empty() && !func_name.empty()) func_name = func_name + " [" + anno + "]"; StringId func_name_id = interner->InternString(base::StringView(func_name)); Function func(func_name_id, func_sysname_id, filename_id); return tracker.InternFunction(func); }; // Inlining information available if (symbol_set_id.has_value()) { auto it = inlining_info.find(*symbol_set_id); if (it == inlining_info.end()) { PERFETTO_DFATAL_OR_ELOG( "Failed to find stack_profile_symbol entry for symbol_set_id " "%" PRIi64 "", *symbol_set_id); return {}; } // N inlined functions for (const auto& line : it->second) { int64_t func_id = intern_function(line.system_name_id, line.filename_id, annotation); loc.inlined_functions.emplace_back(func_id, line.line_no); } } else { // Otherwise - single function int64_t func_id = intern_function(interner->InternString(func_sysname), /*filename_id=*/StringId::Null(), annotation); loc.single_function_id = func_id; } int64_t loc_id = tracker.InternLocation(std::move(loc)); // Update the tracker with the locations so far (for example, at depth 2, // we'll have 3 root-most locations in |callstack_loc_ids|). callstack_loc_ids.push_back(loc_id); tracker.MaybeSetCallsiteLocations(cid, callstack_loc_ids); } if (!c_it.Status().ok()) { PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", c_it.Status().message().c_str()); return {}; } } if (!cid_it.Status().ok()) { PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", cid_it.Status().message().c_str()); return {}; } return tracker; } // Builds the |perftools.profiles.Profile| proto. class GProfileBuilder { public: GProfileBuilder(const LocationTracker& locations, trace_processor::StringPool* interner) : locations_(locations), interner_(interner) { // The pprof format requires the first entry in the string table to be the // empty string. int64_t empty_id = ToStringTableId(StringId::Null()); PERFETTO_CHECK(empty_id == 0); } void WriteSampleTypes( const std::vector>& sample_types) { for (const auto& st : sample_types) { auto* sample_type = result_->add_sample_type(); sample_type->set_type( ToStringTableId(interner_->InternString(base::StringView(st.first)))); sample_type->set_unit(ToStringTableId( interner_->InternString(base::StringView(st.second)))); } } bool AddSample(const protozero::PackedVarInt& values, int64_t callstack_id) { const auto& location_ids = locations_.LocationsForCallstack(callstack_id); if (location_ids.empty()) { PERFETTO_DFATAL_OR_ELOG( "Failed to find frames for callstack id %" PRIi64 "", callstack_id); return false; } // LocationTracker stores location lists root-first, but the pprof format // requires leaf-first. protozero::PackedVarInt packed_locs; for (auto it = location_ids.rbegin(); it != location_ids.rend(); ++it) packed_locs.Append(ToPprofId(*it)); auto* gsample = result_->add_sample(); gsample->set_value(values); gsample->set_location_id(packed_locs); // Remember the locations s.t. we only serialize the referenced ones. seen_locations_.insert(location_ids.cbegin(), location_ids.cend()); return true; } std::string CompleteProfile(trace_processor::TraceProcessor* tp) { std::set seen_mappings; std::set seen_functions; if (!WriteLocations(&seen_mappings, &seen_functions)) return {}; if (!WriteFunctions(seen_functions)) return {}; if (!WriteMappings(tp, seen_mappings)) return {}; WriteStringTable(); return result_.SerializeAsString(); } private: // Serializes the Profile.Location entries referenced by this profile. bool WriteLocations(std::set* seen_mappings, std::set* seen_functions) { const std::unordered_map& locations = locations_.AllLocations(); size_t written_locations = 0; for (const auto& loc_and_id : locations) { const auto& loc = loc_and_id.first; int64_t id = loc_and_id.second; if (seen_locations_.find(id) == seen_locations_.end()) continue; written_locations += 1; seen_mappings->emplace(loc.mapping_id); auto* glocation = result_->add_location(); glocation->set_id(ToPprofId(id)); glocation->set_mapping_id(ToPprofId(loc.mapping_id)); if (!loc.inlined_functions.empty()) { for (const auto& line : loc.inlined_functions) { seen_functions->insert(line.function_id); auto* gline = glocation->add_line(); gline->set_function_id(ToPprofId(line.function_id)); gline->set_line(line.line_no); } } else { seen_functions->insert(loc.single_function_id); glocation->add_line()->set_function_id( ToPprofId(loc.single_function_id)); } } if (written_locations != seen_locations_.size()) { PERFETTO_DFATAL_OR_ELOG( "Found only %zu/%zu locations during serialization.", written_locations, seen_locations_.size()); return false; } return true; } // Serializes the Profile.Function entries referenced by this profile. bool WriteFunctions(const std::set& seen_functions) { const std::unordered_map& functions = locations_.AllFunctions(); size_t written_functions = 0; for (const auto& func_and_id : functions) { const auto& func = func_and_id.first; int64_t id = func_and_id.second; if (seen_functions.find(id) == seen_functions.end()) continue; written_functions += 1; auto* gfunction = result_->add_function(); gfunction->set_id(ToPprofId(id)); gfunction->set_name(ToStringTableId(func.name_id)); gfunction->set_system_name(ToStringTableId(func.system_name_id)); if (!func.filename_id.is_null()) gfunction->set_filename(ToStringTableId(func.filename_id)); } if (written_functions != seen_functions.size()) { PERFETTO_DFATAL_OR_ELOG( "Found only %zu/%zu functions during serialization.", written_functions, seen_functions.size()); return false; } return true; } // Serializes the Profile.Mapping entries referenced by this profile. bool WriteMappings(trace_processor::TraceProcessor* tp, const std::set& seen_mappings) { Iterator mapping_it = tp->ExecuteQuery( "SELECT id, exact_offset, start, end, name " "FROM stack_profile_mapping;"); size_t mappings_no = 0; while (mapping_it.Next()) { int64_t id = mapping_it.Get(0).AsLong(); if (seen_mappings.find(id) == seen_mappings.end()) continue; ++mappings_no; auto interned_filename = ToStringTableId( interner_->InternString(mapping_it.Get(4).AsString())); auto* gmapping = result_->add_mapping(); gmapping->set_id(ToPprofId(id)); // Do not set the build_id here to avoid downstream services // trying to symbolize (e.g. b/141735056) gmapping->set_file_offset( static_cast(mapping_it.Get(1).AsLong())); gmapping->set_memory_start( static_cast(mapping_it.Get(2).AsLong())); gmapping->set_memory_limit( static_cast(mapping_it.Get(3).AsLong())); gmapping->set_filename(interned_filename); } if (!mapping_it.Status().ok()) { PERFETTO_DFATAL_OR_ELOG("Invalid mapping iterator: %s", mapping_it.Status().message().c_str()); return false; } if (mappings_no != seen_mappings.size()) { PERFETTO_DFATAL_OR_ELOG("Missing mappings."); return false; } return true; } void WriteStringTable() { for (StringId id : string_table_) { trace_processor::NullTermStringView s = interner_->Get(id); result_->add_string_table(s.data(), s.size()); } } int64_t ToStringTableId(StringId interned_id) { auto it = interning_remapper_.find(interned_id); if (it == interning_remapper_.end()) { int64_t table_id = static_cast(string_table_.size()); string_table_.push_back(interned_id); bool inserted = false; std::tie(it, inserted) = interning_remapper_.emplace(interned_id, table_id); PERFETTO_DCHECK(inserted); } return it->second; } // Contains all locations, lines, functions (in memory): const LocationTracker& locations_; // String interner, strings referenced by LocationTracker are already // interned. The new internings will come from mappings, and sample types. trace_processor::StringPool* interner_; // The profile format uses the repeated string_table field's index as an // implicit id, so these structures remap the interned strings into sequential // ids. Only the strings referenced by this GProfileBuilder instance will be // added to the table. std::unordered_map interning_remapper_; std::vector string_table_; // Profile proto being serialized. protozero::HeapBuffered result_; // Set of locations referenced by the added samples. std::set seen_locations_; }; namespace heap_profile { struct View { const char* type; const char* unit; const char* aggregator; const char* filter; }; const View kSpaceView{"space", "bytes", "SUM(size)", nullptr}; const View kAllocSpaceView{"alloc_space", "bytes", "SUM(size)", "size >= 0"}; const View kAllocObjectsView{"alloc_objects", "count", "sum(count)", "size >= 0"}; const View kObjectsView{"objects", "count", "SUM(count)", nullptr}; const View kViews[] = {kAllocObjectsView, kObjectsView, kAllocSpaceView, kSpaceView}; static bool VerifyPIDStats(trace_processor::TraceProcessor* tp, uint64_t pid) { bool success = true; base::Optional stat = GetStatsEntry(tp, "heapprofd_buffer_corrupted", base::make_optional(pid)); if (!stat.has_value()) { PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_corrupted stat"); } else if (stat.value() > 0) { success = false; PERFETTO_ELOG("WARNING: The profile for %" PRIu64 " ended early due to a buffer corruption." " THIS IS ALWAYS A BUG IN HEAPPROFD OR" " CLIENT MEMORY CORRUPTION.", pid); } stat = GetStatsEntry(tp, "heapprofd_buffer_overran", base::make_optional(pid)); if (!stat.has_value()) { PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_overran stat"); } else if (stat.value() > 0) { success = false; PERFETTO_ELOG("WARNING: The profile for %" PRIu64 " ended early due to a buffer overrun.", pid); } stat = GetStatsEntry(tp, "heapprofd_rejected_concurrent", pid); if (!stat.has_value()) { PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_rejected_concurrent stat"); } else if (stat.value() > 0) { success = false; PERFETTO_ELOG("WARNING: The profile for %" PRIu64 " was rejected due to a concurrent profile.", pid); } return success; } static std::vector BuildViewIterators( trace_processor::TraceProcessor* tp, uint64_t upid, uint64_t ts, const char* heap_name) { std::vector view_its; for (size_t i = 0; i < base::ArraySize(kViews); ++i) { const View& v = kViews[i]; std::string query = "SELECT hpa.callsite_id "; query += ", " + std::string(v.aggregator) + " FROM heap_profile_allocation hpa "; // TODO(fmayer): Figure out where negative callsite_id comes from. query += "WHERE hpa.callsite_id >= 0 "; query += "AND hpa.upid = " + std::to_string(upid) + " "; query += "AND hpa.ts <= " + std::to_string(ts) + " "; query += "AND hpa.heap_name = '" + std::string(heap_name) + "' "; if (v.filter) query += "AND " + std::string(v.filter) + " "; query += "GROUP BY hpa.callsite_id;"; view_its.emplace_back(tp->ExecuteQuery(query)); } return view_its; } static bool WriteAllocations(GProfileBuilder* builder, std::vector* view_its) { for (;;) { bool all_next = true; bool any_next = false; for (size_t i = 0; i < base::ArraySize(kViews); ++i) { Iterator& it = (*view_its)[i]; bool next = it.Next(); if (!it.Status().ok()) { PERFETTO_DFATAL_OR_ELOG("Invalid view iterator: %s", it.Status().message().c_str()); return false; } all_next = all_next && next; any_next = any_next || next; } if (!all_next) { PERFETTO_CHECK(!any_next); break; } protozero::PackedVarInt sample_values; int64_t callstack_id = -1; for (size_t i = 0; i < base::ArraySize(kViews); ++i) { if (i == 0) { callstack_id = (*view_its)[i].Get(0).AsLong(); } else if (callstack_id != (*view_its)[i].Get(0).AsLong()) { PERFETTO_DFATAL_OR_ELOG("Wrong callstack."); return false; } sample_values.Append((*view_its)[i].Get(1).AsLong()); } if (!builder->AddSample(sample_values, callstack_id)) return false; } return true; } static bool TraceToHeapPprof(trace_processor::TraceProcessor* tp, std::vector* output, bool annotate_frames, uint64_t target_pid, const std::vector& target_timestamps) { trace_processor::StringPool interner; LocationTracker locations = PreprocessLocations(tp, &interner, annotate_frames); bool any_fail = false; Iterator it = tp->ExecuteQuery( "select distinct hpa.upid, hpa.ts, p.pid, hpa.heap_name " "from heap_profile_allocation hpa, " "process p where p.upid = hpa.upid;"); while (it.Next()) { GProfileBuilder builder(locations, &interner); uint64_t upid = static_cast(it.Get(0).AsLong()); uint64_t ts = static_cast(it.Get(1).AsLong()); uint64_t profile_pid = static_cast(it.Get(2).AsLong()); const char* heap_name = it.Get(3).AsString(); if ((target_pid > 0 && profile_pid != target_pid) || (!target_timestamps.empty() && std::find(target_timestamps.begin(), target_timestamps.end(), ts) == target_timestamps.end())) { continue; } if (!VerifyPIDStats(tp, profile_pid)) any_fail = true; std::vector> sample_types; for (size_t i = 0; i < base::ArraySize(kViews); ++i) { sample_types.emplace_back(std::string(kViews[i].type), std::string(kViews[i].unit)); } builder.WriteSampleTypes(sample_types); std::vector view_its = BuildViewIterators(tp, upid, ts, heap_name); std::string profile_proto; if (WriteAllocations(&builder, &view_its)) { profile_proto = builder.CompleteProfile(tp); } output->emplace_back( SerializedProfile{ProfileType::kHeapProfile, profile_pid, std::move(profile_proto), heap_name}); } if (!it.Status().ok()) { PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", it.Status().message().c_str()); return false; } if (any_fail) { PERFETTO_ELOG( "One or more of your profiles had an issue. Please consult " "https://perfetto.dev/docs/data-sources/" "native-heap-profiler#troubleshooting"); } return true; } } // namespace heap_profile namespace perf_profile { struct ProcessInfo { uint64_t pid; std::vector utids; }; // Returns a map of upid -> {pid, utids[]} for sampled processes. static std::map GetProcessMap( trace_processor::TraceProcessor* tp) { Iterator it = tp->ExecuteQuery( "select distinct process.upid, process.pid, thread.utid from perf_sample " "join thread using (utid) join process using (upid) where callsite_id is " "not null order by process.upid asc"); std::map process_map; while (it.Next()) { uint64_t upid = static_cast(it.Get(0).AsLong()); uint64_t pid = static_cast(it.Get(1).AsLong()); uint64_t utid = static_cast(it.Get(2).AsLong()); process_map[upid].pid = pid; process_map[upid].utids.push_back(utid); } if (!it.Status().ok()) { PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", it.Status().message().c_str()); return {}; } return process_map; } static void LogTracePerfEventIssues(trace_processor::TraceProcessor* tp) { base::Optional stat = GetStatsEntry(tp, "perf_samples_skipped"); if (!stat.has_value()) { PERFETTO_DFATAL_OR_ELOG("Failed to look up perf_samples_skipped stat"); } else if (stat.value() > 0) { PERFETTO_ELOG( "Warning: the trace recorded %" PRIi64 " skipped samples, which otherwise matched the tracing config. This " "would cause a process to be completely absent from the trace, but " "does *not* imply data loss in any of the output profiles.", stat.value()); } stat = GetStatsEntry(tp, "perf_samples_skipped_dataloss"); if (!stat.has_value()) { PERFETTO_DFATAL_OR_ELOG( "Failed to look up perf_samples_skipped_dataloss stat"); } else if (stat.value() > 0) { PERFETTO_ELOG("DATA LOSS: the trace recorded %" PRIi64 " lost perf samples (within traced_perf). This means that " "the trace is missing information, but it is not known " "which profile that affected.", stat.value()); } // Check if any per-cpu ringbuffers encountered dataloss (as recorded by the // kernel). Iterator it = tp->ExecuteQuery( "select idx, value from stats where name == 'perf_cpu_lost_records' and " "value > 0 order by idx asc"); while (it.Next()) { PERFETTO_ELOG( "DATA LOSS: during the trace, the per-cpu kernel ring buffer for cpu " "%" PRIi64 " recorded %" PRIi64 " lost samples. This means that the trace is missing information, " "but it is not known which profile that affected.", static_cast(it.Get(0).AsLong()), static_cast(it.Get(1).AsLong())); } if (!it.Status().ok()) { PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", it.Status().message().c_str()); } } // TODO(rsavitski): decide whether errors in |AddSample| should result in an // empty profile (and/or whether they should make the overall conversion // unsuccessful). Furthermore, clarify the return value's semantics for both // perf and heap profiles. static bool TraceToPerfPprof(trace_processor::TraceProcessor* tp, std::vector* output, bool annotate_frames, uint64_t target_pid) { trace_processor::StringPool interner; LocationTracker locations = PreprocessLocations(tp, &interner, annotate_frames); LogTracePerfEventIssues(tp); // Aggregate samples by upid when building profiles. std::map process_map = GetProcessMap(tp); for (const auto& p : process_map) { const ProcessInfo& process = p.second; if (target_pid != 0 && process.pid != target_pid) continue; GProfileBuilder builder(locations, &interner); builder.WriteSampleTypes({{"samples", "count"}}); std::string query = "select callsite_id from perf_sample where utid in (" + AsCsvString(process.utids) + ") and callsite_id is not null order by ts asc;"; protozero::PackedVarInt single_count_value; single_count_value.Append(1); Iterator it = tp->ExecuteQuery(query); while (it.Next()) { int64_t callsite_id = static_cast(it.Get(0).AsLong()); builder.AddSample(single_count_value, callsite_id); } if (!it.Status().ok()) { PERFETTO_DFATAL_OR_ELOG("Failed to iterate over samples: %s", it.Status().c_message()); return false; } std::string profile_proto = builder.CompleteProfile(tp); output->emplace_back(SerializedProfile{ ProfileType::kPerfProfile, process.pid, std::move(profile_proto), ""}); } return true; } } // namespace perf_profile } // namespace bool TraceToPprof(trace_processor::TraceProcessor* tp, std::vector* output, ConversionMode mode, uint64_t flags, uint64_t pid, const std::vector& timestamps) { bool annotate_frames = flags & static_cast(ConversionFlags::kAnnotateFrames); switch (mode) { case (ConversionMode::kHeapProfile): return heap_profile::TraceToHeapPprof(tp, output, annotate_frames, pid, timestamps); case (ConversionMode::kPerfProfile): return perf_profile::TraceToPerfPprof(tp, output, annotate_frames, pid); } PERFETTO_FATAL("unknown conversion option"); // for gcc } } // namespace trace_to_text } // namespace perfetto