profiling: factor callstack trie out into p/common/

+ leftovers from previous traced_perf review

Bug: 144281346
Change-Id: I74a8e7da9e9d66423dcfbf4edde02325b067959a
diff --git a/Android.bp b/Android.bp
index 5589098..12f93ef 100644
--- a/Android.bp
+++ b/Android.bp
@@ -110,6 +110,8 @@
     ":perfetto_src_base_unix_socket",
     ":perfetto_src_ipc_client",
     ":perfetto_src_ipc_common",
+    ":perfetto_src_profiling_common_callstack_trie",
+    ":perfetto_src_profiling_common_interner",
     ":perfetto_src_profiling_common_unwind_support",
     ":perfetto_src_profiling_memory_daemon",
     ":perfetto_src_profiling_memory_proc_utils",
@@ -1342,6 +1344,8 @@
     ":perfetto_src_ipc_common",
     ":perfetto_src_ipc_host",
     ":perfetto_src_perfetto_cmd_perfetto_atoms",
+    ":perfetto_src_profiling_common_callstack_trie",
+    ":perfetto_src_profiling_common_interner",
     ":perfetto_src_profiling_common_unwind_support",
     ":perfetto_src_profiling_memory_client",
     ":perfetto_src_profiling_memory_daemon",
@@ -5594,6 +5598,27 @@
   ],
 }
 
+// GN: //src/profiling/common:callstack_trie
+filegroup {
+  name: "perfetto_src_profiling_common_callstack_trie",
+  srcs: [
+    "src/profiling/common/callstack_trie.cc",
+  ],
+}
+
+// GN: //src/profiling/common:interner
+filegroup {
+  name: "perfetto_src_profiling_common_interner",
+}
+
+// GN: //src/profiling/common:unittests
+filegroup {
+  name: "perfetto_src_profiling_common_unittests",
+  srcs: [
+    "src/profiling/common/interner_unittest.cc",
+  ],
+}
+
 // GN: //src/profiling/common:unwind_support
 filegroup {
   name: "perfetto_src_profiling_common_unwind_support",
@@ -5687,7 +5712,6 @@
     "src/profiling/memory/bookkeeping_unittest.cc",
     "src/profiling/memory/client_unittest.cc",
     "src/profiling/memory/heapprofd_producer_unittest.cc",
-    "src/profiling/memory/interner_unittest.cc",
     "src/profiling/memory/page_idle_checker_unittest.cc",
     "src/profiling/memory/proc_utils_unittest.cc",
     "src/profiling/memory/sampler_unittest.cc",
@@ -6970,6 +6994,9 @@
     ":perfetto_src_perfetto_cmd_protos_gen",
     ":perfetto_src_perfetto_cmd_trigger_producer",
     ":perfetto_src_perfetto_cmd_unittests",
+    ":perfetto_src_profiling_common_callstack_trie",
+    ":perfetto_src_profiling_common_interner",
+    ":perfetto_src_profiling_common_unittests",
     ":perfetto_src_profiling_common_unwind_support",
     ":perfetto_src_profiling_deobfuscator",
     ":perfetto_src_profiling_memory_client",
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index 84467e6..aae3085 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -57,6 +57,10 @@
   ]
 }
 
