profiling: Write bookkeeping to trace

Also retain more information about mappings.

See example trace: https://fmayer.users.x20web.corp.google.com/www/heapprofd_trace.textpb

Test: m
Test: flash sailfish
Test: profile system_server

Change-Id: Ia5db70fbffbf9ec58a6d7a942d52d7df2f2d409a
diff --git a/Android.bp b/Android.bp
index cfeeb8d..3881385 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,7 @@
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
+    ":perfetto_protos_perfetto_trace_profiling_zero_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
@@ -56,7 +57,6 @@
     "src/profiling/memory/main.cc",
     "src/profiling/memory/record_reader.cc",
     "src/profiling/memory/socket_listener.cc",
-    "src/profiling/memory/string_interner.cc",
     "src/profiling/memory/unwinding.cc",
     "src/profiling/memory/wire_protocol.cc",
     "src/protozero/message.cc",
@@ -113,6 +113,7 @@
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
@@ -180,6 +181,7 @@
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
+    ":perfetto_protos_perfetto_trace_profiling_zero_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
@@ -279,6 +281,7 @@
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
@@ -307,6 +310,7 @@
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
+    ":perfetto_protos_perfetto_trace_profiling_zero_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
@@ -391,6 +395,7 @@
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
@@ -450,6 +455,8 @@
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
     ":perfetto_protos_perfetto_trace_lite_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
+    ":perfetto_protos_perfetto_trace_profiling_lite_gen",
+    ":perfetto_protos_perfetto_trace_profiling_zero_gen",
     ":perfetto_protos_perfetto_trace_ps_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_lite_gen",
@@ -564,6 +571,8 @@
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
@@ -3600,6 +3609,74 @@
   ],
 }
 
+// GN target: //protos/perfetto/trace/profiling:lite_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_profiling_lite_gen",
+  srcs: [
+    "protos/perfetto/trace/profiling/profile_packet.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/profiling/profile_packet.pb.cc",
+  ],
+}
+
+// GN target: //protos/perfetto/trace/profiling:lite_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_profiling_lite_gen_headers",
+  srcs: [
+    "protos/perfetto/trace/profiling/profile_packet.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/profiling/profile_packet.pb.h",
+  ],
+  export_include_dirs: [
+    "protos",
+  ],
+}
+
+// GN target: //protos/perfetto/trace/profiling:zero_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_profiling_zero_gen",
+  srcs: [
+    "protos/perfetto/trace/profiling/profile_packet.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/profiling/profile_packet.pbzero.cc",
+  ],
+}
+
+// GN target: //protos/perfetto/trace/profiling:zero_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
+  srcs: [
+    "protos/perfetto/trace/profiling/profile_packet.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_protoc_plugin___gn_standalone_toolchain_gcc_like_host_) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/profiling/profile_packet.pbzero.h",
+  ],
+  export_include_dirs: [
+    "protos",
+  ],
+}
+
 // GN target: //protos/perfetto/trace/ps:lite_gen
 genrule {
   name: "perfetto_protos_perfetto_trace_ps_lite_gen",
@@ -4158,6 +4235,7 @@
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
+    ":perfetto_protos_perfetto_trace_profiling_zero_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
@@ -4239,6 +4317,7 @@
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
@@ -4255,6 +4334,7 @@
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
@@ -4281,6 +4361,7 @@
     ":perfetto_protos_perfetto_trace_ftrace_lite_gen",
     ":perfetto_protos_perfetto_trace_lite_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
+    ":perfetto_protos_perfetto_trace_profiling_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_lite_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_lite_gen",
   ],
@@ -4300,6 +4381,7 @@
     "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
   ],
@@ -4311,6 +4393,7 @@
     "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
   ],
@@ -4340,6 +4423,8 @@
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
     ":perfetto_protos_perfetto_trace_lite_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
+    ":perfetto_protos_perfetto_trace_profiling_lite_gen",
+    ":perfetto_protos_perfetto_trace_profiling_zero_gen",
     ":perfetto_protos_perfetto_trace_ps_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_lite_gen",
@@ -4406,14 +4491,13 @@
     "src/profiling/memory/client_unittest.cc",
     "src/profiling/memory/heapprofd_integrationtest.cc",
     "src/profiling/memory/heapprofd_producer.cc",
+    "src/profiling/memory/interner_unittest.cc",
     "src/profiling/memory/record_reader.cc",
     "src/profiling/memory/record_reader_unittest.cc",
     "src/profiling/memory/sampler.cc",
     "src/profiling/memory/sampler_unittest.cc",
     "src/profiling/memory/socket_listener.cc",
     "src/profiling/memory/socket_listener_unittest.cc",
-    "src/profiling/memory/string_interner.cc",
-    "src/profiling/memory/string_interner_unittest.cc",
     "src/profiling/memory/unwinding.cc",
     "src/profiling/memory/unwinding_unittest.cc",
     "src/profiling/memory/wire_protocol.cc",
@@ -4550,6 +4634,8 @@
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
@@ -4589,6 +4675,7 @@
     ":perfetto_protos_perfetto_trace_ftrace_lite_gen",
     ":perfetto_protos_perfetto_trace_lite_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
+    ":perfetto_protos_perfetto_trace_profiling_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_lite_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_lite_gen",
     "tools/trace_to_text/ftrace_event_formatter.cc",
@@ -4615,6 +4702,7 @@
     "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
   ],
diff --git a/BUILD.gn b/BUILD.gn
index a6d4a63..f5a0eb1 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -54,8 +54,6 @@
       ":traced",
       ":traced_probes",
       "protos/perfetto/config:merged_config",  # For syntax-checking the proto.
-      "protos/perfetto/profiling:lite",  # For syntax-checking the proto.
-                                         # TODO(fmayer): Remove once used.
       "src/ipc/protoc_plugin:ipc_plugin($host_toolchain)",
       "tools:protoc_helper",
     ]
