Merge "Deflake TracingServiceImplTest.WriteIntoFileAndStopOnMaxSize."
diff --git a/Android.bp b/Android.bp
index 4b67947..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,13 +4675,13 @@
     ":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",
     "tools/trace_to_text/ftrace_inode_handler.cc",
     "tools/trace_to_text/main.cc",
     "tools/trace_to_text/proto_full_utils.cc",
-    "tools/trace_to_text/trace_to_summary.cc",
     "tools/trace_to_text/trace_to_systrace.cc",
     "tools/trace_to_text/trace_to_text.cc",
     "tools/trace_to_text/utils.cc",
@@ -4616,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/perfetto_cmd/pbtxt_to_pb.cc b/src/perfetto_cmd/pbtxt_to_pb.cc
index ec4af7d..cf923d6 100644
--- a/src/perfetto_cmd/pbtxt_to_pb.cc
+++ b/src/perfetto_cmd/pbtxt_to_pb.cc
@@ -405,8 +405,8 @@
   bool saw_colon_for_this_key = false;
   bool saw_semicolon_for_this_value = true;
   bool comment_till_eol = false;
-  Token key;
-  Token value;
+  Token key{};
+  Token value{};
 
   for (size_t i = 0; i < input.size(); i++, column++) {
     bool last_character = i + 1 == input.size();
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
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 72d22b4..7f5fbca 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -73,6 +73,8 @@
     "sql_stats_table.cc",
     "sql_stats_table.h",
     "sqlite_utils.h",
+    "stats_table.cc",
+    "stats_table.h",
     "storage_cursor.cc",
     "storage_cursor.h",
     "storage_schema.cc",
diff --git a/src/trace_processor/event_tracker.cc b/src/trace_processor/event_tracker.cc
index fdf1d4e..7119edd 100644
--- a/src/trace_processor/event_tracker.cc
+++ b/src/trace_processor/event_tracker.cc
@@ -55,7 +55,7 @@
     // If the this events previous pid does not match the previous event's next
     // pid, make a note of this.
     if (prev_pid != pending_slice->pid) {
-      context_->storage->AddMismatchedSchedSwitch();
+      context_->storage->mutable_stats()->mismatched_sched_switch_tids++;
     }
 
     size_t idx = pending_slice->storage_index;
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index f7e6a12..fc6ad1f 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -448,6 +448,7 @@
     PERFETTO_DLOG("Could not find process associated with utid %" PRIu32
                   " when parsing mem counters.",
                   utid);
+    context_->storage->mutable_stats()->mem_counter_no_process++;
     return;
   }
 
@@ -670,6 +671,7 @@
     PERFETTO_DLOG("Could not find process associated with utid %" PRIu32
                   " when parsing rss stat.",
                   utid);
+    context_->storage->mutable_stats()->rss_stat_no_process++;
     return;
   }
 