+if (enable_perfetto_heapprofd || enable_perfetto_traced_perf) {
+  perfetto_unittests_targets += [ "src/profiling/common:unittests" ]
+}
+
 if (enable_perfetto_heapprofd) {
   perfetto_unittests_targets += [
     "src/profiling/memory:unittests",
diff --git a/src/profiling/common/BUILD.gn b/src/profiling/common/BUILD.gn
index 7e0d767..8bd7784 100644
--- a/src/profiling/common/BUILD.gn
+++ b/src/profiling/common/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("../../../gn/perfetto.gni")
+import("../../../gn/test.gni")
 
 source_set("unwind_support") {
   public_deps = [ "../../../gn:libunwindstack" ]
@@ -25,3 +26,36 @@
     "unwind_support.h",
   ]
 }
+
+source_set("callstack_trie") {
+  deps = [
+    ":interner",
+    ":unwind_support",
+    "../../../gn:default_deps",
+    "../../../src/base",
+  ]
+  sources = [
+    "callstack_trie.cc",
+    "callstack_trie.h",
+  ]
+}
+
+source_set("interner") {
+  deps = [
+    "../../../gn:default_deps",
+    "../../../src/base",
+  ]
+  sources = [ "interner.h" ]
+}
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  deps = [
+    ":interner",
+    "../../../gn:default_deps",
+    "../../../gn:gtest_and_gmock",
+    "../../base",
+    "../../base:test_support",
+  ]
+  sources = [ "interner_unittest.cc" ]
+}
diff --git a/src/profiling/common/callstack_trie.cc b/src/profiling/common/callstack_trie.cc
new file mode 100644
index 0000000..a97d3aa
--- /dev/null
+++ b/src/profiling/common/callstack_trie.cc
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 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/common/callstack_trie.h"
+
+#include <vector>
+
+#include "perfetto/ext/base/string_splitter.h"
+#include "src/profiling/common/interner.h"
+#include "src/profiling/common/unwind_support.h"
+
+namespace perfetto {
+namespace profiling {
+
+GlobalCallstackTrie::Node* GlobalCallstackTrie::GetOrCreateChild(
+    Node* self,
+    const Interned<Frame>& loc) {
+  Node* child = self->children_.Get(loc);
+  if (!child)
+    child = self->children_.Emplace(loc, ++next_callstack_id_, self);
+  return child;
+}
+
+std::vector<Interned<Frame>> GlobalCallstackTrie::BuildCallstack(
+    const Node* node) const {
+  std::vector<Interned<Frame>> res;
+  while (node != &root_) {
+    res.emplace_back(node->location_);
+    node = node->parent_;
+  }
+  return res;
+}
+
+GlobalCallstackTrie::Node* GlobalCallstackTrie::CreateCallsite(
+    const std::vector<FrameData>& callstack) {
+  Node* node = &root_;
+  for (const FrameData& loc : callstack) {
+    node = GetOrCreateChild(node, InternCodeLocation(loc));
+  }
+  return node;
+}
+
+void GlobalCallstackTrie::IncrementNode(Node* node) {
+  while (node != nullptr) {
+    node->ref_count_ += 1;
+    node = node->parent_;
+  }
+}
+
+void GlobalCallstackTrie::DecrementNode(Node* node) {
+  PERFETTO_DCHECK(node->ref_count_ >= 1);
+
+  bool delete_prev = false;
+  Node* prev = nullptr;
+  while (node != nullptr) {
+    if (delete_prev)
+      node->children_.Remove(*prev);
+    node->ref_count_ -= 1;
+    delete_prev = node->ref_count_ == 0;
+    prev = node;
+    node = node->parent_;
+  }
+}
+
+Interned<Frame> GlobalCallstackTrie::InternCodeLocation(const FrameData& loc) {
+  Mapping map(string_interner_.Intern(loc.build_id));
+  map.exact_offset = loc.frame.map_exact_offset;
+  map.start_offset = loc.frame.map_elf_start_offset;
+  map.start = loc.frame.map_start;
+  map.end = loc.frame.map_end;
+  map.load_bias = loc.frame.map_load_bias;
+  base::StringSplitter sp(loc.frame.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.frame.function_name),
+              loc.frame.rel_pc);
+
+  return frame_interner_.Intern(frame);
+}
+
+Interned<Frame> GlobalCallstackTrie::MakeRootFrame() {
+  Mapping map(string_interner_.Intern(""));
+
+  Frame frame(mapping_interner_.Intern(std::move(map)),
+              string_interner_.Intern(""), 0);
+
+  return frame_interner_.Intern(frame);
+}
+
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/common/callstack_trie.h b/src/profiling/common/callstack_trie.h
new file mode 100644
index 0000000..b9bbf78
--- /dev/null
+++ b/src/profiling/common/callstack_trie.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2020 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_COMMON_CALLSTACK_TRIE_H_
+#define SRC_PROFILING_COMMON_CALLSTACK_TRIE_H_
+
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/lookup_set.h"
+#include "src/profiling/common/interner.h"
+#include "src/profiling/common/unwind_support.h"
+
+namespace perfetto {
+namespace profiling {
+
+struct Mapping {
+  Mapping(Interned<std::string> b) : build_id(std::move(b)) {}
+
+  Interned<std::string> build_id;
+  uint64_t exact_offset = 0;
+  uint64_t start_offset = 0;
+  uint64_t start = 0;
+  uint64_t end = 0;
+  uint64_t load_bias = 0;
+  std::vector<Interned<std::string>> path_components{};
+
+  bool operator<(const Mapping& other) const {
+    return std::tie(build_id, exact_offset, start_offset, start, end, load_bias,
+                    path_components) <
+           std::tie(other.build_id, other.exact_offset, other.start_offset,
+                    other.start, other.end, other.load_bias,
+                    other.path_components);
+  }
+  bool operator==(const Mapping& other) const {
+    return std::tie(build_id, exact_offset, start_offset, start, end, load_bias,
+                    path_components) ==
+           std::tie(other.build_id, other.exact_offset, other.start_offset,
+                    other.start, other.end, other.load_bias,
+                    other.path_components);
+  }
+};
+
+struct Frame {
+  Frame(Interned<Mapping> m, Interned<std::string> fn_name, uint64_t pc)
+      : mapping(m), function_name(fn_name), rel_pc(pc) {}
+  Interned<Mapping> mapping;
+  Interned<std::string> function_name;
+  uint64_t rel_pc;
+
+  bool operator<(const Frame& other) const {
+    return std::tie(mapping, function_name, rel_pc) <
+           std::tie(other.mapping, other.function_name, other.rel_pc);
+  }
+
+  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. A single instance can be used for callsites from
+// different processes. Each call site is represented by a
+// GlobalCallstackTrie::Node that is owned by the parent callsite. Each node has
+// a pointer to its parent, which means the function call-graph can be
+// reconstructed from a GlobalCallstackTrie::Node by walking down the parent
+// chain.
+//
+// For the following two callstacks:
+//  * libc_init -> main -> foo -> alloc_buf
+//  * libc_init -> main -> bar -> alloc_buf
+// The tree looks as following:
+//
+//             libc_init   libc_init
+//                   |      |
+//                  main   main
+//                   |      |
+//                  foo    bar
+//                    \    /
+//                   alloc_buf
+//                       |
+//                    [root_]
+//
+// TODO(fmayer): we currently insert starting with the leafmost frame. So the
+// root_ frame has all distinct leaf nodes as its immediate children, and
+// |CreateCallsite| returns a pointer to the "outermost" function node (so
+// there's a distinct libc_init node per callstack). Most likely less efficient
+// than interning in the "intuitive" way, but not guaranteed (consider a deeply
+// nested callchain fragment at the leaf, which is called from many places).
+class GlobalCallstackTrie {
+ public:
+  class Node {
+   public:
+    // This is opaque except to GlobalCallstackTrie.
+    friend class GlobalCallstackTrie;
+
+    // Allow building a node out of a frame for base::LookupSet.
+    Node(Interned<Frame> frame) : Node(std::move(frame), 0, nullptr) {}
+    Node(Interned<Frame> frame, uint64_t id)
+        : Node(std::move(frame), id, nullptr) {}
+    Node(Interned<Frame> frame, uint64_t id, Node* parent)
+        : id_(id), parent_(parent), location_(std::move(frame)) {}
+
+    uint64_t id() const { return id_; }
+
+   private:
+    Node* GetOrCreateChild(const Interned<Frame>& loc);
+
+    uint64_t ref_count_ = 0;
+    uint64_t id_;
+    Node* const parent_;
+    const Interned<Frame> location_;
+    base::LookupSet<Node, const Interned<Frame>, &Node::location_> children_;
+  };
+
+  GlobalCallstackTrie() = default;
+  GlobalCallstackTrie(const GlobalCallstackTrie&) = delete;
+  GlobalCallstackTrie& operator=(const GlobalCallstackTrie&) = delete;
+
+  // Moving this would invalidate the back pointers to the root_ node.
+  GlobalCallstackTrie(GlobalCallstackTrie&&) = delete;
+  GlobalCallstackTrie& operator=(GlobalCallstackTrie&&) = delete;
+
+  Node* CreateCallsite(const std::vector<FrameData>& callstack);
+  static void DecrementNode(Node* node);
+  static void IncrementNode(Node* node);
+
+  std::vector<Interned<Frame>> BuildCallstack(const Node* node) const;
+
+ private:
+  Node* GetOrCreateChild(Node* self, const Interned<Frame>& loc);
+
+  Interned<Frame> InternCodeLocation(const FrameData& loc);
+  Interned<Frame> MakeRootFrame();
+
+  Interner<std::string> string_interner_;
+  Interner<Mapping> mapping_interner_;
+  Interner<Frame> frame_interner_;
+
+  uint64_t next_callstack_id_ = 0;
+
+  Node root_{MakeRootFrame(), ++next_callstack_id_};
+};
+
+}  // namespace profiling
+}  // namespace perfetto
+
+namespace std {
+template <>
+struct hash<::perfetto::profiling::Mapping> {
+  using argument_type = ::perfetto::profiling::Mapping;
+  using result_type = size_t;
+  result_type operator()(const argument_type& mapping) {
+    size_t h =
+        std::hash<::perfetto::profiling::InternID>{}(mapping.build_id.id());
+    h ^= std::hash<uint64_t>{}(mapping.exact_offset);
+    h ^= std::hash<uint64_t>{}(mapping.start_offset);
+    h ^= std::hash<uint64_t>{}(mapping.start);
+    h ^= std::hash<uint64_t>{}(mapping.end);
+    h ^= std::hash<uint64_t>{}(mapping.load_bias);
+    for (const auto& path : mapping.path_components)
+      h ^= std::hash<uint64_t>{}(path.id());
+    return h;
+  }
+};
+
+template <>
+struct hash<::perfetto::profiling::Frame> {
+  using argument_type = ::perfetto::profiling::Frame;
+  using result_type = size_t;
+  result_type operator()(const argument_type& frame) {
+    size_t h = std::hash<::perfetto::profiling::InternID>{}(frame.mapping.id());
+    h ^= std::hash<::perfetto::profiling::InternID>{}(frame.function_name.id());
+    h ^= std::hash<uint64_t>{}(frame.rel_pc);
+    return h;
+  }
+};
+}  // namespace std
+
+#endif  // SRC_PROFILING_COMMON_CALLSTACK_TRIE_H_
diff --git a/src/profiling/memory/interner.h b/src/profiling/common/interner.h
similarity index 95%
rename from src/profiling/memory/interner.h
rename to src/profiling/common/interner.h
index 2d3c677..7490ef2 100644
--- a/src/profiling/memory/interner.h
+++ b/src/profiling/common/interner.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_PROFILING_MEMORY_INTERNER_H_
-#define SRC_PROFILING_MEMORY_INTERNER_H_
+#ifndef SRC_PROFILING_COMMON_INTERNER_H_
+#define SRC_PROFILING_COMMON_INTERNER_H_
 
 #include <stddef.h>
 #include <stdint.h>
@@ -29,6 +29,7 @@
 
 using InternID = uint32_t;
 
+// Interner that hands out refcounted references.
 template <typename T>
 class Interner {
  private:
@@ -121,6 +122,7 @@
     if (--entry->ref_count == 0)
       entries_.erase(*entry);
   }
+
   InternID next_id = 1;
   std::unordered_set<Entry, typename Entry::Hash> entries_;
   static_assert(sizeof(Interned) == sizeof(void*),
@@ -138,4 +140,4 @@
 }  // namespace profiling
 }  // namespace perfetto
 
-#endif  // SRC_PROFILING_MEMORY_INTERNER_H_
+#endif  // SRC_PROFILING_COMMON_INTERNER_H_
diff --git a/src/profiling/memory/interner_unittest.cc b/src/profiling/common/interner_unittest.cc
similarity index 98%
rename from src/profiling/memory/interner_unittest.cc
rename to src/profiling/common/interner_unittest.cc
index e43b348..f7f1c21 100644
--- a/src/profiling/memory/interner_unittest.cc
+++ b/src/profiling/common/interner_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/profiling/memory/interner.h"
+#include "src/profiling/common/interner.h"
 
 #include "test/gtest_and_gmock.h"
 
diff --git a/src/profiling/common/unwind_support.cc b/src/profiling/common/unwind_support.cc
index 53d0e4a..1d8bc14 100644
--- a/src/profiling/common/unwind_support.cc
+++ b/src/profiling/common/unwind_support.cc
@@ -120,7 +120,7 @@
 
 FrameData UnwindingMetadata::AnnotateFrame(unwindstack::FrameData frame) {
   std::string build_id;
-  if (frame.map_name != "") {
+  if (!frame.map_name.empty()) {
     unwindstack::MapInfo* map_info = fd_maps.Find(frame.pc);
     if (map_info)
       build_id = map_info->GetBuildID();
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index 3c7adfd..c814b6b 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -140,6 +140,8 @@
     "../../base:unix_socket",
     "../../tracing/core",
     "../../tracing/ipc/producer",
+    "../common:callstack_trie",
+    "../common:interner",
     "../common:unwind_support",
   ]
   public_deps = [
@@ -156,7 +158,6 @@
     "bookkeeping_dump.h",
     "heapprofd_producer.cc",
     "heapprofd_producer.h",
-    "interner.h",
     "java_hprof_producer.cc",
     "java_hprof_producer.h",
     "page_idle_checker.cc",
@@ -207,7 +208,6 @@
     "bookkeeping_unittest.cc",
     "client_unittest.cc",
     "heapprofd_producer_unittest.cc",
-    "interner_unittest.cc",
     "page_idle_checker_unittest.cc",
     "proc_utils_unittest.cc",
     "sampler_unittest.cc",
diff --git a/src/profiling/memory/bookkeeping.cc b/src/profiling/memory/bookkeeping.cc
index bd6ee97..979d88f 100644
--- a/src/profiling/memory/bookkeeping.cc
+++ b/src/profiling/memory/bookkeeping.cc
@@ -24,6 +24,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
+#include "src/profiling/common/callstack_trie.h"
 
 namespace perfetto {
 namespace profiling {
@@ -146,82 +147,6 @@
   return alloc.value.retain_max.max;
 }
 
-GlobalCallstackTrie::Node* GlobalCallstackTrie::GetOrCreateChild(
-    Node* self,
-    const Interned<Frame>& loc) {
-  Node* child = self->children_.Get(loc);
-  if (!child)
-    child = self->children_.Emplace(loc, ++next_callstack_id_, self);
-  return child;
-}
-
-std::vector<Interned<Frame>> GlobalCallstackTrie::BuildCallstack(
-    const Node* node) const {
-  std::vector<Interned<Frame>> res;
-  while (node != &root_) {
-    res.emplace_back(node->location_);
-    node = node->parent_;
-  }
-  return res;
-}
-
-GlobalCallstackTrie::Node* GlobalCallstackTrie::CreateCallsite(
-    const std::vector<FrameData>& callstack) {
-  Node* node = &root_;
-  for (const FrameData& loc : callstack) {
-    node = GetOrCreateChild(node, InternCodeLocation(loc));
-  }
-  return node;
-}
-
-void GlobalCallstackTrie::IncrementNode(Node* node) {
-  while (node != nullptr) {
-    node->ref_count_ += 1;
-    node = node->parent_;
-  }
-}
-
-void GlobalCallstackTrie::DecrementNode(Node* node) {
-  PERFETTO_DCHECK(node->ref_count_ >= 1);
-
-  bool delete_prev = false;
-  Node* prev = nullptr;
-  while (node != nullptr) {
-    if (delete_prev)
-      node->children_.Remove(*prev);
-    node->ref_count_ -= 1;
-    delete_prev = node->ref_count_ == 0;
-    prev = node;
-    node = node->parent_;
-  }
-}
-
-Interned<Frame> GlobalCallstackTrie::InternCodeLocation(const FrameData& loc) {
-  Mapping map(string_interner_.Intern(loc.build_id));
-  map.exact_offset = loc.frame.map_exact_offset;
-  map.start_offset = loc.frame.map_elf_start_offset;
-  map.start = loc.frame.map_start;
-  map.end = loc.frame.map_end;
-  map.load_bias = loc.frame.map_load_bias;
-  base::StringSplitter sp(loc.frame.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.frame.function_name),
-              loc.frame.rel_pc);
-
-  return frame_interner_.Intern(frame);
-}
-
-Interned<Frame> GlobalCallstackTrie::MakeRootFrame() {
-  Mapping map(string_interner_.Intern(""));
-
-  Frame frame(mapping_interner_.Intern(std::move(map)),
-              string_interner_.Intern(""), 0);
-
-  return frame_interner_.Intern(frame);
-}
 
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/memory/bookkeeping.h b/src/profiling/memory/bookkeeping.h
index c157e07..7ae384c 100644
--- a/src/profiling/memory/bookkeeping.h
+++ b/src/profiling/memory/bookkeeping.h
@@ -18,13 +18,11 @@
 #define SRC_PROFILING_MEMORY_BOOKKEEPING_H_
 
 #include <map>
-#include <string>
 #include <vector>
 
 #include "perfetto/base/time.h"
-#include "perfetto/ext/base/lookup_set.h"
-#include "perfetto/ext/base/string_splitter.h"
-#include "src/profiling/memory/interner.h"
+#include "src/profiling/common/callstack_trie.h"
+#include "src/profiling/common/interner.h"
 #include "src/profiling/memory/unwound_messages.h"
 
 // Below is an illustration of the bookkeeping system state where
@@ -91,129 +89,6 @@
 namespace perfetto {
 namespace profiling {
 
-class HeapTracker;
-
-struct Mapping {
-  Mapping(Interned<std::string> b) : build_id(std::move(b)) {}
-
-  Interned<std::string> build_id;
-  uint64_t exact_offset = 0;
-  uint64_t start_offset = 0;
-  uint64_t start = 0;
-  uint64_t end = 0;
-  uint64_t load_bias = 0;
-  std::vector<Interned<std::string>> path_components{};
-
-  bool operator<(const Mapping& other) const {
-    return std::tie(build_id, exact_offset, start_offset, start, end, load_bias,
-                    path_components) <
-           std::tie(other.build_id, other.exact_offset, other.start_offset,
-                    other.start, other.end, other.load_bias,
-                    other.path_components);
-  }
-  bool operator==(const Mapping& other) const {
-    return std::tie(build_id, exact_offset, start_offset, start, end, load_bias,
-                    path_components) ==
-           std::tie(other.build_id, other.exact_offset, other.start_offset,
-                    other.start, other.end, other.load_bias,
-                    other.path_components);
-  }
-};
-
-struct Frame {
-  Frame(Interned<Mapping> m, Interned<std::string> fn_name, uint64_t pc)
-      : mapping(m), function_name(fn_name), rel_pc(pc) {}
-  Interned<Mapping> mapping;
-  Interned<std::string> function_name;
-  uint64_t rel_pc;
-
-  bool operator<(const Frame& other) const {
-    return std::tie(mapping, function_name, rel_pc) <
-           std::tie(other.mapping, other.function_name, other.rel_pc);
-  }
-
-  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.
-class GlobalCallstackTrie {
- public:
-  // Node in a tree of function traces that resulted in an allocation. For
-  // instance, if alloc_buf is called from foo and bar, which are called from
-  // main, the tree looks as following.
-  //
-  //            alloc_buf    alloc_buf
-  //                   |      |
-  //                  foo    bar
-  //                    \    /
-  //                      main
-  //                       |
-  //                   libc_init
-  //                       |
-  //                    [root_]
-  //
-  // allocations_ will hold a map from the pointers returned from malloc to
-  // alloc_buf to the leafs of this tree.
-  class Node {
-   public:
-    // This is opaque except to GlobalCallstackTrie.
-    friend class GlobalCallstackTrie;
-
-    Node(Interned<Frame> frame) : Node(std::move(frame), 0, nullptr) {}
-    Node(Interned<Frame> frame, uint64_t id)
-        : Node(std::move(frame), id, nullptr) {}
-    Node(Interned<Frame> frame, uint64_t id, Node* parent)
-        : id_(id), parent_(parent), location_(std::move(frame)) {}
-
-    uint64_t id() const { return id_; }
-
-   private:
-    Node* GetOrCreateChild(const Interned<Frame>& loc);
-
-    uint64_t ref_count_ = 0;
-    uint64_t id_;
-    Node* const parent_;
-    const Interned<Frame> location_;
-    base::LookupSet<Node, const Interned<Frame>, &Node::location_> children_;
-  };
-
-  GlobalCallstackTrie() = default;
-  GlobalCallstackTrie(const GlobalCallstackTrie&) = delete;
-  GlobalCallstackTrie& operator=(const GlobalCallstackTrie&) = delete;
-
-  // Moving this would invalidate the back pointers to the root_ node.
-  GlobalCallstackTrie(GlobalCallstackTrie&&) = delete;
-  GlobalCallstackTrie& operator=(GlobalCallstackTrie&&) = delete;
-
-  Node* CreateCallsite(const std::vector<FrameData>& locs);
-  static void DecrementNode(Node* node);
-  static void IncrementNode(Node* node);
-
-  std::vector<Interned<Frame>> BuildCallstack(const Node* node) const;
-
- private:
-  Node* GetOrCreateChild(Node* self, const Interned<Frame>& loc);
-
-  Interned<Frame> InternCodeLocation(const FrameData& loc);
-  Interned<Frame> MakeRootFrame();
-
-  Interner<std::string> string_interner_;
-  Interner<Mapping> mapping_interner_;
-  Interner<Frame> frame_interner_;
-
-  uint64_t next_callstack_id_ = 0;
-
-  Node root_{MakeRootFrame(), ++next_callstack_id_};
-};
-
 // Snapshot for memory allocations of a particular process. Shares callsites
 // with other processes.
 class HeapTracker {
@@ -460,36 +335,4 @@
 }  // namespace profiling
 }  // namespace perfetto
 
-namespace std {
-template <>
-struct hash<::perfetto::profiling::Mapping> {
-  using argument_type = ::perfetto::profiling::Mapping;
-  using result_type = size_t;
-  result_type operator()(const argument_type& mapping) {
-    size_t h =
-        std::hash<::perfetto::profiling::InternID>{}(mapping.build_id.id());
-    h ^= std::hash<uint64_t>{}(mapping.exact_offset);
-    h ^= std::hash<uint64_t>{}(mapping.start_offset);
-    h ^= std::hash<uint64_t>{}(mapping.start);
-    h ^= std::hash<uint64_t>{}(mapping.end);
-    h ^= std::hash<uint64_t>{}(mapping.load_bias);
-    for (const auto& path : mapping.path_components)
-      h ^= std::hash<uint64_t>{}(path.id());
-    return h;
-  }
-};
-
-template <>
-struct hash<::perfetto::profiling::Frame> {
-  using argument_type = ::perfetto::profiling::Frame;
-  using result_type = size_t;
-  result_type operator()(const argument_type& frame) {
-    size_t h = std::hash<::perfetto::profiling::InternID>{}(frame.mapping.id());
-    h ^= std::hash<::perfetto::profiling::InternID>{}(frame.function_name.id());
-    h ^= std::hash<uint64_t>{}(frame.rel_pc);
-    return h;
-  }
-};
-}  // namespace std
-
 #endif  // SRC_PROFILING_MEMORY_BOOKKEEPING_H_
diff --git a/src/profiling/memory/bookkeeping_dump.h b/src/profiling/memory/bookkeeping_dump.h
index 517d02c..a983781 100644
--- a/src/profiling/memory/bookkeeping_dump.h
+++ b/src/profiling/memory/bookkeeping_dump.h
@@ -29,8 +29,8 @@
 
 #include "perfetto/ext/tracing/core/trace_writer.h"
 
+#include "src/profiling/common/interner.h"
 #include "src/profiling/memory/bookkeeping.h"
-#include "src/profiling/memory/interner.h"
 
 namespace perfetto {
 namespace profiling {
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index 8969980..cb85212 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -411,6 +411,12 @@
   return ret;
 }
 
+// TODO(rsavitski): reconsider posting bookkeping tasks directly (without a
+// queue). Doesn't allow flushing only a given instance's backlog, but
+// we shouldn't have the main thread so backlogged for it to matter (reading
+// will break before that). On the other hand, the tick approach would let us
+// rate-limit bookkeeping more directly (by bounding the number of events
+// processed).
 void PerfProducer::TickDataSourceBookkeep(DataSourceInstanceID ds_id) {
   auto q_it = bookkeping_queues_.find(ds_id);
   if (q_it == bookkeping_queues_.end()) {