Separate the actual trace to pprof conversion
Bug: b/135648427
Change-Id: I275dc329a40ef9d7a1c9fe8c7b9b4891913ee198
diff --git a/Android.bp b/Android.bp
index 7a30101..81856fe 100644
--- a/Android.bp
+++ b/Android.bp
@@ -3892,6 +3892,7 @@
"src/trace_processor/virtual_destructors.cc",
"src/trace_processor/window_operator_table.cc",
"tools/trace_to_text/main.cc",
+ "tools/trace_to_text/pprof_builder.cc",
"tools/trace_to_text/proto_full_utils.cc",
"tools/trace_to_text/trace_to_profile.cc",
"tools/trace_to_text/trace_to_systrace.cc",
diff --git a/BUILD b/BUILD
index 24967d0..aca32e3 100644
--- a/BUILD
+++ b/BUILD
@@ -120,6 +120,90 @@
],
)
+# GN target: //tools/trace_to_text:libpprofbuilder
+cc_library(
+ name = "tools_trace_to_text_libpprofbuilder",
+ srcs = [
+ "src/protozero/message.cc",
+ "src/protozero/message_handle.cc",
+ "src/protozero/proto_decoder.cc",
+ "src/protozero/scattered_heap_buffer.cc",
+ "src/protozero/scattered_stream_null_delegate.cc",
+ "src/protozero/scattered_stream_writer.cc",
+ "tools/trace_to_text/pprof_builder.cc",
+ "tools/trace_to_text/pprof_builder.h",
+ "tools/trace_to_text/utils.cc",
+ "tools/trace_to_text/utils.h",
+ ],
+ hdrs = [
+ "include/perfetto/base/build_config.h",
+ "include/perfetto/base/compiler.h",
+ "include/perfetto/base/export.h",
+ "include/perfetto/base/logging.h",
+ "include/perfetto/base/task_runner.h",
+ "include/perfetto/ext/base/circular_queue.h",
+ "include/perfetto/ext/base/container_annotations.h",
+ "include/perfetto/ext/base/event.h",
+ "include/perfetto/ext/base/file_utils.h",
+ "include/perfetto/ext/base/hash.h",
+ "include/perfetto/ext/base/metatrace.h",
+ "include/perfetto/ext/base/metatrace_events.h",
+ "include/perfetto/ext/base/no_destructor.h",
+ "include/perfetto/ext/base/optional.h",
+ "include/perfetto/ext/base/paged_memory.h",
+ "include/perfetto/ext/base/pipe.h",
+ "include/perfetto/ext/base/scoped_file.h",
+ "include/perfetto/ext/base/small_set.h",
+ "include/perfetto/ext/base/string_splitter.h",
+ "include/perfetto/ext/base/string_utils.h",
+ "include/perfetto/ext/base/string_view.h",
+ "include/perfetto/ext/base/string_writer.h",
+ "include/perfetto/ext/base/temp_file.h",
+ "include/perfetto/ext/base/thread_annotations.h",
+ "include/perfetto/ext/base/thread_checker.h",
+ "include/perfetto/ext/base/thread_task_runner.h",
+ "include/perfetto/ext/base/thread_utils.h",
+ "include/perfetto/ext/base/time.h",
+ "include/perfetto/ext/base/unix_socket.h",
+ "include/perfetto/ext/base/unix_task_runner.h",
+ "include/perfetto/ext/base/utils.h",
+ "include/perfetto/ext/base/watchdog.h",
+ "include/perfetto/ext/base/watchdog_noop.h",
+ "include/perfetto/ext/base/watchdog_posix.h",
+ "include/perfetto/ext/base/weak_ptr.h",
+ "include/perfetto/ext/traced/sys_stats_counters.h",
+ "include/perfetto/protozero/contiguous_memory_range.h",
+ "include/perfetto/protozero/field.h",
+ "include/perfetto/protozero/message.h",
+ "include/perfetto/protozero/message_handle.h",
+ "include/perfetto/protozero/proto_decoder.h",
+ "include/perfetto/protozero/proto_utils.h",
+ "include/perfetto/protozero/scattered_heap_buffer.h",
+ "include/perfetto/protozero/scattered_stream_null_delegate.h",
+ "include/perfetto/protozero/scattered_stream_writer.h",
+ ],
+ deps = [
+ "//third_party/perfetto/protos:common_cc_proto",
+ "//third_party/perfetto/protos:common_zero_cc_proto",
+ "//third_party/perfetto/protos:config_cc_proto",
+ "//third_party/perfetto/protos:protos_third_party_pprof_cc_proto",
+ "//third_party/perfetto/protos:trace_android_cc_proto",
+ "//third_party/perfetto/protos:trace_cc_proto",
+ "//third_party/perfetto/protos:trace_chrome_cc_proto",
+ "//third_party/perfetto/protos:trace_filesystem_cc_proto",
+ "//third_party/perfetto/protos:trace_ftrace_cc_proto",
+ "//third_party/perfetto/protos:trace_gpu_cc_proto",
+ "//third_party/perfetto/protos:trace_interned_data_cc_proto",
+ "//third_party/perfetto/protos:trace_minimal_cc_proto",
+ "//third_party/perfetto/protos:trace_perfetto_cc_proto",
+ "//third_party/perfetto/protos:trace_power_cc_proto",
+ "//third_party/perfetto/protos:trace_profiling_cc_proto",
+ "//third_party/perfetto/protos:trace_ps_cc_proto",
+ "//third_party/perfetto/protos:trace_sys_stats_cc_proto",
+ "//third_party/perfetto/protos:trace_track_event_cc_proto",
+ ],
+)
+
# GN target: //src/trace_processor:trace_processor
cc_library(
name = "trace_processor",
@@ -802,6 +886,8 @@
"src/trace_processor/window_operator_table.cc",
"src/trace_processor/window_operator_table.h",
"tools/trace_to_text/main.cc",
+ "tools/trace_to_text/pprof_builder.cc",
+ "tools/trace_to_text/pprof_builder.h",
"tools/trace_to_text/proto_full_utils.cc",
"tools/trace_to_text/proto_full_utils.h",
"tools/trace_to_text/trace_to_profile.cc",
diff --git a/tools/gen_bazel b/tools/gen_bazel
index 34f9083..010f69d 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -62,6 +62,7 @@
'//src/protozero:libprotozero',
'//src/trace_processor:trace_processor',
'//tools/trace_to_text:trace_to_text_host(//gn/standalone/toolchain:gcc_like_host)',
+ '//tools/trace_to_text:libpprofbuilder',
'//protos/perfetto/config:merged_config_gen',
'//protos/perfetto/trace:merged_trace_gen',
'//protos/perfetto/trace_processor:lite_gen',
diff --git a/tools/trace_to_text/BUILD.gn b/tools/trace_to_text/BUILD.gn
index e337971..2e4c789 100644
--- a/tools/trace_to_text/BUILD.gn
+++ b/tools/trace_to_text/BUILD.gn
@@ -16,9 +16,50 @@
import("../../gn/proto_library.gni")
import("../../gn/wasm.gni")
+source_set("utils") {
+ public_deps = [
+ "../../gn:default_deps",
+ "../../include/perfetto/base",
+ "../../include/perfetto/ext/traced:sys_stats_counters",
+ "../../protos/perfetto/trace:lite",
+ "../../protos/perfetto/trace/ftrace:lite",
+ ]
+ sources = [
+ "utils.cc",
+ "utils.h",
+ ]
+}
+
+source_set("pprofbuilder") {
+ deps = [
+ ":utils",
+ "../../gn:default_deps",
+ "../../include/perfetto/base",
+ "../../protos/perfetto/trace:lite",
+ "../../protos/perfetto/trace/profiling:lite",
+ "../../protos/third_party/pprof:lite",
+ ]
+ sources = [
+ "pprof_builder.cc",
+ "pprof_builder.h",
+ ]
+}
+
+if (perfetto_build_standalone) {
+ static_library("libpprofbuilder") {
+ deps = [
+ ":pprofbuilder",
+ ]
+ }
+}
+
# The core source files that are used both by the "full" version (the host
# executable) and by the "lite" version (the WASM module for the UI).
source_set("common") {
+ deps = [
+ ":pprofbuilder",
+ ":utils",
+ ]
public_deps = [
"../../gn:default_deps",
"../../include/perfetto/base",
@@ -26,7 +67,6 @@
"../../protos/perfetto/trace:lite",
"../../protos/perfetto/trace/ftrace:lite",
"../../protos/perfetto/trace/profiling:lite",
- "../../protos/third_party/pprof:lite",
"../../src/base",
"../../src/trace_processor:lib",
]
@@ -37,13 +77,9 @@
"trace_to_systrace.cc",
"trace_to_systrace.h",
"trace_to_text.h",
- "utils.cc",
- "utils.h",
]
if (perfetto_build_standalone) {
- deps = [
- "../../gn/standalone:gen_git_revision",
- ]
+ deps += [ "../../gn/standalone:gen_git_revision" ]
}
}
@@ -63,6 +99,7 @@
testonly = true
deps = [
":common",
+ ":utils",
"../../gn:default_deps",
"../../gn:protobuf_full",
]
diff --git a/tools/trace_to_text/pprof_builder.cc b/tools/trace_to_text/pprof_builder.cc
new file mode 100644
index 0000000..3123dee
--- /dev/null
+++ b/tools/trace_to_text/pprof_builder.cc
@@ -0,0 +1,297 @@
+/*
+ * 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 "tools/trace_to_text/pprof_builder.h"
+
+#include <cxxabi.h>
+#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/trace/profiling/profile_common.pb.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 {
+
+void MaybeDemangle(std::string* name) {
+ int ignored;
+ char* data = abi::__cxa_demangle(name->c_str(), nullptr, nullptr, &ignored);
+ if (data) {
+ *name = data;
+ free(data);
+ }
+}
+
+using ::perfetto::protos::Callstack;
+using ::perfetto::protos::Frame;
+using ::perfetto::protos::InternedString;
+using ::perfetto::protos::Mapping;
+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,
+ kIdleSpace,
+};
+
+void DumpProfilePacket(const std::vector<ProfilePacket>& packet_fragments,
+ std::vector<SerializedProfile>* output) {
+ std::map<uint64_t, std::string> 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 InternedString& interned_string : packet.strings())
+ string_lookup.emplace(interned_string.iid(), interned_string.str());
+ }
+
+ std::map<uint64_t, const std::vector<uint64_t>> callstack_lookup;
+ for (const ProfilePacket& packet : packet_fragments) {
+ for (const Callstack& callstack : packet.callstacks()) {
+ std::vector<uint64_t> frame_ids(
+ static_cast<size_t>(callstack.frame_ids().size()));
+ std::reverse_copy(callstack.frame_ids().cbegin(),
+ callstack.frame_ids().cend(), frame_ids.begin());
+ callstack_lookup.emplace(callstack.iid(), std::move(frame_ids));
+ }
+ }
+
+ std::map<std::string, uint64_t> 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;
+ string_table["idle_space"] = kIdleSpace;
+
+ 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(kIdleSpace);
+ value_type->set_unit(kBytes);
+
+ 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 Mapping& mapping : packet.mappings()) {
+ GMapping* gmapping = profile.add_mapping();
+ gmapping->set_id(mapping.iid());
+ 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<uint64_t>(mapping.iid()), 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<int64_t>(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<int64_t>(it->second));
+ }
+ }
+ }
+
+ std::set<uint64_t> functions_to_dump;
+ for (const ProfilePacket& packet : packet_fragments) {
+ for (const Frame& frame : packet.frames()) {
+ GLocation* glocation = profile.add_location();
+ glocation->set_id(frame.iid());
+ 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<int64_t>(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<uint64_t, std::string> 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<uint64_t, std::vector<const ProfilePacket::ProcessHeapSamples*>>
+ 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<uint64_t>(sample.callstack_id()));
+ continue;
+ }
+ for (uint64_t frame_id : it->second)
+ gsample->add_location_id(frame_id);
+ gsample->add_value(
+ static_cast<int64_t>(sample.alloc_count() - sample.free_count()));
+ gsample->add_value(static_cast<int64_t>(sample.alloc_count()));
+ gsample->add_value(static_cast<int64_t>(sample.self_idle()));
+ gsample->add_value(static_cast<int64_t>(sample.self_allocated()));
+ gsample->add_value(static_cast<int64_t>(sample.self_allocated() -
+ sample.self_freed()));
+ }
+ }
+ output->push_back({pid, cur_profile.SerializeAsString()});
+ }
+}
+
+} // namespace
+
+void TraceToPprof(std::istream* input, std::vector<SerializedProfile>* output) {
+ std::vector<ProfilePacket> rolling_profile_packets;
+ ForEachPacketInTrace(input, [output, &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, output);
+ rolling_profile_packets.clear();
+ }
+ });
+
+ if (!rolling_profile_packets.empty()) {
+ PERFETTO_ELOG("WARNING: Truncated heap dump. Not generating profile.");
+ }
+}
+
+} // namespace trace_to_text
+} // namespace perfetto
diff --git a/tools/trace_to_text/pprof_builder.h b/tools/trace_to_text/pprof_builder.h
new file mode 100644
index 0000000..b4450d9
--- /dev/null
+++ b/tools/trace_to_text/pprof_builder.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef TOOLS_TRACE_TO_TEXT_PPROF_BUILDER_H_
+#define TOOLS_TRACE_TO_TEXT_PPROF_BUILDER_H_
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace perfetto {
+namespace trace_to_text {
+
+struct SerializedProfile {
+ uint64_t pid;
+ std::string serialized;
+};
+
+void TraceToPprof(std::istream* input, std::vector<SerializedProfile>* output);
+
+} // namespace trace_to_text
+} // namespace perfetto
+
+#endif // TOOLS_TRACE_TO_TEXT_PPROF_BUILDER_H_
diff --git a/tools/trace_to_text/trace_to_profile.cc b/tools/trace_to_text/trace_to_profile.cc
index 707b611..553584b 100644
--- a/tools/trace_to_text/trace_to_profile.cc
+++ b/tools/trace_to_text/trace_to_profile.cc
@@ -16,44 +16,20 @@
#include "tools/trace_to_text/trace_to_profile.h"
-#include <cxxabi.h>
-#include <inttypes.h>
-
-#include <algorithm>
-#include <map>
-#include <set>
+#include <string>
#include <vector>
-#include "tools/trace_to_text/utils.h"
+#include "tools/trace_to_text/pprof_builder.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/temp_file.h"
#include "perfetto/ext/base/utils.h"
-#include "perfetto/trace/profiling/profile_common.pb.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)
@@ -61,261 +37,32 @@
return tmp;
}
-using ::perfetto::protos::Callstack;
-using ::perfetto::protos::Frame;
-using ::perfetto::protos::InternedString;
-using ::perfetto::protos::Mapping;
-using ::perfetto::protos::ProfilePacket;
+} // namespace
-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;
+namespace perfetto {
+namespace trace_to_text {
-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,
- kIdleSpace,
-};
-
-void DumpProfilePacket(std::vector<ProfilePacket>& packet_fragments,
- const std::string& file_prefix) {
- std::map<uint64_t, std::string> 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 InternedString& interned_string : packet.strings())
- string_lookup.emplace(interned_string.iid(), interned_string.str());
+int TraceToProfile(std::istream* input, std::ostream* output) {
+ std::vector<SerializedProfile> profiles;
+ TraceToPprof(input, &profiles);
+ if (profiles.empty()) {
+ return 0;
}
- std::map<uint64_t, const std::vector<uint64_t>> callstack_lookup;
- for (const ProfilePacket& packet : packet_fragments) {
- for (const Callstack& callstack : packet.callstacks()) {
- std::vector<uint64_t> frame_ids(
- static_cast<size_t>(callstack.frame_ids().size()));
- std::reverse_copy(callstack.frame_ids().cbegin(),
- callstack.frame_ids().cend(), frame_ids.begin());
- callstack_lookup.emplace(callstack.iid(), std::move(frame_ids));
- }
- }
-
- std::map<std::string, uint64_t> 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;
- string_table["idle_space"] = kIdleSpace;
-
- 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(kIdleSpace);
- value_type->set_unit(kBytes);
-
- 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 Mapping& mapping : packet.mappings()) {
- GMapping* gmapping = profile.add_mapping();
- gmapping->set_id(mapping.iid());
- 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<uint64_t>(mapping.iid()), 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<int64_t>(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<int64_t>(it->second));
- }
- }
- }
-
- std::set<uint64_t> functions_to_dump;
- for (const ProfilePacket& packet : packet_fragments) {
- for (const Frame& frame : packet.frames()) {
- GLocation* glocation = profile.add_location();
- glocation->set_id(frame.iid());
- 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<int64_t>(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<uint64_t, std::string> 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<uint64_t, std::vector<const ProfilePacket::ProcessHeapSamples*>>
- 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<uint64_t>(sample.callstack_id()));
- continue;
- }
- for (uint64_t frame_id : it->second)
- gsample->add_location_id(frame_id);
- gsample->add_value(
- static_cast<int64_t>(sample.alloc_count() - sample.free_count()));
- gsample->add_value(static_cast<int64_t>(sample.alloc_count()));
- gsample->add_value(static_cast<int64_t>(sample.self_idle()));
- gsample->add_value(static_cast<int64_t>(sample.self_allocated()));
- gsample->add_value(static_cast<int64_t>(sample.self_allocated() -
- sample.self_freed()));
- }
- }
- std::string filename = file_prefix + std::to_string(pid) + ".pb";
+ std::string temp_dir = GetTemp() + "/heap_profile-XXXXXXX";
+ PERFETTO_CHECK(mkdtemp(&temp_dir[0]));
+ size_t itr = 0;
+ for (const auto& profile : profiles) {
+ std::string filename = temp_dir + "/heap_dump." + std::to_string(++itr) +
+ "." + std::to_string(profile.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<ssize_t>(serialized.size()));
+ PERFETTO_CHECK(base::WriteAll(*fd, profile.serialized.c_str(),
+ profile.serialized.size()) ==
+ static_cast<ssize_t>(profile.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<ProfilePacket> 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;
}