| /* |
| * 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/profiling/pprof_builder.h" |
| |
| #include "perfetto/base/build_config.h" |
| |
| #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) |
| #include <cxxabi.h> |
| #endif |
| |
| #include <inttypes.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "tools/trace_to_text/utils.h" |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/base/time.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 "protos/perfetto/trace/trace.pbzero.h" |
| #include "protos/perfetto/trace/trace_packet.pbzero.h" |
| #include "protos/third_party/pprof/profile.pbzero.h" |
| |
| 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<uint64_t>(id) + 1; |
| } |
| |
| std::string AsCsvString(std::vector<uint64_t> 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; |
| } |
| |
| // Return map from callsite_id to list of frame_ids that make up the callstack. |
| std::vector<std::vector<int64_t>> GetCallsiteToFrames( |
| trace_processor::TraceProcessor* tp) { |
| Iterator count_it = |
| tp->ExecuteQuery("select count(*) from stack_profile_callsite;"); |
| if (!count_it.Next()) { |
| PERFETTO_DFATAL_OR_ELOG("Failed to get number of callsites: %s", |
| count_it.Status().message().c_str()); |
| return {}; |
| } |
| int64_t count = count_it.Get(0).AsLong(); |
| |
| Iterator it = tp->ExecuteQuery( |
| "select id, parent_id, frame_id from stack_profile_callsite order by " |
| "depth;"); |
| std::vector<std::vector<int64_t>> result(static_cast<size_t>(count)); |
| while (it.Next()) { |
| int64_t id = it.Get(0).AsLong(); |
| int64_t frame_id = it.Get(2).AsLong(); |
| std::vector<int64_t>& path = result[static_cast<size_t>(id)]; |
| path.push_back(frame_id); |
| |
| auto parent_id_value = it.Get(1); |
| if (!parent_id_value.is_null()) { |
| const std::vector<int64_t>& parent_path = |
| result[static_cast<size_t>(parent_id_value.AsLong())]; |
| path.insert(path.end(), parent_path.begin(), parent_path.end()); |
| } |
| } |
| |
| if (!it.Status().ok()) { |
| PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", |
| it.Status().message().c_str()); |
| return {}; |
| } |
| return result; |
| } |
| |
| base::Optional<int64_t> GetMaxSymbolId(trace_processor::TraceProcessor* tp) { |
| auto max_symbol_id_it = |
| tp->ExecuteQuery("select max(id) from stack_profile_symbol"); |
| if (!max_symbol_id_it.Next()) { |
| PERFETTO_DFATAL_OR_ELOG("Failed to get max symbol set id: %s", |
| max_symbol_id_it.Status().message().c_str()); |
| return base::nullopt; |
| } |
| return base::make_optional(max_symbol_id_it.Get(0).AsLong()); |
| } |
| |
| struct Line { |
| int64_t symbol_id; |
| uint32_t line_number; |
| }; |
| |
| std::map<int64_t, std::vector<Line>> GetSymbolSetIdToLines( |
| trace_processor::TraceProcessor* tp) { |
| std::map<int64_t, std::vector<Line>> result; |
| Iterator it = tp->ExecuteQuery( |
| "SELECT symbol_set_id, id, line_number FROM stack_profile_symbol;"); |
| while (it.Next()) { |
| int64_t symbol_set_id = it.Get(0).AsLong(); |
| int64_t id = it.Get(1).AsLong(); |
| int64_t line_number = it.Get(2).AsLong(); |
| result[symbol_set_id].emplace_back( |
| Line{id, static_cast<uint32_t>(line_number)}); |
| } |
| |
| if (!it.Status().ok()) { |
| PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", |
| it.Status().message().c_str()); |
| return {}; |
| } |
| return result; |
| } |
| |
| base::Optional<int64_t> GetStatsEntry( |
| trace_processor::TraceProcessor* tp, |
| const std::string& name, |
| base::Optional<uint64_t> 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()); |
| } |
| |
| // Helper for constructing |perftools.profiles.Profile| protos. |
| class GProfileBuilder { |
| public: |
| GProfileBuilder( |
| const std::vector<std::vector<int64_t>>& callsite_to_frames, |
| const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines, |
| int64_t max_symbol_id) |
| : callsite_to_frames_(callsite_to_frames), |
| symbol_set_id_to_lines_(symbol_set_id_to_lines), |
| max_symbol_id_(max_symbol_id) { |
| // The pprof format expects the first entry in the string table to be the |
| // empty string. |
| int64_t empty_id = Intern(""); |
| PERFETTO_CHECK(empty_id == 0); |
| } |
| |
| void WriteSampleTypes( |
| const std::vector<std::pair<std::string, std::string>>& sample_types) { |
| // The interner might eagerly append to the profile proto, prevent it from |
| // breaking up other messages by making a separate pass. |
| for (const auto& st : sample_types) { |
| Intern(st.first); |
| Intern(st.second); |
| } |
| for (const auto& st : sample_types) { |
| auto* sample_type = result_->add_sample_type(); |
| sample_type->set_type(Intern(st.first)); |
| sample_type->set_unit(Intern(st.second)); |
| } |
| } |
| |
| bool AddSample(const protozero::PackedVarInt& values, int64_t callstack_id) { |
| const auto& frames = FramesForCallstack(callstack_id); |
| if (frames.empty()) { |
| PERFETTO_DFATAL_OR_ELOG( |
| "Failed to find frames for callstack id %" PRIi64 "", callstack_id); |
| return false; |
| } |
| protozero::PackedVarInt location_ids; |
| for (int64_t frame : frames) |
| location_ids.Append(ToPprofId(frame)); |
| |
| auto* gsample = result_->add_sample(); |
| gsample->set_value(values); |
| gsample->set_location_id(location_ids); |
| |
| // remember frames to be emitted |
| seen_frames_.insert(frames.cbegin(), frames.cend()); |
| |
| return true; |
| } |
| |
| std::string CompleteProfile(trace_processor::TraceProcessor* tp) { |
| std::set<int64_t> seen_mappings; |
| std::set<int64_t> seen_symbol_ids; |
| |
| // Write the location info for frames referenced by the added samples. |
| if (!WriteFrames(tp, &seen_mappings, &seen_symbol_ids)) |
| return {}; |
| if (!WriteMappings(tp, seen_mappings)) |
| return {}; |
| if (!WriteSymbols(tp, seen_symbol_ids)) |
| return {}; |
| return result_.SerializeAsString(); |
| } |
| |
| private: |
| bool WriteMappings(trace_processor::TraceProcessor* tp, |
| const std::set<int64_t>& 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 = Intern(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<uint64_t>(mapping_it.Get(1).AsLong())); |
| gmapping->set_memory_start( |
| static_cast<uint64_t>(mapping_it.Get(2).AsLong())); |
| gmapping->set_memory_limit( |
| static_cast<uint64_t>(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; |
| } |
| |
| bool WriteSymbols(trace_processor::TraceProcessor* tp, |
| const std::set<int64_t>& seen_symbol_ids) { |
| Iterator symbol_it = tp->ExecuteQuery( |
| "SELECT id, name, source_file FROM stack_profile_symbol"); |
| size_t symbols_no = 0; |
| while (symbol_it.Next()) { |
| int64_t id = symbol_it.Get(0).AsLong(); |
| if (seen_symbol_ids.find(id) == seen_symbol_ids.end()) |
| continue; |
| ++symbols_no; |
| const std::string& name = symbol_it.Get(1).AsString(); |
| std::string demangled_name = name; |
| MaybeDemangle(&demangled_name); |
| |
| auto interned_demangled_name = Intern(demangled_name); |
| auto interned_system_name = Intern(name); |
| auto interned_filename = Intern(symbol_it.Get(2).AsString()); |
| auto* gfunction = result_->add_function(); |
| gfunction->set_id(ToPprofId(id)); |
| gfunction->set_name(interned_demangled_name); |
| gfunction->set_system_name(interned_system_name); |
| gfunction->set_filename(interned_filename); |
| } |
| |
| if (!symbol_it.Status().ok()) { |
| PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", |
| symbol_it.Status().message().c_str()); |
| return false; |
| } |
| |
| if (symbols_no != seen_symbol_ids.size()) { |
| PERFETTO_DFATAL_OR_ELOG("Missing symbols."); |
| return false; |
| } |
| return true; |
| } |
| |
| bool WriteFrames(trace_processor::TraceProcessor* tp, |
| std::set<int64_t>* seen_mappings, |
| std::set<int64_t>* seen_symbol_ids) { |
| Iterator frame_it = tp->ExecuteQuery( |
| "SELECT spf.id, IFNULL(spf.deobfuscated_name, spf.name), spf.mapping, " |
| "spf.rel_pc, spf.symbol_set_id " |
| "FROM stack_profile_frame spf;"); |
| size_t frames_no = 0; |
| while (frame_it.Next()) { |
| int64_t frame_id = frame_it.Get(0).AsLong(); |
| if (seen_frames_.find(frame_id) == seen_frames_.end()) |
| continue; |
| frames_no++; |
| std::string frame_name = frame_it.Get(1).AsString(); |
| int64_t mapping_id = frame_it.Get(2).AsLong(); |
| int64_t rel_pc = frame_it.Get(3).AsLong(); |
| base::Optional<int64_t> symbol_set_id; |
| if (!frame_it.Get(4).is_null()) |
| symbol_set_id = frame_it.Get(4).AsLong(); |
| |
| seen_mappings->emplace(mapping_id); |
| auto* glocation = result_->add_location(); |
| glocation->set_id(ToPprofId(frame_id)); |
| glocation->set_mapping_id(ToPprofId(mapping_id)); |
| // TODO(fmayer): Convert to abspc. |
| // relpc + (mapping.start - (mapping.exact_offset - |
| // mapping.start_offset)). |
| glocation->set_address(static_cast<uint64_t>(rel_pc)); |
| if (symbol_set_id) { |
| for (const Line& line : LineForSymbolSetId(*symbol_set_id)) { |
| seen_symbol_ids->emplace(line.symbol_id); |
| auto* gline = glocation->add_line(); |
| gline->set_line(line.line_number); |
| gline->set_function_id(ToPprofId(line.symbol_id)); |
| } |
| } else { |
| int64_t synthesized_symbol_id = ++max_symbol_id_; |
| std::string demangled_name = frame_name; |
| MaybeDemangle(&demangled_name); |
| |
| auto* gline = glocation->add_line(); |
| gline->set_line(0); |
| gline->set_function_id(ToPprofId(synthesized_symbol_id)); |
| |
| auto interned_demangled_name = Intern(demangled_name); |
| auto interned_system_name = Intern(frame_name); |
| auto* gfunction = result_->add_function(); |
| gfunction->set_id(ToPprofId(synthesized_symbol_id)); |
| gfunction->set_name(interned_demangled_name); |
| gfunction->set_system_name(interned_system_name); |
| } |
| } |
| |
| if (!frame_it.Status().ok()) { |
| PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", |
| frame_it.Status().message().c_str()); |
| return false; |
| } |
| if (frames_no != seen_frames_.size()) { |
| PERFETTO_DFATAL_OR_ELOG("Missing frames."); |
| return false; |
| } |
| return true; |
| } |
| |
| const std::vector<int64_t>& FramesForCallstack(int64_t callstack_id) { |
| size_t callsite_idx = static_cast<size_t>(callstack_id); |
| PERFETTO_CHECK(callstack_id >= 0 && |
| callsite_idx < callsite_to_frames_.size()); |
| return callsite_to_frames_[callsite_idx]; |
| } |
| |
| const std::vector<Line>& LineForSymbolSetId(int64_t symbol_set_id) { |
| auto it = symbol_set_id_to_lines_.find(symbol_set_id); |
| if (it == symbol_set_id_to_lines_.end()) |
| return empty_line_vector_; |
| return it->second; |
| } |
| |
| int64_t Intern(const std::string& s) { |
| auto it = string_table_.find(s); |
| if (it == string_table_.end()) { |
| std::tie(it, std::ignore) = |
| string_table_.emplace(s, string_table_.size()); |
| result_->add_string_table(s); |
| } |
| return it->second; |
| } |
| |
| protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile> |
| result_; |
| std::map<std::string, int64_t> string_table_; |
| const std::vector<std::vector<int64_t>>& callsite_to_frames_; |
| const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines_; |
| const std::vector<Line> empty_line_vector_; |
| int64_t max_symbol_id_; |
| |
| std::set<int64_t> seen_frames_; |
| }; |
| |
| } // namespace |
| |
| 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<int64_t> 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<Iterator> BuildViewIterators( |
| trace_processor::TraceProcessor* tp, |
| uint64_t upid, |
| uint64_t ts, |
| const char* heap_name) { |
| std::vector<Iterator> 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<Iterator>* 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<SerializedProfile>* output, |
| uint64_t target_pid, |
| const std::vector<uint64_t>& target_timestamps) { |
| const auto callsite_to_frames = GetCallsiteToFrames(tp); |
| const auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp); |
| base::Optional<int64_t> max_symbol_id = GetMaxSymbolId(tp); |
| if (!max_symbol_id.has_value()) |
| return false; |
| |
| 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(callsite_to_frames, symbol_set_id_to_lines, |
| max_symbol_id.value()); |
| uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong()); |
| uint64_t ts = static_cast<uint64_t>(it.Get(1).AsLong()); |
| uint64_t profile_pid = static_cast<uint64_t>(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<std::pair<std::string, std::string>> 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<Iterator> 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<uint64_t> utids; |
| }; |
| |
| // Returns a map of upid -> {pid, utids[]} for sampled processes. |
| static std::map<uint64_t, ProcessInfo> 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) order by " |
| "process.upid asc"); |
| std::map<uint64_t, ProcessInfo> process_map; |
| while (it.Next()) { |
| uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong()); |
| uint64_t pid = static_cast<uint64_t>(it.Get(1).AsLong()); |
| uint64_t utid = static_cast<uint64_t>(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<int64_t> 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<int64_t>(it.Get(0).AsLong()), |
| static_cast<int64_t>(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<SerializedProfile>* output, |
| uint64_t target_pid) { |
| const auto callsite_to_frames = GetCallsiteToFrames(tp); |
| const auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp); |
| base::Optional<int64_t> max_symbol_id = GetMaxSymbolId(tp); |
| if (!max_symbol_id.has_value()) |
| return false; |
| |
| LogTracePerfEventIssues(tp); |
| |
| // Aggregate samples by upid when building profiles. |
| std::map<uint64_t, ProcessInfo> 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(callsite_to_frames, symbol_set_id_to_lines, |
| max_symbol_id.value()); |
| |
| builder.WriteSampleTypes({{"samples", "count"}}); |
| |
| std::string query = "select callsite_id from perf_sample where utid in (" + |
| AsCsvString(process.utids) + ") 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<int64_t>(it.Get(0).AsLong()); |
| builder.AddSample(single_count_value, callsite_id); |
| } |
| if (!it.Status().ok()) { |
| PERFETTO_DFATAL_OR_ELOG("Failed to iterate over samples."); |
| 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 |
| |
| bool TraceToPprof(trace_processor::TraceProcessor* tp, |
| std::vector<SerializedProfile>* output, |
| ConversionMode mode, |
| uint64_t pid, |
| const std::vector<uint64_t>& timestamps) { |
| switch (mode) { |
| case (ConversionMode::kHeapProfile): |
| return heap_profile::TraceToHeapPprof(tp, output, pid, timestamps); |
| case (ConversionMode::kPerfProfile): |
| return perf_profile::TraceToPerfPprof(tp, output, pid); |
| } |
| PERFETTO_FATAL("unknown conversion option"); // for gcc |
| } |
| |
| } // namespace trace_to_text |
| } // namespace perfetto |