@@ -298,6 +296,7 @@
   executable("heapprofd") {
     deps = [
       "gn:default_deps",
+      "protos/perfetto/trace:zero",
       "src/base",
       "src/base:unix_socket",
       "src/profiling/memory:daemon",
diff --git a/protos/perfetto/trace/BUILD.gn b/protos/perfetto/trace/BUILD.gn
index 7cef317..a29eddc 100644
--- a/protos/perfetto/trace/BUILD.gn
+++ b/protos/perfetto/trace/BUILD.gn
@@ -38,6 +38,7 @@
     "chrome:zero",
     "filesystem:zero",
     "ftrace:zero",
+    "profiling:zero",
     "ps:zero",
     "sys_stats:zero",
   ]
@@ -56,6 +57,7 @@
     "chrome:lite",
     "filesystem:lite",
     "ftrace:lite",
+    "profiling:lite",
     "ps:lite",
     "sys_stats:lite",
   ]
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 8f00dcf..e7a85e4 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -192,6 +192,7 @@
     // removed field with id 33
     // removed field with id 34
     // removed field with id 35
+    // removed field with id 37
 
     // This field is emitted at periodic intervals (~10s) and
     // contains always the binary representation of the UUID
diff --git a/protos/perfetto/profiling/BUILD.gn b/protos/perfetto/trace/profiling/BUILD.gn
similarity index 88%
rename from protos/perfetto/profiling/BUILD.gn
rename to protos/perfetto/trace/profiling/BUILD.gn
index 4eaf38b..a16d5eb 100644
--- a/protos/perfetto/profiling/BUILD.gn
+++ b/protos/perfetto/trace/profiling/BUILD.gn
@@ -12,9 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import("../../../gn/perfetto.gni")
-import("../../../gn/proto_library.gni")
-import("../../../gn/protozero_library.gni")
+import("../../../../gn/perfetto.gni")
+import("../../../../gn/proto_library.gni")
+import("../../../../gn/protozero_library.gni")
 
 profiling_proto_names = [ "profile_packet.proto" ]
 
diff --git a/protos/perfetto/profiling/profile_packet.proto b/protos/perfetto/trace/profiling/profile_packet.proto
similarity index 96%
rename from protos/perfetto/profiling/profile_packet.proto
rename to protos/perfetto/trace/profiling/profile_packet.proto
index 23943b7..f6bf95c 100644
--- a/protos/perfetto/profiling/profile_packet.proto
+++ b/protos/perfetto/trace/profiling/profile_packet.proto
@@ -17,7 +17,7 @@
 syntax = "proto2";
 option optimize_for = LITE_RUNTIME;
 
-package perfetto.proto;
+package perfetto.protos;
 
 message ProfilePacket {
   // either a function or library name.
@@ -40,7 +40,7 @@
   message Callstack {
     optional uint64 id = 1;
     // Frames of this callstack. Bottom frame first.
-    repeated Frame frames = 2;
+    repeated uint64 frame_ids = 2;
   }
 
   repeated Mapping mappings = 4;
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index 7d0f8af..e7852a5 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -28,6 +28,7 @@
 import "perfetto/trace/sys_stats/sys_stats.proto";
 import "perfetto/trace/test_event.proto";
 import "perfetto/trace/trace_stats.proto";
+import "perfetto/trace/profiling/profile_packet.proto";
 
 package perfetto.protos;
 
@@ -55,6 +56,7 @@
     TraceConfig trace_config = 33;
     FtraceStats ftrace_stats = 34;
     TraceStats trace_stats = 35;
+    ProfilePacket profile_packet = 37;
 
     // This field is emitted at periodic intervals (~10s) and
     // contains always the binary representation of the UUID
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index 8fc7552..9514ea9 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -48,18 +48,21 @@
     "../../tracing",
     "../../tracing:ipc",
   ]
+  public_deps = [
+    "../../../protos/perfetto/trace:zero",
+    "../../../protos/perfetto/trace/profiling:zero",
+  ]
   sources = [
     "bookkeeping.cc",
     "bookkeeping.h",
     "heapprofd_producer.cc",
     "heapprofd_producer.h",
+    "interner.h",
     "queue_messages.h",
     "record_reader.cc",
     "record_reader.h",
     "socket_listener.cc",
     "socket_listener.h",
-    "string_interner.cc",
-    "string_interner.h",
     "unwinding.cc",
     "unwinding.h",
   ]
@@ -99,10 +102,10 @@
     "bounded_queue_unittest.cc",
     "client_unittest.cc",
     "heapprofd_integrationtest.cc",
+    "interner_unittest.cc",
     "record_reader_unittest.cc",
     "sampler_unittest.cc",
     "socket_listener_unittest.cc",
-    "string_interner_unittest.cc",
     "unwinding_unittest.cc",
     "wire_protocol_unittest.cc",
   ]
