/* * Copyright (C) 2018 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 "tools/trace_to_text/trace_to_profile.h" #include #include #include #include #include #include #include "tools/trace_to_text/utils.h" #include "perfetto/base/file_utils.h" #include "perfetto/base/logging.h" #include "perfetto/base/temp_file.h" #include "perfetto/trace/profiling/profile_packet.pb.h" #include "perfetto/trace/trace.pb.h" #include "perfetto/trace/trace_packet.pb.h" #include "third_party/pprof/profile.pb.h" namespace perfetto { namespace trace_to_text { namespace { constexpr const char* kDefaultTmp = "/tmp"; void MaybeDemangle(std::string* name) { int ignored; char* data = abi::__cxa_demangle(name->c_str(), nullptr, nullptr, &ignored); if (data) { *name = data; free(data); } } std::string GetTemp() { const char* tmp = getenv("TMPDIR"); if (tmp == nullptr) tmp = kDefaultTmp; return tmp; } using ::perfetto::protos::ProfilePacket; using GLine = ::perftools::profiles::Line; using GMapping = ::perftools::profiles::Mapping; using GLocation = ::perftools::profiles::Location; using GProfile = ::perftools::profiles::Profile; using GValueType = ::perftools::profiles::ValueType; using GFunction = ::perftools::profiles::Function; using GSample = ::perftools::profiles::Sample; std::string ToHex(const std::string& build_id) { std::string hex_build_id(2 * build_id.size() + 1, ' '); for (size_t i = 0; i < build_id.size(); ++i) snprintf(&(hex_build_id[2 * i]), 3, "%02hhx", build_id[i]); // Remove the trailing nullbyte. hex_build_id.resize(2 * build_id.size()); return hex_build_id; } enum Strings : int64_t { kEmpty = 0, kObjects, kAllocObjects, kCount, kSpace, kAllocSpace, kBytes }; void DumpProfilePacket(std::vector& packet_fragments, const std::string& file_prefix) { std::map string_lookup; // A profile packet can be split into multiple fragments. We need to iterate // over all of them to reconstruct the original packet. for (const ProfilePacket& packet : packet_fragments) { for (const ProfilePacket::InternedString& interned_string : packet.strings()) string_lookup.emplace(interned_string.id(), interned_string.str()); } std::map> callstack_lookup; for (const ProfilePacket& packet : packet_fragments) { for (const ProfilePacket::Callstack& callstack : packet.callstacks()) { std::vector frame_ids( static_cast(callstack.frame_ids().size())); std::reverse_copy(callstack.frame_ids().cbegin(), callstack.frame_ids().cend(), frame_ids.begin()); callstack_lookup.emplace(callstack.id(), std::move(frame_ids)); } } std::map string_table; string_table[""] = kEmpty; string_table["objects"] = kObjects; string_table["alloc_objects"] = kAllocObjects; string_table["count"] = kCount; string_table["space"] = kSpace; string_table["alloc_space"] = kAllocSpace; string_table["bytes"] = kBytes; GProfile profile; GValueType* value_type = profile.add_sample_type(); value_type->set_type(kObjects); value_type->set_unit(kCount); value_type = profile.add_sample_type(); value_type->set_type(kAllocObjects); value_type->set_unit(kCount); value_type = profile.add_sample_type(); value_type->set_type(kAllocSpace); value_type->set_unit(kBytes); // The last value is the default one selected. value_type = profile.add_sample_type(); value_type->set_type(kSpace); value_type->set_unit(kBytes); for (const ProfilePacket& packet : packet_fragments) { for (const ProfilePacket::Mapping& mapping : packet.mappings()) { GMapping* gmapping = profile.add_mapping(); gmapping->set_id(mapping.id()); gmapping->set_memory_start(mapping.start()); gmapping->set_memory_limit(mapping.end()); gmapping->set_file_offset(mapping.offset()); std::string filename; for (uint64_t str_id : mapping.path_string_ids()) { auto it = string_lookup.find(str_id); if (it == string_lookup.end()) { PERFETTO_ELOG("Mapping %" PRIu64 " referring to invalid string_id %" PRIu64 ".", static_cast(mapping.id()), str_id); continue; } filename += "/" + it->second; } decltype(string_table)::iterator it; std::tie(it, std::ignore) = string_table.emplace(filename, string_table.size()); gmapping->set_filename(static_cast(it->second)); auto str_it = string_lookup.find(mapping.build_id()); if (str_it != string_lookup.end()) { const std::string& build_id = str_it->second; std::tie(it, std::ignore) = string_table.emplace(ToHex(build_id), string_table.size()); gmapping->set_build_id(static_cast(it->second)); } } } std::set functions_to_dump; for (const ProfilePacket& packet : packet_fragments) { for (const ProfilePacket::Frame& frame : packet.frames()) { GLocation* glocation = profile.add_location(); glocation->set_id(frame.id()); glocation->set_mapping_id(frame.mapping_id()); // TODO(fmayer): This is probably incorrect. Probably should be abs pc. glocation->set_address(frame.rel_pc()); GLine* gline = glocation->add_line(); gline->set_function_id(frame.function_name_id()); functions_to_dump.emplace(frame.function_name_id()); } } for (uint64_t function_name_id : functions_to_dump) { auto str_it = string_lookup.find(function_name_id); if (str_it == string_lookup.end()) { PERFETTO_ELOG("Function referring to invalid string id %" PRIu64, function_name_id); continue; } decltype(string_table)::iterator it; std::string function_name = str_it->second; // This assumes both the device that captured the trace and the host // machine use the same mangling scheme. This is a reasonable // assumption as the Itanium ABI is the de-facto standard for mangling. MaybeDemangle(&function_name); std::tie(it, std::ignore) = string_table.emplace(std::move(function_name), string_table.size()); GFunction* gfunction = profile.add_function(); gfunction->set_id(function_name_id); gfunction->set_name(static_cast(it->second)); } // We keep the interning table as string -> uint64_t for fast and easy // lookup. When dumping, we need to turn it into a uint64_t -> string // table so we get it sorted by key order. std::map inverted_string_table; for (const auto& p : string_table) inverted_string_table[p.second] = p.first; for (const auto& p : inverted_string_table) profile.add_string_table(p.second); std::map> heap_samples; for (const ProfilePacket& packet : packet_fragments) { for (const ProfilePacket::ProcessHeapSamples& samples : packet.process_dumps()) { heap_samples[samples.pid()].emplace_back(&samples); } } for (const auto& p : heap_samples) { GProfile cur_profile = profile; uint64_t pid = p.first; for (const ProfilePacket::ProcessHeapSamples* samples : p.second) { if (samples->rejected_concurrent()) { PERFETTO_ELOG("WARNING: The profile for %" PRIu64 " was rejected due to a concurrent profile.", pid); } if (samples->buffer_overran()) { PERFETTO_ELOG("WARNING: The profile for %" PRIu64 " ended early due to a buffer overrun.", pid); } if (samples->buffer_corrupted()) { 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); } for (const ProfilePacket::HeapSample& sample : samples->samples()) { GSample* gsample = cur_profile.add_sample(); auto it = callstack_lookup.find(sample.callstack_id()); if (it == callstack_lookup.end()) { PERFETTO_ELOG("Callstack referring to invalid callstack id %" PRIu64, static_cast(sample.callstack_id())); continue; } for (uint64_t frame_id : it->second) gsample->add_location_id(frame_id); gsample->add_value( static_cast(sample.alloc_count() - sample.free_count())); gsample->add_value(static_cast(sample.alloc_count())); gsample->add_value(static_cast(sample.self_allocated())); gsample->add_value(static_cast(sample.self_allocated() - sample.self_freed())); } } std::string filename = file_prefix + std::to_string(pid) + ".pb"; base::ScopedFile fd(base::OpenFile(filename, O_CREAT | O_WRONLY, 0700)); if (!fd) PERFETTO_FATAL("Failed to open %s", filename.c_str()); std::string serialized = cur_profile.SerializeAsString(); PERFETTO_CHECK(base::WriteAll(*fd, serialized.c_str(), serialized.size()) == static_cast(serialized.size())); } } } // namespace int TraceToProfile(std::istream* input, std::ostream* output) { std::string temp_dir = GetTemp() + "/heap_profile-XXXXXXX"; size_t itr = 0; PERFETTO_CHECK(mkdtemp(&temp_dir[0])); std::vector rolling_profile_packets; ForEachPacketInTrace(input, [&temp_dir, &itr, &rolling_profile_packets]( const protos::TracePacket& packet) { if (!packet.has_profile_packet()) return; rolling_profile_packets.emplace_back(packet.profile_packet()); if (!packet.profile_packet().continued()) { for (size_t i = 1; i < rolling_profile_packets.size(); ++i) { // Ensure we are not missing a chunk. PERFETTO_CHECK(rolling_profile_packets[i - 1].index() + 1 == rolling_profile_packets[i].index()); } DumpProfilePacket(rolling_profile_packets, temp_dir + "/heap_dump." + std::to_string(++itr) + "."); rolling_profile_packets.clear(); } }); if (!rolling_profile_packets.empty()) { *output << "WARNING: Truncated heap dump. Not generating profile." << std::endl; } *output << "Wrote profiles to " << temp_dir << std::endl; return 0; } } // namespace trace_to_text } // namespace perfetto