diff --git a/src/trace_processor/stats_table.cc b/src/trace_processor/stats_table.cc
new file mode 100644
index 0000000..afbb974
--- /dev/null
+++ b/src/trace_processor/stats_table.cc
@@ -0,0 +1,106 @@
+/*
+ * 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/trace_processor/stats_table.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+StatsTable::StatsTable(sqlite3*, const TraceStorage* storage)
+    : storage_(storage) {}
+
+void StatsTable::RegisterTable(sqlite3* db, const TraceStorage* storage) {
+  Table::Register<StatsTable>(db, storage, "stats");
+}
+
+Table::Schema StatsTable::CreateSchema(int, const char* const*) {
+  return Schema(
+      {
+          Table::Column(Column::kKey, "key", ColumnType::kString),
+          Table::Column(Column::kValue, "value", ColumnType::kInt),
+      },
+      {Column::kKey});
+}
+
+std::unique_ptr<Table::Cursor> StatsTable::CreateCursor(const QueryConstraints&,
+                                                        sqlite3_value**) {
+  return std::unique_ptr<Table::Cursor>(new Cursor(storage_));
+}
+
+int StatsTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
+  return SQLITE_OK;
+}
+
+StatsTable::Cursor::Cursor(const TraceStorage* storage) : storage_(storage) {}
+
+int StatsTable::Cursor::Column(sqlite3_context* context, int N) {
+  switch (N) {
+    case Column::kKey:
+      sqlite3_result_text(context, KeyForRow(row_), -1, nullptr);
+      break;
+    case Column::kValue:
+      sqlite3_result_int(context, ValueForRow(row_));
+      break;
+    default:
+      PERFETTO_FATAL("Unknown column %d", N);
+      break;
+  }
+  return SQLITE_OK;
+}
+
+int StatsTable::Cursor::Next() {
+  ++row_;
+  return SQLITE_OK;
+}
+
+int StatsTable::Cursor::Eof() {
+  return row_ >= Row::kMax;
+}
+
+const char* StatsTable::Cursor::KeyForRow(uint8_t row) {
+  switch (row) {
+    case StatsTable::Row::kMismatchedSchedSwitch:
+      return "mismatched_ss";
+    case StatsTable::Row::kRssStatNoProcess:
+      return "rss_stat_no_process";
+    case StatsTable::Row::kMemCounterNoProcess:
+      return "mem_count_no_process";
+    default:
+      PERFETTO_FATAL("Unknown row %u", row);
+  }
+}
+
+int StatsTable::Cursor::ValueForRow(uint8_t row) {
+  switch (row) {
+    case StatsTable::Row::kMismatchedSchedSwitch: {
+      auto val = storage_->stats().mismatched_sched_switch_tids;
+      return static_cast<int>(val);
+    }
+    case StatsTable::Row::kRssStatNoProcess: {
+      auto val = storage_->stats().rss_stat_no_process;
+      return static_cast<int>(val);
+    }
+    case StatsTable::Row::kMemCounterNoProcess: {
+      auto val = storage_->stats().mem_counter_no_process;
+      return static_cast<int>(val);
+    }
+    default:
+      PERFETTO_FATAL("Unknown row %u", row);
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/stats_table.h b/src/trace_processor/stats_table.h
new file mode 100644
index 0000000..2d2ff81
--- /dev/null
+++ b/src/trace_processor/stats_table.h
@@ -0,0 +1,72 @@
+/*
+ * 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_TRACE_PROCESSOR_STATS_TABLE_H_
+#define SRC_TRACE_PROCESSOR_STATS_TABLE_H_
+
+#include <limits>
+#include <memory>
+
+#include "src/trace_processor/table.h"
+#include "src/trace_processor/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class StatsTable : public Table {
+ public:
+  enum Row {
+    kMismatchedSchedSwitch = 0,
+    kRssStatNoProcess = 1,
+    kMemCounterNoProcess = 2,
+    kMax = kMemCounterNoProcess + 1
+  };
+  enum Column { kKey = 0, kValue = 1 };
+
+  static void RegisterTable(sqlite3* db, const TraceStorage* storage);
+
+  StatsTable(sqlite3*, const TraceStorage*);
+
+  // Table implementation.
+  Table::Schema CreateSchema(int argc, const char* const* argv) override;
+  std::unique_ptr<Table::Cursor> CreateCursor(const QueryConstraints&,
+                                              sqlite3_value**) override;
+  int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
+
+ private:
+  class Cursor : public Table::Cursor {
+   public:
+    Cursor(const TraceStorage*);
+
+    // Implementation of Table::Cursor.
+    int Next() override;
+    int Eof() override;
+    int Column(sqlite3_context*, int N) override;
+
+   private:
+    const char* KeyForRow(uint8_t row);
+    int ValueForRow(uint8_t row);
+
+    uint8_t row_ = 0;
+    const TraceStorage* const storage_;
+  };
+
+  const TraceStorage* const storage_;
+};
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_STATS_TABLE_H_
diff --git a/src/trace_processor/table.h b/src/trace_processor/table.h
index d8cd20d..bccf710 100644
--- a/src/trace_processor/table.h
+++ b/src/trace_processor/table.h
@@ -159,6 +159,11 @@
   // At registration time, the function should also pass true for |read_write|.
   virtual int Update(int, sqlite3_value**, sqlite3_int64*);
 
+  void SetErrorMessage(char* error) {
+    sqlite3_free(zErrMsg);
+    zErrMsg = error;
+  }
+
   const Schema& schema() { return schema_; }
 
  private:
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 7189c1b..614cb21 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -34,6 +34,7 @@
 #include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/span_join_operator_table.h"
 #include "src/trace_processor/sql_stats_table.h"
+#include "src/trace_processor/stats_table.h"
 #include "src/trace_processor/string_table.h"
 #include "src/trace_processor/table.h"
 #include "src/trace_processor/thread_table.h"
@@ -110,6 +111,7 @@
   SpanJoinOperatorTable::RegisterTable(*db_, context_.storage.get());
   WindowOperatorTable::RegisterTable(*db_, context_.storage.get());
   InstantsTable::RegisterTable(*db_, context_.storage.get());
+  StatsTable::RegisterTable(*db_, context_.storage.get());
 }
 
 TraceProcessorImpl::~TraceProcessorImpl() = default;
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 9c44cd1..1cc5d0d 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -175,7 +175,7 @@
           break;
         case protos::RawQueryResult_ColumnDesc_Type_LONG: {
           auto value = res.columns(c).long_values(r);
-          printf((value < 0xffffffll) ? "%20lld" : "%20llx", value);
+          printf("%20lld", value);
           break;
         }
       }
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index b42b539..621bae7 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -64,7 +64,9 @@
   virtual ~TraceStorage();
 
   struct Stats {
-    uint64_t mismatched_sched_switch_tids_ = 0;
+    uint64_t mismatched_sched_switch_tids = 0;
+    uint64_t rss_stat_no_process = 0;
+    uint64_t mem_counter_no_process = 0;
   };
 
   // Information about a unique process seen in a trace.
@@ -285,8 +287,6 @@
     return static_cast<UniquePid>(unique_processes_.size() - 1);
   }
 
-  void AddMismatchedSchedSwitch() { ++stats_.mismatched_sched_switch_tids_; }
-
   // Return an unqiue identifier for the contents of each string.
   // The string is copied internally and can be destroyed after this called.
   // Virtual for testing.
@@ -334,6 +334,9 @@
   const Instants& instants() const { return instants_; }
   Instants* mutable_instants() { return &instants_; }
 
+  const Stats& stats() const { return stats_; }
+  Stats* mutable_stats() { return &stats_; }
+
   const std::deque<std::string>& string_pool() const { return string_pool_; }
 
   // |unique_processes_| always contains at least 1 element becuase the 0th ID
@@ -352,7 +355,7 @@
 
   using StringHash = uint64_t;
 
-  // Metadata counters for events being added.
+  // Stats about parsing the trace.
   Stats stats_;
 
   // One entry for each CPU in the trace.
diff --git a/src/trace_processor/window_operator_table.cc b/src/trace_processor/window_operator_table.cc
index 396e18b..6e48e1f 100644
--- a/src/trace_processor/window_operator_table.cc
+++ b/src/trace_processor/window_operator_table.cc
@@ -81,9 +81,18 @@
   if (argc < 2 || sqlite3_value_type(argv[0]) == SQLITE_NULL)
     return SQLITE_READONLY;
 
-  quantum_ = static_cast<uint64_t>(sqlite3_value_int64(argv[3]));
-  window_start_ = static_cast<uint64_t>(sqlite3_value_int64(argv[4]));
-  window_dur_ = static_cast<uint64_t>(sqlite3_value_int64(argv[5]));
+  uint64_t new_quantum = static_cast<uint64_t>(sqlite3_value_int64(argv[3]));
+  uint64_t new_start = static_cast<uint64_t>(sqlite3_value_int64(argv[4]));
+  uint64_t new_dur = static_cast<uint64_t>(sqlite3_value_int64(argv[5]));
+  if (new_dur == 0) {
+    auto* err = sqlite3_mprintf("Cannot set duration of window table to zero.");
+    SetErrorMessage(err);
+    return SQLITE_ERROR;
+  }
+
+  quantum_ = new_quantum;
+  window_start_ = new_start;
+  window_dur_ = new_dur;
 
   return SQLITE_OK;
 }
diff --git a/src/tracing/core/patch_list.h b/src/tracing/core/patch_list.h
index bdfd028..c356006 100644
--- a/src/tracing/core/patch_list.h
+++ b/src/tracing/core/patch_list.h
@@ -30,6 +30,8 @@
 // messages when a proto is fragmented over several chunks. These patches are
 // sent out-of-band to the tracing service, after having returned the initial
 // chunks of the fragment.
+// TODO(crbug.com/904477): Re-disable the move constructors when all usses of
+// this class have been fixed.
 class Patch {
  public:
   using PatchContent = std::array<uint8_t, SharedMemoryABI::kPacketHeaderSize>;
@@ -54,8 +56,6 @@
 
  private:
   Patch& operator=(const Patch&) = delete;
-  Patch(Patch&&) noexcept = delete;
-  Patch& operator=(Patch&&) = delete;
 };
 
 // Note: the protozero::Message(s) will take pointers to the |size_field| of
diff --git a/test/trace_processor/android_sched_and_ps_stats.out b/test/trace_processor/android_sched_and_ps_stats.out
new file mode 100644
index 0000000..37889b6
--- /dev/null
+++ b/test/trace_processor/android_sched_and_ps_stats.out
@@ -0,0 +1,4 @@
+"key","value"
+"mismatched_ss",9
+"rss_stat_no_process",0
+"mem_count_no_process",0
diff --git a/test/trace_processor/index b/test/trace_processor/index
index ecdcebb..ea24c2d 100644
--- a/test/trace_processor/index
+++ b/test/trace_processor/index
@@ -4,6 +4,7 @@
 ../data/android_sched_and_ps.pb smoke_window.sql android_sched_and_ps_smoke_window.out
 ../data/android_sched_and_ps.pb slice_span_join_b118665515.sql android_sched_and_ps_slice_span_join_b118665515.out
 ../data/android_sched_and_ps.pb b119301023.sql android_sched_and_ps_b119301023.out
+../data/android_sched_and_ps.pb stats.sql android_sched_and_ps_stats.out
 synth_1.py smoke.sql synth_1_smoke.out
 synth_1.py filter_sched.sql synth_1_filter_sched.out
 synth_1.py filter_counters.sql synth_1_filter_counters.out
diff --git a/test/trace_processor/stats.sql b/test/trace_processor/stats.sql
new file mode 100644
index 0000000..96f15bc
--- /dev/null
+++ b/test/trace_processor/stats.sql
@@ -0,0 +1 @@
+select * from stats;
diff --git a/tools/trace_to_text/BUILD.gn b/tools/trace_to_text/BUILD.gn
index 08b095c..cec37f4 100644
--- a/tools/trace_to_text/BUILD.gn
+++ b/tools/trace_to_text/BUILD.gn
@@ -32,7 +32,6 @@
     "ftrace_inode_handler.h",
     "main.cc",
     "process_formatter.h",
-    "trace_to_summary.h",
     "trace_to_systrace.cc",
     "trace_to_systrace.h",
     "trace_to_text.h",
@@ -63,7 +62,6 @@
   sources = [
     "proto_full_utils.cc",
     "proto_full_utils.h",
-    "trace_to_summary.cc",
     "trace_to_text.cc",
   ]
 }
diff --git a/tools/trace_to_text/ftrace_event_formatter.cc b/tools/trace_to_text/ftrace_event_formatter.cc
index 913188a..2357eef 100644
--- a/tools/trace_to_text/ftrace_event_formatter.cc
+++ b/tools/trace_to_text/ftrace_event_formatter.cc
@@ -3534,13 +3534,21 @@
     uint64_t timestamp,
     uint32_t cpu,
     const protos::FtraceEvent& event,
-    const std::unordered_map<uint32_t /*tid*/, uint32_t /*tgid*/>& thread_map) {
+    const std::unordered_map<uint32_t /*tid*/, uint32_t /*tgid*/>& thread_map,
+    std::unordered_map<uint32_t /*tid*/, std::string>& thread_names) {
   // Sched_switch events contain the thread name so use that in the prefix.
   std::string name;
   if (event.has_sched_switch()) {
     name = event.sched_switch().prev_comm();
+    thread_names[event.pid()] = event.sched_switch().prev_comm();
   } else {
-    name = "<...>";
+    // For non sched switch events use name stored from a sched switch event.
+    auto it = thread_names.find(event.pid());
+    if (it != thread_names.end()) {
+      name = it->second;
+    } else {
+      name = "<...>";
+    }
   }
 
   std::string line = FormatEventText(event);
diff --git a/tools/trace_to_text/ftrace_event_formatter.h b/tools/trace_to_text/ftrace_event_formatter.h
index 593cee2..0309a0e 100644
--- a/tools/trace_to_text/ftrace_event_formatter.h
+++ b/tools/trace_to_text/ftrace_event_formatter.h
@@ -30,7 +30,8 @@
     uint64_t timestamp,
     uint32_t cpu,
     const protos::FtraceEvent&,
-    const std::unordered_map<uint32_t /*tid*/, uint32_t /*tgid*/>& thread_map);
+    const std::unordered_map<uint32_t /*tid*/, uint32_t /*tgid*/>& thread_map,
+    std::unordered_map<uint32_t /*tid*/, std::string>& thread_names);
 
 }  // namespace perfetto
 