diff --git a/src/profiling/memory/bookkeeping.cc b/src/profiling/memory/bookkeeping.cc
index 51e0d30..8f78d25 100644
--- a/src/profiling/memory/bookkeeping.cc
+++ b/src/profiling/memory/bookkeeping.cc
@@ -27,19 +27,22 @@
 
 namespace perfetto {
 namespace profiling {
+namespace {
+using ::perfetto::protos::pbzero::ProfilePacket;
+}
 
 GlobalCallstackTrie::Node* GlobalCallstackTrie::Node::GetOrCreateChild(
-    const InternedCodeLocation& loc) {
+    const Interner<Frame>::Interned& loc) {
   Node* child = children_.Get(loc);
   if (!child)
     child = children_.Emplace(loc, this);
   return child;
 }
 
-std::vector<InternedCodeLocation> GlobalCallstackTrie::Node::BuildCallstack()
-    const {
+std::vector<Interner<Frame>::Interned>
+GlobalCallstackTrie::Node::BuildCallstack() const {
   const Node* node = this;
-  std::vector<InternedCodeLocation> res;
+  std::vector<Interner<Frame>::Interned> res;
   while (node) {
     res.emplace_back(node->location_);
     node = node->parent_;
@@ -47,10 +50,11 @@
   return res;
 }
 
-void HeapTracker::RecordMalloc(const std::vector<CodeLocation>& callstack,
-                               uint64_t address,
-                               uint64_t size,
-                               uint64_t sequence_number) {
+void HeapTracker::RecordMalloc(
+    const std::vector<unwindstack::FrameData>& callstack,
+    uint64_t address,
+    uint64_t size,
+    uint64_t sequence_number) {
   auto it = allocations_.find(address);
   if (it != allocations_.end()) {
     if (it->second.sequence_number > sequence_number) {
@@ -104,28 +108,22 @@
   allocations_.erase(leaf_it);
 }
 
-void HeapTracker::Dump(int fd) {
-  // TODO(fmayer): This should dump protocol buffers into the perfetto service.
-  // For now, output a text file compatible with flamegraph.pl.
+void HeapTracker::Dump(
+    ProfilePacket::ProcessHeapSamples* proto,
+    std::set<GlobalCallstackTrie::Node*>* callstacks_to_dump) {
   for (const auto& p : allocations_) {
-    std::string data;
     const Allocation& alloc = p.second;
-    const std::vector<InternedCodeLocation> callstack =
-        alloc.node->BuildCallstack();
-    for (auto it = callstack.begin(); it != callstack.end(); ++it) {
-      if (it != callstack.begin())
-        data += ";";
-      data += it->function_name.str();
-    }
-    data += " " + std::to_string(alloc.total_size) + "\n";
-    base::WriteAll(fd, data.c_str(), data.size());
+    callstacks_to_dump->emplace(alloc.node);
+    ProfilePacket::HeapSample* sample = proto->add_samples();
+    sample->set_callstack_id(alloc.node->id());
+    sample->set_cumulative_allocated(alloc.total_size);
   }
 }
 
 uint64_t GlobalCallstackTrie::GetCumSizeForTesting(
-    const std::vector<CodeLocation>& callstack) {
+    const std::vector<unwindstack::FrameData>& callstack) {
   Node* node = &root_;
-  for (const CodeLocation& loc : callstack) {
+  for (const unwindstack::FrameData& loc : callstack) {
     node = node->children_.Get(InternCodeLocation(loc));
     if (node == nullptr)
       return 0;
@@ -134,11 +132,11 @@
 }
 
 GlobalCallstackTrie::Node* GlobalCallstackTrie::IncrementCallsite(
-    const std::vector<CodeLocation>& callstack,
+    const std::vector<unwindstack::FrameData>& callstack,
     uint64_t size) {
   Node* node = &root_;
   node->cum_size_ += size;
-  for (const CodeLocation& loc : callstack) {
+  for (const unwindstack::FrameData& loc : callstack) {
     node = node->GetOrCreateChild(InternCodeLocation(loc));
     node->cum_size_ += size;
   }
@@ -160,6 +158,75 @@
   }
 }
 
+Interner<Frame>::Interned GlobalCallstackTrie::InternCodeLocation(
+    const unwindstack::FrameData& loc) {
+  Mapping map{};
+  map.offset = loc.map_offset;
+  map.start = loc.map_start;
+  map.end = loc.map_end;
+  map.load_bias = loc.map_load_bias;
+  base::StringSplitter sp(loc.map_name, '/');
+  while (sp.Next())
+    map.path_components.emplace_back(string_interner_.Intern(sp.cur_token()));
+
+  Frame frame(mapping_interner_.Intern(std::move(map)),
+              string_interner_.Intern(loc.function_name), loc.rel_pc);
+
+  return frame_interner_.Intern(frame);
+}
+
+Interner<Frame>::Interned GlobalCallstackTrie::MakeRootFrame() {
+  Mapping map{};
+
+  Frame frame(mapping_interner_.Intern(std::move(map)),
+              string_interner_.Intern(""), 0);
+
+  return frame_interner_.Intern(frame);
+}
+
+void DumpState::WriteMap(ProfilePacket* packet,
+                         const Interner<Mapping>::Interned map) {
+  auto map_it_and_inserted = dumped_mappings.emplace(map.id());
+  if (map_it_and_inserted.second) {
+    for (const Interner<std::string>::Interned& str : map->path_components)
+      WriteString(packet, str);
+
+    auto mapping = packet->add_mappings();
+    mapping->set_offset(map->offset);
+    mapping->set_start(map->start);
+    mapping->set_end(map->end);
+    mapping->set_load_bias(map->load_bias);
+    for (const Interner<std::string>::Interned& str : map->path_components)
+      mapping->add_path_string_ids(str.id());
+  }
+}
+
+void DumpState::WriteFrame(ProfilePacket* packet,
+                           Interner<Frame>::Interned frame) {
+  WriteMap(packet, frame->mapping);
+  WriteString(packet, frame->function_name);
+  bool inserted;
+  std::tie(std::ignore, inserted) = dumped_frames.emplace(frame.id());
+  if (inserted) {
+    auto frame_proto = packet->add_frames();
+    frame_proto->set_id(frame.id());
+    frame_proto->set_function_name_id(frame->function_name.id());
+    frame_proto->set_mapping_id(frame->mapping.id());
+    frame_proto->set_rel_pc(frame->rel_pc);
+  }
+}
+
+void DumpState::WriteString(ProfilePacket* packet,
+                            const Interner<std::string>::Interned& str) {
+  bool inserted;
+  std::tie(std::ignore, inserted) = dumped_strings.emplace(str.id());
+  if (inserted) {
+    auto interned_string = packet->add_strings();
+    interned_string->set_id(str.id());
+    interned_string->set_str(str->c_str(), str->size());
+  }
+}
+
 void BookkeepingThread::HandleBookkeepingRecord(BookkeepingRecord* rec) {
   BookkeepingData* bookkeeping_data = nullptr;
   if (rec->pid != 0) {
@@ -178,20 +245,45 @@
     if (!trace_writer)
       return;
     PERFETTO_LOG("Dumping heaps");
+    std::set<GlobalCallstackTrie::Node*> callstacks_to_dump;
+    TraceWriter::TracePacketHandle trace_packet =
+        trace_writer->NewTracePacket();
+    auto profile_packet = trace_packet->set_profile_packet();
+    for (const pid_t pid : dump_rec.pids) {
+      ProfilePacket::ProcessHeapSamples* sample =
+          profile_packet->add_process_dumps();
+      auto it = bookkeeping_data_.find(pid);
+      if (it == bookkeeping_data_.end())
+        continue;
+
+      PERFETTO_LOG("Dumping %d ", it->first);
+      it->second.heap_tracker.Dump(sample, &callstacks_to_dump);
+    }
+
+    // TODO(fmayer): For incremental dumps, this should be owned by the
+    // producer. This way we can keep track on what we dumped accross multiple
+    // dumps.
+    DumpState dump_state;
+
+    for (GlobalCallstackTrie::Node* node : callstacks_to_dump) {
+      // There need to be two separate loops over built_callstack because
+      // protozero cannot interleave different messages.
+      auto built_callstack = node->BuildCallstack();
+      for (const Interner<Frame>::Interned& frame : built_callstack)
+        dump_state.WriteFrame(profile_packet, frame);
+      ProfilePacket::Callstack* callstack = profile_packet->add_callstacks();
+      callstack->set_id(node->id());
+      for (const Interner<Frame>::Interned& frame : built_callstack)
+        callstack->add_frame_ids(frame.id());
+    }
+
+    // We cannot garbage collect until we have finished dumping, as the state
+    // in DumpState points into the GlobalCallstackTrie.
     for (const pid_t pid : dump_rec.pids) {
       auto it = bookkeeping_data_.find(pid);
       if (it == bookkeeping_data_.end())
         continue;
 
-      std::string dump_file_name = file_name_ + "." + std::to_string(it->first);
-      PERFETTO_LOG("Dumping %d to %s", it->first, dump_file_name.c_str());
-      base::ScopedFile fd =
-          base::OpenFile(dump_file_name, O_WRONLY | O_CREAT, 0644);
-      if (fd)
-        it->second.heap_tracker.Dump(fd.get());
-      else
-        PERFETTO_PLOG("Failed to open %s", dump_file_name.c_str());
-      // Garbage collect for processes that already went away.
       if (it->second.ref_count == 0) {
         std::lock_guard<std::mutex> l(bookkeeping_mutex_);
         it = bookkeeping_data_.erase(it);
@@ -211,11 +303,8 @@
     }
   } else if (rec->record_type == BookkeepingRecord::Type::Malloc) {
     AllocRecord& alloc_rec = rec->alloc_record;
-    std::vector<CodeLocation> code_locations;
-    for (unwindstack::FrameData& frame : alloc_rec.frames)
-      code_locations.emplace_back(frame.map_name, frame.function_name);
     bookkeeping_data->heap_tracker.RecordMalloc(
-        code_locations, alloc_rec.alloc_metadata.alloc_address,
+        alloc_rec.frames, alloc_rec.alloc_metadata.alloc_address,
         alloc_rec.alloc_metadata.total_size,
         alloc_rec.alloc_metadata.sequence_number);
   } else {
diff --git a/src/profiling/memory/bookkeeping.h b/src/profiling/memory/bookkeeping.h
index e90d957..2ec1491 100644
--- a/src/profiling/memory/bookkeeping.h
+++ b/src/profiling/memory/bookkeeping.h
@@ -17,47 +17,59 @@
 #ifndef SRC_PROFILING_MEMORY_BOOKKEEPING_H_
 #define SRC_PROFILING_MEMORY_BOOKKEEPING_H_
 
-#include "perfetto/base/lookup_set.h"
-#include "src/profiling/memory/bounded_queue.h"
-#include "src/profiling/memory/queue_messages.h"
-#include "src/profiling/memory/string_interner.h"
-
 #include <map>
 #include <string>
 #include <vector>
 
+#include "perfetto/base/lookup_set.h"
+#include "perfetto/base/string_splitter.h"
+#include "perfetto/trace/profiling/profile_packet.pbzero.h"
+#include "perfetto/trace/trace_packet.pbzero.h"
+#include "src/profiling/memory/bounded_queue.h"
+#include "src/profiling/memory/interner.h"
+#include "src/profiling/memory/queue_messages.h"
+
 namespace perfetto {
 namespace profiling {
 
 class HeapTracker;
 
-struct CodeLocation {
-  CodeLocation(std::string map_n, std::string function_n)
-      : map_name(std::move(map_n)), function_name(std::move(function_n)) {}
+struct Mapping {
+  uint64_t build_id;
+  uint64_t offset;
+  uint64_t start;
+  uint64_t end;
+  uint64_t load_bias;
+  std::vector<Interner<std::string>::Interned> path_components;
 
-  std::string map_name;
-  std::string function_name;
+  bool operator<(const Mapping& other) const {
+    return std::tie(build_id, offset, start, end, load_bias, path_components) <
+           std::tie(other.build_id, other.offset, other.start, other.end,
+                    other.load_bias, other.path_components);
+  }
 };
 
-// Internal data-structure for GlobalCallstackTrie to save memory if the same
-// function is named multiple times.
-struct InternedCodeLocation {
-  StringInterner::InternedString map_name;
-  StringInterner::InternedString function_name;
+struct Frame {
+  Frame(Interner<Mapping>::Interned m,
+        Interner<std::string>::Interned fn_name,
+        uint64_t pc)
+      : mapping(m), function_name(fn_name), rel_pc(pc) {}
+  Interner<Mapping>::Interned mapping;
+  Interner<std::string>::Interned function_name;
+  uint64_t rel_pc;
 
-  bool operator<(const InternedCodeLocation& other) const {
-    if (map_name.id() == other.map_name.id())
-      return function_name.id() < other.function_name.id();
-    return map_name.id() < other.map_name.id();
+  bool operator<(const Frame& other) const {
+    return std::tie(mapping, function_name, rel_pc) <
+           std::tie(other.mapping, other.function_name, other.rel_pc);
   }
 };
 
 // Graph of function callsites. This is shared between heap dumps for
 // different processes. Each call site is represented by a
 // GlobalCallstackTrie::Node that is owned by the parent (i.e. calling)
-// callsite. It has a pointer to its parent, which means the function call-graph
-// can be reconstructed from a GlobalCallstackTrie::Node by walking down the
-// pointers to the parents.
+// callsite. It has a pointer to its parent, which means the function
+// call-graph can be reconstructed from a GlobalCallstackTrie::Node by walking
+// down the pointers to the parents.
 class GlobalCallstackTrie {
  public:
   // Node in a tree of function traces that resulted in an allocation. For
@@ -81,19 +93,20 @@
     // This is opaque except to GlobalCallstackTrie.
     friend class GlobalCallstackTrie;
 
-    Node(InternedCodeLocation location) : Node(std::move(location), nullptr) {}
-    Node(InternedCodeLocation location, Node* parent)
-        : parent_(parent), location_(std::move(location)) {}
+    Node(Interner<Frame>::Interned frame) : Node(std::move(frame), nullptr) {}
+    Node(Interner<Frame>::Interned frame, Node* parent)
+        : parent_(parent), location_(std::move(frame)) {}
 
-    std::vector<InternedCodeLocation> BuildCallstack() const;
+    std::vector<Interner<Frame>::Interned> BuildCallstack() const;
+    uintptr_t id() const { return reinterpret_cast<uintptr_t>(this); }
 
    private:
-    Node* GetOrCreateChild(const InternedCodeLocation& loc);
+    Node* GetOrCreateChild(const Interner<Frame>::Interned& loc);
 
     uint64_t cum_size_ = 0;
     Node* const parent_;
-    const InternedCodeLocation location_;
-    base::LookupSet<Node, const InternedCodeLocation, &Node::location_>
+    const Interner<Frame>::Interned location_;
+    base::LookupSet<Node, const Interner<Frame>::Interned, &Node::location_>
         children_;
   };
 
@@ -101,18 +114,35 @@
   GlobalCallstackTrie(const GlobalCallstackTrie&) = delete;
   GlobalCallstackTrie& operator=(const GlobalCallstackTrie&) = delete;
 
-  uint64_t GetCumSizeForTesting(const std::vector<CodeLocation>& stack);
-  Node* IncrementCallsite(const std::vector<CodeLocation>& locs, uint64_t size);
+  uint64_t GetCumSizeForTesting(
+      const std::vector<unwindstack::FrameData>& stack);
+  Node* IncrementCallsite(const std::vector<unwindstack::FrameData>& locs,
+                          uint64_t size);
   static void DecrementNode(Node* node, uint64_t size);
 
  private:
-  InternedCodeLocation InternCodeLocation(const CodeLocation& loc) {
-    return {interner_.Intern(loc.map_name),
-            interner_.Intern(loc.function_name)};
-  }
+  Interner<Frame>::Interned InternCodeLocation(
+      const unwindstack::FrameData& loc);
+  Interner<Frame>::Interned MakeRootFrame();
 
-  StringInterner interner_;
-  Node root_{{interner_.Intern(""), interner_.Intern("")}};
+  Interner<std::string> string_interner_;
+  Interner<Mapping> mapping_interner_;
+  Interner<Frame> frame_interner_;
+
+  Node root_{MakeRootFrame()};
+};
+
+struct DumpState {
+  void WriteMap(protos::pbzero::ProfilePacket* packet,
+                const Interner<Mapping>::Interned map);
+  void WriteFrame(protos::pbzero::ProfilePacket* packet,
+                  const Interner<Frame>::Interned frame);
+  void WriteString(protos::pbzero::ProfilePacket* packet,
+                   const Interner<std::string>::Interned& str);
+
+  std::set<InternID> dumped_strings;
+  std::set<InternID> dumped_frames;
+  std::set<InternID> dumped_mappings;
 };
 
 // Snapshot for memory allocations of a particular process. Shares callsites
@@ -123,12 +153,13 @@
   explicit HeapTracker(GlobalCallstackTrie* callsites)
       : callsites_(callsites) {}
 
-  void RecordMalloc(const std::vector<CodeLocation>& stack,
+  void RecordMalloc(const std::vector<unwindstack::FrameData>& stack,
                     uint64_t address,
                     uint64_t size,
                     uint64_t sequence_number);
   void RecordFree(uint64_t address, uint64_t sequence_number);
-  void Dump(int fd);
+  void Dump(protos::pbzero::ProfilePacket::ProcessHeapSamples* proto,
+            std::set<GlobalCallstackTrie::Node*>* callstacks_to_dump);
 
  private:
   static constexpr uint64_t kNoopFree = 0;
@@ -195,7 +226,8 @@
 };
 
 struct BookkeepingData {
-  // Ownership of callsites remains with caller and has to outlive this object.
+  // Ownership of callsites remains with caller and has to outlive this
+  // object.
   explicit BookkeepingData(GlobalCallstackTrie* callsites)
       : heap_tracker(callsites) {}
 
diff --git a/src/profiling/memory/bookkeeping_unittest.cc b/src/profiling/memory/bookkeeping_unittest.cc
index a000cce..8049ae6 100644
--- a/src/profiling/memory/bookkeeping_unittest.cc
+++ b/src/profiling/memory/bookkeeping_unittest.cc
@@ -23,16 +23,39 @@
 namespace profiling {
 namespace {
 
-std::vector<CodeLocation> stack() {
-  return {
-      {"map1", "fun1"}, {"map2", "fun2"},
-  };
+std::vector<unwindstack::FrameData> stack() {
+  std::vector<unwindstack::FrameData> res;
+  unwindstack::FrameData data{};
+  data.function_name = "fun1";
+  data.map_name = "map1";
+  res.emplace_back(std::move(data));
+  data = {};
+  data.function_name = "fun2";
+  data.map_name = "map2";
+  res.emplace_back(std::move(data));
+  return res;
 }
 
-std::vector<CodeLocation> stack2() {
-  return {
-      {"map1", "fun1"}, {"map3", "fun3"},
-  };
+std::vector<unwindstack::FrameData> stack2() {
+  std::vector<unwindstack::FrameData> res;
+  unwindstack::FrameData data{};
+  data.function_name = "fun1";
+  data.map_name = "map1";
+  res.emplace_back(std::move(data));
+  data = {};
+  data.function_name = "fun3";
+  data.map_name = "map3";
+  res.emplace_back(std::move(data));
+  return res;
+}
+
+std::vector<unwindstack::FrameData> topframe() {
+  std::vector<unwindstack::FrameData> res;
+  unwindstack::FrameData data{};
+  data.function_name = "fun1";
+  data.map_name = "map1";
+  res.emplace_back(std::move(data));
+  return res;
 }
 
 TEST(BookkeepingTest, Basic) {
@@ -42,11 +65,11 @@
 
   hd.RecordMalloc(stack(), 1, 5, sequence_number++);
   hd.RecordMalloc(stack2(), 2, 2, sequence_number++);
-  ASSERT_EQ(c.GetCumSizeForTesting({{"map1", "fun1"}}), 7);
+  ASSERT_EQ(c.GetCumSizeForTesting(topframe()), 7);
   hd.RecordFree(2, sequence_number++);
-  ASSERT_EQ(c.GetCumSizeForTesting({{"map1", "fun1"}}), 5);
+  ASSERT_EQ(c.GetCumSizeForTesting(topframe()), 5);
   hd.RecordFree(1, sequence_number++);
-  ASSERT_EQ(c.GetCumSizeForTesting({{"map1", "fun1"}}), 0);
+  ASSERT_EQ(c.GetCumSizeForTesting(topframe()), 0);
 }
 
 TEST(BookkeepingTest, TwoHeapTrackers) {
@@ -58,9 +81,9 @@
 
     hd.RecordMalloc(stack(), 1, 5, sequence_number++);
     hd2.RecordMalloc(stack2(), 2, 2, sequence_number++);
-    ASSERT_EQ(c.GetCumSizeForTesting({{"map1", "fun1"}}), 7);
+    ASSERT_EQ(c.GetCumSizeForTesting(topframe()), 7);
   }
-  ASSERT_EQ(c.GetCumSizeForTesting({{"map1", "fun1"}}), 5);
+  ASSERT_EQ(c.GetCumSizeForTesting(topframe()), 5);
 }
 
 TEST(BookkeepingTest, ReplaceAlloc) {
diff --git a/src/profiling/memory/interner.h b/src/profiling/memory/interner.h
new file mode 100644
index 0000000..5de578f
--- /dev/null
+++ b/src/profiling/memory/interner.h
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_PROFILING_MEMORY_INTERNER_H_
+#define SRC_PROFILING_MEMORY_INTERNER_H_
+
+#include <stddef.h>
+#include <set>
+
+namespace perfetto {
+namespace profiling {
+
+using InternID = uintptr_t;
+
+template <typename T>
+class Interner {
+ private:
+  struct Entry {
+    Entry(T d, Interner<T>* in) : data(std::move(d)), interner(in) {}
+    bool operator<(const Entry& other) const { return data < other.data; }
+
+    const T data;
+    size_t ref_count = 0;
+    Interner<T>* interner;
+  };
+
+ public:
+  class Interned {
+   public:
+    friend class Interner<T>;
+    Interned(Entry* entry) : entry_(entry) {}
+    Interned(const Interned& other) : entry_(other.entry_) {
+      if (entry_ != nullptr)
+        entry_->ref_count++;
+    }
+
+    Interned(Interned&& other) noexcept : entry_(other.entry_) {
+      other.entry_ = nullptr;
+    }
+
+    Interned& operator=(Interned&& other) {
+      entry_ = other.entry_;
+      other.entry_ = nullptr;
+      return *this;
+    }
+
+    Interned& operator=(Interned& other) noexcept {
+      entry_ = other.entry_;
+      if (entry_ != nullptr)
+        entry_->ref_count++;
+      return *this;
+    }
+
+    const T& data() const { return entry_->data; }
+
+    InternID id() const { return reinterpret_cast<InternID>(entry_); }
+
+    ~Interned() {
+      if (entry_ != nullptr)
+        entry_->interner->Return(entry_);
+    }
+
+    bool operator<(const Interned& other) const {
+      if (entry_ == nullptr || other.entry_ == nullptr)
+        return entry_ < other.entry_;
+      return *entry_ < *(other.entry_);
+    }
+
+    const T* operator->() const { return &entry_->data; }
+
+   private:
+    Interner::Entry* entry_;
+  };
+
+  Interned Intern(const T& data) {
+    auto itr = entries_.emplace(data, this);
+    Entry& entry = const_cast<Entry&>(*itr.first);
+    entry.ref_count++;
+    return Interned(&entry);
+  }
+  size_t entry_count_for_testing() { return entries_.size(); }
+
+ private:
+  void Return(Entry* entry) {
+    if (--entry->ref_count == 0)
+      entries_.erase(*entry);
+  }
+  std::set<Entry> entries_;
+  static_assert(sizeof(Interned) == sizeof(void*),
+                "interned things should be small");
+};
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_MEMORY_INTERNER_H_
diff --git a/src/profiling/memory/interner_unittest.cc b/src/profiling/memory/interner_unittest.cc
new file mode 100644
index 0000000..d94fa17
--- /dev/null
+++ b/src/profiling/memory/interner_unittest.cc
@@ -0,0 +1,114 @@
+/*
+ * 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 "src/profiling/memory/interner.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+TEST(InternerStringTest, Basic) {
+  Interner<std::string> interner;
+  {
+    Interner<std::string>::Interned interned_str = interner.Intern("foo");
+    ASSERT_EQ(interned_str.data(), "foo");
+  }
+  ASSERT_EQ(interner.entry_count_for_testing(), 0);
+}
+
+TEST(InternerStringTest, TwoStrings) {
+  Interner<std::string> interner;
+  {
+    Interner<std::string>::Interned interned_str = interner.Intern("foo");
+    Interner<std::string>::Interned other_interned_str = interner.Intern("bar");
+    ASSERT_EQ(interned_str.data(), "foo");
+    ASSERT_EQ(other_interned_str.data(), "bar");
+  }
+  ASSERT_EQ(interner.entry_count_for_testing(), 0);
+}
+
+TEST(InternerStringTest, TwoReferences) {
+  Interner<std::string> interner;
+  {
+    Interner<std::string>::Interned interned_str = interner.Intern("foo");
+    ASSERT_EQ(interned_str.data(), "foo");
+    Interner<std::string>::Interned interned_str2 = interner.Intern("foo");
+    ASSERT_EQ(interner.entry_count_for_testing(), 1);
+    ASSERT_EQ(interned_str2.data(), "foo");
+  }
+  ASSERT_EQ(interner.entry_count_for_testing(), 0);
+}
+
+TEST(InternerStringTest, Move) {
+  Interner<std::string> interner;
+  {
+    Interner<std::string>::Interned interned_str = interner.Intern("foo");
+    {
+      Interner<std::string>::Interned interned_str2(std::move(interned_str));
+      ASSERT_EQ(interner.entry_count_for_testing(), 1);
+      ASSERT_EQ(interned_str2.data(), "foo");
+    }
+    ASSERT_EQ(interner.entry_count_for_testing(), 0);
+  }
+}
+
+TEST(InternerStringTest, Copy) {
+  Interner<std::string> interner;
+  {
+    Interner<std::string>::Interned interned_str = interner.Intern("foo");
+    {
+      Interner<std::string>::Interned interned_str2(interned_str);
+      ASSERT_EQ(interner.entry_count_for_testing(), 1);
+      ASSERT_EQ(interned_str2.data(), "foo");
+    }
+    ASSERT_EQ(interner.entry_count_for_testing(), 1);
+    ASSERT_EQ(interned_str.data(), "foo");
+  }
+}
+
+TEST(InternerStringTest, MoveAssign) {
+  Interner<std::string> interner;
+  {
+    Interner<std::string>::Interned interned_str = interner.Intern("foo");
+    {
+      Interner<std::string>::Interned interned_str2 = std::move(interned_str);
+      ASSERT_EQ(interner.entry_count_for_testing(), 1);
+      ASSERT_EQ(interned_str2.data(), "foo");
+    }
+    ASSERT_EQ(interner.entry_count_for_testing(), 0);
+  }
+}
+
+TEST(InternerStringTest, CopyAssign) {
+  Interner<std::string> interner;
+  {
+    Interner<std::string>::Interned interned_str = interner.Intern("foo");
+    {
+      Interner<std::string>::Interned interned_str2 = interned_str;
+      ASSERT_EQ(interner.entry_count_for_testing(), 1);
+      ASSERT_EQ(interned_str2.data(), "foo");
+    }
+    ASSERT_EQ(interner.entry_count_for_testing(), 1);
+    ASSERT_EQ(interned_str.data(), "foo");
+  }
+}
+
+}  // namespace
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/memory/string_interner.cc b/src/profiling/memory/string_interner.cc
deleted file mode 100644
index f913fcf..0000000
--- a/src/profiling/memory/string_interner.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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 "src/profiling/memory/string_interner.h"
-
-namespace perfetto {
-namespace profiling {
-
-StringInterner::Entry::Entry(std::string s, StringInterner* in)
-    : string(std::move(s)), interner(in) {}
-
-bool StringInterner::Entry::operator<(const Entry& other) const {
-  return string < other.string;
-}
-
-StringInterner::InternedString::InternedString(Entry* entry) : entry_(entry) {}
-
-const std::string& StringInterner::InternedString::str() const {
-  return entry_->string;
-}
-
-void* StringInterner::InternedString::id() const {
-  return entry_;
-}
-
-StringInterner::InternedString::~InternedString() {
-  if (entry_ != nullptr)
-    entry_->interner->Return(entry_);
-}
-
-StringInterner::InternedString StringInterner::Intern(const std::string& str) {
-  auto itr = entries_.emplace(str, this);
-  Entry& entry = const_cast<Entry&>(*itr.first);
-  entry.ref_count++;
-  return InternedString(&entry);
-}
-
-StringInterner::InternedString::InternedString(const InternedString& other)
-    : entry_(other.entry_) {
-  if (entry_ != nullptr)
-    entry_->ref_count++;
-}
-
-StringInterner::InternedString::InternedString(InternedString&& other) noexcept
-    : entry_(other.entry_) {
-  other.entry_ = nullptr;
-}
-
-StringInterner::InternedString& StringInterner::InternedString::operator=(
-    InternedString other) {
-  std::swap(*this, other);
-  return *this;
-}
-
-size_t StringInterner::entry_count_for_testing() {
-  return entries_.size();
-}
-
-void StringInterner::Return(Entry* entry) {
-  if (--entry->ref_count == 0)
-    entries_.erase(*entry);
-}
-
-}  // namespace profiling
-}  // namespace perfetto
diff --git a/src/profiling/memory/string_interner.h b/src/profiling/memory/string_interner.h
deleted file mode 100644
index 29f0c22..0000000
--- a/src/profiling/memory/string_interner.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef SRC_PROFILING_MEMORY_STRING_INTERNER_H_
-#define SRC_PROFILING_MEMORY_STRING_INTERNER_H_
-
-#include <stddef.h>
-#include <set>
-#include <string>
-
-namespace perfetto {
-namespace profiling {
-
-class StringInterner {
- private:
-  struct Entry {
-    Entry(std::string string, StringInterner* interner);
-    bool operator<(const Entry& other) const;
-
-    const std::string string;
-    size_t ref_count = 0;
-    StringInterner* interner;
-  };
-
- public:
-  class InternedString {
-   public:
-    friend class StringInterner;
-    InternedString(StringInterner::Entry* str);
-    InternedString(const InternedString& other);
-    InternedString(InternedString&& other) noexcept;
-    InternedString& operator=(InternedString other);
-
-    const std::string& str() const;
-    void* id() const;
-    ~InternedString();
-
-   private:
-    StringInterner::Entry* entry_;
-  };
-
-  InternedString Intern(const std::string& str);
-  size_t entry_count_for_testing();
-
- private:
-  void Return(Entry* entry);
-  std::set<Entry> entries_;
-};
-
-static_assert(sizeof(StringInterner::InternedString) == sizeof(void*),
-              "interned strings should be small");
-
-}  // namespace profiling
-}  // namespace perfetto
-
-#endif  // SRC_PROFILING_MEMORY_STRING_INTERNER_H_
diff --git a/src/profiling/memory/string_interner_unittest.cc b/src/profiling/memory/string_interner_unittest.cc
deleted file mode 100644
index 28dd619..0000000
--- a/src/profiling/memory/string_interner_unittest.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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 "src/profiling/memory/string_interner.h"
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-
-namespace perfetto {
-namespace profiling {
-namespace {
-
-TEST(StringInternerTest, Basic) {
-  StringInterner interner;
-  {
-    StringInterner::InternedString interned_str = interner.Intern("foo");
-    ASSERT_EQ(interned_str.str(), "foo");
-  }
-  ASSERT_EQ(interner.entry_count_for_testing(), 0);
-}
-
-TEST(StringInternerTest, TwoStrings) {
-  StringInterner interner;
-  {
-    StringInterner::InternedString interned_str = interner.Intern("foo");
-    StringInterner::InternedString other_interned_str = interner.Intern("bar");
-    ASSERT_EQ(interned_str.str(), "foo");
-    ASSERT_EQ(other_interned_str.str(), "bar");
-  }
-  ASSERT_EQ(interner.entry_count_for_testing(), 0);
-}
-
-TEST(StringInternerTest, TwoReferences) {
-  StringInterner interner;
-  {
-    StringInterner::InternedString interned_str = interner.Intern("foo");
-    ASSERT_EQ(interned_str.str(), "foo");
-    StringInterner::InternedString interned_str2 = interner.Intern("foo");
-    ASSERT_EQ(interner.entry_count_for_testing(), 1);
-    ASSERT_EQ(interned_str2.str(), "foo");
-  }
-  ASSERT_EQ(interner.entry_count_for_testing(), 0);
-}
-
-TEST(StringInternerTest, Move) {
-  StringInterner interner;
-  {
-    StringInterner::InternedString interned_str = interner.Intern("foo");
-    {
-      StringInterner::InternedString interned_str2(std::move(interned_str));
-      ASSERT_EQ(interner.entry_count_for_testing(), 1);
-      ASSERT_EQ(interned_str2.str(), "foo");
-    }
-    ASSERT_EQ(interner.entry_count_for_testing(), 0);
-  }
-}
-
-TEST(StringInternerTest, Copy) {
-  StringInterner interner;
-  {
-    StringInterner::InternedString interned_str = interner.Intern("foo");
-    {
-      StringInterner::InternedString interned_str2(interned_str);
-      ASSERT_EQ(interner.entry_count_for_testing(), 1);
-      ASSERT_EQ(interned_str2.str(), "foo");
-    }
-    ASSERT_EQ(interner.entry_count_for_testing(), 1);
-    ASSERT_EQ(interned_str.str(), "foo");
-  }
-}
-
-TEST(StringInternerTest, MoveAssign) {
-  StringInterner interner;
-  {
-    StringInterner::InternedString interned_str = interner.Intern("foo");
-    {
-      StringInterner::InternedString interned_str2 = std::move(interned_str);
-      ASSERT_EQ(interner.entry_count_for_testing(), 1);
-      ASSERT_EQ(interned_str2.str(), "foo");
-    }
-    ASSERT_EQ(interner.entry_count_for_testing(), 0);
-  }
-}
-
-TEST(StringInternerTest, CopyAssign) {
-  StringInterner interner;
-  {
-    StringInterner::InternedString interned_str = interner.Intern("foo");
-    {
-      StringInterner::InternedString interned_str2 = interned_str;
-      ASSERT_EQ(interner.entry_count_for_testing(), 1);
-      ASSERT_EQ(interned_str2.str(), "foo");
-    }
-    ASSERT_EQ(interner.entry_count_for_testing(), 1);
-    ASSERT_EQ(interned_str.str(), "foo");
-  }
-}
-
-}  // namespace
-}  // namespace profiling
-}  // namespace perfetto