diff --git a/tools/trace_to_text/lite_fallbacks.cc b/tools/trace_to_text/lite_fallbacks.cc
index 1f9df32..82e09ff 100644
--- a/tools/trace_to_text/lite_fallbacks.cc
+++ b/tools/trace_to_text/lite_fallbacks.cc
@@ -15,21 +15,15 @@
  */
 
 // This file is used when targeting the protobuf-lite only target. It provides
-// fallback implementations for TraceToText and TraceToSummary which simply
-// return an error message.
+// fallback implementations for TraceToText which simply return an error
+// message.
 
 #include "perfetto/base/logging.h"
-#include "tools/trace_to_text/trace_to_summary.h"
 #include "tools/trace_to_text/trace_to_text.h"
 
 namespace perfetto {
 namespace trace_to_text {
 
-int TraceToSummary(std::istream*, std::ostream*, bool) {
-  PERFETTO_FATAL(
-      "The 'summary' command is not available in lite builds of trace_to_text");
-}
-
 int TraceToText(std::istream*, std::ostream*) {
   PERFETTO_FATAL(
       "The 'text' command is not available in lite builds of trace_to_text");
diff --git a/tools/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index 72f5d22..68ca085 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -18,7 +18,6 @@
 #include <iostream>
 
 #include "perfetto/base/logging.h"
-#include "tools/trace_to_text/trace_to_summary.h"
 #include "tools/trace_to_text/trace_to_systrace.h"
 #include "tools/trace_to_text/trace_to_text.h"
 
@@ -26,7 +25,7 @@
 
 int Usage(const char* argv0) {
   printf(
-      "Usage: %s systrace|json|text|summary|short_summary [trace.proto] "
+      "Usage: %s systrace|json|text [trace.proto] "
       "[trace.txt]\n",
       argv0);
   return 1;
@@ -79,12 +78,5 @@
   if (format == "text")
     return perfetto::trace_to_text::TraceToText(input_stream, output_stream);
 
-  if (format == "summary")
-    return perfetto::trace_to_text::TraceToSummary(input_stream, output_stream,
-                                                   /* compact_output */ false);
-  if (format == "short_summary")
-    return perfetto::trace_to_text::TraceToSummary(input_stream, output_stream,
-                                                   /* compact_output */ true);
-
   return Usage(argv[0]);
 }
diff --git a/tools/trace_to_text/trace_to_summary.cc b/tools/trace_to_text/trace_to_summary.cc
deleted file mode 100644
index 34dacad..0000000
--- a/tools/trace_to_text/trace_to_summary.cc
+++ /dev/null
@@ -1,463 +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 "tools/trace_to_text/trace_to_summary.h"
-
-#include <inttypes.h>
-
-#include <stdio.h>
-#include <sys/ioctl.h>
-
-#include <algorithm>
-#include <fstream>
-#include <functional>
-#include <iostream>
-#include <istream>
-#include <limits>
-#include <map>
-#include <memory>
-#include <ostream>
-#include <utility>
-
-#include <google/protobuf/compiler/importer.h>
-#include <google/protobuf/dynamic_message.h>
-#include <google/protobuf/io/zero_copy_stream_impl.h>
-#include <google/protobuf/text_format.h>
-
-#include "perfetto/base/build_config.h"
-#include "perfetto/base/logging.h"
-#include "perfetto/traced/sys_stats_counters.h"
-#include "tools/trace_to_text/ftrace_inode_handler.h"
-#include "tools/trace_to_text/process_formatter.h"
-#include "tools/trace_to_text/proto_full_utils.h"
-#include "tools/trace_to_text/utils.h"
-
-#include "perfetto/trace/ftrace/ftrace_event.pb.h"
-#include "perfetto/trace/ftrace/ftrace_stats.pb.h"
-#include "perfetto/trace/trace.pb.h"
-#include "perfetto/trace/trace_packet.pb.h"
-
-namespace perfetto {
-namespace trace_to_text {
-
-namespace {
-
-using google::protobuf::Descriptor;
-using google::protobuf::DynamicMessageFactory;
-using google::protobuf::FileDescriptor;
-using google::protobuf::Message;
-using google::protobuf::TextFormat;
-using google::protobuf::compiler::DiskSourceTree;
-using google::protobuf::compiler::Importer;
-using google::protobuf::io::OstreamOutputStream;
-
-using protos::FtraceEvent;
-using protos::FtraceEventBundle;
-using protos::InodeFileMap;
-using protos::PrintFtraceEvent;
-using protos::ProcessTree;
-using protos::Trace;
-using protos::TracePacket;
-using protos::FtraceStats;
-using protos::FtraceStats_Phase_START_OF_TRACE;
-using protos::FtraceStats_Phase_END_OF_TRACE;
-using protos::SysStats;
-using Entry = protos::InodeFileMap::Entry;
-using Process = protos::ProcessTree::Process;
-
-void PrintFtraceTrack(std::ostream* output,
-                      const uint64_t& start,
-                      const uint64_t& end,
-                      const std::multiset<uint64_t>& ftrace_timestamps) {
-  constexpr char kFtraceTrackName[] = "ftrace ";
-  size_t width = GetTerminalWidth();
-  size_t bucket_count = width - strlen(kFtraceTrackName);
-  size_t bucket_size = static_cast<size_t>(end - start) / bucket_count;
-  size_t max = 0;
-  std::vector<size_t> buckets(bucket_count);
-  for (size_t i = 0; i < bucket_count; i++) {
-    auto low = ftrace_timestamps.lower_bound(i * bucket_size + start);
-    auto high = ftrace_timestamps.upper_bound((i + 1) * bucket_size + start);
-    buckets[i] = static_cast<size_t>(std::distance(low, high));
-    max = std::max(max, buckets[i]);
-  }
-
-  std::vector<std::string> out =
-      std::vector<std::string>({" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇"});
-  *output << "-------------------- " << kFtraceTrackName
-          << "--------------------\n";
-  char line[2048];
-  for (size_t i = 0; i < bucket_count; i++) {
-    sprintf(
-        line, "%s",
-        out[std::min(buckets[i] / (max / out.size()), out.size() - 1)].c_str());
-    *output << std::string(line);
-  }
-  *output << "\n\n";
-}
-
-void PrintFtraceStats(std::ostream* output,
-                      uint64_t overwrite_count,
-                      std::map<FtraceEvent::EventCase, uint64_t> event_counts,
-                      const FtraceStats& before_stats,
-                      const FtraceStats& after_stats,
-                      bool compact_output) {
-  if (!compact_output)
-    *output << "--------------------Ftrace Stats-------------------\n";
-
-  char line[2048];
-  if (compact_output) {
-    sprintf(line, "ftrace_overwrite_count,%" PRIu64 "\n", overwrite_count);
-  } else {
-    sprintf(line, "Events overwritten: %" PRIu64 "\n", overwrite_count);
-  }
-  *output << std::string(line);
-
-  DiskSourceTree dst;
-  dst.MapPath("perfetto", "protos/perfetto");
-  perfetto::trace_to_text::MultiFileErrorCollectorImpl mfe;
-  Importer importer(&dst, &mfe);
-  const FileDescriptor* parsed_file =
-      importer.Import("perfetto/trace/ftrace/ftrace_event.proto");
-
-  DynamicMessageFactory dmf;
-  const Descriptor* ftrace_descriptor = parsed_file->message_type(0);
-  for (const auto& event_to_count : event_counts) {
-    const std::string& event_name =
-        ftrace_descriptor->FindFieldByNumber(event_to_count.first)->name();
-    uint64_t count = event_to_count.second;
-    if (compact_output) {
-      sprintf(line, "%s,%" PRIu64 "\n", event_name.c_str(), count);
-    } else {
-      sprintf(line, "%s count: %" PRIu64 "\n", event_name.c_str(), count);
-    }
-    *output << std::string(line);
-  }
-
-  uint64_t before_total_overrun = 0;
-  uint64_t after_total_overrun = 0;
-  for (const auto& cpu_stats : before_stats.cpu_stats()) {
-    before_total_overrun += cpu_stats.overrun();
-  }
-  for (const auto& cpu_stats : after_stats.cpu_stats()) {
-    after_total_overrun += cpu_stats.overrun();
-  }
-
-  if (compact_output) {
-    sprintf(line, "total_overrun,%" PRIu64 "\n",
-            after_total_overrun - before_total_overrun);
-  } else {
-    sprintf(line, "total_overrun: %" PRIu64 " (= %" PRIu64 " - %" PRIu64 ")\n",
-            after_total_overrun - before_total_overrun, after_total_overrun,
-            before_total_overrun);
-  }
-  *output << std::string(line);
-
-  if (!compact_output)
-    *output << "\n";
-}
-
-void PrintInodeStats(std::ostream* output,
-                     const std::set<uint64_t>& ftrace_inodes,
-                     const uint64_t& ftrace_inode_count,
-                     const std::set<uint64_t>& resolved_map_inodes,
-                     const std::set<uint64_t>& resolved_scan_inodes,
-                     bool compact_output) {
-  if (!compact_output)
-    *output << "--------------------Inode Stats-------------------\n";
-
-  char line[2048];
-  if (compact_output) {
-    sprintf(line, "events_inodes,%" PRIu64 "\n", ftrace_inode_count);
-  } else {
-    sprintf(line, "Events with inodes: %" PRIu64 "\n", ftrace_inode_count);
-  }
-  *output << std::string(line);
-
-  if (compact_output) {
-    sprintf(line, "events_unique_inodes,%zu\n", ftrace_inodes.size());
-  } else {
-    sprintf(line, "Unique inodes from events: %zu\n", ftrace_inodes.size());
-  }
-  *output << std::string(line);
-
-  if (compact_output) {
-    sprintf(line, "resolved_inodes_static,%zu\n", resolved_map_inodes.size());
-  } else {
-    sprintf(line, "Resolved inodes from static map: %zu\n",
-            resolved_map_inodes.size());
-  }
-  *output << std::string(line);
-
-  if (compact_output) {
-    sprintf(line, "resolved_inodes_scan_cache,%zu\n",
-            resolved_scan_inodes.size());
-  } else {
-    sprintf(line, "Resolved inodes from scan and cache: %zu\n",
-            resolved_scan_inodes.size());
-  }
-  *output << std::string(line);
-
-  std::set<uint64_t> resolved_inodes;
-  set_union(resolved_map_inodes.begin(), resolved_map_inodes.end(),
-            resolved_scan_inodes.begin(), resolved_scan_inodes.end(),
-            std::inserter(resolved_inodes, resolved_inodes.begin()));
-
-  if (compact_output) {
-    sprintf(line, "total_resolved_inodes,%zu\n", resolved_inodes.size());
-  } else {
-    sprintf(line, "Total resolved inodes: %zu\n", resolved_inodes.size());
-  }
-  *output << std::string(line);
-
-  std::set<uint64_t> intersect;
-  set_intersection(resolved_inodes.begin(), resolved_inodes.end(),
-                   ftrace_inodes.begin(), ftrace_inodes.end(),
-                   std::inserter(intersect, intersect.begin()));
-
-  size_t unresolved_inodes = ftrace_inodes.size() - intersect.size();
-  if (compact_output) {
-    sprintf(line, "unresolved_inodes,%zu\n", unresolved_inodes);
-  } else {
-    sprintf(line, "Unresolved inodes: %zu\n", unresolved_inodes);
-  }
-  *output << std::string(line);
-
-  size_t unexpected_inodes = resolved_inodes.size() - intersect.size();
-  if (compact_output) {
-    sprintf(line, "unexpected_inodes_fs,%zu\n", unexpected_inodes);
-  } else {
-    sprintf(line, "Unexpected inodes from filesystem: %zu\n",
-            unexpected_inodes);
-  }
-  *output << std::string(line);
-
-  if (!compact_output)
-    *output << "\n";
-}
-
-void PrintProcessStats(std::ostream* output,
-                       const std::set<pid_t>& tids_in_tree,
-                       const std::set<pid_t>& tids_in_events,
-                       bool compact_output) {
-  if (!compact_output)
-    *output << "----------------Process Tree Stats----------------\n";
-
-  char tid[2048];
-  if (compact_output) {
-    sprintf(tid, "unique_thread_process,%zu\n", tids_in_tree.size());
-  } else {
-    sprintf(tid, "Unique thread ids in process tree: %zu\n",
-            tids_in_tree.size());
-  }
-  *output << std::string(tid);
-
-  char tid_event[2048];
-  if (compact_output) {
-    sprintf(tid_event, "unique_thread_ftrace,%zu\n", tids_in_events.size());
-  } else {
-    sprintf(tid_event, "Unique thread ids in ftrace events: %zu\n",
-            tids_in_events.size());
-  }
-  *output << std::string(tid_event);
-
-  std::set<pid_t> intersect;
-  set_intersection(tids_in_tree.begin(), tids_in_tree.end(),
-                   tids_in_events.begin(), tids_in_events.end(),
-                   std::inserter(intersect, intersect.begin()));
-
-  char matching[2048];
-  size_t thread_id_process_info =
-      (intersect.size() * 100) / tids_in_events.size();
-  if (compact_output) {
-    sprintf(matching,
-            "tids_with_pinfo,%zu\ntids,%zu\ntids_with_pinfo_percentage,%zu\n",
-            intersect.size(), tids_in_events.size(), thread_id_process_info);
-  } else {
-    sprintf(matching, "Thread ids with process info: %zu/%zu -> %zu %%\n",
-            intersect.size(), tids_in_events.size(), thread_id_process_info);
-  }
-  *output << std::string(matching);
-
-  if (!compact_output)
-    *output << "\n";
-}
-
-void PrintTraceStats(std::ostream* output,
-                     const protos::TraceStats& stats,
-                     bool compact_output) {
-  if (compact_output)
-    return;
-  *output << "--------------------Trace Stats-------------------\n";
-  size_t buf_num = 0;
-  for (const auto& buf : stats.buffer_stats()) {
-    *output << "Buffer " << buf_num++ << "\n"
-            << "  bytes_written: " << buf.bytes_written() << "\n"
-            << "  chunks_written: " << buf.chunks_written() << "\n"
-            << "  chunks_overwritten: " << buf.chunks_overwritten() << "\n"
-            << "  write_wrap_count: " << buf.write_wrap_count() << "\n"
-            << "  patches_succeeded: " << buf.patches_succeeded() << "\n"
-            << "  patches_failed: " << buf.patches_failed() << "\n"
-            << "  readaheads_succeeded: " << buf.readaheads_succeeded() << "\n"
-            << "  readaheads_failed: " << buf.readaheads_failed() << "\n"
-            << "  abi_violations: " << buf.abi_violations() << "\n";
-  }
-  *output << "producers_connected: " << stats.producers_connected() << "\n"
-          << "producers_seen: " << stats.producers_seen() << "\n"
-          << "data_sources_reg: " << stats.data_sources_registered() << "\n"
-          << "data_sources_seen: " << stats.data_sources_seen() << "\n"
-          << "tracing_sessions: " << stats.tracing_sessions() << "\n"
-          << "total_buffers: " << stats.total_buffers() << "\n";
-}
-
-}  // namespace
-
-int TraceToSummary(std::istream* input,
-                   std::ostream* output,
-                   bool compact_output) {
-  uint64_t ftrace_start = std::numeric_limits<uint64_t>::max();
-  uint64_t ftrace_end = 0;
-  uint64_t boottime_start = std::numeric_limits<uint64_t>::max();
-  uint64_t boottime_end = 0;
-  uint64_t ftrace_overwrites = 0;
-  std::map<FtraceEvent::EventCase, uint64_t> ftrace_event_counts;
-  std::multiset<uint64_t> ftrace_timestamps;
-  std::set<pid_t> tids_in_tree;
-  std::set<pid_t> tids_in_events;
-  std::set<uint64_t> ftrace_inodes;
-  uint64_t ftrace_inode_count = 0;
-  std::set<uint64_t> resolved_map_inodes;
-  std::set<uint64_t> resolved_scan_inodes;
-  protos::TraceStats last_stats;
-
-  FtraceStats before_stats;
-  FtraceStats after_stats;
-
-  ForEachPacketInTrace(
-      input,
-      [&ftrace_start, &ftrace_end, &ftrace_overwrites, &ftrace_event_counts,
-       &before_stats, &after_stats, &ftrace_timestamps, &tids_in_tree,
-       &tids_in_events, &ftrace_inodes, &ftrace_inode_count,
-       &resolved_map_inodes, &resolved_scan_inodes, &last_stats,
-       &boottime_start, &boottime_end](const protos::TracePacket& packet) {
-        if (packet.has_process_tree()) {
-          const ProcessTree& tree = packet.process_tree();
-          for (Process process : tree.processes()) {
-            tids_in_tree.insert(process.pid());
-            for (ProcessTree::Thread thread : process.threads_deprecated())
-              tids_in_tree.insert(thread.tid());
-          }
-          for (ProcessTree::Thread thread : tree.threads())
-            tids_in_tree.insert(thread.tid());
-        }
-
-        if (packet.has_inode_file_map()) {
-          const InodeFileMap& inode_file_map = packet.inode_file_map();
-          const auto& mount_points = inode_file_map.mount_points();
-          bool from_scan = std::find(mount_points.begin(), mount_points.end(),
-                                     "/data") != mount_points.end();
-          for (const auto& entry : inode_file_map.entries())
-            if (from_scan)
-              resolved_scan_inodes.insert(entry.inode_number());
-            else
-              resolved_map_inodes.insert(entry.inode_number());
-        }
-
-        if (packet.has_trace_stats())
-          last_stats = packet.trace_stats();
-
-        if (packet.has_ftrace_stats()) {
-          const auto& ftrace_stats = packet.ftrace_stats();
-          if (ftrace_stats.phase() == FtraceStats_Phase_START_OF_TRACE) {
-            before_stats = ftrace_stats;
-            // TODO(hjd): Check not yet set.
-          } else if (ftrace_stats.phase() == FtraceStats_Phase_END_OF_TRACE) {
-            after_stats = ftrace_stats;
-            // TODO(hjd): Check not yet set.
-          } else {
-            // TODO(hjd): Error here.
-          }
-        }
-
-        if (packet.has_clock_snapshot()) {
-          for (const auto& clock : packet.clock_snapshot().clocks()) {
-            if (clock.type() == protos::ClockSnapshot_Clock_Type_MONOTONIC) {
-              boottime_start =
-                  std::min<uint64_t>(boottime_start, clock.timestamp());
-              boottime_end =
-                  std::max<uint64_t>(boottime_end, clock.timestamp());
-            }
-          }
-        }
-
-        if (!packet.has_ftrace_events())
-          return;
-
-        const FtraceEventBundle& bundle = packet.ftrace_events();
-        ftrace_overwrites += bundle.overwrite_count();
-
-        uint64_t inode_number = 0;
-        for (const FtraceEvent& event : bundle.event()) {
-          ftrace_event_counts[event.event_case()] += 1;
-
-          if (ParseInode(event, &inode_number)) {
-            ftrace_inodes.insert(inode_number);
-            ftrace_inode_count++;
-          }
-          if (event.pid()) {
-            tids_in_events.insert(static_cast<int>(event.pid()));
-          }
-          if (event.timestamp()) {
-            ftrace_start = std::min<uint64_t>(ftrace_start, event.timestamp());
-            ftrace_end = std::max<uint64_t>(ftrace_end, event.timestamp());
-            ftrace_timestamps.insert(event.timestamp());
-          }
-        }
-      });
-
-  fprintf(stderr, "\n");
-
-  char line[2048];
-  uint64_t ftrace_duration = (ftrace_end - ftrace_start) / (1000 * 1000);
-  if (compact_output) {
-    sprintf(line, "ftrace duration,%" PRIu64 "\n", ftrace_duration);
-  } else {
-    sprintf(line, "Ftrace duration: %" PRIu64 "ms\n", ftrace_duration);
-  }
-  *output << std::string(line);
-
-  uint64_t boottime_duration = (boottime_end - boottime_start) / (1000 * 1000);
-  if (compact_output) {
-    sprintf(line, "boottime duration,%" PRIu64 "\n", boottime_duration);
-  } else {
-    sprintf(line, "Boottime duration: %" PRIu64 "ms\n", boottime_duration);
-  }
-  *output << std::string(line);
-
-  if (!compact_output)
-    PrintFtraceTrack(output, ftrace_start, ftrace_end, ftrace_timestamps);
-  PrintFtraceStats(output, ftrace_overwrites, ftrace_event_counts, before_stats,
-                   after_stats, compact_output);
-  PrintProcessStats(output, tids_in_tree, tids_in_events, compact_output);
-  PrintInodeStats(output, ftrace_inodes, ftrace_inode_count,
-                  resolved_map_inodes, resolved_scan_inodes, compact_output);
-  PrintTraceStats(output, last_stats, compact_output);
-
-  return 0;
-}
-
-}  // namespace trace_to_text
-}  // namespace perfetto
diff --git a/tools/trace_to_text/trace_to_summary.h b/tools/trace_to_text/trace_to_summary.h
deleted file mode 100644
index 1b3120d..0000000
--- a/tools/trace_to_text/trace_to_summary.h
+++ /dev/null
@@ -1,32 +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 TOOLS_TRACE_TO_TEXT_TRACE_TO_SUMMARY_H_
-#define TOOLS_TRACE_TO_TEXT_TRACE_TO_SUMMARY_H_
-
-#include <iostream>
-
-namespace perfetto {
-namespace trace_to_text {
-
-int TraceToSummary(std::istream* input,
-                   std::ostream* output,
-                   bool compact_output);
-
-}  // namespace trace_to_text
-}  // namespace perfetto
-
-#endif  // TOOLS_TRACE_TO_TEXT_TRACE_TO_SUMMARY_H_
diff --git a/tools/trace_to_text/trace_to_systrace.cc b/tools/trace_to_text/trace_to_systrace.cc
index 19818a9..0b2cc12 100644
--- a/tools/trace_to_text/trace_to_systrace.cc
+++ b/tools/trace_to_text/trace_to_systrace.cc
@@ -100,6 +100,7 @@
   std::vector<std::string> proc_dump;
   std::vector<std::string> thread_dump;
   std::unordered_map<uint32_t /*tid*/, uint32_t /*tgid*/> thread_map;
+  std::unordered_map<uint32_t /*tid*/, std::string> thread_names;
 
   std::vector<const char*> meminfo_strs = BuildMeminfoCounterNames();
   std::vector<const char*> vmstat_strs = BuildVmstatCounterNames();
@@ -107,7 +108,7 @@
   std::vector<protos::TracePacket> packets_to_process;
 
   ForEachPacketInTrace(
-      input, [&thread_map, &packets_to_process, &proc_dump,
+      input, [&thread_map, &packets_to_process, &proc_dump, &thread_names,
               &thread_dump](const protos::TracePacket& packet) {
         if (!packet.has_process_tree()) {
           packets_to_process.emplace_back(std::move(packet));
@@ -125,6 +126,9 @@
           // Populate thread map for matching tids to tgids.
           thread_map[static_cast<uint32_t>(thread.tid())] =
               static_cast<uint32_t>(thread.tgid());
+          if (thread.has_name()) {
+            thread_names[static_cast<uint32_t>(thread.tid())] = thread.name();
+          }
           std::string t = FormatThread(thread);
           thread_dump.emplace_back(t);
         }
@@ -135,7 +139,7 @@
       const FtraceEventBundle& bundle = packet.ftrace_events();
       for (const FtraceEvent& event : bundle.event()) {
         std::string line = FormatFtraceEvent(event.timestamp(), bundle.cpu(),
-                                             event, thread_map);
+                                             event, thread_map, thread_names);
         if (line == "")
           continue;
         ftrace_sorted.emplace(event.timestamp(), line);
@@ -153,7 +157,8 @@
         sprintf(str, "C|1|%s|%" PRIu64, meminfo_strs[meminfo.key()],
                 static_cast<uint64_t>(meminfo.value()));
         event.mutable_print()->set_buf(str);
-        ftrace_sorted.emplace(ts, FormatFtraceEvent(ts, 0, event, thread_map));
+        ftrace_sorted.emplace(
+            ts, FormatFtraceEvent(ts, 0, event, thread_map, thread_names));
       }
       for (const auto& vmstat : sys_stats.vmstat()) {
         FtraceEvent event;
@@ -164,7 +169,8 @@
         sprintf(str, "C|1|%s|%" PRIu64, vmstat_strs[vmstat.key()],
                 static_cast<uint64_t>(vmstat.value()));
         event.mutable_print()->set_buf(str);
-        ftrace_sorted.emplace(ts, FormatFtraceEvent(ts, 0, event, thread_map));
+        ftrace_sorted.emplace(
+            ts, FormatFtraceEvent(ts, 0, event, thread_map, thread_names));
       }
     }
   }
diff --git a/ui/src/tracks/cpu_slices/controller.ts b/ui/src/tracks/cpu_slices/controller.ts
index 26e46ba..bb987f9 100644
--- a/ui/src/tracks/cpu_slices/controller.ts
+++ b/ui/src/tracks/cpu_slices/controller.ts
@@ -63,7 +63,7 @@
     if (isQuantized) {
       windowStartNs = Math.floor(windowStartNs / bucketSizeNs) * bucketSizeNs;
     }
-    const windowDurNs = endNs - windowStartNs;
+    const windowDurNs = Math.max(1, endNs - windowStartNs);
 
     this.query(`update window_${this.trackState.id} set
       window_start=${windowStartNs},
diff --git a/ui/src/tracks/process_summary/controller.ts b/ui/src/tracks/process_summary/controller.ts
index 462b525..b61838f 100644
--- a/ui/src/tracks/process_summary/controller.ts
+++ b/ui/src/tracks/process_summary/controller.ts
@@ -66,7 +66,7 @@
     // |resolution| is in s/px we want # ns for 10px window:
     const bucketSizeNs = Math.round(resolution * 10 * 1e9);
     const windowStartNs = Math.floor(startNs / bucketSizeNs) * bucketSizeNs;
-    const windowDurNs = endNs - windowStartNs;
+    const windowDurNs = Math.max(1, endNs - windowStartNs);
 
     this.query(`update ${this.tableName('window')} set
       window_start=${windowStartNs},