Merge "perfetto-ui: Add origin trial"
diff --git a/Android.bp b/Android.bp
index 7a622db..19675b8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -979,9 +979,9 @@
   name: "perfetto_include_perfetto_profiling_normalize",
 }
 
-// GN: //include/perfetto/profiling:symbolizer
+// GN: //include/perfetto/profiling:pprof_builder
 filegroup {
-  name: "perfetto_include_perfetto_profiling_symbolizer",
+  name: "perfetto_include_perfetto_profiling_pprof_builder",
 }
 
 // GN: //include/perfetto/protozero:protozero
@@ -5416,6 +5416,31 @@
   ],
 }
 
+// GN: //src/profiling/perf:unwind_support
+filegroup {
+  name: "perfetto_src_profiling_perf_unwind_support",
+  srcs: [
+    "src/profiling/perf/unwind_support.cc",
+  ],
+}
+
+// GN: //src/profiling/symbolizer:symbolize_database
+filegroup {
+  name: "perfetto_src_profiling_symbolizer_symbolize_database",
+  srcs: [
+    "src/profiling/symbolizer/symbolize_database.cc",
+  ],
+}
+
+// GN: //src/profiling/symbolizer:symbolizer
+filegroup {
+  name: "perfetto_src_profiling_symbolizer_symbolizer",
+  srcs: [
+    "src/profiling/symbolizer/local_symbolizer.cc",
+    "src/profiling/symbolizer/symbolizer.cc",
+  ],
+}
+
 // GN: //src/profiling:unittests
 filegroup {
   name: "perfetto_src_profiling_unittests",
@@ -5620,11 +5645,26 @@
   ],
 }
 
-// GN: //src/trace_processor:common
+// GN: //src/trace_processor/containers:containers
 filegroup {
-  name: "perfetto_src_trace_processor_common",
+  name: "perfetto_src_trace_processor_containers_containers",
   srcs: [
-    "src/trace_processor/string_pool.cc",
+    "src/trace_processor/containers/bit_vector.cc",
+    "src/trace_processor/containers/bit_vector_iterators.cc",
+    "src/trace_processor/containers/row_map.cc",
+    "src/trace_processor/containers/string_pool.cc",
+  ],
+}
+
+// GN: //src/trace_processor/containers:unittests
+filegroup {
+  name: "perfetto_src_trace_processor_containers_unittests",
+  srcs: [
+    "src/trace_processor/containers/bit_vector_unittest.cc",
+    "src/trace_processor/containers/null_term_string_view_unittest.cc",
+    "src/trace_processor/containers/row_map_unittest.cc",
+    "src/trace_processor/containers/sparse_vector_unittest.cc",
+    "src/trace_processor/containers/string_pool_unittest.cc",
   ],
 }
 
@@ -5632,10 +5672,7 @@
 filegroup {
   name: "perfetto_src_trace_processor_db_lib",
   srcs: [
-    "src/trace_processor/db/bit_vector.cc",
-    "src/trace_processor/db/bit_vector_iterators.cc",
     "src/trace_processor/db/column.cc",
-    "src/trace_processor/db/row_map.cc",
     "src/trace_processor/db/table.cc",
   ],
 }
@@ -5644,10 +5681,7 @@
 filegroup {
   name: "perfetto_src_trace_processor_db_unittests",
   srcs: [
-    "src/trace_processor/db/bit_vector_unittest.cc",
     "src/trace_processor/db/compare_unittest.cc",
-    "src/trace_processor/db/row_map_unittest.cc",
-    "src/trace_processor/db/sparse_vector_unittest.cc",
   ],
 }
 
@@ -5668,7 +5702,6 @@
     "src/trace_processor/cpu_profile_stack_sample_table.cc",
     "src/trace_processor/filtered_row_index.cc",
     "src/trace_processor/gfp_flags.cc",
-    "src/trace_processor/heap_profile_allocation_table.cc",
     "src/trace_processor/instants_table.cc",
     "src/trace_processor/metadata_table.cc",
     "src/trace_processor/process_table.cc",
@@ -5740,8 +5773,15 @@
 filegroup {
   name: "perfetto_src_trace_processor_storage_full",
   srcs: [
+    "src/trace_processor/importers/proto/android_probes_module.cc",
+    "src/trace_processor/importers/proto/android_probes_parser.cc",
     "src/trace_processor/importers/proto/graphics_event_module.cc",
     "src/trace_processor/importers/proto/graphics_event_parser.cc",
+    "src/trace_processor/importers/proto/heap_graph_module.cc",
+    "src/trace_processor/importers/proto/heap_graph_tracker.cc",
+    "src/trace_processor/importers/proto/heap_graph_walker.cc",
+    "src/trace_processor/importers/proto/system_probes_module.cc",
+    "src/trace_processor/importers/proto/system_probes_parser.cc",
     "src/trace_processor/importers/proto/vulkan_memory_tracker.cc",
     "src/trace_processor/register_additional_modules.cc",
   ],
@@ -5752,13 +5792,14 @@
   name: "perfetto_src_trace_processor_storage_minimal",
   srcs: [
     "src/trace_processor/args_tracker.cc",
-    "src/trace_processor/binder_tracker.cc",
     "src/trace_processor/clock_tracker.cc",
+    "src/trace_processor/destructible.cc",
     "src/trace_processor/event_tracker.cc",
     "src/trace_processor/forwarding_trace_parser.cc",
     "src/trace_processor/ftrace_utils.cc",
     "src/trace_processor/gzip_trace_parser.cc",
     "src/trace_processor/heap_profile_tracker.cc",
+    "src/trace_processor/importers/ftrace/binder_tracker.cc",
     "src/trace_processor/importers/ftrace/ftrace_descriptors.cc",
     "src/trace_processor/importers/ftrace/ftrace_module.cc",
     "src/trace_processor/importers/ftrace/ftrace_module_impl.cc",
@@ -5769,17 +5810,10 @@
     "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc",
     "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc",
     "src/trace_processor/importers/fuchsia/fuchsia_trace_utils.cc",
-    "src/trace_processor/importers/proto/android_probes_module.cc",
-    "src/trace_processor/importers/proto/android_probes_parser.cc",
     "src/trace_processor/importers/proto/args_table_utils.cc",
-    "src/trace_processor/importers/proto/heap_graph_module.cc",
-    "src/trace_processor/importers/proto/heap_graph_tracker.cc",
-    "src/trace_processor/importers/proto/heap_graph_walker.cc",
     "src/trace_processor/importers/proto/proto_importer_module.cc",
     "src/trace_processor/importers/proto/proto_trace_parser.cc",
     "src/trace_processor/importers/proto/proto_trace_tokenizer.cc",
-    "src/trace_processor/importers/proto/system_probes_module.cc",
-    "src/trace_processor/importers/proto/system_probes_parser.cc",
     "src/trace_processor/importers/proto/track_event_module.cc",
     "src/trace_processor/importers/proto/track_event_parser.cc",
     "src/trace_processor/importers/proto/track_event_tokenizer.cc",
@@ -5829,14 +5863,12 @@
     "src/trace_processor/importers/proto/proto_trace_parser_unittest.cc",
     "src/trace_processor/importers/systrace/systrace_parser_unittest.cc",
     "src/trace_processor/metadata_table_unittest.cc",
-    "src/trace_processor/null_term_string_view_unittest.cc",
     "src/trace_processor/process_table_unittest.cc",
     "src/trace_processor/process_tracker_unittest.cc",
     "src/trace_processor/protozero_to_text_unittests.cc",
     "src/trace_processor/sched_slice_table_unittest.cc",
     "src/trace_processor/slice_tracker_unittest.cc",
     "src/trace_processor/span_join_operator_table_unittest.cc",
-    "src/trace_processor/string_pool_unittest.cc",
     "src/trace_processor/syscall_tracker_unittest.cc",
     "src/trace_processor/thread_table_unittest.cc",
     "src/trace_processor/trace_sorter_unittest.cc",
@@ -6352,14 +6384,6 @@
   ],
 }
 
-// GN: //tools/trace_to_text:local_symbolizer
-filegroup {
-  name: "perfetto_tools_trace_to_text_local_symbolizer",
-  srcs: [
-    "tools/trace_to_text/local_symbolizer.cc",
-  ],
-}
-
 // GN: //tools/trace_to_text:pprofbuilder
 filegroup {
   name: "perfetto_tools_trace_to_text_pprofbuilder",
@@ -6368,14 +6392,6 @@
   ],
 }
 
-// GN: //tools/trace_to_text:symbolizer
-filegroup {
-  name: "perfetto_tools_trace_to_text_symbolizer",
-  srcs: [
-    "tools/trace_to_text/symbolizer.cc",
-  ],
-}
-
 // GN: //tools/trace_to_text:utils
 filegroup {
   name: "perfetto_tools_trace_to_text_utils",
@@ -6582,13 +6598,15 @@
     ":perfetto_src_profiling_memory_wire_protocol",
     ":perfetto_src_profiling_perf_producer",
     ":perfetto_src_profiling_perf_producer_unittests",
+    ":perfetto_src_profiling_perf_unwind_support",
     ":perfetto_src_profiling_unittests",
     ":perfetto_src_protozero_protozero",
     ":perfetto_src_protozero_testing_messages_cpp_gen",
     ":perfetto_src_protozero_testing_messages_lite_gen",
     ":perfetto_src_protozero_testing_messages_zero_gen",
     ":perfetto_src_protozero_unittests",
-    ":perfetto_src_trace_processor_common",
+    ":perfetto_src_trace_processor_containers_containers",
+    ":perfetto_src_trace_processor_containers_unittests",
     ":perfetto_src_trace_processor_db_lib",
     ":perfetto_src_trace_processor_db_unittests",
     ":perfetto_src_trace_processor_descriptors",
@@ -6720,6 +6738,9 @@
     "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
     "-DHAVE_HIDDEN",
   ],
+  include_dirs: [
+    "bionic/libc/kernel",
+  ],
   data: [
     "src/traced/probes/filesystem/testdata/**/*",
     "src/traced/probes/ftrace/test/data/**/*",
@@ -6802,7 +6823,7 @@
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_base_base",
     ":perfetto_src_protozero_protozero",
-    ":perfetto_src_trace_processor_common",
+    ":perfetto_src_trace_processor_containers_containers",
     ":perfetto_src_trace_processor_db_lib",
     ":perfetto_src_trace_processor_descriptors",
     ":perfetto_src_trace_processor_lib",
@@ -6873,7 +6894,7 @@
     ":perfetto_include_perfetto_ext_base_base",
     ":perfetto_include_perfetto_ext_traced_sys_stats_counters",
     ":perfetto_include_perfetto_profiling_deobfuscator",
-    ":perfetto_include_perfetto_profiling_symbolizer",
+    ":perfetto_include_perfetto_profiling_pprof_builder",
     ":perfetto_include_perfetto_protozero_protozero",
     ":perfetto_include_perfetto_trace_processor_basic_types",
     ":perfetto_include_perfetto_trace_processor_storage",
@@ -6908,8 +6929,10 @@
     ":perfetto_protos_third_party_pprof_zero_gen",
     ":perfetto_src_base_base",
     ":perfetto_src_profiling_deobfuscator",
+    ":perfetto_src_profiling_symbolizer_symbolize_database",
+    ":perfetto_src_profiling_symbolizer_symbolizer",
     ":perfetto_src_protozero_protozero",
-    ":perfetto_src_trace_processor_common",
+    ":perfetto_src_trace_processor_containers_containers",
     ":perfetto_src_trace_processor_db_lib",
     ":perfetto_src_trace_processor_descriptors",
     ":perfetto_src_trace_processor_lib",
@@ -6920,9 +6943,7 @@
     ":perfetto_src_trace_processor_tables_tables",
     ":perfetto_tools_trace_to_text_common",
     ":perfetto_tools_trace_to_text_full",
-    ":perfetto_tools_trace_to_text_local_symbolizer",
     ":perfetto_tools_trace_to_text_pprofbuilder",
-    ":perfetto_tools_trace_to_text_symbolizer",
     ":perfetto_tools_trace_to_text_utils",
   ],
   shared_libs: [
@@ -7047,6 +7068,7 @@
     ":perfetto_src_ipc_ipc",
     ":perfetto_src_profiling_perf_producer",
     ":perfetto_src_profiling_perf_traced_perf_main",
+    ":perfetto_src_profiling_perf_unwind_support",
     ":perfetto_src_protozero_protozero",
     ":perfetto_src_tracing_common",
     ":perfetto_src_tracing_ipc",
@@ -7054,7 +7076,10 @@
     "src/profiling/perf/main.cc",
   ],
   shared_libs: [
+    "libbase",
     "liblog",
+    "libprocinfo",
+    "libunwindstack",
   ],
   generated_headers: [
     "perfetto_protos_perfetto_common_cpp_gen_headers",
@@ -7102,6 +7127,9 @@
     "-DGOOGLE_PROTOBUF_NO_RTTI",
     "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
   ],
+  include_dirs: [
+    "bionic/libc/kernel",
+  ],
 }
 
 // GN: //src/traced/probes:traced_probes
diff --git a/BUILD b/BUILD
index 1dde88e..4d01126 100644
--- a/BUILD
+++ b/BUILD
@@ -332,12 +332,11 @@
     ],
 )
 
-# GN target: //include/perfetto/profiling:symbolizer
+# GN target: //include/perfetto/profiling:pprof_builder
 filegroup(
-    name = "include_perfetto_profiling_symbolizer",
+    name = "include_perfetto_profiling_pprof_builder",
     srcs = [
         "include/perfetto/profiling/pprof_builder.h",
-        "include/perfetto/profiling/symbolizer.h",
     ],
 )
 
@@ -547,6 +546,26 @@
     ],
 )
 
+# GN target: //src/profiling/symbolizer:symbolize_database
+filegroup(
+    name = "src_profiling_symbolizer_symbolize_database",
+    srcs = [
+        "src/profiling/symbolizer/symbolize_database.cc",
+        "src/profiling/symbolizer/symbolize_database.h",
+    ],
+)
+
+# GN target: //src/profiling/symbolizer:symbolizer
+filegroup(
+    name = "src_profiling_symbolizer_symbolizer",
+    srcs = [
+        "src/profiling/symbolizer/local_symbolizer.cc",
+        "src/profiling/symbolizer/local_symbolizer.h",
+        "src/profiling/symbolizer/symbolizer.cc",
+        "src/profiling/symbolizer/symbolizer.h",
+    ],
+)
+
 # GN target: //src/profiling:deobfuscator
 filegroup(
     name = "src_profiling_deobfuscator",
@@ -572,20 +591,30 @@
     ],
 )
 
+# GN target: //src/trace_processor/containers:containers
+filegroup(
+    name = "src_trace_processor_containers_containers",
+    srcs = [
+        "src/trace_processor/containers/bit_vector.cc",
+        "src/trace_processor/containers/bit_vector.h",
+        "src/trace_processor/containers/bit_vector_iterators.cc",
+        "src/trace_processor/containers/bit_vector_iterators.h",
+        "src/trace_processor/containers/null_term_string_view.h",
+        "src/trace_processor/containers/row_map.cc",
+        "src/trace_processor/containers/row_map.h",
+        "src/trace_processor/containers/sparse_vector.h",
+        "src/trace_processor/containers/string_pool.cc",
+        "src/trace_processor/containers/string_pool.h",
+    ],
+)
+
 # GN target: //src/trace_processor/db:lib
 filegroup(
     name = "src_trace_processor_db_lib",
     srcs = [
-        "src/trace_processor/db/bit_vector.cc",
-        "src/trace_processor/db/bit_vector.h",
-        "src/trace_processor/db/bit_vector_iterators.cc",
-        "src/trace_processor/db/bit_vector_iterators.h",
         "src/trace_processor/db/column.cc",
         "src/trace_processor/db/column.h",
         "src/trace_processor/db/compare.h",
-        "src/trace_processor/db/row_map.cc",
-        "src/trace_processor/db/row_map.h",
-        "src/trace_processor/db/sparse_vector.h",
         "src/trace_processor/db/table.cc",
         "src/trace_processor/db/table.h",
         "src/trace_processor/db/typed_column.h",
@@ -688,16 +717,6 @@
     ],
 )
 
-# GN target: //src/trace_processor:common
-filegroup(
-    name = "src_trace_processor_common",
-    srcs = [
-        "src/trace_processor/null_term_string_view.h",
-        "src/trace_processor/string_pool.cc",
-        "src/trace_processor/string_pool.h",
-    ],
-)
-
 # GN target: //src/trace_processor:descriptors
 filegroup(
     name = "src_trace_processor_descriptors",
@@ -730,8 +749,6 @@
         "src/trace_processor/filtered_row_index.h",
         "src/trace_processor/gfp_flags.cc",
         "src/trace_processor/gfp_flags.h",
-        "src/trace_processor/heap_profile_allocation_table.cc",
-        "src/trace_processor/heap_profile_allocation_table.h",
         "src/trace_processor/instants_table.cc",
         "src/trace_processor/instants_table.h",
         "src/trace_processor/metadata_table.cc",
@@ -775,10 +792,24 @@
 filegroup(
     name = "src_trace_processor_storage_full",
     srcs = [
+        "src/trace_processor/importers/proto/android_probes_module.cc",
+        "src/trace_processor/importers/proto/android_probes_module.h",
+        "src/trace_processor/importers/proto/android_probes_parser.cc",
+        "src/trace_processor/importers/proto/android_probes_parser.h",
         "src/trace_processor/importers/proto/graphics_event_module.cc",
         "src/trace_processor/importers/proto/graphics_event_module.h",
         "src/trace_processor/importers/proto/graphics_event_parser.cc",
         "src/trace_processor/importers/proto/graphics_event_parser.h",
+        "src/trace_processor/importers/proto/heap_graph_module.cc",
+        "src/trace_processor/importers/proto/heap_graph_module.h",
+        "src/trace_processor/importers/proto/heap_graph_tracker.cc",
+        "src/trace_processor/importers/proto/heap_graph_tracker.h",
+        "src/trace_processor/importers/proto/heap_graph_walker.cc",
+        "src/trace_processor/importers/proto/heap_graph_walker.h",
+        "src/trace_processor/importers/proto/system_probes_module.cc",
+        "src/trace_processor/importers/proto/system_probes_module.h",
+        "src/trace_processor/importers/proto/system_probes_parser.cc",
+        "src/trace_processor/importers/proto/system_probes_parser.h",
         "src/trace_processor/importers/proto/vulkan_memory_tracker.cc",
         "src/trace_processor/importers/proto/vulkan_memory_tracker.h",
         "src/trace_processor/register_additional_modules.cc",
@@ -792,11 +823,11 @@
     srcs = [
         "src/trace_processor/args_tracker.cc",
         "src/trace_processor/args_tracker.h",
-        "src/trace_processor/binder_tracker.cc",
-        "src/trace_processor/binder_tracker.h",
         "src/trace_processor/chunked_trace_reader.h",
         "src/trace_processor/clock_tracker.cc",
         "src/trace_processor/clock_tracker.h",
+        "src/trace_processor/destructible.cc",
+        "src/trace_processor/destructible.h",
         "src/trace_processor/event_tracker.cc",
         "src/trace_processor/event_tracker.h",
         "src/trace_processor/forwarding_trace_parser.cc",
@@ -807,6 +838,8 @@
         "src/trace_processor/gzip_trace_parser.h",
         "src/trace_processor/heap_profile_tracker.cc",
         "src/trace_processor/heap_profile_tracker.h",
+        "src/trace_processor/importers/ftrace/binder_tracker.cc",
+        "src/trace_processor/importers/ftrace/binder_tracker.h",
         "src/trace_processor/importers/ftrace/ftrace_descriptors.cc",
         "src/trace_processor/importers/ftrace/ftrace_descriptors.h",
         "src/trace_processor/importers/ftrace/ftrace_module.cc",
@@ -832,19 +865,9 @@
         "src/trace_processor/importers/json/json_trace_tokenizer.h",
         "src/trace_processor/importers/json/json_trace_utils.cc",
         "src/trace_processor/importers/json/json_trace_utils.h",
-        "src/trace_processor/importers/proto/android_probes_module.cc",
-        "src/trace_processor/importers/proto/android_probes_module.h",
-        "src/trace_processor/importers/proto/android_probes_parser.cc",
-        "src/trace_processor/importers/proto/android_probes_parser.h",
         "src/trace_processor/importers/proto/args_table_utils.cc",
         "src/trace_processor/importers/proto/args_table_utils.h",
         "src/trace_processor/importers/proto/chrome_compositor_scheduler_state.descriptor.h",
-        "src/trace_processor/importers/proto/heap_graph_module.cc",
-        "src/trace_processor/importers/proto/heap_graph_module.h",
-        "src/trace_processor/importers/proto/heap_graph_tracker.cc",
-        "src/trace_processor/importers/proto/heap_graph_tracker.h",
-        "src/trace_processor/importers/proto/heap_graph_walker.cc",
-        "src/trace_processor/importers/proto/heap_graph_walker.h",
         "src/trace_processor/importers/proto/packet_sequence_state.h",
         "src/trace_processor/importers/proto/proto_importer_module.cc",
         "src/trace_processor/importers/proto/proto_importer_module.h",
@@ -853,10 +876,6 @@
         "src/trace_processor/importers/proto/proto_trace_parser.h",
         "src/trace_processor/importers/proto/proto_trace_tokenizer.cc",
         "src/trace_processor/importers/proto/proto_trace_tokenizer.h",
-        "src/trace_processor/importers/proto/system_probes_module.cc",
-        "src/trace_processor/importers/proto/system_probes_module.h",
-        "src/trace_processor/importers/proto/system_probes_parser.cc",
-        "src/trace_processor/importers/proto/system_probes_parser.h",
         "src/trace_processor/importers/proto/track_event_module.cc",
         "src/trace_processor/importers/proto/track_event_module.h",
         "src/trace_processor/importers/proto/track_event_parser.cc",
@@ -1177,15 +1196,6 @@
     ],
 )
 
-# GN target: //tools/trace_to_text:local_symbolizer
-filegroup(
-    name = "tools_trace_to_text_local_symbolizer",
-    srcs = [
-        "tools/trace_to_text/local_symbolizer.cc",
-        "tools/trace_to_text/local_symbolizer.h",
-    ],
-)
-
 # GN target: //tools/trace_to_text:pprofbuilder
 filegroup(
     name = "tools_trace_to_text_pprofbuilder",
@@ -1194,14 +1204,6 @@
     ],
 )
 
-# GN target: //tools/trace_to_text:symbolizer
-filegroup(
-    name = "tools_trace_to_text_symbolizer",
-    srcs = [
-        "tools/trace_to_text/symbolizer.cc",
-    ],
-)
-
 # GN target: //tools/trace_to_text:utils
 filegroup(
     name = "tools_trace_to_text_utils",
@@ -2518,7 +2520,7 @@
     srcs = [
         ":src_base_base",
         ":src_protozero_protozero",
-        ":src_trace_processor_common",
+        ":src_trace_processor_containers_containers",
         ":src_trace_processor_db_lib",
         ":src_trace_processor_descriptors",
         ":src_trace_processor_export_json",
@@ -2596,7 +2598,7 @@
         ":src_base_base",
         ":src_base_unix_socket",
         ":src_protozero_protozero",
-        ":src_trace_processor_common",
+        ":src_trace_processor_containers_containers",
         ":src_trace_processor_db_lib",
         ":src_trace_processor_descriptors",
         ":src_trace_processor_export_json",
@@ -2686,15 +2688,16 @@
     name = "libpprofbuilder",
     srcs = [
         ":src_profiling_deobfuscator",
+        ":src_profiling_symbolizer_symbolize_database",
+        ":src_profiling_symbolizer_symbolizer",
         ":tools_trace_to_text_pprofbuilder",
-        ":tools_trace_to_text_symbolizer",
         ":tools_trace_to_text_utils",
     ],
     hdrs = [
         ":include_perfetto_base_base",
         ":include_perfetto_ext_base_base",
         ":include_perfetto_profiling_deobfuscator",
-        ":include_perfetto_profiling_symbolizer",
+        ":include_perfetto_profiling_pprof_builder",
         ":include_perfetto_protozero_protozero",
         ":include_perfetto_trace_processor_basic_types",
         ":include_perfetto_trace_processor_storage",
@@ -2741,15 +2744,17 @@
         ":include_perfetto_ext_trace_processor_export_json",
         ":include_perfetto_ext_traced_sys_stats_counters",
         ":include_perfetto_profiling_deobfuscator",
-        ":include_perfetto_profiling_symbolizer",
+        ":include_perfetto_profiling_pprof_builder",
         ":include_perfetto_protozero_protozero",
         ":include_perfetto_trace_processor_basic_types",
         ":include_perfetto_trace_processor_storage",
         ":include_perfetto_trace_processor_trace_processor",
         ":src_base_base",
         ":src_profiling_deobfuscator",
+        ":src_profiling_symbolizer_symbolize_database",
+        ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_protozero",
-        ":src_trace_processor_common",
+        ":src_trace_processor_containers_containers",
         ":src_trace_processor_db_lib",
         ":src_trace_processor_descriptors",
         ":src_trace_processor_export_json",
@@ -2761,9 +2766,7 @@
         ":src_trace_processor_tables_tables",
         ":tools_trace_to_text_common",
         ":tools_trace_to_text_full",
-        ":tools_trace_to_text_local_symbolizer",
         ":tools_trace_to_text_pprofbuilder",
-        ":tools_trace_to_text_symbolizer",
         ":tools_trace_to_text_utils",
     ],
     visibility = [
diff --git a/BUILD.gn b/BUILD.gn
index 30f269d..4fdd937 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -82,6 +82,17 @@
   ]
 }
 
+if (enable_perfetto_trace_processor_json) {
+  executable("trace_processor_minimal_smoke_tests") {
+    testonly = true
+    deps = [
+      "gn:default_deps",
+      "src/trace_processor:storage_minimal_smoke_tests",
+    ]
+  }
+  all_targets += [ ":trace_processor_minimal_smoke_tests" ]
+}
+
 if (enable_perfetto_benchmarks) {
   import("gn/perfetto_benchmarks.gni")
   executable("perfetto_benchmarks") {
diff --git a/docs/heapprofd.md b/docs/heapprofd.md
index 9096993..50ae55f 100644
--- a/docs/heapprofd.md
+++ b/docs/heapprofd.md
@@ -47,9 +47,6 @@
   moment the dump was created.
 * **alloc\_space**: how many bytes were allocated (including ones freed at the
   moment of the dump) at this callstack
-* **idle\_space**: if [idle page tracking](#idle-page-tracking) is being used,
-  the number of bytes that were allocated at this callstack and are on pages
-  that have not been touched since the last dump.
 * **objects**: how many allocations without matching frees were done at this
   callstack.
 * **alloc\_objects**: how many allocations (including ones with matching frees)
@@ -179,45 +176,6 @@
 
 You can save the symbolized version by issuing the `proto` command in pprof.
 
-## Idle page tracking
-This is only available in Android versions newer than 10.
-
-Idle page tracking allows you to analyze which allocations made by your
-program are being used by a workload. This can be useful for finding leaks
-as well as unused cached values.
-
-**Do not follow these instructions on devices containing valuable data.**
-They require you turn off SELinux on your device, significantly lowering
-your device's security level.
-
-Use the following command to profile the next startup of your program with idle
-tracking enabled.
-
-1. `$ adb root`
-2. `$ tools/heap_profile -n ${NAME} --no-running --disable-selinux
---idle-allocations`
-
-Then run the following commands in a separate shell.
-
-1. `$ adb shell killall ${ROOT}` to restart your program.
-2. Wait for your program to finish starting.
-3. `adb shell killall -USR1 heapprofd` to trigger the first dump (see
-[Manual Dumping](#manual-dumping) above). This will mark all allocations as
-idle.
-4. Interact with your program.
-
-Once you are done interacting, `Ctrl-C` the invokation of
-`tools/heap_profile`, and upload the `heap_dump.2.*.pb.gz` file to pprof.
-You can then see the memory that was idle in the `idle_space` tab.
-
-This will show allocations that are on pages that have not been touched since
-the last dump. Small allocations that are not touched might not show up, as
-they might share a page with an allocation that was.
-
-If heapprofd is operating in sampling mode (i.e. `--interval` is larger than 1),
-the values in `idle_space` will not correct for the sampling, so they are not
-comparable to values in `space` and `alloc_space`, which do.
-
 ## Troubleshooting
 
 ### Buffer overrun
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index ab87bc9..4e86d3b 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -84,9 +84,6 @@
     "PERFETTO_TP_METRICS=$enable_perfetto_trace_processor_metrics",
     "PERFETTO_TP_FTRACE=$enable_perfetto_trace_processor_ftrace",
     "PERFETTO_TP_HTTPD=$perfetto_tp_httpd",
-    "PERFETTO_TP_SYSTEM_PROBES=$enable_perfetto_trace_processor_system_probes",
-    "PERFETTO_TP_ANDROID_PROBES=$enable_perfetto_trace_processor_android_probes",
-    "PERFETTO_TP_HEAP_GRAPHS=$enable_perfetto_trace_processor_heap_graphs",
     "PERFETTO_TP_JSON=$enable_perfetto_trace_processor_json",
     "PERFETTO_TP_JSON_IMPORT=$enable_perfetto_trace_processor_json_import",
     "PERFETTO_TP_FUCHSIA=$enable_perfetto_trace_processor_fuchsia",
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index 4aba7e1..cc1232f 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -149,9 +149,7 @@
 
   # Build the perf event profiler (traced_perf).
   # TODO(b/144281346): under development.
-  enable_perfetto_traced_perf =
-      perfetto_build_with_android ||
-      (perfetto_build_standalone && (is_linux || is_android))
+  enable_perfetto_traced_perf = perfetto_build_with_android
 
   # The Trace Processor: offline analytical engine to process traces and compute
   # metrics using a SQL engine.
@@ -219,18 +217,6 @@
   enable_perfetto_trace_processor_ftrace =
       enable_perfetto_trace_processor && !(build_with_chromium && is_android)
 
-  # Enables parsing support for system probes in trace processor.
-  enable_perfetto_trace_processor_system_probes =
-      enable_perfetto_trace_processor && !(build_with_chromium && is_android)
-
-  # Enables parsing support for android system probes in trace processor.
-  enable_perfetto_trace_processor_android_probes =
-      enable_perfetto_trace_processor && !(build_with_chromium && is_android)
-
-  # Enables parsing support for heap graphs in trace processor.
-  enable_perfetto_trace_processor_heap_graphs =
-      enable_perfetto_trace_processor && !(build_with_chromium && is_android)
-
   # Enables JSON support in the trace processor. Required for JSON trace import
   # and export. Importer support can also be disabled using
   # |enable_perfetto_trace_processor_json_import|.
@@ -256,9 +242,7 @@
   # Enables syscall support in trace processor. Required for ftrace, system
   # probes, and android probes support.
   enable_perfetto_trace_processor_syscalls =
-      enable_perfetto_trace_processor_ftrace ||
-      enable_perfetto_trace_processor_system_probes ||
-      enable_perfetto_trace_processor_android_probes
+      enable_perfetto_trace_processor_ftrace
 
   # Enables metrics support in the trace processor, which require SQL support.
   enable_perfetto_trace_processor_metrics =
@@ -318,6 +302,4 @@
 
 # Syscall support is required for ftrace, system probes, and android probes.
 assert(enable_perfetto_trace_processor_syscalls ||
-       (!enable_perfetto_trace_processor_ftrace &&
-        !enable_perfetto_trace_processor_system_probes &&
-        !enable_perfetto_trace_processor_android_probes))
+       !enable_perfetto_trace_processor_ftrace)
diff --git a/gn/perfetto_benchmarks.gni b/gn/perfetto_benchmarks.gni
index 4b0f331..122c6ff 100644
--- a/gn/perfetto_benchmarks.gni
+++ b/gn/perfetto_benchmarks.gni
@@ -18,7 +18,7 @@
   "gn:default_deps",
   "src/base:benchmarks",
   "src/traced/probes/ftrace:benchmarks",
-  "src/trace_processor/db:benchmarks",
+  "src/trace_processor/containers:benchmarks",
   "src/trace_processor/tables:benchmarks",
   "src/tracing:benchmarks",
   "test:benchmark_main",
diff --git a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
index 41c3c05..e9cae4f 100644
--- a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
@@ -38,9 +38,6 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_METRICS() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_FTRACE() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HTTPD() (0)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_SYSTEM_PROBES() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_ANDROID_PROBES() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HEAP_GRAPHS() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON_IMPORT() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_FUCHSIA() (1)
diff --git a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
index 04c4e7d..baa1056 100644
--- a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
@@ -38,9 +38,6 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_METRICS() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_FTRACE() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HTTPD() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX())
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_SYSTEM_PROBES() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_ANDROID_PROBES() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HEAP_GRAPHS() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON_IMPORT() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_FUCHSIA() (1)
diff --git a/include/perfetto/profiling/BUILD.gn b/include/perfetto/profiling/BUILD.gn
index 03f8350..ce7c188 100644
--- a/include/perfetto/profiling/BUILD.gn
+++ b/include/perfetto/profiling/BUILD.gn
@@ -12,13 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# TODO(135923303): as part of fixing b/135923303, symbolizer.h will be removed
-# and namespacing of pprof_builder.h will be changed so do not depend on this
-# target.
-source_set("symbolizer") {
+source_set("pprof_builder") {
   sources = [
     "pprof_builder.h",
-    "symbolizer.h",
   ]
 }
 
diff --git a/include/perfetto/profiling/pprof_builder.h b/include/perfetto/profiling/pprof_builder.h
index 4227a8c..7e26f51 100644
--- a/include/perfetto/profiling/pprof_builder.h
+++ b/include/perfetto/profiling/pprof_builder.h
@@ -27,9 +27,11 @@
 class TraceProcessor;
 }
 
-namespace trace_to_text {
-
+namespace profiling {
 class Symbolizer;
+}
+
+namespace trace_to_text {
 
 struct SerializedProfile {
   uint64_t pid;
@@ -38,13 +40,13 @@
 
 bool TraceToPprof(trace_processor::TraceProcessor*,
                   std::vector<SerializedProfile>* output,
-                  Symbolizer* symbolizer,
+                  profiling::Symbolizer* symbolizer,
                   uint64_t pid = 0,
                   const std::vector<uint64_t>& timestamps = {});
 
 bool TraceToPprof(std::istream* input,
                   std::vector<SerializedProfile>* output,
-                  Symbolizer* symbolizer,
+                  profiling::Symbolizer* symbolizer,
                   uint64_t pid = 0,
                   const std::vector<uint64_t>& timestamps = {});
 
diff --git a/src/profiling/perf/BUILD.gn b/src/profiling/perf/BUILD.gn
index dba1ca2..b864674 100644
--- a/src/profiling/perf/BUILD.gn
+++ b/src/profiling/perf/BUILD.gn
@@ -18,6 +18,9 @@
 
 assert(enable_perfetto_traced_perf)
 
+# TODO(rsavitski): only building in-tree at the moment (so this build file is
+# only used for gen_android_bp, expect bitrot).
+
 executable("traced_perf") {
   deps = [
     ":traced_perf_main",
@@ -43,6 +46,7 @@
 
 source_set("producer") {
   deps = [
+    ":unwind_support",
     "../../../gn:default_deps",
     "../../../protos/perfetto/config:cpp",
     "../../../protos/perfetto/config/profiling:zero",
@@ -63,6 +67,17 @@
   ]
 }
 
+source_set("unwind_support") {
+  deps = [
+    "../../../gn:default_deps",
+    "../../../gn:libunwindstack",
+    "../../../src/base",
+  ]
+  sources = [
+    "unwind_support.cc",
+    "unwind_support.h",
+  ]
+}
 source_set("producer_unittests") {
   testonly = true
   deps = [
diff --git a/src/profiling/perf/event_config.h b/src/profiling/perf/event_config.h
index da53a0b..cc750ff 100644
--- a/src/profiling/perf/event_config.h
+++ b/src/profiling/perf/event_config.h
@@ -23,6 +23,7 @@
 
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/tracing/core/data_source_config.h"
+#include "src/profiling/perf/unwind_support.h"
 
 #include "protos/perfetto/config/profiling/perf_event_config.pbzero.h"
 
@@ -42,21 +43,17 @@
     protos::pbzero::PerfEventConfig::Decoder pb_config(
         ds_config.perf_event_config_raw());
 
-    if (!pb_config.has_tid())
-      return base::nullopt;
-
     return EventConfig(pb_config);
   }
 
-  int32_t target_tid() const { return target_tid_; }
+  uint32_t target_cpu() const { return target_cpu_; }
 
   perf_event_attr* perf_attr() const {
     return const_cast<perf_event_attr*>(&perf_event_attr_);
   }
 
  private:
-  EventConfig(const protos::pbzero::PerfEventConfig::Decoder& pb_config)
-      : target_tid_(pb_config.tid()) {
+  EventConfig(const protos::pbzero::PerfEventConfig::Decoder&) {
     auto& pe = perf_event_attr_;
     memset(&pe, 0, sizeof(perf_event_attr));
     pe.size = sizeof(perf_event_attr);
@@ -70,18 +67,20 @@
     pe.sample_freq = 100;
     pe.freq = true;
 
-    pe.sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_STACK_USER;
+    pe.sample_type =
+        PERF_SAMPLE_TID | PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER;
     // Needs to be < ((u16)(~0u)), and have bottom 8 bits clear.
     pe.sample_stack_user = (1u << 15);
-
-    // Note: can't use inherit with task-scoped event mmap
-    pe.inherit = false;
+    pe.sample_regs_user = PerfUserRegsMaskForCurrentArch();
   }
 
-  // TODO(rsavitski): this will have to represent entire event groups, thus this
-  // class will represent N events. So we'll need N cpus/tids, but likely still
-  // a single perf_event_attr.
-  int32_t target_tid_ = 0;
+  // TODO(rsavitski): for now hardcode each session to be for a single cpu's
+  // scope. In general a config will correspond to N cpus and/or tids.
+  uint32_t target_cpu_ = 0;
+
+  // TODO(rsavitski): if we allow for event groups containing multiple sampled
+  // counters, we'll need to vary the .type & .config fields per
+  // perf_event_open.
   perf_event_attr perf_event_attr_;
 };
 
diff --git a/src/profiling/perf/event_config_unittest.cc b/src/profiling/perf/event_config_unittest.cc
index 6598eb4..0d7fa52 100644
--- a/src/profiling/perf/event_config_unittest.cc
+++ b/src/profiling/perf/event_config_unittest.cc
@@ -32,9 +32,8 @@
 namespace profiling {
 namespace {
 
-static DataSourceConfig ConfigForTid(int32_t tid) {
+static DataSourceConfig CreateEmptyConfig() {
   protozero::HeapBuffered<protos::pbzero::PerfEventConfig> pb_config;
-  pb_config->set_tid(tid);
   protozero::HeapBuffered<protos::pbzero::DataSourceConfig> ds_config;
   ds_config->set_perf_event_config_raw(pb_config.SerializeAsString());
   DataSourceConfig cfg;
@@ -42,17 +41,8 @@
   return cfg;
 }
 
-TEST(EventConfigTest, TidRequired) {
-  // Doesn't pass validation without a TID
-  DataSourceConfig cfg;
-  ASSERT_TRUE(cfg.ParseFromString(""));
-
-  base::Optional<EventConfig> event_config = EventConfig::Create(cfg);
-  ASSERT_FALSE(event_config.has_value());
-}
-
 TEST(EventConfigTest, AttrStructConstructed) {
-  auto cfg = ConfigForTid(42);
+  auto cfg = CreateEmptyConfig();
   base::Optional<EventConfig> event_config = EventConfig::Create(cfg);
 
   ASSERT_TRUE(event_config.has_value());
diff --git a/src/profiling/perf/event_reader.cc b/src/profiling/perf/event_reader.cc
index 7bc5ccf..a2d0e87 100644
--- a/src/profiling/perf/event_reader.cc
+++ b/src/profiling/perf/event_reader.cc
@@ -23,6 +23,7 @@
 #include <unistd.h>
 
 #include "perfetto/ext/base/utils.h"
+#include "src/profiling/perf/unwind_support.h"
 
 namespace perfetto {
 namespace profiling {
@@ -48,10 +49,15 @@
       syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags));
 }
 
+// TODO(rsavitski): one EventConfig will correspond to N perf_event_open calls
+// in the general case. Does it make sense to keep a single function which does
+// the N calls, and then returns the group leader's fd? What about cases where
+// we have >1 pid or >1 cpu to open for? Should the entire EventReader be
+// cpu-scoped?
 base::ScopedFile PerfEventOpen(const EventConfig& event_cfg) {
   base::ScopedFile perf_fd{
-      perf_event_open(event_cfg.perf_attr(), event_cfg.target_tid(),
-                      /*cpu=*/-1, /*group_fd=*/-1, PERF_FLAG_FD_CLOEXEC)};
+      perf_event_open(event_cfg.perf_attr(), /*pid=*/-1, event_cfg.target_cpu(),
+                      /*group_fd=*/-1, PERF_FLAG_FD_CLOEXEC)};
   return perf_fd;
 }
 
@@ -195,7 +201,7 @@
 void EventReader::ParseNextSampleBatch() {
   std::vector<char> data = ring_buffer_.ReadAvailable();
   if (data.size() == 0) {
-    PERFETTO_LOG("WIP: no samples");
+    PERFETTO_LOG("no samples (work in progress)");
     return;
   }
 
@@ -217,7 +223,7 @@
   if (event_hdr->type == PERF_RECORD_SAMPLE) {
     ParsePerfRecordSample(sample_start, event_hdr->size);
   } else {
-    PERFETTO_ELOG("WIP: unsupported event type");
+    PERFETTO_ELOG("Unsupported event type (work in progress)");
   }
 
   *ptr = sample_start + event_hdr->size;
@@ -229,9 +235,9 @@
                                         size_t sample_size) {
   const perf_event_attr* cfg = event_cfg_.perf_attr();
 
-  if (cfg->sample_type &
-      (~uint64_t(PERF_SAMPLE_TID | PERF_SAMPLE_STACK_USER))) {
-    PERFETTO_ELOG("WIP: unsupported sampling option.");
+  if (cfg->sample_type & (~uint64_t(PERF_SAMPLE_TID | PERF_SAMPLE_STACK_USER |
+                                    PERF_SAMPLE_REGS_USER))) {
+    PERFETTO_ELOG("Unsupported sampling option (work in progress)");
     return;
   }
 
@@ -249,6 +255,16 @@
     PERFETTO_LOG("tid: %" PRIu32 "", tid);
   }
 
+  if (cfg->sample_type & PERF_SAMPLE_REGS_USER) {
+    auto parsed_regs = ReadPerfUserRegsData(&parse_pos);
+
+    if (parsed_regs) {
+      parsed_regs->IterateRegisters([](const char* name, uint64_t value) {
+        PERFETTO_LOG("reg[%s]: %" PRIx64 "", name, value);
+      });
+    }
+  }
+
   if (cfg->sample_type & PERF_SAMPLE_STACK_USER) {
     uint64_t max_stack_size;  // the requested size
     parse_pos = ReadValue(&max_stack_size, parse_pos);
diff --git a/src/profiling/perf/event_reader.h b/src/profiling/perf/event_reader.h
index 537f489..fa9b96b 100644
--- a/src/profiling/perf/event_reader.h
+++ b/src/profiling/perf/event_reader.h
@@ -86,7 +86,7 @@
   EventReader(EventReader&&) noexcept;
   EventReader& operator=(EventReader&&) noexcept;
 
-  // TODO(rsavitski): temporary.
+  // TODO(rsavitski): temporary one-shot parser for development purposes.
   void ParseNextSampleBatch();
 
  private:
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index dd5d642..e544f86 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -66,20 +66,6 @@
     return;
   }
 
-  std::string maps_path = std::string("/proc/") +
-                          std::to_string(event_config->target_tid()) +
-                          std::string("/maps");
-  auto maps_fd = base::OpenFile(maps_path, O_RDONLY);
-  if (!maps_fd)
-    PERFETTO_PLOG("failed /proc/pid/maps open (proceeding)");
-
-  std::string mem_path = std::string("/proc/") +
-                         std::to_string(event_config->target_tid()) +
-                         std::string("/mem");
-  auto mem_fd = base::OpenFile(mem_path, O_RDONLY);
-  if (!mem_fd)
-    PERFETTO_PLOG("failed /proc/pid/mem open (proceeding)");
-
   base::Optional<EventReader> event_reader =
       EventReader::ConfigureEvents(event_config.value());
   if (!event_reader.has_value()) {
@@ -90,8 +76,7 @@
   // Build the DataSource instance.
   auto it_inserted = data_sources_.emplace(
       std::piecewise_construct, std::forward_as_tuple(instance_id),
-      std::forward_as_tuple(std::move(event_reader.value()), std::move(maps_fd),
-                            std::move(mem_fd)));
+      std::forward_as_tuple(std::move(event_reader.value())));
 
   PERFETTO_DCHECK(it_inserted.second);
 }
diff --git a/src/profiling/perf/perf_producer.h b/src/profiling/perf/perf_producer.h
index d028c75..e373836 100644
--- a/src/profiling/perf/perf_producer.h
+++ b/src/profiling/perf/perf_producer.h
@@ -66,22 +66,13 @@
     kConnected,
   };
 
-  // TODO(rsavitski): proc-fds need to live elsewhere, as they can be shared
-  // across data sources. We might also have arbitrarily many tasks handled
-  // by one data source (if scoping events to a cpu).
   struct DataSource {
-    DataSource(EventReader _event_reader,
-               base::ScopedFile _maps_fd,
-               base::ScopedFile _mem_fd)
-        : event_reader(std::move(_event_reader)),
-          maps_fd(std::move(_maps_fd)),
-          mem_fd(std::move(_mem_fd)) {}
+    DataSource(EventReader _event_reader)
+        : event_reader(std::move(_event_reader)) {}
 
+    // TODO(rsavitski): current thinking is an EventReader per cpu-scoped ring
+    // buffer. And a central bookkeeper.
     EventReader event_reader;
-
-    // note: currently populated, but unused.
-    base::ScopedFile maps_fd;
-    base::ScopedFile mem_fd;
   };
 
   void ConnectService();
diff --git a/src/profiling/perf/unwind_support.cc b/src/profiling/perf/unwind_support.cc
new file mode 100644
index 0000000..47dc3c0
--- /dev/null
+++ b/src/profiling/perf/unwind_support.cc
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/perf/unwind_support.h"
+
+#include <inttypes.h>
+#include <linux/perf_event.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <memory>
+
+#include <unwindstack/Elf.h>
+#include <unwindstack/MachineArm.h>
+#include <unwindstack/MachineArm64.h>
+#include <unwindstack/Regs.h>
+#include <unwindstack/RegsArm.h>
+#include <unwindstack/RegsArm64.h>
+#include <unwindstack/UserArm.h>
+#include <unwindstack/UserArm64.h>
+
+// TODO(rsavitski): this includes the kernel uapi constant definitions (for
+// register sampling). For now hardcoded for in-tree builds (specifically,
+// bionic/include/kernel/). Standalone builds will need to source the headers
+// from elsewhere (without depending on the host machine's system headers).
+#include <uapi/asm-arm/asm/perf_regs.h>
+#include <uapi/asm-x86/asm/perf_regs.h>
+#define perf_event_arm_regs perf_event_arm64_regs
+#include <uapi/asm-arm64/asm/perf_regs.h>
+#undef perf_event_arm_regs
+
+namespace perfetto {
+namespace profiling {
+
+namespace {
+
+template <typename T>
+const char* ReadValue(T* value_out, const char* ptr) {
+  memcpy(value_out, reinterpret_cast<const void*>(ptr), sizeof(T));
+  return ptr + sizeof(T);
+}
+
+// Supported configurations:
+// * 32 bit daemon, 32 bit userspace
+// * 64 bit daemon, mixed bitness userspace
+// Therefore give the kernel the mask corresponding to our build architecture.
+// Register parsing handles the mixed userspace ABI cases.
+// TODO(rsavitski): cleanly detect 32 bit builds being side-loaded onto a system
+// with 64 bit userspace processes.
+uint64_t PerfUserRegsMask(unwindstack::ArchEnum arch) {
+  // TODO(rsavitski): support the rest of the architectures.
+  switch (arch) {
+    case unwindstack::ARCH_ARM64:
+      return (1ULL << PERF_REG_ARM64_MAX) - 1;
+    case unwindstack::ARCH_ARM:
+      return ((1ULL << PERF_REG_ARM_MAX) - 1);
+    default:
+      PERFETTO_FATAL("Unsupported architecture (work in progress)");
+  }
+}
+
+// Adjusts the given architecture enum based on the ABI (as recorded in the perf
+// sample). Note: we do not support 64 bit samples on a 32 bit daemon build, so
+// this only converts from 64 bit to 32 bit architectures.
+unwindstack::ArchEnum ArchForAbi(unwindstack::ArchEnum arch, uint64_t abi) {
+  if (arch == unwindstack::ARCH_ARM64 && abi == PERF_SAMPLE_REGS_ABI_32) {
+    return unwindstack::ARCH_ARM;
+  }
+  if (arch == unwindstack::ARCH_X86_64 && abi == PERF_SAMPLE_REGS_ABI_32) {
+    return unwindstack::ARCH_X86;
+  }
+  return arch;
+}
+
+// Register values as an array, indexed using the kernel uapi perf_events.h enum
+// values. Unsampled values will be left as zeroes.
+// TODO(rsavitski): support all relevant architectures (allocate enough space
+// for the widest register bank).
+struct RawRegisterData {
+  static constexpr uint64_t kMaxSize = PERF_REG_ARM64_MAX;
+  uint64_t regs[kMaxSize] = {};
+};
+
+std::unique_ptr<unwindstack::Regs> ToLibUnwindstackRegs(
+    const RawRegisterData& raw_regs,
+    unwindstack::ArchEnum arch) {
+  // First converts the |RawRegisterData| array to libunwindstack's raw register
+  // format, then constructs the relevant unwindstack::Regs subclass out of the
+  // latter.
+  if (arch == unwindstack::ARCH_ARM64) {
+    static_assert(static_cast<int>(unwindstack::ARM64_REG_R0) ==
+                      static_cast<int>(PERF_REG_ARM64_X0),
+                  "register layout mismatch");
+    static_assert(static_cast<int>(unwindstack::ARM64_REG_R30) ==
+                      static_cast<int>(PERF_REG_ARM64_LR),
+                  "register layout mismatch");
+
+    unwindstack::arm64_user_regs arm64_user_regs;
+    memset(&arm64_user_regs, 0, sizeof(arm64_user_regs));
+    memcpy(&arm64_user_regs.regs[unwindstack::ARM64_REG_R0],
+           &raw_regs.regs[PERF_REG_ARM64_X0],
+           sizeof(uint64_t) * (PERF_REG_ARM64_LR - PERF_REG_ARM64_X0 + 1));
+    arm64_user_regs.sp = raw_regs.regs[PERF_REG_ARM64_SP];
+    arm64_user_regs.pc = raw_regs.regs[PERF_REG_ARM64_PC];
+
+    return std::unique_ptr<unwindstack::Regs>(
+        unwindstack::RegsArm64::Read(&arm64_user_regs));
+  }
+
+  if (arch == unwindstack::ARCH_ARM) {
+    static_assert(static_cast<int>(unwindstack::ARM_REG_R0) ==
+                      static_cast<int>(PERF_REG_ARM_R0),
+                  "register layout mismatch");
+    static_assert(static_cast<int>(unwindstack::ARM_REG_LAST) ==
+                      static_cast<int>(PERF_REG_ARM_MAX),
+                  "register layout mismatch");
+
+    unwindstack::arm_user_regs arm_user_regs;
+    memset(&arm_user_regs, 0, sizeof(arm_user_regs));
+    for (size_t i = unwindstack::ARM_REG_R0; i < unwindstack::ARM_REG_LAST;
+         i++) {
+      arm_user_regs.regs[i] = static_cast<uint32_t>(raw_regs.regs[i]);
+    }
+
+    return std::unique_ptr<unwindstack::Regs>(
+        unwindstack::RegsArm::Read(&arm_user_regs));
+  }
+
+  PERFETTO_FATAL("Unsupported architecture (work in progress)");
+}
+
+}  // namespace
+
+uint64_t PerfUserRegsMaskForCurrentArch() {
+  return PerfUserRegsMask(unwindstack::Regs::CurrentArch());
+}
+
+// Assumes that the sampling was configured with
+// |PerfUserRegsMaskForCurrentArch|.
+std::unique_ptr<unwindstack::Regs> ReadPerfUserRegsData(const char** data) {
+  unwindstack::ArchEnum requested_arch = unwindstack::Regs::CurrentArch();
+
+  // Layout, assuming a sparse bitmask requesting r1 and r15:
+  // [u64 abi] [u64 r1] [u64 r15]
+  const char* parse_pos = *data;
+  uint64_t sampled_abi;
+  parse_pos = ReadValue(&sampled_abi, parse_pos);
+  PERFETTO_LOG("WIP: abi: %" PRIu64 "", sampled_abi);
+
+  // Unpack the densely-packed register values into |RawRegisterData|, which has
+  // a value for every register (unsampled registers will be left at zero).
+  RawRegisterData raw_regs{};
+  uint64_t regs_mask = PerfUserRegsMaskForCurrentArch();
+  for (size_t i = 0; regs_mask && (i < RawRegisterData::kMaxSize); i++) {
+    if (regs_mask & (1u << i)) {
+      parse_pos = ReadValue(&raw_regs.regs[i], parse_pos);
+    }
+  }
+
+  // Special case: we've requested arm64 registers from a 64 bit kernel, but
+  // ended up sampling a 32 bit arm userspace process. The 32 bit execution
+  // state of the target process was saved by the exception entry in an
+  // ISA-specific way. The userspace R0-R14 end up saved as arm64 W0-W14, but
+  // the program counter (R15 on arm32) is still in PERF_REG_ARM64_PC (the 33rd
+  // register). So we can take the kernel-dumped 64 bit register state, reassign
+  // the PC into the R15 slot, and treat the resulting RawRegisterData as an
+  // arm32 register bank. See "Fundamentals of ARMv8-A" (ARM DOC
+  // 100878_0100_en), page 28.
+  if (requested_arch == unwindstack::ARCH_ARM64 &&
+      sampled_abi == PERF_SAMPLE_REGS_ABI_32) {
+    raw_regs.regs[PERF_REG_ARM_PC] = raw_regs.regs[PERF_REG_ARM64_PC];
+  }
+
+  // Adjust caller's parsing position.
+  *data = parse_pos;
+
+  // ABI_NONE means there were no registers (e.g. we've sampled a kernel thread,
+  // which doesn't have userspace registers). We still walk over the empty data
+  // above, but return an empty result to the caller.
+  if (sampled_abi == PERF_SAMPLE_REGS_ABI_NONE) {
+    return nullptr;
+  } else {
+    unwindstack::ArchEnum sampled_arch =
+        ArchForAbi(requested_arch, sampled_abi);
+    return ToLibUnwindstackRegs(raw_regs, sampled_arch);
+  }
+}
+
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/perf/unwind_support.h b/src/profiling/perf/unwind_support.h
new file mode 100644
index 0000000..f0764d2
--- /dev/null
+++ b/src/profiling/perf/unwind_support.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_PROFILING_PERF_UNWIND_SUPPORT_H_
+#define SRC_PROFILING_PERF_UNWIND_SUPPORT_H_
+
+#include <stdint.h>
+#include <unwindstack/Regs.h>
+
+#include <memory>
+
+#include "perfetto/ext/base/scoped_file.h"
+
+namespace perfetto {
+namespace profiling {
+
+// Returns a bitmask for sampling the userspace register set, used when
+// configuring perf events.
+uint64_t PerfUserRegsMaskForCurrentArch();
+
+// Converts the raw sampled register bytes to libunwindstack's representation
+// (correct arch-dependent subclass). Advances |data| pointer to past the
+// register data. The unique_ptr can be empty, if there were no userspace
+// registers to sample (i.e. we've sampled a kernel thread).
+// TODO(rsavitski): come up with a better signature (also consider how much to
+// isolate libunwindstack types).
+std::unique_ptr<unwindstack::Regs> ReadPerfUserRegsData(const char** data);
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_PERF_UNWIND_SUPPORT_H_
diff --git a/src/profiling/symbolizer/BUILD.gn b/src/profiling/symbolizer/BUILD.gn
new file mode 100644
index 0000000..3bbc385
--- /dev/null
+++ b/src/profiling/symbolizer/BUILD.gn
@@ -0,0 +1,48 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../gn/perfetto.gni")
+
+source_set("symbolizer") {
+  public_deps = [
+    "../../../include/perfetto/ext/base",
+  ]
+  deps = [
+    "../../../gn:default_deps",
+  ]
+  sources = [
+    "local_symbolizer.cc",
+    "local_symbolizer.h",
+    "symbolizer.cc",
+    "symbolizer.h",
+  ]
+}
+
+source_set("symbolize_database") {
+  public_deps = [
+    ":symbolizer",
+    "../../../include/perfetto/ext/base",
+  ]
+  deps = [
+    "../../../gn:default_deps",
+    "../../../include/perfetto/protozero",
+    "../../../include/perfetto/trace_processor:trace_processor",
+    "../../../protos/perfetto/trace:zero",
+    "../../../protos/perfetto/trace/profiling:zero",
+  ]
+  sources = [
+    "symbolize_database.cc",
+    "symbolize_database.h",
+  ]
+}
diff --git a/tools/trace_to_text/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
similarity index 98%
rename from tools/trace_to_text/local_symbolizer.cc
rename to src/profiling/symbolizer/local_symbolizer.cc
index 3daa0be..1874df1 100644
--- a/tools/trace_to_text/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -20,7 +20,7 @@
 // This translation unit is built only on Linux. See //gn/BUILD.gn.
 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
 
-#include "tools/trace_to_text/local_symbolizer.h"
+#include "src/profiling/symbolizer/local_symbolizer.h"
 
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
@@ -35,7 +35,7 @@
 #include <unistd.h>
 
 namespace perfetto {
-namespace trace_to_text {
+namespace profiling {
 
 namespace {
 
@@ -60,7 +60,6 @@
   return lines;
 }
 
-
 struct Elf32 {
   using Ehdr = Elf32_Ehdr;
   using Shdr = Elf32_Shdr;
@@ -402,7 +401,7 @@
 
 LocalSymbolizer::~LocalSymbolizer() = default;
 
-}  // namespace trace_to_text
+}  // namespace profiling
 }  // namespace perfetto
 
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
diff --git a/tools/trace_to_text/local_symbolizer.h b/src/profiling/symbolizer/local_symbolizer.h
similarity index 90%
rename from tools/trace_to_text/local_symbolizer.h
rename to src/profiling/symbolizer/local_symbolizer.h
index e1eef9e..eb3a717 100644
--- a/tools/trace_to_text/local_symbolizer.h
+++ b/src/profiling/symbolizer/local_symbolizer.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef TOOLS_TRACE_TO_TEXT_LOCAL_SYMBOLIZER_H_
-#define TOOLS_TRACE_TO_TEXT_LOCAL_SYMBOLIZER_H_
+#ifndef SRC_PROFILING_SYMBOLIZER_LOCAL_SYMBOLIZER_H_
+#define SRC_PROFILING_SYMBOLIZER_LOCAL_SYMBOLIZER_H_
 
 #include <map>
 #include <string>
@@ -23,10 +23,10 @@
 
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/pipe.h"
-#include "perfetto/profiling/symbolizer.h"
+#include "src/profiling/symbolizer/symbolizer.h"
 
 namespace perfetto {
-namespace trace_to_text {
+namespace profiling {
 
 class LocalBinaryFinder {
  public:
@@ -93,7 +93,7 @@
   LocalBinaryFinder finder_;
 };
 
-}  // namespace trace_to_text
+}  // namespace profiling
 }  // namespace perfetto
 
-#endif  // TOOLS_TRACE_TO_TEXT_LOCAL_SYMBOLIZER_H_
+#endif  // SRC_PROFILING_SYMBOLIZER_LOCAL_SYMBOLIZER_H_
diff --git a/src/profiling/symbolizer/symbolize_database.cc b/src/profiling/symbolizer/symbolize_database.cc
new file mode 100644
index 0000000..39e09a4
--- /dev/null
+++ b/src/profiling/symbolizer/symbolize_database.cc
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/symbolize_database.h"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "perfetto/trace_processor/trace_processor.h"
+
+#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+namespace profiling {
+
+namespace {
+using Iterator = trace_processor::TraceProcessor::Iterator;
+
+constexpr const char* kQueryUnsymbolized =
+    "select spm.name, spm.build_id, spf.rel_pc "
+    "from stack_profile_frame spf "
+    "join stack_profile_mapping spm "
+    "on spf.mapping = spm.id "
+    "where spm.build_id != '' and spf.symbol_set_id == 0";
+
+using NameAndBuildIdPair = std::pair<std::string, std::string>;
+
+std::string FromHex(const char* str, size_t size) {
+  if (size % 2) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
+    return "";
+  }
+  std::string result(size / 2, '\0');
+  for (size_t i = 0; i < size; i += 2) {
+    char hex_byte[3];
+    hex_byte[0] = str[i];
+    hex_byte[1] = str[i + 1];
+    hex_byte[2] = '\0';
+    char* end;
+    long int byte = strtol(hex_byte, &end, 16);
+    if (*end != '\0') {
+      PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
+      return "";
+    }
+    result[i / 2] = static_cast<char>(byte);
+  }
+  return result;
+}
+
+std::string FromHex(const std::string& str) {
+  return FromHex(str.c_str(), str.size());
+}
+
+std::map<NameAndBuildIdPair, std::vector<uint64_t>> GetUnsymbolizedFrames(
+    trace_processor::TraceProcessor* tp) {
+  std::map<std::pair<std::string, std::string>, std::vector<uint64_t>> res;
+  Iterator it = tp->ExecuteQuery(kQueryUnsymbolized);
+  while (it.Next()) {
+    auto name_and_buildid =
+        std::make_pair(it.Get(0).string_value, FromHex(it.Get(1).string_value));
+    int64_t rel_pc = it.Get(2).long_value;
+    res[name_and_buildid].emplace_back(rel_pc);
+  }
+  if (!it.Status().ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                            it.Status().message().c_str());
+    return {};
+  }
+  return res;
+}
+}  // namespace
+
+void SymbolizeDatabase(trace_processor::TraceProcessor* tp,
+                       Symbolizer* symbolizer,
+                       std::function<void(const std::string&)> callback) {
+  PERFETTO_CHECK(symbolizer);
+  auto unsymbolized = GetUnsymbolizedFrames(tp);
+  for (auto it = unsymbolized.cbegin(); it != unsymbolized.cend(); ++it) {
+    const auto& name_and_buildid = it->first;
+    const std::vector<uint64_t>& rel_pcs = it->second;
+    auto res = symbolizer->Symbolize(name_and_buildid.first,
+                                     name_and_buildid.second, rel_pcs);
+    if (res.empty())
+      continue;
+
+    protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> packet;
+    auto* module_symbols = packet->set_module_symbols();
+    module_symbols->set_path(name_and_buildid.first);
+    module_symbols->set_build_id(name_and_buildid.second);
+    PERFETTO_DCHECK(res.size() == rel_pcs.size());
+    for (size_t i = 0; i < res.size(); ++i) {
+      auto* address_symbols = module_symbols->add_address_symbols();
+      address_symbols->set_address(rel_pcs[i]);
+      for (const SymbolizedFrame& frame : res[i]) {
+        auto* line = address_symbols->add_lines();
+        line->set_function_name(frame.function_name);
+        line->set_source_file_name(frame.file_name);
+        line->set_line_number(frame.line);
+      }
+    }
+    callback(packet.SerializeAsString());
+  }
+}
+
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/symbolizer/symbolize_database.h b/src/profiling/symbolizer/symbolize_database.h
new file mode 100644
index 0000000..6c9ab1b
--- /dev/null
+++ b/src/profiling/symbolizer/symbolize_database.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_PROFILING_SYMBOLIZER_SYMBOLIZE_DATABASE_H_
+#define SRC_PROFILING_SYMBOLIZER_SYMBOLIZE_DATABASE_H_
+
+#include "src/profiling/symbolizer/symbolizer.h"
+
+#include <functional>
+#include <string>
+
+namespace perfetto {
+namespace trace_processor {
+class TraceProcessor;
+}
+namespace profiling {
+// Generate ModuleSymbol protos for all unsymbolized frames in the database.
+// Wrap them in proto-encoded TracePackets messages and call callback.
+void SymbolizeDatabase(trace_processor::TraceProcessor* tp,
+                       Symbolizer* symbolizer,
+                       std::function<void(const std::string&)> callback);
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_SYMBOLIZER_SYMBOLIZE_DATABASE_H_
diff --git a/tools/trace_to_text/symbolizer.cc b/src/profiling/symbolizer/symbolizer.cc
similarity index 87%
rename from tools/trace_to_text/symbolizer.cc
rename to src/profiling/symbolizer/symbolizer.cc
index 1cd83cb..0aa5d8b 100644
--- a/tools/trace_to_text/symbolizer.cc
+++ b/src/profiling/symbolizer/symbolizer.cc
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#include "perfetto/profiling/symbolizer.h"
+#include "src/profiling/symbolizer/symbolizer.h"
 
 namespace perfetto {
-namespace trace_to_text {
+namespace profiling {
 
 Symbolizer::~Symbolizer() = default;
 
-}  // namespace trace_to_text
+}  // namespace profiling
 }  // namespace perfetto
diff --git a/include/perfetto/profiling/symbolizer.h b/src/profiling/symbolizer/symbolizer.h
similarity index 80%
rename from include/perfetto/profiling/symbolizer.h
rename to src/profiling/symbolizer/symbolizer.h
index 2a5a79e..ff4ee50 100644
--- a/include/perfetto/profiling/symbolizer.h
+++ b/src/profiling/symbolizer/symbolizer.h
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
-#ifndef INCLUDE_PERFETTO_PROFILING_SYMBOLIZER_H_
-#define INCLUDE_PERFETTO_PROFILING_SYMBOLIZER_H_
+#ifndef SRC_PROFILING_SYMBOLIZER_SYMBOLIZER_H_
+#define SRC_PROFILING_SYMBOLIZER_SYMBOLIZER_H_
 
 #include <map>
 #include <string>
 #include <vector>
 
-// TODO(135923303): do not depend on anything in this file as it will be
-// removed as part of fixing b/135923303.
 namespace perfetto {
-namespace trace_to_text {
+namespace profiling {
 
 struct SymbolizedFrame {
   std::string function_name;
@@ -46,7 +44,7 @@
   virtual ~Symbolizer();
 };
 
-}  // namespace trace_to_text
+}  // namespace profiling
 }  // namespace perfetto
 
-#endif  // INCLUDE_PERFETTO_PROFILING_SYMBOLIZER_H_
+#endif  // SRC_PROFILING_SYMBOLIZER_SYMBOLIZER_H_
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index df2329f..cb2f5cf 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -78,11 +78,11 @@
   sources = [
     "args_tracker.cc",
     "args_tracker.h",
-    "binder_tracker.cc",
-    "binder_tracker.h",
     "chunked_trace_reader.h",
     "clock_tracker.cc",
     "clock_tracker.h",
+    "destructible.cc",
+    "destructible.h",
     "event_tracker.cc",
     "event_tracker.h",
     "forwarding_trace_parser.cc",
@@ -99,14 +99,9 @@
     "importers/ftrace/ftrace_tokenizer.h",
     "importers/ftrace/sched_event_tracker.h",
     "importers/fuchsia/fuchsia_provider_view.h",
-    "importers/proto/android_probes_module.h",
-    "importers/proto/android_probes_parser.h",
     "importers/proto/args_table_utils.cc",
     "importers/proto/args_table_utils.h",
     "importers/proto/chrome_compositor_scheduler_state.descriptor.h",
-    "importers/proto/heap_graph_module.h",
-    "importers/proto/heap_graph_tracker.h",
-    "importers/proto/heap_graph_walker.h",
     "importers/proto/packet_sequence_state.h",
     "importers/proto/proto_importer_module.cc",
     "importers/proto/proto_importer_module.h",
@@ -115,8 +110,6 @@
     "importers/proto/proto_trace_parser.h",
     "importers/proto/proto_trace_tokenizer.cc",
     "importers/proto/proto_trace_tokenizer.h",
-    "importers/proto/system_probes_module.h",
-    "importers/proto/system_probes_parser.h",
     "importers/proto/track_event_module.cc",
     "importers/proto/track_event_module.h",
     "importers/proto/track_event_parser.cc",
@@ -153,12 +146,12 @@
   ]
 
   deps = [
-    ":common",
     ":descriptors",
     "../../gn:default_deps",
     "../../gn:zlib",
     "../base",
     "../protozero",
+    "containers",
     "tables",
   ]
   public_deps = [
@@ -190,6 +183,8 @@
   }
   if (enable_perfetto_trace_processor_ftrace) {
     sources += [
+      "importers/ftrace/binder_tracker.cc",
+      "importers/ftrace/binder_tracker.h",
       "importers/ftrace/ftrace_descriptors.cc",
       "importers/ftrace/ftrace_module_impl.cc",
       "importers/ftrace/ftrace_parser.cc",
@@ -203,26 +198,6 @@
       enable_perfetto_trace_processor_fuchsia) {
     sources += [ "ftrace_utils.cc" ]
   }
-  if (enable_perfetto_trace_processor_system_probes) {
-    sources += [
-      "importers/proto/system_probes_module.cc",
-      "importers/proto/system_probes_parser.cc",
-    ]
-    deps += [ "../../include/perfetto/ext/traced:sys_stats_counters" ]
-  }
-  if (enable_perfetto_trace_processor_android_probes) {
-    sources += [
-      "importers/proto/android_probes_module.cc",
-      "importers/proto/android_probes_parser.cc",
-    ]
-  }
-  if (enable_perfetto_trace_processor_heap_graphs) {
-    sources += [
-      "importers/proto/heap_graph_module.cc",
-      "importers/proto/heap_graph_tracker.cc",
-      "importers/proto/heap_graph_walker.cc",
-    ]
-  }
   if (enable_perfetto_trace_processor_fuchsia) {
     sources += [
       "importers/fuchsia/fuchsia_provider_view.cc",
@@ -247,10 +222,24 @@
 
 source_set("storage_full") {
   sources = [
+    "importers/proto/android_probes_module.cc",
+    "importers/proto/android_probes_module.h",
+    "importers/proto/android_probes_parser.cc",
+    "importers/proto/android_probes_parser.h",
     "importers/proto/graphics_event_module.cc",
     "importers/proto/graphics_event_module.h",
     "importers/proto/graphics_event_parser.cc",
     "importers/proto/graphics_event_parser.h",
+    "importers/proto/heap_graph_module.cc",
+    "importers/proto/heap_graph_module.h",
+    "importers/proto/heap_graph_tracker.cc",
+    "importers/proto/heap_graph_tracker.h",
+    "importers/proto/heap_graph_walker.cc",
+    "importers/proto/heap_graph_walker.h",
+    "importers/proto/system_probes_module.cc",
+    "importers/proto/system_probes_module.h",
+    "importers/proto/system_probes_parser.cc",
+    "importers/proto/system_probes_parser.h",
     "importers/proto/vulkan_memory_tracker.cc",
     "importers/proto/vulkan_memory_tracker.h",
     "register_additional_modules.cc",
@@ -260,6 +249,7 @@
     ":storage_minimal",
   ]
   deps = [
+    "../../include/perfetto/ext/traced:sys_stats_counters",
     "../../protos/perfetto/common:zero",
     "../../protos/perfetto/trace:zero",
     "../../protos/perfetto/trace/android:zero",
@@ -270,6 +260,20 @@
   if (enable_perfetto_trace_processor_json) {
     public_deps += [ "../../gn:jsoncpp" ]
   }
+
+  # Include these sources only if they are not already included in
+  # storage_minimal.
+  # TODO(khokhlov): Remove this 'if' when modules that use this tracker are
+  # migrated to storage_full.
+  if (!enable_perfetto_trace_processor_syscalls) {
+    sources += [
+      "syscall_tracker.cc",
+      "syscalls_aarch32.h",
+      "syscalls_aarch64.h",
+      "syscalls_armeabi.h",
+      "syscalls_x86_64.h",
+    ]
+  }
 }
 
 if (enable_perfetto_trace_processor_json) {
@@ -303,8 +307,6 @@
       "filtered_row_index.h",
       "gfp_flags.cc",
       "gfp_flags.h",
-      "heap_profile_allocation_table.cc",
-      "heap_profile_allocation_table.h",
       "instants_table.cc",
       "instants_table.h",
       "metadata_table.cc",
@@ -343,7 +345,6 @@
     ]
 
     deps = [
-      ":common",
       ":storage_full",
       "../../gn:default_deps",
       "../../gn:sqlite",
@@ -372,21 +373,6 @@
   }
 }
 
-# TODO(lalitm): we need to find a better home for the classes here.
-source_set("common") {
-  sources = [
-    "null_term_string_view.h",
-    "string_pool.cc",
-    "string_pool.h",
-  ]
-
-  deps = [
-    "../../gn:default_deps",
-    "../base",
-    "../protozero",
-  ]
-}
-
 if (enable_perfetto_trace_processor_metrics) {  # shell requires metrics.
   perfetto_host_executable("trace_processor_shell") {
     deps = [
@@ -421,17 +407,15 @@
     "forwarding_trace_parser_unittest.cc",
     "heap_profile_tracker_unittest.cc",
     "importers/proto/args_table_utils_unittest.cc",
+    "importers/proto/heap_graph_walker_unittest.cc",
     "importers/proto/proto_trace_parser_unittest.cc",
     "importers/systrace/systrace_parser_unittest.cc",
-    "null_term_string_view_unittest.cc",
     "process_tracker_unittest.cc",
     "protozero_to_text_unittests.cc",
     "slice_tracker_unittest.cc",
-    "string_pool_unittest.cc",
     "trace_sorter_unittest.cc",
   ]
   deps = [
-    ":common",
     ":descriptors",
     ":protozero_to_text",
     ":storage_full",
@@ -452,6 +436,7 @@
     "../base",
     "../protozero",
     "../protozero:testing_messages_zero",
+    "containers:unittests",
     "db:unittests",
     "tables:unittests",
   ]
@@ -496,9 +481,6 @@
       "thread_table_unittest.cc",
     ]
   }
-  if (enable_perfetto_trace_processor_heap_graphs) {
-    sources += [ "importers/proto/heap_graph_walker_unittest.cc" ]
-  }
   if (enable_perfetto_trace_processor_fuchsia) {
     sources += [ "importers/fuchsia/fuchsia_trace_utils_unittest.cc" ]
   }
@@ -529,6 +511,24 @@
   }
 }
 
+if (enable_perfetto_trace_processor_json) {
+  source_set("storage_minimal_smoke_tests") {
+    testonly = true
+    sources = [
+      "storage_minimal_smoke_test.cc",
+    ]
+    deps = [
+      ":export_json",
+      ":storage_minimal",
+      "../../gn:default_deps",
+      "../../gn:gtest_and_gmock",
+      "../../gn:gtest_main",
+      "../../gn:jsoncpp",
+      "../base:test_support",
+    ]
+  }
+}
+
 perfetto_fuzzer_test("trace_processor_fuzzer") {
   testonly = true
   sources = [
diff --git a/src/trace_processor/containers/BUILD.gn b/src/trace_processor/containers/BUILD.gn
new file mode 100644
index 0000000..32e0d28
--- /dev/null
+++ b/src/trace_processor/containers/BUILD.gn
@@ -0,0 +1,68 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../gn/test.gni")
+
+source_set("containers") {
+  sources = [
+    "bit_vector.cc",
+    "bit_vector.h",
+    "bit_vector_iterators.cc",
+    "bit_vector_iterators.h",
+    "null_term_string_view.h",
+    "row_map.cc",
+    "row_map.h",
+    "sparse_vector.h",
+    "string_pool.cc",
+    "string_pool.h",
+  ]
+  deps = [
+    "../../../gn:default_deps",
+    "../../../include/perfetto/base",
+    "../../../include/perfetto/ext/base",
+    "../../../include/perfetto/protozero",
+  ]
+}
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  sources = [
+    "bit_vector_unittest.cc",
+    "null_term_string_view_unittest.cc",
+    "row_map_unittest.cc",
+    "sparse_vector_unittest.cc",
+    "string_pool_unittest.cc",
+  ]
+  deps = [
+    ":containers",
+    "../../../gn:default_deps",
+    "../../../gn:gtest_and_gmock",
+  ]
+}
+
+if (enable_perfetto_benchmarks) {
+  source_set("benchmarks") {
+    testonly = true
+    deps = [
+      ":containers",
+      "../../../gn:benchmark",
+      "../../../gn:default_deps",
+    ]
+    sources = [
+      "bit_vector_benchmark.cc",
+      "row_map_benchmark.cc",
+      "sparse_vector_benchmark.cc",
+    ]
+  }
+}
diff --git a/src/trace_processor/db/bit_vector.cc b/src/trace_processor/containers/bit_vector.cc
similarity index 94%
rename from src/trace_processor/db/bit_vector.cc
rename to src/trace_processor/containers/bit_vector.cc
index 0d2f3d8..0ec5bcc 100644
--- a/src/trace_processor/db/bit_vector.cc
+++ b/src/trace_processor/containers/bit_vector.cc
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/db/bit_vector.h"
+#include "src/trace_processor/containers/bit_vector.h"
 
-#include "src/trace_processor/db/bit_vector_iterators.h"
+#include "src/trace_processor/containers/bit_vector_iterators.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/db/bit_vector.h b/src/trace_processor/containers/bit_vector.h
similarity index 98%
rename from src/trace_processor/db/bit_vector.h
rename to src/trace_processor/containers/bit_vector.h
index bed71a6..58b9e23 100644
--- a/src/trace_processor/db/bit_vector.h
+++ b/src/trace_processor/containers/bit_vector.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_DB_BIT_VECTOR_H_
-#define SRC_TRACE_PROCESSOR_DB_BIT_VECTOR_H_
+#ifndef SRC_TRACE_PROCESSOR_CONTAINERS_BIT_VECTOR_H_
+#define SRC_TRACE_PROCESSOR_CONTAINERS_BIT_VECTOR_H_
 
 #include <stddef.h>
 #include <stdint.h>
@@ -655,4 +655,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_DB_BIT_VECTOR_H_
+#endif  // SRC_TRACE_PROCESSOR_CONTAINERS_BIT_VECTOR_H_
diff --git a/src/trace_processor/db/bit_vector_benchmark.cc b/src/trace_processor/containers/bit_vector_benchmark.cc
similarity index 98%
rename from src/trace_processor/db/bit_vector_benchmark.cc
rename to src/trace_processor/containers/bit_vector_benchmark.cc
index 54c00f9..b4a0c27 100644
--- a/src/trace_processor/db/bit_vector_benchmark.cc
+++ b/src/trace_processor/containers/bit_vector_benchmark.cc
@@ -16,7 +16,7 @@
 
 #include <benchmark/benchmark.h>
 
-#include "src/trace_processor/db/bit_vector.h"
+#include "src/trace_processor/containers/bit_vector.h"
 
 namespace {
 
@@ -36,7 +36,7 @@
     b->Arg(1234567);
   }
 }
-}
+}  // namespace
 
 static void BM_BitVectorAppendTrue(benchmark::State& state) {
   BitVector bv;
diff --git a/src/trace_processor/db/bit_vector_iterators.cc b/src/trace_processor/containers/bit_vector_iterators.cc
similarity index 98%
rename from src/trace_processor/db/bit_vector_iterators.cc
rename to src/trace_processor/containers/bit_vector_iterators.cc
index 281384c..5424365 100644
--- a/src/trace_processor/db/bit_vector_iterators.cc
+++ b/src/trace_processor/containers/bit_vector_iterators.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/db/bit_vector_iterators.h"
+#include "src/trace_processor/containers/bit_vector_iterators.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/db/bit_vector_iterators.h b/src/trace_processor/containers/bit_vector_iterators.h
similarity index 95%
rename from src/trace_processor/db/bit_vector_iterators.h
rename to src/trace_processor/containers/bit_vector_iterators.h
index 365e8ef..0047812 100644
--- a/src/trace_processor/db/bit_vector_iterators.h
+++ b/src/trace_processor/containers/bit_vector_iterators.h
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_DB_BIT_VECTOR_ITERATORS_H_
-#define SRC_TRACE_PROCESSOR_DB_BIT_VECTOR_ITERATORS_H_
+#ifndef SRC_TRACE_PROCESSOR_CONTAINERS_BIT_VECTOR_ITERATORS_H_
+#define SRC_TRACE_PROCESSOR_CONTAINERS_BIT_VECTOR_ITERATORS_H_
 
-#include "src/trace_processor/db/bit_vector.h"
+#include "src/trace_processor/containers/bit_vector.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -187,4 +187,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_DB_BIT_VECTOR_ITERATORS_H_
+#endif  // SRC_TRACE_PROCESSOR_CONTAINERS_BIT_VECTOR_ITERATORS_H_
diff --git a/src/trace_processor/db/bit_vector_unittest.cc b/src/trace_processor/containers/bit_vector_unittest.cc
similarity index 98%
rename from src/trace_processor/db/bit_vector_unittest.cc
rename to src/trace_processor/containers/bit_vector_unittest.cc
index 7b5aad7..e185afd 100644
--- a/src/trace_processor/db/bit_vector_unittest.cc
+++ b/src/trace_processor/containers/bit_vector_unittest.cc
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/db/bit_vector.h"
+#include "src/trace_processor/containers/bit_vector.h"
 
 #include <random>
 
-#include "src/trace_processor/db/bit_vector_iterators.h"
+#include "src/trace_processor/containers/bit_vector_iterators.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/null_term_string_view.h b/src/trace_processor/containers/null_term_string_view.h
similarity index 91%
rename from src/trace_processor/null_term_string_view.h
rename to src/trace_processor/containers/null_term_string_view.h
index f576006..5ae5a29 100644
--- a/src/trace_processor/null_term_string_view.h
+++ b/src/trace_processor/containers/null_term_string_view.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_NULL_TERM_STRING_VIEW_H_
-#define SRC_TRACE_PROCESSOR_NULL_TERM_STRING_VIEW_H_
+#ifndef SRC_TRACE_PROCESSOR_CONTAINERS_NULL_TERM_STRING_VIEW_H_
+#define SRC_TRACE_PROCESSOR_CONTAINERS_NULL_TERM_STRING_VIEW_H_
 
 #include "perfetto/ext/base/string_view.h"
 
@@ -57,4 +57,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_NULL_TERM_STRING_VIEW_H_
+#endif  // SRC_TRACE_PROCESSOR_CONTAINERS_NULL_TERM_STRING_VIEW_H_
diff --git a/src/trace_processor/null_term_string_view_unittest.cc b/src/trace_processor/containers/null_term_string_view_unittest.cc
similarity index 97%
rename from src/trace_processor/null_term_string_view_unittest.cc
rename to src/trace_processor/containers/null_term_string_view_unittest.cc
index c8cc29e..eb4d23f 100644
--- a/src/trace_processor/null_term_string_view_unittest.cc
+++ b/src/trace_processor/containers/null_term_string_view_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/null_term_string_view.h"
+#include "src/trace_processor/containers/null_term_string_view.h"
 
 #include "test/gtest_and_gmock.h"
 
diff --git a/src/trace_processor/db/row_map.cc b/src/trace_processor/containers/row_map.cc
similarity index 98%
rename from src/trace_processor/db/row_map.cc
rename to src/trace_processor/containers/row_map.cc
index cb16e4d..b10d0cb 100644
--- a/src/trace_processor/db/row_map.cc
+++ b/src/trace_processor/containers/row_map.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/db/row_map.h"
+#include "src/trace_processor/containers/row_map.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/db/row_map.h b/src/trace_processor/containers/row_map.h
similarity index 95%
rename from src/trace_processor/db/row_map.h
rename to src/trace_processor/containers/row_map.h
index 9b2805e..00ad7d2 100644
--- a/src/trace_processor/db/row_map.h
+++ b/src/trace_processor/containers/row_map.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_DB_ROW_MAP_H_
-#define SRC_TRACE_PROCESSOR_DB_ROW_MAP_H_
+#ifndef SRC_TRACE_PROCESSOR_CONTAINERS_ROW_MAP_H_
+#define SRC_TRACE_PROCESSOR_CONTAINERS_ROW_MAP_H_
 
 #include <stdint.h>
 
@@ -24,8 +24,8 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/optional.h"
-#include "src/trace_processor/db/bit_vector.h"
-#include "src/trace_processor/db/bit_vector_iterators.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/containers/bit_vector_iterators.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -381,31 +381,13 @@
   //   if (!other.Contains(idx))
   //     Remove(idx)
   void Intersect(const RowMap& other) {
-    uint32_t size = other.size();
-
-    if (size == 0u) {
-      // If other is empty, then we will also end up being empty.
-      *this = RowMap();
-      return;
-    }
-
-    if (size == 1u) {
-      // If other just has a single row, see if we also have that row. If we
-      // do, then just return that row. Otherwise, make ourselves empty.
-      uint32_t row = other.Get(0);
-      *this = Contains(row) ? RowMap::SingleRow(row) : RowMap();
-      return;
-    }
-
     if (mode_ == Mode::kRange && other.mode_ == Mode::kRange) {
       // If both RowMaps have ranges, we can just take the smallest intersection
       // of them as the new RowMap.
-      // This case is important to optimize as it comes up with sorted columns.
+      // We have this as an explicit fast path as this is very common for
+      // constraints on id and sorted columns to satisfy this condition.
       start_idx_ = std::max(start_idx_, other.start_idx_);
-      end_idx_ = std::min(end_idx_, other.end_idx_);
-
-      if (end_idx_ <= start_idx_)
-        *this = RowMap();
+      end_idx_ = std::max(start_idx_, std::min(end_idx_, other.end_idx_));
       return;
     }
 
@@ -634,4 +616,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_DB_ROW_MAP_H_
+#endif  // SRC_TRACE_PROCESSOR_CONTAINERS_ROW_MAP_H_
diff --git a/src/trace_processor/db/row_map_benchmark.cc b/src/trace_processor/containers/row_map_benchmark.cc
similarity index 99%
rename from src/trace_processor/db/row_map_benchmark.cc
rename to src/trace_processor/containers/row_map_benchmark.cc
index 1d6892e..47d66cd 100644
--- a/src/trace_processor/db/row_map_benchmark.cc
+++ b/src/trace_processor/containers/row_map_benchmark.cc
@@ -16,7 +16,7 @@
 
 #include <benchmark/benchmark.h>
 
-#include "src/trace_processor/db/row_map.h"
+#include "src/trace_processor/containers/row_map.h"
 
 using perfetto::trace_processor::BitVector;
 using perfetto::trace_processor::RowMap;
diff --git a/src/trace_processor/db/row_map_unittest.cc b/src/trace_processor/containers/row_map_unittest.cc
similarity index 99%
rename from src/trace_processor/db/row_map_unittest.cc
rename to src/trace_processor/containers/row_map_unittest.cc
index 042154d..c85b5fe 100644
--- a/src/trace_processor/db/row_map_unittest.cc
+++ b/src/trace_processor/containers/row_map_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/db/row_map.h"
+#include "src/trace_processor/containers/row_map.h"
 
 #include <memory>
 
diff --git a/src/trace_processor/db/sparse_vector.h b/src/trace_processor/containers/sparse_vector.h
similarity index 93%
rename from src/trace_processor/db/sparse_vector.h
rename to src/trace_processor/containers/sparse_vector.h
index 7fb0d87..5c803d3 100644
--- a/src/trace_processor/db/sparse_vector.h
+++ b/src/trace_processor/containers/sparse_vector.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_DB_SPARSE_VECTOR_H_
-#define SRC_TRACE_PROCESSOR_DB_SPARSE_VECTOR_H_
+#ifndef SRC_TRACE_PROCESSOR_CONTAINERS_SPARSE_VECTOR_H_
+#define SRC_TRACE_PROCESSOR_CONTAINERS_SPARSE_VECTOR_H_
 
 #include <stdint.h>
 
@@ -23,7 +23,7 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/optional.h"
-#include "src/trace_processor/db/row_map.h"
+#include "src/trace_processor/containers/row_map.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -115,4 +115,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_DB_SPARSE_VECTOR_H_
+#endif  // SRC_TRACE_PROCESSOR_CONTAINERS_SPARSE_VECTOR_H_
diff --git a/src/trace_processor/db/sparse_vector_benchmark.cc b/src/trace_processor/containers/sparse_vector_benchmark.cc
similarity index 96%
rename from src/trace_processor/db/sparse_vector_benchmark.cc
rename to src/trace_processor/containers/sparse_vector_benchmark.cc
index ed1bed2..626b738 100644
--- a/src/trace_processor/db/sparse_vector_benchmark.cc
+++ b/src/trace_processor/containers/sparse_vector_benchmark.cc
@@ -16,7 +16,7 @@
 
 #include <benchmark/benchmark.h>
 
-#include "src/trace_processor/db/sparse_vector.h"
+#include "src/trace_processor/containers/sparse_vector.h"
 
 namespace {
 
diff --git a/src/trace_processor/db/sparse_vector_unittest.cc b/src/trace_processor/containers/sparse_vector_unittest.cc
similarity index 96%
rename from src/trace_processor/db/sparse_vector_unittest.cc
rename to src/trace_processor/containers/sparse_vector_unittest.cc
index cd8e43a..6e81b0b 100644
--- a/src/trace_processor/db/sparse_vector_unittest.cc
+++ b/src/trace_processor/containers/sparse_vector_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/db/sparse_vector.h"
+#include "src/trace_processor/containers/sparse_vector.h"
 
 #include "test/gtest_and_gmock.h"
 
diff --git a/src/trace_processor/string_pool.cc b/src/trace_processor/containers/string_pool.cc
similarity index 98%
rename from src/trace_processor/string_pool.cc
rename to src/trace_processor/containers/string_pool.cc
index 1a45e27..020de1c 100644
--- a/src/trace_processor/string_pool.cc
+++ b/src/trace_processor/containers/string_pool.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/string_pool.h"
+#include "src/trace_processor/containers/string_pool.h"
 
 #include <limits>
 
diff --git a/src/trace_processor/string_pool.h b/src/trace_processor/containers/string_pool.h
similarity index 96%
rename from src/trace_processor/string_pool.h
rename to src/trace_processor/containers/string_pool.h
index d632764..bdfc39f 100644
--- a/src/trace_processor/string_pool.h
+++ b/src/trace_processor/containers/string_pool.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_STRING_POOL_H_
-#define SRC_TRACE_PROCESSOR_STRING_POOL_H_
+#ifndef SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_
+#define SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_
 
 #include <stddef.h>
 #include <stdint.h>
@@ -26,7 +26,7 @@
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/paged_memory.h"
 #include "perfetto/protozero/proto_utils.h"
-#include "src/trace_processor/null_term_string_view.h"
+#include "src/trace_processor/containers/null_term_string_view.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -250,4 +250,4 @@
 
 }  // namespace std
 
-#endif  // SRC_TRACE_PROCESSOR_STRING_POOL_H_
+#endif  // SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_
diff --git a/src/trace_processor/string_pool_unittest.cc b/src/trace_processor/containers/string_pool_unittest.cc
similarity index 98%
rename from src/trace_processor/string_pool_unittest.cc
rename to src/trace_processor/containers/string_pool_unittest.cc
index 9021f01..0144d7c 100644
--- a/src/trace_processor/string_pool_unittest.cc
+++ b/src/trace_processor/containers/string_pool_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/string_pool.h"
+#include "src/trace_processor/containers/string_pool.h"
 
 #include <random>
 
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index 30e2518..be92403 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -16,36 +16,26 @@
 
 source_set("lib") {
   sources = [
-    "bit_vector.cc",
-    "bit_vector.h",
-    "bit_vector_iterators.cc",
-    "bit_vector_iterators.h",
     "column.cc",
     "column.h",
     "compare.h",
-    "row_map.cc",
-    "row_map.h",
-    "sparse_vector.h",
     "table.cc",
     "table.h",
     "typed_column.h",
   ]
   deps = [
-    "../:common",
     "../../../gn:default_deps",
     "../../../include/perfetto/base",
     "../../../include/perfetto/ext/base",
     "../../../include/perfetto/trace_processor",
+    "../containers",
   ]
 }
 
 perfetto_unittest_source_set("unittests") {
   testonly = true
   sources = [
-    "bit_vector_unittest.cc",
     "compare_unittest.cc",
-    "row_map_unittest.cc",
-    "sparse_vector_unittest.cc",
   ]
   deps = [
     ":lib",
@@ -53,19 +43,3 @@
     "../../../gn:gtest_and_gmock",
   ]
 }
-
-if (enable_perfetto_benchmarks) {
-  source_set("benchmarks") {
-    testonly = true
-    deps = [
-      ":lib",
-      "../../../gn:benchmark",
-      "../../../gn:default_deps",
-    ]
-    sources = [
-      "bit_vector_benchmark.cc",
-      "row_map_benchmark.cc",
-      "sparse_vector_benchmark.cc",
-    ]
-  }
-}
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 0e3d927..0c411dd 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -22,10 +22,10 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/containers/sparse_vector.h"
+#include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/compare.h"
-#include "src/trace_processor/db/row_map.h"
-#include "src/trace_processor/db/sparse_vector.h"
-#include "src/trace_processor/string_pool.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/db/table.cc b/src/trace_processor/db/table.cc
index 823f57f..6fa723c 100644
--- a/src/trace_processor/db/table.cc
+++ b/src/trace_processor/db/table.cc
@@ -32,7 +32,7 @@
 }
 
 Table& Table::operator=(Table&& other) noexcept {
-  size_ = other.size_;
+  row_count_ = other.row_count_;
   string_pool_ = other.string_pool_;
 
   row_maps_ = std::move(other.row_maps_);
@@ -53,7 +53,7 @@
 
 Table Table::CopyExceptRowMaps() const {
   Table table(string_pool_, nullptr);
-  table.size_ = size_;
+  table.row_count_ = row_count_;
   for (const Column& col : columns_) {
     table.columns_.emplace_back(col, &table, col.index_in_table(),
                                 col.row_map_idx_);
@@ -66,7 +66,7 @@
     return Copy();
 
   // Build an index vector with all the indices for the first |size_| rows.
-  std::vector<uint32_t> idx(size_);
+  std::vector<uint32_t> idx(row_count_);
   std::iota(idx.begin(), idx.end(), 0);
 
   // As our data is columnar, it's always more efficient to sort one column
@@ -108,7 +108,7 @@
   RowMap rm(std::move(idx));
   for (const RowMap& map : row_maps_) {
     table.row_maps_.emplace_back(map.SelectRows(rm));
-    PERFETTO_DCHECK(table.row_maps_.back().size() == table.size());
+    PERFETTO_DCHECK(table.row_maps_.back().size() == table.row_count());
   }
 
   // Remove the sorted flag from all the columns.
@@ -126,7 +126,7 @@
   // The join table will have the same size and RowMaps as the left (this)
   // table because the left column is indexing the right table.
   Table table(string_pool_, nullptr);
-  table.size_ = size_;
+  table.row_count_ = row_count_;
   for (const RowMap& rm : row_maps_) {
     table.row_maps_.emplace_back(rm.Copy());
   }
@@ -146,8 +146,8 @@
   // the RowMap of the right column. By getting the index of the row rather
   // than the row number itself, we can call |Apply| on the other RowMaps
   // in the right table.
-  std::vector<uint32_t> indices(size_);
-  for (uint32_t i = 0; i < size_; ++i) {
+  std::vector<uint32_t> indices(row_count_);
+  for (uint32_t i = 0; i < row_count_; ++i) {
     SqlValue val = left_col.Get(i);
     PERFETTO_CHECK(val.type != SqlValue::Type::kNull);
     indices[i] = right_col.IndexOf(val).value();
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index cc3a3d8..058dac7 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -25,8 +25,8 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/optional.h"
+#include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/column.h"
-#include "src/trace_processor/string_pool.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -84,7 +84,7 @@
   // Returns a RowMap which, if applied to the table, would contain the rows
   // post filter.
   RowMap FilterToRowMap(const std::vector<Constraint>& cs) const {
-    RowMap rm(0, size_);
+    RowMap rm(0, row_count_);
     for (const Constraint& c : cs) {
       columns_[c.col_idx].FilterInto(c.op, c.value, &rm);
     }
@@ -97,10 +97,10 @@
   // passed RowMap is generated using |FilterToRowMap|.
   Table Apply(RowMap rm) const {
     Table table = CopyExceptRowMaps();
-    table.size_ = rm.size();
+    table.row_count_ = rm.size();
     for (const RowMap& map : row_maps_) {
       table.row_maps_.emplace_back(map.SelectRows(rm));
-      PERFETTO_DCHECK(table.row_maps_.back().size() == table.size());
+      PERFETTO_DCHECK(table.row_maps_.back().size() == table.row_count());
     }
     return table;
   }
@@ -145,7 +145,7 @@
   // Returns an iterator into the Table.
   Iterator IterateRows() const { return Iterator(this); }
 
-  uint32_t size() const { return size_; }
+  uint32_t row_count() const { return row_count_; }
   const std::vector<RowMap>& row_maps() const { return row_maps_; }
 
  protected:
@@ -153,7 +153,7 @@
 
   std::vector<RowMap> row_maps_;
   std::vector<Column> columns_;
-  uint32_t size_ = 0;
+  uint32_t row_count_ = 0;
 
   StringPool* string_pool_ = nullptr;
 
diff --git a/tools/trace_to_text/symbolizer.cc b/src/trace_processor/destructible.cc
similarity index 81%
copy from tools/trace_to_text/symbolizer.cc
copy to src/trace_processor/destructible.cc
index 1cd83cb..22bcf6a 100644
--- a/tools/trace_to_text/symbolizer.cc
+++ b/src/trace_processor/destructible.cc
@@ -13,13 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-#include "perfetto/profiling/symbolizer.h"
+#include "src/trace_processor/destructible.h"
 
 namespace perfetto {
-namespace trace_to_text {
+namespace trace_processor {
 
-Symbolizer::~Symbolizer() = default;
+Destructible::~Destructible() = default;
 
-}  // namespace trace_to_text
+}  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/destructible.h b/src/trace_processor/destructible.h
new file mode 100644
index 0000000..9b9c20f
--- /dev/null
+++ b/src/trace_processor/destructible.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_DESTRUCTIBLE_H_
+#define SRC_TRACE_PROCESSOR_DESTRUCTIBLE_H_
+
+namespace perfetto {
+namespace trace_processor {
+
+// To reduce Chrome binary size, we exclude the source code of several
+// trackers from the storage_minimal build target. So the trace processor
+// context can't always know the exact types of the tracker classes, but
+// it still needs to destruct them. To solve this, we subclass all trackers
+// from this Desctructible class.
+class Destructible {
+ public:
+  virtual ~Destructible();
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DESTRUCTIBLE_H_
diff --git a/src/trace_processor/event_tracker_unittest.cc b/src/trace_processor/event_tracker_unittest.cc
index bebd42d..61968ab 100644
--- a/src/trace_processor/event_tracker_unittest.cc
+++ b/src/trace_processor/event_tracker_unittest.cc
@@ -40,12 +40,13 @@
     context.event_tracker.reset(new EventTracker(&context));
     context.track_tracker.reset(new TrackTracker(&context));
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
-    context.sched_tracker.reset(new SchedEventTracker(&context));
+    sched_tracker = SchedEventTracker::GetOrCreate(&context);
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
   }
 
  protected:
   TraceProcessorContext context;
+  SchedEventTracker* sched_tracker;
 };
 
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
@@ -60,14 +61,12 @@
   int32_t prio = 1024;
 
   const auto& timestamps = context.storage->slices().start_ns();
-  context.sched_tracker->PushSchedSwitch(cpu, timestamp, pid_1, kCommProc2,
-                                         prio, prev_state, pid_2, kCommProc1,
-                                         prio);
+  sched_tracker->PushSchedSwitch(cpu, timestamp, pid_1, kCommProc2, prio,
+                                 prev_state, pid_2, kCommProc1, prio);
   ASSERT_EQ(timestamps.size(), 1u);
 
-  context.sched_tracker->PushSchedSwitch(cpu, timestamp + 1, pid_2, kCommProc1,
-                                         prio, prev_state, pid_1, kCommProc2,
-                                         prio);
+  sched_tracker->PushSchedSwitch(cpu, timestamp + 1, pid_2, kCommProc1, prio,
+                                 prev_state, pid_1, kCommProc2, prio);
 
   ASSERT_EQ(timestamps.size(), 2ul);
   ASSERT_EQ(timestamps[0], timestamp);
@@ -88,20 +87,20 @@
   int32_t prio = 1024;
 
   const auto& timestamps = context.storage->slices().start_ns();
-  context.sched_tracker->PushSchedSwitch(cpu, timestamp, /*tid=*/4, kCommProc2,
-                                         prio, prev_state,
-                                         /*tid=*/2, kCommProc1, prio);
+  sched_tracker->PushSchedSwitch(cpu, timestamp, /*tid=*/4, kCommProc2, prio,
+                                 prev_state,
+                                 /*tid=*/2, kCommProc1, prio);
   ASSERT_EQ(timestamps.size(), 1u);
 
-  context.sched_tracker->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/2,
-                                         kCommProc1, prio, prev_state,
-                                         /*tid=*/4, kCommProc2, prio);
-  context.sched_tracker->PushSchedSwitch(cpu, timestamp + 11, /*tid=*/4,
-                                         kCommProc2, prio, prev_state,
-                                         /*tid=*/2, kCommProc1, prio);
-  context.sched_tracker->PushSchedSwitch(cpu, timestamp + 31, /*tid=*/2,
-                                         kCommProc1, prio, prev_state,
-                                         /*tid=*/4, kCommProc2, prio);
+  sched_tracker->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/2, kCommProc1,
+                                 prio, prev_state,
+                                 /*tid=*/4, kCommProc2, prio);
+  sched_tracker->PushSchedSwitch(cpu, timestamp + 11, /*tid=*/4, kCommProc2,
+                                 prio, prev_state,
+                                 /*tid=*/2, kCommProc1, prio);
+  sched_tracker->PushSchedSwitch(cpu, timestamp + 31, /*tid=*/2, kCommProc1,
+                                 prio, prev_state,
+                                 /*tid=*/4, kCommProc2, prio);
 
   ASSERT_EQ(timestamps.size(), 4ul);
   ASSERT_EQ(timestamps[0], timestamp);
@@ -125,9 +124,9 @@
   context.event_tracker->PushCounter(timestamp + 3, 5000, track);
   context.event_tracker->PushCounter(timestamp + 9, 1000, track);
 
-  ASSERT_EQ(context.storage->counter_track_table().size(), 1ul);
+  ASSERT_EQ(context.storage->counter_track_table().row_count(), 1ul);
 
-  ASSERT_EQ(context.storage->counter_table().size(), 4ul);
+  ASSERT_EQ(context.storage->counter_table().row_count(), 4ul);
   ASSERT_EQ(context.storage->counter_table().ts()[0], timestamp);
   ASSERT_DOUBLE_EQ(context.storage->counter_table().value()[0], 1000);
 
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index df5f2b5..9ac286e 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -458,7 +458,7 @@
                           const ArgsBuilder& args_builder,
                           TraceFormatWriter* writer) {
   const auto& slices = storage->slice_table();
-  for (uint32_t i = 0; i < slices.size(); ++i) {
+  for (uint32_t i = 0; i < slices.row_count(); ++i) {
     Json::Value event;
     event["ts"] = Json::Int64(slices.ts()[i] / 1000);
     event["cat"] = GetNonNullString(storage, slices.category()[i]);
@@ -845,7 +845,7 @@
     const auto& callsites = storage->stack_profile_callsite_table();
     int64_t maybe_callsite_id = samples.callsite_ids()[i];
     PERFETTO_DCHECK(maybe_callsite_id >= 0 &&
-                    maybe_callsite_id < callsites.size());
+                    maybe_callsite_id < callsites.row_count());
     while (maybe_callsite_id >= 0) {
       uint32_t callsite_id = static_cast<uint32_t>(maybe_callsite_id);
 
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index 02fe44d..4d72fcc 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -1150,7 +1150,7 @@
 
   uint32_t frame_row_id_1 = storage->mutable_stack_profile_frames()->Insert(
       {/*name_id=*/0, module_row_id_1, 0x42});
-  uint32_t symbol_set_id = storage->symbol_table().size();
+  uint32_t symbol_set_id = storage->symbol_table().row_count();
   storage->mutable_symbol_table()->Insert(
       {symbol_set_id, storage->InternString("foo_func"),
        storage->InternString("foo_file"), 66});
@@ -1159,7 +1159,7 @@
 
   uint32_t frame_row_id_2 = storage->mutable_stack_profile_frames()->Insert(
       {/*name_id=*/0, module_row_id_2, 0x4242});
-  symbol_set_id = storage->symbol_table().size();
+  symbol_set_id = storage->symbol_table().row_count();
   storage->mutable_symbol_table()->Insert(
       {symbol_set_id, storage->InternString("bar_func"),
        storage->InternString("bar_file"), 77});
diff --git a/src/trace_processor/heap_profile_allocation_table.cc b/src/trace_processor/heap_profile_allocation_table.cc
deleted file mode 100644
index dcdb825..0000000
--- a/src/trace_processor/heap_profile_allocation_table.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/heap_profile_allocation_table.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-HeapProfileAllocationTable::HeapProfileAllocationTable(
-    sqlite3*,
-    const TraceStorage* storage)
-    : storage_(storage) {}
-
-void HeapProfileAllocationTable::RegisterTable(sqlite3* db,
-                                               const TraceStorage* storage) {
-  SqliteTable::Register<HeapProfileAllocationTable>(db, storage,
-                                                    "heap_profile_allocation");
-}
-
-StorageSchema HeapProfileAllocationTable::CreateStorageSchema() {
-  const auto& allocs = storage_->heap_profile_allocations();
-  return StorageSchema::Builder()
-      .AddGenericNumericColumn("id", RowAccessor())
-      .AddOrderedNumericColumn("ts", &allocs.timestamps())
-      .AddNumericColumn("upid", &allocs.upids())
-      .AddNumericColumn("callsite_id", &allocs.callsite_ids())
-      .AddNumericColumn("count", &allocs.counts())
-      .AddNumericColumn("size", &allocs.sizes())
-      .Build({"id"});
-}
-
-uint32_t HeapProfileAllocationTable::RowCount() {
-  return storage_->heap_profile_allocations().size();
-}
-
-int HeapProfileAllocationTable::BestIndex(const QueryConstraints& qc,
-                                          BestIndexInfo* info) {
-  info->sqlite_omit_order_by = true;
-  info->estimated_cost = HasEqConstraint(qc, "id") ? 1 : RowCount();
-  return SQLITE_OK;
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/heap_profile_allocation_table.h b/src/trace_processor/heap_profile_allocation_table.h
deleted file mode 100644
index 9f7fd44..0000000
--- a/src/trace_processor/heap_profile_allocation_table.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_HEAP_PROFILE_ALLOCATION_TABLE_H_
-#define SRC_TRACE_PROCESSOR_HEAP_PROFILE_ALLOCATION_TABLE_H_
-
-#include "src/trace_processor/storage_table.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-class HeapProfileAllocationTable : public StorageTable {
- public:
-  static void RegisterTable(sqlite3* db, const TraceStorage* storage);
-
-  HeapProfileAllocationTable(sqlite3*, const TraceStorage*);
-
-  // StorageTable implementation.
-  StorageSchema CreateStorageSchema() override;
-  uint32_t RowCount() override;
-  int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
-
- private:
-  const TraceStorage* const storage_;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_HEAP_PROFILE_ALLOCATION_TABLE_H_
diff --git a/src/trace_processor/heap_profile_tracker.cc b/src/trace_processor/heap_profile_tracker.cc
index dc637c9..a666dad 100644
--- a/src/trace_processor/heap_profile_tracker.cc
+++ b/src/trace_processor/heap_profile_tracker.cc
@@ -55,27 +55,27 @@
   UniquePid upid = context_->process_tracker->GetOrCreateProcess(
       static_cast<uint32_t>(alloc.pid));
 
-  TraceStorage::HeapProfileAllocations::Row alloc_row{
+  tables::HeapProfileAllocationTable::Row alloc_row{
       alloc.timestamp, upid, callstack_id,
       static_cast<int64_t>(alloc.alloc_count),
       static_cast<int64_t>(alloc.self_allocated)};
 
-  TraceStorage::HeapProfileAllocations::Row free_row{
+  tables::HeapProfileAllocationTable::Row free_row{
       alloc.timestamp, upid, callstack_id,
       -static_cast<int64_t>(alloc.free_count),
       -static_cast<int64_t>(alloc.self_freed)};
 
-  TraceStorage::HeapProfileAllocations::Row alloc_delta = alloc_row;
-  TraceStorage::HeapProfileAllocations::Row free_delta = free_row;
+  tables::HeapProfileAllocationTable::Row alloc_delta = alloc_row;
+  tables::HeapProfileAllocationTable::Row free_delta = free_row;
 
   auto prev_alloc_it = sequence_state.prev_alloc.find({upid, callstack_id});
   if (prev_alloc_it == sequence_state.prev_alloc.end()) {
     std::tie(prev_alloc_it, std::ignore) = sequence_state.prev_alloc.emplace(
         std::make_pair(upid, callstack_id),
-        TraceStorage::HeapProfileAllocations::Row{});
+        tables::HeapProfileAllocationTable::Row{});
   }
 
-  TraceStorage::HeapProfileAllocations::Row& prev_alloc = prev_alloc_it->second;
+  tables::HeapProfileAllocationTable::Row& prev_alloc = prev_alloc_it->second;
   alloc_delta.count -= prev_alloc.count;
   alloc_delta.size -= prev_alloc.size;
 
@@ -83,17 +83,19 @@
   if (prev_free_it == sequence_state.prev_free.end()) {
     std::tie(prev_free_it, std::ignore) = sequence_state.prev_free.emplace(
         std::make_pair(upid, callstack_id),
-        TraceStorage::HeapProfileAllocations::Row{});
+        tables::HeapProfileAllocationTable::Row{});
   }
 
-  TraceStorage::HeapProfileAllocations::Row& prev_free = prev_free_it->second;
+  tables::HeapProfileAllocationTable::Row& prev_free = prev_free_it->second;
   free_delta.count -= prev_free.count;
   free_delta.size -= prev_free.size;
 
   if (alloc_delta.count)
-    context_->storage->mutable_heap_profile_allocations()->Insert(alloc_delta);
+    context_->storage->mutable_heap_profile_allocation_table()->Insert(
+        alloc_delta);
   if (free_delta.count)
-    context_->storage->mutable_heap_profile_allocations()->Insert(free_delta);
+    context_->storage->mutable_heap_profile_allocation_table()->Insert(
+        free_delta);
 
   prev_alloc = alloc_row;
   prev_free = free_row;
diff --git a/src/trace_processor/heap_profile_tracker.h b/src/trace_processor/heap_profile_tracker.h
index 30629b5..1d121aa 100644
--- a/src/trace_processor/heap_profile_tracker.h
+++ b/src/trace_processor/heap_profile_tracker.h
@@ -78,10 +78,10 @@
     std::vector<SourceAllocation> pending_allocs;
 
     std::unordered_map<std::pair<UniquePid, int64_t>,
-                       TraceStorage::HeapProfileAllocations::Row>
+                       tables::HeapProfileAllocationTable::Row>
         prev_alloc;
     std::unordered_map<std::pair<UniquePid, int64_t>,
-                       TraceStorage::HeapProfileAllocations::Row>
+                       tables::HeapProfileAllocationTable::Row>
         prev_free;
 
     uint64_t last_profile_packet_index = 0;
diff --git a/src/trace_processor/heap_profile_tracker_unittest.cc b/src/trace_processor/heap_profile_tracker_unittest.cc
index ce978bf..f7c3734 100644
--- a/src/trace_processor/heap_profile_tracker_unittest.cc
+++ b/src/trace_processor/heap_profile_tracker_unittest.cc
@@ -185,7 +185,7 @@
                       int64_t parent,
                       int64_t frame_id) {
   const auto& callsites = storage.stack_profile_callsite_table();
-  for (uint32_t i = 0; i < callsites.size(); ++i) {
+  for (uint32_t i = 0; i < callsites.row_count(); ++i) {
     if (callsites.depth()[i] == depth && callsites.parent_id()[i] == parent &&
         callsites.frame_id()[i] == frame_id) {
       return static_cast<int64_t>(i);
diff --git a/src/trace_processor/binder_tracker.cc b/src/trace_processor/importers/ftrace/binder_tracker.cc
similarity index 95%
rename from src/trace_processor/binder_tracker.cc
rename to src/trace_processor/importers/ftrace/binder_tracker.cc
index 2bc3953..10bab08 100644
--- a/src/trace_processor/binder_tracker.cc
+++ b/src/trace_processor/importers/ftrace/binder_tracker.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/binder_tracker.h"
+#include "src/trace_processor/importers/ftrace/binder_tracker.h"
 
 #include "perfetto/base/compiler.h"
 
diff --git a/src/trace_processor/binder_tracker.h b/src/trace_processor/importers/ftrace/binder_tracker.h
similarity index 86%
rename from src/trace_processor/binder_tracker.h
rename to src/trace_processor/importers/ftrace/binder_tracker.h
index c3f1994..6b50fe2 100644
--- a/src/trace_processor/binder_tracker.h
+++ b/src/trace_processor/importers/ftrace/binder_tracker.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_BINDER_TRACKER_H_
-#define SRC_TRACE_PROCESSOR_BINDER_TRACKER_H_
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_BINDER_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_BINDER_TRACKER_H_
 
 #include <stdint.h>
 
@@ -45,4 +45,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_BINDER_TRACKER_H_
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_BINDER_TRACKER_H_
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index c07fd8d..4335a84 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -19,7 +19,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/protozero/proto_decoder.h"
 #include "src/trace_processor/args_tracker.h"
-#include "src/trace_processor/binder_tracker.h"
+#include "src/trace_processor/importers/ftrace/binder_tracker.h"
 #include "src/trace_processor/importers/systrace/systrace_parser.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/stats.h"
@@ -60,6 +60,7 @@
 
 FtraceParser::FtraceParser(TraceProcessorContext* context)
     : context_(context),
+      binder_tracker_(context),
       sched_wakeup_name_id_(context->storage->InternString("sched_wakeup")),
       sched_waking_name_id_(context->storage->InternString("sched_waking")),
       cpu_freq_name_id_(context->storage->InternString("cpufreq")),
@@ -188,20 +189,21 @@
                                             const TimestampedTracePiece& ttp) {
   using protos::pbzero::FtraceEvent;
   int64_t ts = ttp.timestamp;
+  SchedEventTracker* sched_tracker = SchedEventTracker::GetOrCreate(context_);
 
   // Handle the (optional) alternative encoding format for sched_switch.
   if (ttp.inline_event.type == InlineEvent::Type::kSchedSwitch) {
     const auto& event = ttp.inline_event.sched_switch;
-    context_->sched_tracker->PushSchedSwitchCompact(
-        cpu, ts, event.prev_state, static_cast<uint32_t>(event.next_pid),
-        event.next_prio, event.next_comm);
+    sched_tracker->PushSchedSwitchCompact(cpu, ts, event.prev_state,
+                                          static_cast<uint32_t>(event.next_pid),
+                                          event.next_prio, event.next_comm);
     return util::OkStatus();
   }
 
   // Handle the (optional) alternative encoding format for sched_waking.
   if (ttp.inline_event.type == InlineEvent::Type::kSchedWaking) {
     const auto& event = ttp.inline_event.sched_waking;
-    context_->sched_tracker->PushSchedWakingCompact(
+    sched_tracker->PushSchedWakingCompact(
         cpu, ts, static_cast<uint32_t>(event.pid), event.target_cpu, event.prio,
         event.comm);
     return util::OkStatus();
@@ -465,7 +467,7 @@
   protos::pbzero::SchedSwitchFtraceEvent::Decoder ss(blob.data, blob.size);
   uint32_t prev_pid = static_cast<uint32_t>(ss.prev_pid());
   uint32_t next_pid = static_cast<uint32_t>(ss.next_pid());
-  context_->sched_tracker->PushSchedSwitch(
+  SchedEventTracker::GetOrCreate(context_)->PushSchedSwitch(
       cpu, ts, prev_pid, ss.prev_comm(), ss.prev_prio(), ss.prev_state(),
       next_pid, ss.next_comm(), ss.next_prio());
 }
@@ -523,14 +525,14 @@
 
 void FtraceParser::ParsePrint(int64_t ts, uint32_t pid, ConstBytes blob) {
   protos::pbzero::PrintFtraceEvent::Decoder evt(blob.data, blob.size);
-  context_->systrace_parser->ParsePrintEvent(ts, pid, evt.buf());
+  SystraceParser::GetOrCreate(context_)->ParsePrintEvent(ts, pid, evt.buf());
 }
 
 void FtraceParser::ParseZero(int64_t ts, uint32_t pid, ConstBytes blob) {
   protos::pbzero::ZeroFtraceEvent::Decoder evt(blob.data, blob.size);
   uint32_t tgid = static_cast<uint32_t>(evt.pid());
-  context_->systrace_parser->ParseZeroEvent(ts, pid, evt.flag(), evt.name(),
-                                            tgid, evt.value());
+  SystraceParser::GetOrCreate(context_)->ParseZeroEvent(
+      ts, pid, evt.flag(), evt.name(), tgid, evt.value());
 }
 
 void FtraceParser::ParseSdeTracingMarkWrite(int64_t ts,
@@ -544,7 +546,7 @@
   }
 
   uint32_t tgid = static_cast<uint32_t>(evt.pid());
-  context_->systrace_parser->ParseSdeTracingMarkWrite(
+  SystraceParser::GetOrCreate(context_)->ParseSdeTracingMarkWrite(
       ts, pid, static_cast<char>(evt.trace_type()), evt.trace_begin(),
       evt.trace_name(), tgid, evt.value());
 }
@@ -707,10 +709,11 @@
   uint32_t syscall_num = static_cast<uint32_t>(evt.id());
   UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
 
+  SyscallTracker* syscall_tracker = SyscallTracker::GetOrCreate(context_);
   if (is_enter) {
-    context_->syscall_tracker->Enter(ts, utid, syscall_num);
+    syscall_tracker->Enter(ts, utid, syscall_num);
   } else {
-    context_->syscall_tracker->Exit(ts, utid, syscall_num);
+    syscall_tracker->Exit(ts, utid, syscall_num);
   }
 
   // We are reusing the same function for sys_enter and sys_exit.
@@ -768,7 +771,7 @@
                                           ConstBytes blob) {
   protos::pbzero::BinderTransactionFtraceEvent::Decoder evt(blob.data,
                                                             blob.size);
-  context_->binder_tracker->Transaction(timestamp, pid);
+  binder_tracker_.Transaction(timestamp, pid);
 }
 
 void FtraceParser::ParseBinderTransactionReceived(int64_t timestamp,
@@ -776,7 +779,7 @@
                                                   ConstBytes blob) {
   protos::pbzero::BinderTransactionReceivedFtraceEvent::Decoder evt(blob.data,
                                                                     blob.size);
-  context_->binder_tracker->TransactionReceived(timestamp, pid);
+  binder_tracker_.TransactionReceived(timestamp, pid);
 }
 
 void FtraceParser::ParseBinderTransactionAllocBuf(int64_t timestamp,
@@ -784,28 +787,28 @@
                                                   ConstBytes blob) {
   protos::pbzero::BinderTransactionAllocBufFtraceEvent::Decoder evt(blob.data,
                                                                     blob.size);
-  context_->binder_tracker->TransactionAllocBuf(timestamp, pid);
+  binder_tracker_.TransactionAllocBuf(timestamp, pid);
 }
 
 void FtraceParser::ParseBinderLocked(int64_t timestamp,
                                      uint32_t pid,
                                      ConstBytes blob) {
   protos::pbzero::BinderLockedFtraceEvent::Decoder evt(blob.data, blob.size);
-  context_->binder_tracker->Locked(timestamp, pid);
+  binder_tracker_.Locked(timestamp, pid);
 }
 
 void FtraceParser::ParseBinderLock(int64_t timestamp,
                                    uint32_t pid,
                                    ConstBytes blob) {
   protos::pbzero::BinderLockFtraceEvent::Decoder evt(blob.data, blob.size);
-  context_->binder_tracker->Lock(timestamp, pid);
+  binder_tracker_.Lock(timestamp, pid);
 }
 
 void FtraceParser::ParseBinderUnlock(int64_t timestamp,
                                      uint32_t pid,
                                      ConstBytes blob) {
   protos::pbzero::BinderUnlockFtraceEvent::Decoder evt(blob.data, blob.size);
-  context_->binder_tracker->Unlock(timestamp, pid);
+  binder_tracker_.Unlock(timestamp, pid);
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index 15bc2ef..6dfdfb7 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -19,6 +19,7 @@
 
 #include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/event_tracker.h"
+#include "src/trace_processor/importers/ftrace/binder_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
 #include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
@@ -94,6 +95,7 @@
                          protozero::ConstBytes);
 
   TraceProcessorContext* context_;
+  BinderTracker binder_tracker_;
 
   const StringId sched_wakeup_name_id_;
   const StringId sched_waking_name_id_;
diff --git a/src/trace_processor/importers/ftrace/sched_event_tracker.h b/src/trace_processor/importers/ftrace/sched_event_tracker.h
index 84ebd53..7a141ff 100644
--- a/src/trace_processor/importers/ftrace/sched_event_tracker.h
+++ b/src/trace_processor/importers/ftrace/sched_event_tracker.h
@@ -22,21 +22,29 @@
 
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/utils.h"
+#include "src/trace_processor/destructible.h"
+#include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/trace_storage.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-class TraceProcessorContext;
 class EventTracker;
 
 // Tracks sched events and stores them into the storage as sched slices.
-class SchedEventTracker {
+class SchedEventTracker : public Destructible {
  public:
+  // Declared public for testing only.
   explicit SchedEventTracker(TraceProcessorContext*);
   SchedEventTracker(const SchedEventTracker&) = delete;
   SchedEventTracker& operator=(const SchedEventTracker&) = delete;
-  virtual ~SchedEventTracker();
+  ~SchedEventTracker() override;
+  static SchedEventTracker* GetOrCreate(TraceProcessorContext* context) {
+    if (!context->sched_tracker) {
+      context->sched_tracker.reset(new SchedEventTracker(context));
+    }
+    return static_cast<SchedEventTracker*>(context->sched_tracker.get());
+  }
 
   // This method is called when a sched_switch event is seen in the trace.
   // Virtual for testing.
diff --git a/src/trace_processor/importers/proto/heap_graph_module.cc b/src/trace_processor/importers/proto/heap_graph_module.cc
index 3004131..49d2f02 100644
--- a/src/trace_processor/importers/proto/heap_graph_module.cc
+++ b/src/trace_processor/importers/proto/heap_graph_module.cc
@@ -92,8 +92,7 @@
 using perfetto::protos::pbzero::TracePacket;
 
 HeapGraphModule::HeapGraphModule(TraceProcessorContext* context)
-    : context_(context) {
-  context_->heap_graph_tracker.reset(new HeapGraphTracker(context_));
+    : context_(context), heap_graph_tracker_(context) {
   RegisterForField(TracePacket::kHeapGraphFieldNumber, context);
   RegisterForField(TracePacket::kDeobfuscationMappingFieldNumber, context);
 }
@@ -119,7 +118,7 @@
   protos::pbzero::HeapGraph::Decoder heap_graph(blob.data, blob.size);
   UniquePid upid = context_->process_tracker->GetOrCreateProcess(
       static_cast<uint32_t>(heap_graph.pid()));
-  context_->heap_graph_tracker->SetPacketIndex(seq_id, heap_graph.index());
+  heap_graph_tracker_.SetPacketIndex(seq_id, heap_graph.index());
   for (auto it = heap_graph.objects(); it; ++it) {
     protos::pbzero::HeapGraphObject::Decoder object(*it);
     HeapGraphTracker::SourceObject obj;
@@ -157,14 +156,14 @@
       ref.owned_object_id = object_ids[i];
       obj.references.emplace_back(std::move(ref));
     }
-    context_->heap_graph_tracker->AddObject(seq_id, upid, ts, std::move(obj));
+    heap_graph_tracker_.AddObject(seq_id, upid, ts, std::move(obj));
   }
   for (auto it = heap_graph.type_names(); it; ++it) {
     protos::pbzero::InternedString::Decoder entry(*it);
     const char* str = reinterpret_cast<const char*>(entry.str().data);
     auto str_view = base::StringView(str, entry.str().size);
 
-    context_->heap_graph_tracker->AddInternedTypeName(
+    heap_graph_tracker_.AddInternedTypeName(
         seq_id, entry.iid(), context_->storage->InternString(str_view));
   }
   for (auto it = heap_graph.field_names(); it; ++it) {
@@ -172,7 +171,7 @@
     const char* str = reinterpret_cast<const char*>(entry.str().data);
     auto str_view = base::StringView(str, entry.str().size);
 
-    context_->heap_graph_tracker->AddInternedFieldName(
+    heap_graph_tracker_.AddInternedFieldName(
         seq_id, entry.iid(), context_->storage->InternString(str_view));
   }
   for (auto it = heap_graph.roots(); it; ++it) {
@@ -192,11 +191,10 @@
           stats::heap_graph_malformed_packet, static_cast<int>(upid));
       break;
     }
-    context_->heap_graph_tracker->AddRoot(seq_id, upid, ts,
-                                          std::move(src_root));
+    heap_graph_tracker_.AddRoot(seq_id, upid, ts, std::move(src_root));
   }
   if (!heap_graph.continued()) {
-    context_->heap_graph_tracker->FinalizeProfile(seq_id);
+    heap_graph_tracker_.FinalizeProfile(seq_id);
   }
 }
 
@@ -214,7 +212,7 @@
                     cls.obfuscated_name().ToStdString().c_str());
     } else {
       const std::vector<int64_t>* cls_objects =
-          context_->heap_graph_tracker->RowsForType(*obfuscated_class_name_id);
+          heap_graph_tracker_.RowsForType(*obfuscated_class_name_id);
 
       if (cls_objects) {
         auto interned_deobfuscated_name =
@@ -247,7 +245,7 @@
       }
 
       const std::vector<int64_t>* field_references =
-          context_->heap_graph_tracker->RowsForField(*obfuscated_field_name_id);
+          heap_graph_tracker_.RowsForField(*obfuscated_field_name_id);
       if (field_references) {
         auto interned_deobfuscated_name = context_->storage->InternString(
             base::StringView(merged_deobfuscated));
diff --git a/src/trace_processor/importers/proto/heap_graph_module.h b/src/trace_processor/importers/proto/heap_graph_module.h
index 2fde80e..492ba60 100644
--- a/src/trace_processor/importers/proto/heap_graph_module.h
+++ b/src/trace_processor/importers/proto/heap_graph_module.h
@@ -40,6 +40,7 @@
   void ParseDeobfuscationMapping(protozero::ConstBytes);
 
   TraceProcessorContext* context_;
+  HeapGraphTracker heap_graph_tracker_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index 930528d..7e8ca92 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -117,10 +117,11 @@
          /*reachable=*/0, /*type_name=*/type_name,
          /*deobfuscated_type_name=*/base::nullopt,
          /*root_type=*/base::nullopt});
-    int64_t row = context_->storage->heap_graph_object_table().size() - 1;
+    int64_t row = context_->storage->heap_graph_object_table().row_count() - 1;
     sequence_state.object_id_to_row.emplace(obj.object_id, row);
     class_to_rows_[type_name].emplace_back(row);
-    sequence_state.walker.AddNode(row, obj.self_size);
+    sequence_state.walker.AddNode(row, obj.self_size,
+                                  static_cast<int32_t>(type_name.id));
   }
 
   for (const SourceObject& obj : sequence_state.current_objects) {
@@ -130,7 +131,7 @@
     int64_t owner_row = it->second;
 
     int64_t reference_set_id =
-        context_->storage->heap_graph_reference_table().size();
+        context_->storage->heap_graph_reference_table().row_count();
     std::set<int64_t> seen_owned;
     for (const SourceObject::Reference& ref : obj.references) {
       // This is true for unset reference fields.
@@ -161,7 +162,8 @@
       context_->storage->mutable_heap_graph_reference_table()->Insert(
           {reference_set_id, owner_row, owned_row, field_name,
            /*deobfuscated_field_name=*/base::nullopt});
-      int64_t row = context_->storage->heap_graph_reference_table().size() - 1;
+      int64_t row =
+          context_->storage->heap_graph_reference_table().row_count() - 1;
       field_to_rows_[field_name].emplace_back(row);
     }
     context_->storage->mutable_heap_graph_object_table()
@@ -185,10 +187,46 @@
     }
   }
 
-  sequence_state.walker.CalculateRetained();
+  TraceStorage::StackProfileMappings::Row mapping_row{};
+  mapping_row.name_id = context_->storage->InternString("JAVA");
+  uint32_t mapping_id =
+      context_->storage->mutable_stack_profile_mappings()->Insert(mapping_row);
+
+  auto paths = sequence_state.walker.FindPathsFromRoot();
+  for (const auto& p : paths.children)
+    WriteFlamegraph(sequence_state, p.second, -1, 0, mapping_id);
+
   sequence_state_.erase(seq_id);
 }
 
+void HeapGraphTracker::WriteFlamegraph(
+    const SequenceState& sequence_state,
+    const HeapGraphWalker::PathFromRoot& path,
+    int32_t parent_id,
+    uint32_t depth,
+    uint32_t mapping_id) {
+  TraceStorage::StackProfileFrames::Row row{};
+  row.name_id = StringId(static_cast<uint32_t>(path.class_name));
+  row.mapping_row = mapping_id;
+  int32_t frame_id = static_cast<int32_t>(
+      context_->storage->mutable_stack_profile_frames()->Insert(row));
+
+  parent_id = static_cast<int32_t>(
+      context_->storage->mutable_stack_profile_callsite_table()->Insert(
+          {depth, parent_id, frame_id}));
+  depth++;
+
+  tables::HeapProfileAllocationTable::Row alloc_row{
+      sequence_state.current_ts, sequence_state.current_upid, parent_id,
+      static_cast<int64_t>(path.count), static_cast<int64_t>(path.size)};
+  // TODO(fmayer): Maybe add a separate table for heap graph flamegraphs.
+  context_->storage->mutable_heap_profile_allocation_table()->Insert(alloc_row);
+  for (const auto& p : path.children) {
+    const HeapGraphWalker::PathFromRoot& child = p.second;
+    WriteFlamegraph(sequence_state, child, parent_id, depth, mapping_id);
+  }
+}
+
 void HeapGraphTracker::MarkReachable(int64_t row) {
   context_->storage->mutable_heap_graph_object_table()
       ->mutable_reachable()
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.h b/src/trace_processor/importers/proto/heap_graph_tracker.h
index 86ca98d..219059b 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.h
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.h
@@ -104,6 +104,12 @@
   SequenceState& GetOrCreateSequence(uint32_t seq_id);
   bool SetPidAndTimestamp(SequenceState* seq, UniquePid upid, int64_t ts);
 
+  void WriteFlamegraph(const SequenceState& sequence_state,
+                       const HeapGraphWalker::PathFromRoot& path,
+                       int32_t parent_id,
+                       uint32_t depth,
+                       uint32_t mapping_id);
+
   TraceProcessorContext* const context_;
   std::map<uint32_t, SequenceState> sequence_state_;
 
diff --git a/src/trace_processor/importers/proto/heap_graph_walker.cc b/src/trace_processor/importers/proto/heap_graph_walker.cc
index 020d3b4..90fd398 100644
--- a/src/trace_processor/importers/proto/heap_graph_walker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_walker.cc
@@ -17,6 +17,8 @@
 #include "src/trace_processor/importers/proto/heap_graph_walker.h"
 #include "perfetto/base/logging.h"
 
+#include <deque>
+
 namespace perfetto {
 namespace trace_processor {
 namespace {
@@ -57,12 +59,13 @@
 
 HeapGraphWalker::Delegate::~Delegate() = default;
 
-void HeapGraphWalker::AddNode(int64_t row, uint64_t size) {
+void HeapGraphWalker::AddNode(int64_t row, uint64_t size, int32_t class_name) {
   if (static_cast<size_t>(row) >= nodes_.size())
     nodes_.resize(static_cast<size_t>(row) + 1);
   Node& node = GetNode(row);
   node.self_size = size;
   node.row = row;
+  node.class_name = class_name;
 }
 
 void HeapGraphWalker::AddEdge(int64_t owner_row, int64_t owned_row) {
@@ -74,14 +77,33 @@
 }
 
 void HeapGraphWalker::MarkRoot(int64_t row) {
-  Node& n = GetNode(row);
-  n.root = true;
-  ReachableNode(&n);
+  Node* node = &GetNode(row);
+  roots_.emplace_back(node);
+
+  // Calculate shortest distance to a GC root.
+  std::deque<std::pair<int32_t, Node*>> reachable_nodes{{0, node}};
+  while (!reachable_nodes.empty()) {
+    Node* cur_node;
+    int32_t distance;
+    std::tie(distance, cur_node) = reachable_nodes.front();
+    reachable_nodes.pop_front();
+    if (cur_node->distance_to_root == -1 ||
+        cur_node->distance_to_root > distance) {
+      if (cur_node->distance_to_root == -1)
+        delegate_->MarkReachable(cur_node->row);
+      cur_node->distance_to_root = distance;
+      for (Node* child_node : cur_node->children) {
+        if (child_node->distance_to_root == -1 ||
+            child_node->distance_to_root > distance + 1)
+          reachable_nodes.emplace_back(distance + 1, child_node);
+      }
+    }
+  }
 }
 
 void HeapGraphWalker::CalculateRetained() {
   for (Node& n : nodes_) {
-    if (n.reachable && n.node_index == 0)
+    if (n.reachable() && n.node_index == 0)
       FindSCC(&n);
   }
 
@@ -90,22 +112,6 @@
     PERFETTO_CHECK(c.incoming_edges == 0);
 }
 
-void HeapGraphWalker::ReachableNode(Node* node) {
-  if (node->reachable)
-    return;
-  std::vector<Node*> reachable_nodes{node};
-  while (!reachable_nodes.empty()) {
-    Node* cur_node = reachable_nodes.back();
-    reachable_nodes.pop_back();
-    if (!cur_node->reachable) {
-      delegate_->MarkReachable(cur_node->row);
-      cur_node->reachable = true;
-      reachable_nodes.insert(reachable_nodes.end(), cur_node->children.cbegin(),
-                             cur_node->children.cend());
-    }
-  }
-}
-
 int64_t HeapGraphWalker::RetainedSize(const Component& component) {
   int64_t retained_size =
       static_cast<int64_t>(component.unique_retained_size) +
@@ -163,7 +169,7 @@
     // A node can never be part of two components.
     PERFETTO_CHECK(stack_elem->component == -1);
     stack_elem->component = component_id;
-    if (stack_elem->root)
+    if (stack_elem->root())
       component.root = true;
   } while (stack_elem != node);
 
@@ -171,7 +177,7 @@
     component.unique_retained_size += elem->self_size;
     for (Node* parent : elem->parents) {
       // We do not count intra-component edges.
-      if (parent->reachable && parent->component != component_id)
+      if (parent->reachable() && parent->component != component_id)
         component.incoming_edges++;
     }
     component.orig_incoming_edges = component.incoming_edges;
@@ -306,7 +312,7 @@
       walk_child.pop_back();
     } else {
       Node* child = node->children[child_idx++];
-      PERFETTO_CHECK(child->reachable);
+      PERFETTO_CHECK(child->reachable());
       if (child->node_index == 0) {
         walk_stack.emplace_back(child);
         walk_child.emplace_back(0);
@@ -317,5 +323,31 @@
   }
 }
 
+HeapGraphWalker::PathFromRoot HeapGraphWalker::FindPathsFromRoot() {
+  for (Node* root : roots_)
+    FindPathFromRoot(root, &path_);
+  for (Node& node : nodes_)
+    node.find_paths_from_root_visited = false;
+  return std::move(path_);
+}
+
+// TODO(fmayer): Teach this to handle field names.
+void HeapGraphWalker::FindPathFromRoot(Node* n, PathFromRoot* parent) {
+  PathFromRoot& cur = parent->children[n->class_name];
+  cur.size += n->self_size;
+  cur.count++;
+  cur.parent = parent;
+  cur.class_name = n->class_name;
+  for (Node* child : n->children) {
+    if (child->distance_to_root == n->distance_to_root + 1 &&
+        !child->find_paths_from_root_visited) {
+      // Mark as visited in case there is another path with the same distance
+      // from a root.
+      child->find_paths_from_root_visited = true;
+      FindPathFromRoot(child, &cur);
+    }
+  }
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/heap_graph_walker.h b/src/trace_processor/importers/proto/heap_graph_walker.h
index c05c6df..4ec3dc8 100644
--- a/src/trace_processor/importers/proto/heap_graph_walker.h
+++ b/src/trace_processor/importers/proto/heap_graph_walker.h
@@ -97,6 +97,14 @@
 
 class HeapGraphWalker {
  public:
+  struct PathFromRoot {
+    uint64_t size = 0;
+    uint64_t count = 0;
+    int32_t class_name = -1;
+    std::map<int32_t, PathFromRoot> children;
+    PathFromRoot* parent = nullptr;
+  };
+
   class Delegate {
    public:
     virtual ~Delegate();
@@ -109,7 +117,8 @@
   HeapGraphWalker(Delegate* delegate) : delegate_(delegate) {}
 
   void AddEdge(int64_t owner_row, int64_t owned_row);
-  void AddNode(int64_t row, uint64_t size);
+  void AddNode(int64_t row, uint64_t size) { AddNode(row, size, -1); }
+  void AddNode(int64_t row, uint64_t size, int32_t class_name);
 
   // Mark a a node as root. This marks all the nodes reachable from it as
   // reachable.
@@ -118,6 +127,8 @@
   // includes nodes not reachable from roots.
   void CalculateRetained();
 
+  PathFromRoot FindPathsFromRoot();
+
  private:
   struct Node {
     std::vector<Node*> children;
@@ -130,9 +141,14 @@
     uint64_t lowlink = 0;
     int64_t component = -1;
 
-    bool reachable = false;
+    int32_t class_name = -1;
+    int32_t distance_to_root = -1;
+
     bool on_stack = false;
-    bool root = false;
+    bool find_paths_from_root_visited = false;
+
+    bool root() { return distance_to_root == 0; }
+    bool reachable() { return distance_to_root >= 0; }
   };
 
   struct Component {
@@ -154,14 +170,16 @@
   void FoundSCC(Node*);
   int64_t RetainedSize(const Component&);
 
-  // Make node and all transitive children as reachable.
-  void ReachableNode(Node*);
+  void FindPathFromRoot(Node* n, PathFromRoot* parent);
 
   std::vector<Component> components_;
   std::vector<Node*> node_stack_;
   uint64_t next_node_index_ = 1;
   std::vector<Node> nodes_;
 
+  PathFromRoot path_;
+  std::vector<Node*> roots_;
+
   Delegate* delegate_;
 };
 
diff --git a/src/trace_processor/importers/proto/heap_graph_walker_unittest.cc b/src/trace_processor/importers/proto/heap_graph_walker_unittest.cc
index 7ec9c09..4b1b586 100644
--- a/src/trace_processor/importers/proto/heap_graph_walker_unittest.cc
+++ b/src/trace_processor/importers/proto/heap_graph_walker_unittest.cc
@@ -583,6 +583,76 @@
   });
 }
 
+bool HasPath(const HeapGraphWalker::PathFromRoot& path,
+             std::vector<int32_t> class_names) {
+  if (class_names.empty())
+    return true;
+  auto it = path.children.find(class_names[0]);
+  if (it == path.children.end())
+    return false;
+  class_names.erase(class_names.begin());
+  return HasPath(it->second, class_names);
+}
+
+//    1      |
+//    ^^     |
+//   /  \    |
+//  2<-> 3   |
+//  ^        |
+//  |        |
+//  4R       |
+TEST(HeapGraphWalkeTest, ShortestPath) {
+  HeapGraphWalkerTestDelegate delegate;
+  HeapGraphWalker walker(&delegate);
+  walker.AddNode(1, 1, 1);
+  walker.AddNode(2, 2, 2);
+  walker.AddNode(3, 3, 3);
+  walker.AddNode(4, 4, 4);
+
+  walker.AddEdge(2, 1);
+  walker.AddEdge(2, 3);
+  walker.AddEdge(3, 1);
+  walker.AddEdge(3, 2);
+  walker.AddEdge(4, 2);
+
+  walker.MarkRoot(4);
+  auto path = walker.FindPathsFromRoot();
+
+  EXPECT_TRUE(HasPath(path, {4, 2, 1}));
+  EXPECT_TRUE(HasPath(path, {4, 2, 3}));
+  EXPECT_FALSE(HasPath(path, {4, 2, 3, 1}));
+}
+
+//    1      |
+//    ^^     |
+//   /  \    |
+//  2R<->3   |
+//  ^        |
+//  |        |
+//  4R       |
+TEST(HeapGraphWalkeTest, ShortestPathMultipleRoots) {
+  HeapGraphWalkerTestDelegate delegate;
+  HeapGraphWalker walker(&delegate);
+  walker.AddNode(1, 1, 1);
+  walker.AddNode(2, 2, 2);
+  walker.AddNode(3, 3, 3);
+  walker.AddNode(4, 4, 4);
+
+  walker.AddEdge(2, 1);
+  walker.AddEdge(2, 3);
+  walker.AddEdge(3, 1);
+  walker.AddEdge(3, 2);
+  walker.AddEdge(4, 2);
+
+  walker.MarkRoot(4);
+  walker.MarkRoot(2);
+  auto path = walker.FindPathsFromRoot();
+
+  EXPECT_TRUE(HasPath(path, {2, 1}));
+  EXPECT_TRUE(HasPath(path, {2, 3}));
+  EXPECT_FALSE(HasPath(path, {4, 2, 3}));
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/packet_sequence_state.h b/src/trace_processor/importers/proto/packet_sequence_state.h
index b6bc5a6..c020758 100644
--- a/src/trace_processor/importers/proto/packet_sequence_state.h
+++ b/src/trace_processor/importers/proto/packet_sequence_state.h
@@ -29,6 +29,8 @@
 #include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/trace_storage.h"
 
+#include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
+
 namespace perfetto {
 namespace trace_processor {
 
@@ -43,51 +45,124 @@
 class PacketSequenceState {
  public:
   // Entry in an interning index, refers to the interned message.
-  struct InternedMessageView {
-    InternedMessageView(TraceBlobView msg) : message(std::move(msg)) {}
+  class InternedMessageView {
+   public:
+    InternedMessageView(TraceBlobView msg) : message_(std::move(msg)) {}
 
+    InternedMessageView(InternedMessageView&&) noexcept = default;
+    InternedMessageView& operator=(InternedMessageView&&) = default;
+
+    // Allow copy by cloning the TraceBlobView. This is required for
+    // UpdateTracePacketDefaults().
+    InternedMessageView(const InternedMessageView& view)
+        : message_(view.message_.slice(0, view.message_.length())) {}
+    InternedMessageView& operator=(const InternedMessageView& view) {
+      this->message_ = view.message_.slice(0, view.message_.length());
+      this->decoder_ = nullptr;
+      this->decoder_type_ = nullptr;
+      this->submessages_.clear();
+      return *this;
+    }
+
+    // Lazily initializes and returns the decoder object for the message. The
+    // decoder is stored in the InternedMessageView to avoid having to parse the
+    // message multiple times.
     template <typename MessageType>
     typename MessageType::Decoder* GetOrCreateDecoder() {
-      if (!decoder) {
+      if (!decoder_) {
         // Lazy init the decoder and save it away, so that we don't have to
         // reparse the message every time we access the interning entry.
-        decoder = std::unique_ptr<void, std::function<void(void*)>>(
-            new typename MessageType::Decoder(message.data(), message.length()),
+        decoder_ = std::unique_ptr<void, std::function<void(void*)>>(
+            new
+            typename MessageType::Decoder(message_.data(), message_.length()),
             [](void* obj) {
               delete reinterpret_cast<typename MessageType::Decoder*>(obj);
             });
-        decoder_type = PERFETTO_TYPE_IDENTIFIER;
+        decoder_type_ = PERFETTO_TYPE_IDENTIFIER;
       }
       // Verify that the type of the decoder didn't change.
       if (PERFETTO_TYPE_IDENTIFIER &&
-          strcmp(decoder_type,
+          strcmp(decoder_type_,
                  // GCC complains if this arg can be null.
                  PERFETTO_TYPE_IDENTIFIER ? PERFETTO_TYPE_IDENTIFIER : "") !=
               0) {
         PERFETTO_FATAL(
             "Interning entry accessed under different types! previous type: "
             "%s. new type: %s.",
-            decoder_type, __PRETTY_FUNCTION__);
+            decoder_type_, __PRETTY_FUNCTION__);
       }
-      return reinterpret_cast<typename MessageType::Decoder*>(decoder.get());
+      return reinterpret_cast<typename MessageType::Decoder*>(decoder_.get());
     }
 
-    TraceBlobView message;
-    std::unique_ptr<void, std::function<void(void*)>> decoder;
+    // Lookup a submessage of the interned message, which is then itself stored
+    // as InternedMessageView, so that we only need to parse it once. Returns
+    // nullptr if the field isn't set.
+    // TODO(eseckler): Support repeated fields.
+    template <typename MessageType, uint32_t FieldId>
+    InternedMessageView* GetOrCreateSubmessageView() {
+      auto it = submessages_.find(FieldId);
+      if (it != submessages_.end())
+        return it->second.get();
+      auto* decoder = GetOrCreateDecoder<MessageType>();
+      // Calls the at() template method on the decoder.
+      auto field = decoder->template at<FieldId>().as_bytes();
+      if (!field.data)
+        return nullptr;
+      const size_t offset = message_.offset_of(field.data);
+      TraceBlobView submessage = message_.slice(offset, field.size);
+      InternedMessageView* submessage_view =
+          new InternedMessageView(std::move(submessage));
+      submessages_.emplace_hint(
+          it, FieldId, std::unique_ptr<InternedMessageView>(submessage_view));
+      return submessage_view;
+    }
+
+    const TraceBlobView& message() { return message_; }
 
    private:
-    const char* decoder_type = nullptr;
+    using SubMessageViewMap =
+        std::unordered_map<uint32_t /*field_id*/,
+                           std::unique_ptr<InternedMessageView>>;
+
+    TraceBlobView message_;
+
+    // Stores the decoder for the message_, so that the message does not have to
+    // be re-decoded every time the interned message is looked up. Lazily
+    // initialized in GetOrCreateDecoder(). Since we don't know the type of the
+    // decoder until GetOrCreateDecoder() is called, we store the decoder as a
+    // void* unique_pointer with a destructor function that's supplied in
+    // GetOrCreateDecoder() when the decoder is created.
+    std::unique_ptr<void, std::function<void(void*)>> decoder_;
+
+    // Type identifier for the decoder. Only valid in debug builds and on
+    // supported platforms. Used to verify that GetOrCreateDecoder() is always
+    // called with the same template argument.
+    const char* decoder_type_ = nullptr;
+
+    // Views of submessages of the interned message. Submessages are lazily
+    // added by GetOrCreateSubmessageView(). By storing submessages and their
+    // decoders, we avoid having to decode submessages multiple times if they
+    // looked up often.
+    SubMessageViewMap submessages_;
   };
 
   using InternedMessageMap =
       std::unordered_map<uint64_t /*iid*/, InternedMessageView>;
   using InternedFieldMap =
       std::unordered_map<uint32_t /*field_id*/, InternedMessageMap>;
-  using InternedDataGenerationList = std::vector<InternedFieldMap>;
+
+  struct GenerationData {
+    InternedFieldMap interned_data;
+    base::Optional<InternedMessageView> trace_packet_defaults;
+  };
+
+  // TODO(eseckler): Reference count the generations so that we can get rid of
+  // past generations once all packets referring to them have been parsed.
+  using GenerationList = std::vector<GenerationData>;
 
   PacketSequenceState(TraceProcessorContext* context)
       : context_(context), stack_profile_tracker_(context) {
-    interned_data_.emplace_back();
+    generations_.emplace_back();
   }
 
   int64_t IncrementAndGetTrackEventTimeNs(int64_t delta_ns) {
@@ -115,7 +190,7 @@
 
   void OnIncrementalStateCleared() {
     packet_loss_ = false;
-    interned_data_.emplace_back();  // Bump generation number
+    generations_.emplace_back();  // Bump generation number
   }
 
   void SetThreadDescriptor(int32_t pid,
@@ -138,9 +213,8 @@
     return stack_profile_tracker_;
   }
 
-  // Returns the index of the current generation in the
-  // InternedDataGenerationList.
-  size_t current_generation() const { return interned_data_.size() - 1; }
+  // Returns the index of the current generation in the GenerationList.
+  size_t current_generation() const { return generations_.size() - 1; }
 
   bool track_event_timestamps_valid() const {
     return track_event_timestamps_valid_;
@@ -167,7 +241,7 @@
     }
     iid = field.as_uint64();
 
-    auto* map = &interned_data_.back()[field_id];
+    auto* map = &generations_.back().interned_data[field_id];
     auto res = map->emplace(iid, InternedMessageView(std::move(message)));
 
     // If a message with this ID is already interned in the same generation,
@@ -176,16 +250,18 @@
     // TODO(eseckler): This DCHECK assumes that the message is encoded the
     // same way if it is re-emitted.
     PERFETTO_DCHECK(res.second ||
-                    (res.first->second.message.length() == message_size &&
-                     memcmp(res.first->second.message.data(), message_start,
+                    (res.first->second.message().length() == message_size &&
+                     memcmp(res.first->second.message().data(), message_start,
                             message_size) == 0));
   }
 
+  // Returns |nullptr| if the message with the given |iid| was not found (also
+  // records a stat in this case).
   template <uint32_t FieldId, typename MessageType>
   typename MessageType::Decoder* LookupInternedMessage(size_t generation,
                                                        uint64_t iid) {
-    PERFETTO_CHECK(generation <= interned_data_.size());
-    auto* field_map = &interned_data_[generation];
+    PERFETTO_CHECK(generation <= generations_.size());
+    auto* field_map = &generations_[generation].interned_data;
     auto field_it = field_map->find(FieldId);
     if (field_it != field_map->end()) {
       auto* message_map = &field_it->second;
@@ -201,6 +277,37 @@
     return nullptr;
   }
 
+  void UpdateTracePacketDefaults(TraceBlobView trace_packet_defaults) {
+    if (generations_.back().trace_packet_defaults) {
+      // The new defaults should only apply to subsequent messages on the
+      // sequence. Add a new generation with the updated defaults but the
+      // current generation's interned data state.
+      const InternedFieldMap& current_interned_data =
+          generations_.back().interned_data;
+      generations_.emplace_back();
+      generations_.back().interned_data = current_interned_data;
+    }
+    generations_.back().trace_packet_defaults =
+        InternedMessageView(std::move(trace_packet_defaults));
+  }
+
+  // Returns |nullptr| if no defaults were set in the given generation.
+  InternedMessageView* GetTracePacketDefaultsView(size_t generation) {
+    PERFETTO_CHECK(generation <= generations_.size());
+    if (!generations_[generation].trace_packet_defaults)
+      return nullptr;
+    return &generations_[generation].trace_packet_defaults.value();
+  }
+
+  // Returns |nullptr| if no defaults were set in the given generation.
+  typename protos::pbzero::TracePacketDefaults::Decoder* GetTracePacketDefaults(
+      size_t generation) {
+    InternedMessageView* view = GetTracePacketDefaultsView(generation);
+    if (!view)
+      return nullptr;
+    return view->GetOrCreateDecoder<protos::pbzero::TracePacketDefaults>();
+  }
+
  private:
   TraceProcessorContext* context_;
 
@@ -229,7 +336,7 @@
   int64_t track_event_thread_timestamp_ns_ = 0;
   int64_t track_event_thread_instruction_count_ = 0;
 
-  InternedDataGenerationList interned_data_;
+  GenerationList generations_;
   StackProfileTracker stack_profile_tracker_;
 };
 
diff --git a/src/trace_processor/importers/proto/proto_trace_parser.cc b/src/trace_processor/importers/proto/proto_trace_parser.cc
index c08b5e6..01c0156 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser.cc
@@ -623,7 +623,7 @@
   for (auto addr_it = module_symbols.address_symbols(); addr_it; ++addr_it) {
     protos::pbzero::AddressSymbols::Decoder address_symbols(*addr_it);
 
-    uint32_t symbol_set_id = context_->storage->symbol_table().size();
+    uint32_t symbol_set_id = context_->storage->symbol_table().row_count();
     bool frame_found = false;
     for (int64_t mapping_row : mapping_rows) {
       std::vector<int64_t> frame_rows =
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index 4d9150d..a2455ce 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -24,13 +24,9 @@
 #include "src/trace_processor/event_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
 #include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
-#include "src/trace_processor/importers/proto/android_probes_module.h"
-#include "src/trace_processor/importers/proto/heap_graph_module.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/importers/proto/proto_trace_parser.h"
-#include "src/trace_processor/importers/proto/system_probes_module.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
-#include "src/trace_processor/importers/systrace/systrace_parser.h"
 #include "src/trace_processor/metadata.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/register_additional_modules.h"
@@ -84,8 +80,6 @@
 using ::testing::Return;
 using ::testing::UnorderedElementsAreArray;
 
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE) || \
-    PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
 namespace {
 MATCHER_P(DoubleEq, exp, "Double matcher that satisfies -Wfloat-equal") {
   // The IEEE standard says that any comparison operation involving
@@ -97,15 +91,12 @@
   return fabs(d_arg - d_exp) < 1e-128;
 }
 }  // namespace
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE) ||
-        // PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
 
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
 class MockSchedEventTracker : public SchedEventTracker {
  public:
   MockSchedEventTracker(TraceProcessorContext* context)
       : SchedEventTracker(context) {}
-  virtual ~MockSchedEventTracker() = default;
 
   MOCK_METHOD9(PushSchedSwitch,
                void(uint32_t cpu,
@@ -251,7 +242,6 @@
     context_.sorter.reset(new TraceSorter(&context_, 0 /*window size*/));
     context_.parser.reset(new ProtoTraceParser(&context_));
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
-    context_.systrace_parser.reset(new SystraceParser(&context_));
     context_.modules.emplace_back(new FtraceModuleImpl(&context_));
 #else
     context_.modules.emplace_back(new FtraceModule());
@@ -259,15 +249,6 @@
     context_.ftrace_module =
         static_cast<FtraceModule*>(context_.modules.back().get());
 
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_HEAP_GRAPHS)
-    context_.modules.emplace_back(new HeapGraphModule(&context_));
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_HEAP_GRAPHS)
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_ANDROID_PROBES)
-    context_.modules.emplace_back(new AndroidProbesModule(&context_));
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_ANDROID_PROBES)
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
-    context_.modules.emplace_back(new SystemProbesModule(&context_));
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
     context_.modules.emplace_back(new TrackEventModule(&context_));
 
     RegisterAdditionalModules(&context_);
@@ -383,9 +364,6 @@
   static const char buf_value[] = "This is a print event";
   print->set_buf(buf_value);
 
-  EXPECT_CALL(*storage_, InternString(base::StringView(task_newtask)))
-      .Times(AtLeast(1));
-  EXPECT_CALL(*storage_, InternString(base::StringView(buf_value)));
   EXPECT_CALL(*process_, UpdateThread(123, 123));
 
   Tokenize();
@@ -611,8 +589,6 @@
 
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
 
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
-
 TEST_F(ProtoTraceParserTest, LoadMemInfo) {
   auto* packet = trace_.add_packet();
   uint64_t ts = 1000;
@@ -627,7 +603,7 @@
                                    DoubleEq(value * 1024.0), 0u));
   Tokenize();
 
-  EXPECT_EQ(context_.storage->track_table().size(), 1u);
+  EXPECT_EQ(context_.storage->track_table().row_count(), 1u);
 }
 
 TEST_F(ProtoTraceParserTest, LoadVmStats) {
@@ -644,7 +620,7 @@
               PushCounter(static_cast<int64_t>(ts), DoubleEq(value), 0u));
   Tokenize();
 
-  EXPECT_EQ(context_.storage->track_table().size(), 1u);
+  EXPECT_EQ(context_.storage->track_table().row_count(), 1u);
 }
 
 TEST_F(ProtoTraceParserTest, LoadProcessPacket) {
@@ -687,8 +663,6 @@
   Tokenize();
 }
 
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
-
 TEST_F(ProtoTraceParserTest, ThreadNameFromThreadDescriptor) {
   context_.sorter.reset(new TraceSorter(
       &context_, std::numeric_limits<int64_t>::max() /*window size*/));
@@ -1254,12 +1228,12 @@
   context_.sorter->ExtractEventsForced();
 
   // First track is for the thread; others are the async event tracks.
-  EXPECT_EQ(storage_->track_table().size(), 4u);
+  EXPECT_EQ(storage_->track_table().row_count(), 4u);
   EXPECT_EQ(storage_->track_table().name()[1], 2u);
   EXPECT_EQ(storage_->track_table().name()[2], 4u);
   EXPECT_EQ(storage_->track_table().name()[3], 4u);
 
-  EXPECT_EQ(storage_->process_track_table().size(), 3u);
+  EXPECT_EQ(storage_->process_track_table().row_count(), 3u);
   EXPECT_EQ(storage_->process_track_table().upid()[0], 1u);
   EXPECT_EQ(storage_->process_track_table().upid()[1], 1u);
   EXPECT_EQ(storage_->process_track_table().upid()[2], 1u);
@@ -1419,11 +1393,11 @@
 
   // First track is "Thread track 1"; second is "Async track 1", third is
   // "Thread track 2".
-  EXPECT_EQ(storage_->track_table().size(), 3u);
+  EXPECT_EQ(storage_->track_table().row_count(), 3u);
   EXPECT_EQ(storage_->track_table().name()[0], 10u);  // "Thread track 1"
   EXPECT_EQ(storage_->track_table().name()[1], 11u);  // "Async track 1"
   EXPECT_EQ(storage_->track_table().name()[2], 12u);  // "Thread track 2"
-  EXPECT_EQ(storage_->thread_track_table().size(), 2u);
+  EXPECT_EQ(storage_->thread_track_table().row_count(), 2u);
   EXPECT_EQ(storage_->thread_track_table().utid()[0], 1u);
   EXPECT_EQ(storage_->thread_track_table().utid()[1], 2u);
 
@@ -1456,8 +1430,8 @@
   context_.sorter->ExtractEventsForced();
 
   // Track tables shouldn't have changed.
-  EXPECT_EQ(storage_->track_table().size(), 3u);
-  EXPECT_EQ(storage_->thread_track_table().size(), 2u);
+  EXPECT_EQ(storage_->track_table().row_count(), 3u);
+  EXPECT_EQ(storage_->thread_track_table().row_count(), 2u);
 
   EXPECT_EQ(storage_->virtual_track_slices().slice_count(), 1u);
   EXPECT_EQ(storage_->virtual_track_slices().slice_ids()[0], 0u);
@@ -2462,7 +2436,6 @@
                           Variadic::String(3))}));
 }
 
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_ANDROID_PROBES)
 TEST_F(ProtoTraceParserTest, AndroidPackagesList) {
   auto* packet = trace_.add_packet();
   auto* pkg_list = packet->set_packages_list();
@@ -2543,7 +2516,6 @@
             false);
   EXPECT_EQ(find_arg(second_set_id, "version_code").int_value, 43);
 }
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_ANDROID_PROBES)
 
 TEST_F(ProtoTraceParserTest, ParseCPUProfileSamplesIntoTable) {
   {
diff --git a/src/trace_processor/importers/proto/proto_trace_tokenizer.cc b/src/trace_processor/importers/proto/proto_trace_tokenizer.cc
index 97d7689..8ac2b36 100644
--- a/src/trace_processor/importers/proto/proto_trace_tokenizer.cc
+++ b/src/trace_processor/importers/proto/proto_trace_tokenizer.cc
@@ -186,63 +186,8 @@
     return util::ErrStatus(
         "Failed to parse proto packet fully; the trace is probably corrupt.");
 
-  auto timestamp =
-      decoder.has_timestamp()
-          ? static_cast<int64_t>(decoder.timestamp())
-          : std::max(latest_timestamp_, context_->sorter->max_timestamp());
-
   const uint32_t seq_id = decoder.trusted_packet_sequence_id();
-
-  if ((decoder.has_chrome_events() || decoder.has_chrome_metadata()) &&
-      (!decoder.timestamp_clock_id() ||
-       decoder.timestamp_clock_id() ==
-           protos::pbzero::ClockSnapshot::Clock::MONOTONIC)) {
-    // Chrome event timestamps are in MONOTONIC domain, but may occur in traces
-    // where (a) no clock snapshots exist or (b) no clock_id is specified for
-    // their timestamps. Adjust to trace time if we have a clock snapshot.
-    // TODO(eseckler): Set timestamp_clock_id and emit ClockSnapshots in chrome
-    // and then remove this.
-    auto trace_ts = context_->clock_tracker->ToTraceTime(
-        protos::pbzero::ClockSnapshot::Clock::MONOTONIC, timestamp);
-    if (trace_ts.has_value())
-      timestamp = trace_ts.value();
-  } else if (decoder.timestamp_clock_id()) {
-    // If the TracePacket specifies a non-zero clock-id, translate the timestamp
-    // into the trace-time clock domain.
-    PERFETTO_DCHECK(decoder.has_timestamp());
-    ClockTracker::ClockId clock_id = decoder.timestamp_clock_id();
-    bool is_seq_scoped = ClockTracker::IsReservedSeqScopedClockId(clock_id);
-    if (is_seq_scoped) {
-      if (!seq_id) {
-        return util::ErrStatus(
-            "TracePacket specified a sequence-local clock id (%" PRIu32
-            ") but the TraceWriter's sequence_id is zero (the service is "
-            "probably too old)",
-            decoder.timestamp_clock_id());
-      }
-      clock_id = ClockTracker::SeqScopedClockIdToGlobal(
-          seq_id, decoder.timestamp_clock_id());
-    }
-    auto trace_ts = context_->clock_tracker->ToTraceTime(clock_id, timestamp);
-    if (!trace_ts.has_value()) {
-      // ToTraceTime() will increase the |clock_sync_failure| stat on failure.
-      static const char seq_extra_err[] =
-          " Because the clock id is sequence-scoped, the ClockSnapshot must be "
-          "emitted on the same TraceWriter sequence of the packet that refers "
-          "to that clock id.";
-      return util::ErrStatus(
-          "Failed to convert TracePacket's timestamp from clock_id=%" PRIu32
-          " seq_id=%" PRIu32
-          ". This is usually due to the lack of a prior ClockSnapshot proto.%s",
-          decoder.timestamp_clock_id(), seq_id,
-          is_seq_scoped ? seq_extra_err : "");
-    }
-    timestamp = trace_ts.value();
-  }
-  latest_timestamp_ = std::max(timestamp, latest_timestamp_);
-
-  auto* state = GetIncrementalStateForPacketSequence(
-      decoder.trusted_packet_sequence_id());
+  auto* state = GetIncrementalStateForPacketSequence(seq_id);
 
   uint32_t sequence_flags = decoder.sequence_flags();
 
@@ -254,6 +199,25 @@
     HandlePreviousPacketDropped(decoder);
   }
 
+  // It is important that we parse defaults before parsing other fields such as
+  // the timestamp, since the defaults could affect them.
+  if (decoder.has_trace_packet_defaults()) {
+    auto field = decoder.trace_packet_defaults();
+    const size_t offset = packet.offset_of(field.data);
+    ParseTracePacketDefaults(decoder, packet.slice(offset, field.size));
+  }
+
+  if (decoder.has_interned_data()) {
+    auto field = decoder.interned_data();
+    const size_t offset = packet.offset_of(field.data);
+    ParseInternedData(decoder, packet.slice(offset, field.size));
+  }
+
+  if (decoder.has_clock_snapshot()) {
+    return ParseClockSnapshot(decoder.clock_snapshot(),
+                              decoder.trusted_packet_sequence_id());
+  }
+
   if (decoder.sequence_flags() &
       protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE) {
     if (!seq_id) {
@@ -269,18 +233,70 @@
     }
   }
 
-  if (decoder.has_clock_snapshot()) {
-    return ParseClockSnapshot(decoder.clock_snapshot(),
-                              decoder.trusted_packet_sequence_id());
-  }
+  protos::pbzero::TracePacketDefaults::Decoder* defaults =
+      state->GetTracePacketDefaults(state->current_generation());
 
-  // TODO(eseckler): Parse TracePacketDefaults.
+  int64_t timestamp;
+  if (decoder.has_timestamp()) {
+    timestamp = static_cast<int64_t>(decoder.timestamp());
 
-  if (decoder.has_interned_data()) {
-    auto field = decoder.interned_data();
-    const size_t offset = packet.offset_of(field.data);
-    ParseInternedData(decoder, packet.slice(offset, field.size));
+    uint32_t timestamp_clock_id =
+        decoder.has_timestamp_clock_id()
+            ? decoder.timestamp_clock_id()
+            : (defaults ? defaults->timestamp_clock_id() : 0);
+
+    if ((decoder.has_chrome_events() || decoder.has_chrome_metadata()) &&
+        (!timestamp_clock_id ||
+         timestamp_clock_id ==
+             protos::pbzero::ClockSnapshot::Clock::MONOTONIC)) {
+      // Chrome event timestamps are in MONOTONIC domain, but may occur in
+      // traces where (a) no clock snapshots exist or (b) no clock_id is
+      // specified for their timestamps. Adjust to trace time if we have a clock
+      // snapshot.
+      // TODO(eseckler): Set timestamp_clock_id and emit ClockSnapshots in
+      // chrome and then remove this.
+      auto trace_ts = context_->clock_tracker->ToTraceTime(
+          protos::pbzero::ClockSnapshot::Clock::MONOTONIC, timestamp);
+      if (trace_ts.has_value())
+        timestamp = trace_ts.value();
+    } else if (timestamp_clock_id) {
+      // If the TracePacket specifies a non-zero clock-id, translate the
+      // timestamp into the trace-time clock domain.
+      ClockTracker::ClockId converted_clock_id = timestamp_clock_id;
+      bool is_seq_scoped =
+          ClockTracker::IsReservedSeqScopedClockId(converted_clock_id);
+      if (is_seq_scoped) {
+        if (!seq_id) {
+          return util::ErrStatus(
+              "TracePacket specified a sequence-local clock id (%" PRIu32
+              ") but the TraceWriter's sequence_id is zero (the service is "
+              "probably too old)",
+              timestamp_clock_id);
+        }
+        converted_clock_id =
+            ClockTracker::SeqScopedClockIdToGlobal(seq_id, timestamp_clock_id);
+      }
+      auto trace_ts =
+          context_->clock_tracker->ToTraceTime(converted_clock_id, timestamp);
+      if (!trace_ts.has_value()) {
+        // ToTraceTime() will increase the |clock_sync_failure| stat on failure.
+        static const char seq_extra_err[] =
+            " Because the clock id is sequence-scoped, the ClockSnapshot must "
+            "be emitted on the same TraceWriter sequence of the packet that "
+            "refers to that clock id.";
+        return util::ErrStatus(
+            "Failed to convert TracePacket's timestamp from clock_id=%" PRIu32
+            " seq_id=%" PRIu32
+            ". This is usually due to the lack of a prior ClockSnapshot "
+            "proto.%s",
+            timestamp_clock_id, seq_id, is_seq_scoped ? seq_extra_err : "");
+      }
+      timestamp = trace_ts.value();
+    }
+  } else {
+    timestamp = std::max(latest_timestamp_, context_->sorter->max_timestamp());
   }
+  latest_timestamp_ = std::max(timestamp, latest_timestamp_);
 
   auto& modules = context_->modules_by_field;
   for (uint32_t field_id = 1; field_id < modules.size(); ++field_id) {
@@ -380,6 +396,21 @@
       ->OnPacketLoss();
 }
 
+void ProtoTraceTokenizer::ParseTracePacketDefaults(
+    const protos::pbzero::TracePacket_Decoder& packet_decoder,
+    TraceBlobView trace_packet_defaults) {
+  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
+    PERFETTO_ELOG(
+        "TracePacketDefaults packet without trusted_packet_sequence_id");
+    context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
+    return;
+  }
+
+  auto* state = GetIncrementalStateForPacketSequence(
+      packet_decoder.trusted_packet_sequence_id());
+  state->UpdateTracePacketDefaults(std::move(trace_packet_defaults));
+}
+
 void ProtoTraceTokenizer::ParseInternedData(
     const protos::pbzero::TracePacket::Decoder& packet_decoder,
     TraceBlobView interned_data) {
diff --git a/src/trace_processor/importers/proto/proto_trace_tokenizer.h b/src/trace_processor/importers/proto/proto_trace_tokenizer.h
index bdefa2e..0612068 100644
--- a/src/trace_processor/importers/proto/proto_trace_tokenizer.h
+++ b/src/trace_processor/importers/proto/proto_trace_tokenizer.h
@@ -67,6 +67,8 @@
   void HandleIncrementalStateCleared(
       const protos::pbzero::TracePacket_Decoder&);
   void HandlePreviousPacketDropped(const protos::pbzero::TracePacket_Decoder&);
+  void ParseTracePacketDefaults(const protos::pbzero::TracePacket_Decoder&,
+                                TraceBlobView trace_packet_defaults);
   void ParseInternedData(const protos::pbzero::TracePacket_Decoder&,
                          TraceBlobView interned_data);
   PacketSequenceState* GetIncrementalStateForPacketSequence(
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 8fb1281..5beed38 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -290,10 +290,11 @@
     protos::pbzero::Utsname::Decoder utsname(utsname_blob.data,
                                              utsname_blob.size);
     base::StringView machine = utsname.machine();
+    SyscallTracker* syscall_tracker = SyscallTracker::GetOrCreate(context_);
     if (machine == "aarch64" || machine == "armv8l") {
-      context_->syscall_tracker->SetArchitecture(kAarch64);
+      syscall_tracker->SetArchitecture(kAarch64);
     } else if (machine == "x86_64") {
-      context_->syscall_tracker->SetArchitecture(kX86_64);
+      syscall_tracker->SetArchitecture(kX86_64);
     } else {
       PERFETTO_ELOG("Unknown architecture %s", machine.ToStdString().c_str());
     }
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 85507b3..a044ea7 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -28,6 +28,7 @@
 
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.h"
+#include "protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_keyed_service.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_legacy_ipc.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_user_event.pbzero.h"
@@ -145,6 +146,12 @@
           context->storage->InternString("legacy_ipc.line")),
       chrome_keyed_service_name_args_key_id_(
           context->storage->InternString("keyed_service.name")),
+      chrome_histogram_sample_name_hash_args_key_id_(
+          context->storage->InternString("histogram_sample.name_hash")),
+      chrome_histogram_sample_name_args_key_id_(
+          context->storage->InternString("histogram_sample.name")),
+      chrome_histogram_sample_sample_args_key_id_(
+          context->storage->InternString("histogram_sample.sample")),
       chrome_legacy_ipc_class_ids_{
           {context->storage->InternString("UNSPECIFIED"),
            context->storage->InternString("AUTOMATION"),
@@ -193,6 +200,21 @@
                                        ConstBytes blob) {
   using LegacyEvent = protos::pbzero::TrackEvent::LegacyEvent;
 
+  protos::pbzero::TrackEventDefaults::Decoder* defaults = nullptr;
+  auto* packet_defaults_view =
+      sequence_state->GetTracePacketDefaultsView(sequence_state_generation);
+  if (packet_defaults_view) {
+    auto* track_event_defaults_view =
+        packet_defaults_view
+            ->GetOrCreateSubmessageView<protos::pbzero::TracePacketDefaults,
+                                        protos::pbzero::TracePacketDefaults::
+                                            kTrackEventDefaultsFieldNumber>();
+    if (track_event_defaults_view) {
+      defaults = track_event_defaults_view
+                     ->GetOrCreateDecoder<protos::pbzero::TrackEventDefaults>();
+    }
+  }
+
   protos::pbzero::TrackEvent::Decoder event(blob.data, blob.size);
 
   const auto legacy_event_blob = event.legacy_event();
@@ -275,9 +297,14 @@
     name_id = storage->InternString(event.name());
   }
 
-  // TODO(eseckler): Also consider track_uuid from TrackEventDefaults.
-  // Fall back to the default descriptor track (uuid 0).
-  uint64_t track_uuid = event.has_track_uuid() ? event.track_uuid() : 0u;
+  // Consider track_uuid from the packet and TrackEventDefaults, fall back to
+  // the default descriptor track (uuid 0).
+  uint64_t track_uuid =
+      event.has_track_uuid()
+          ? event.track_uuid()
+          : (defaults && defaults->has_track_uuid() ? defaults->track_uuid()
+                                                    : 0u);
+
   TrackId track_id;
   base::Optional<UniqueTid> utid;
   base::Optional<UniqueTid> upid;
@@ -470,6 +497,10 @@
       ParseChromeKeyedService(event.chrome_keyed_service(), args_tracker,
                               row_id);
     }
+    if (event.has_chrome_histogram_sample()) {
+      ParseChromeHistogramSample(event.chrome_histogram_sample(), args_tracker,
+                                 row_id);
+    }
 
     if (legacy_tid) {
       args_tracker->AddArg(row_id, legacy_event_original_tid_id_,
@@ -1100,5 +1131,31 @@
   }
 }
 
+void TrackEventParser::ParseChromeHistogramSample(
+    protozero::ConstBytes chrome_histogram_sample,
+    ArgsTracker* args_tracker,
+    RowId row) {
+  protos::pbzero::ChromeHistogramSample::Decoder event(
+      chrome_histogram_sample.data, chrome_histogram_sample.size);
+  if (event.has_name_hash()) {
+    uint64_t name_hash = static_cast<uint64_t>(event.name_hash());
+    args_tracker->AddArg(row, chrome_histogram_sample_name_hash_args_key_id_,
+                         chrome_histogram_sample_name_hash_args_key_id_,
+                         Variadic::UnsignedInteger(name_hash));
+  }
+  if (event.has_name()) {
+    StringId name = context_->storage->InternString(event.name());
+    args_tracker->AddArg(row, chrome_keyed_service_name_args_key_id_,
+                         chrome_keyed_service_name_args_key_id_,
+                         Variadic::String(name));
+  }
+  if (event.has_sample()) {
+    int64_t sample = static_cast<int64_t>(event.sample());
+    args_tracker->AddArg(row, chrome_histogram_sample_sample_args_key_id_,
+                         chrome_histogram_sample_sample_args_key_id_,
+                         Variadic::Integer(sample));
+  }
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/track_event_parser.h b/src/trace_processor/importers/proto/track_event_parser.h
index e44b76d..6b23187 100644
--- a/src/trace_processor/importers/proto/track_event_parser.h
+++ b/src/trace_processor/importers/proto/track_event_parser.h
@@ -89,6 +89,9 @@
   void ParseChromeKeyedService(protozero::ConstBytes chrome_keyed_service,
                                ArgsTracker*,
                                RowId);
+  void ParseChromeHistogramSample(protozero::ConstBytes chrome_keyed_service,
+                                  ArgsTracker*,
+                                  RowId);
 
  private:
   TraceProcessorContext* context_;
@@ -122,6 +125,9 @@
   const StringId chrome_legacy_ipc_class_args_key_id_;
   const StringId chrome_legacy_ipc_line_args_key_id_;
   const StringId chrome_keyed_service_name_args_key_id_;
+  const StringId chrome_histogram_sample_name_hash_args_key_id_;
+  const StringId chrome_histogram_sample_name_args_key_id_;
+  const StringId chrome_histogram_sample_sample_args_key_id_;
 
   std::array<StringId, 38> chrome_legacy_ipc_class_ids_;
 };
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index 41237d2..a22adeb 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -28,6 +28,8 @@
 SystraceParser::SystraceParser(TraceProcessorContext* ctx)
     : context_(ctx), lmk_id_(ctx->storage->InternString("mem.lmk")) {}
 
+SystraceParser::~SystraceParser() = default;
+
 void SystraceParser::ParsePrintEvent(int64_t ts,
                                      uint32_t pid,
                                      base::StringView event) {
@@ -71,7 +73,7 @@
     context_->storage->IncrementStats(stats::systrace_parse_failure);
     return;
   }
-  context_->systrace_parser->ParseSystracePoint(ts, pid, point);
+  ParseSystracePoint(ts, pid, point);
 }
 
 void SystraceParser::ParseSdeTracingMarkWrite(int64_t ts,
@@ -95,7 +97,7 @@
     return;
   }
 
-  context_->systrace_parser->ParseSystracePoint(ts, pid, point);
+  ParseSystracePoint(ts, pid, point);
 }
 
 void SystraceParser::ParseSystracePoint(
diff --git a/src/trace_processor/importers/systrace/systrace_parser.h b/src/trace_processor/importers/systrace/systrace_parser.h
index ef39bd3..97fd337 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.h
+++ b/src/trace_processor/importers/systrace/systrace_parser.h
@@ -187,9 +187,15 @@
 
 }  // namespace systrace_utils
 
-class SystraceParser {
+class SystraceParser : public Destructible {
  public:
-  explicit SystraceParser(TraceProcessorContext*);
+  static SystraceParser* GetOrCreate(TraceProcessorContext* context) {
+    if (!context->systrace_parser) {
+      context->systrace_parser.reset(new SystraceParser(context));
+    }
+    return static_cast<SystraceParser*>(context->systrace_parser.get());
+  }
+  ~SystraceParser() override;
 
   void ParsePrintEvent(int64_t ts, uint32_t pid, base::StringView event);
 
@@ -209,6 +215,7 @@
                       int64_t value);
 
  private:
+  explicit SystraceParser(TraceProcessorContext*);
   void ParseSystracePoint(int64_t ts,
                           uint32_t pid,
                           systrace_utils::SystraceTracePoint event);
diff --git a/src/trace_processor/importers/systrace/systrace_trace_parser.cc b/src/trace_processor/importers/systrace/systrace_trace_parser.cc
index 33d5cb1..734c566 100644
--- a/src/trace_processor/importers/systrace/systrace_trace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_trace_parser.cc
@@ -195,12 +195,13 @@
       return util::Status("Could not parse sched_switch");
     }
 
-    context_->sched_tracker->PushSchedSwitch(
+    SchedEventTracker::GetOrCreate(context_)->PushSchedSwitch(
         cpu, ts, prev_pid.value(), prev_comm, prev_prio.value(), prev_state,
         next_pid.value(), next_comm, next_prio.value());
   } else if (event_name == "tracing_mark_write" || event_name == "0" ||
              event_name == "print") {
-    context_->systrace_parser->ParsePrintEvent(ts, pid, args_str.c_str());
+    SystraceParser::GetOrCreate(context_)->ParsePrintEvent(ts, pid,
+                                                           args_str.c_str());
   } else if (event_name == "sched_wakeup") {
     auto comm = args["comm"];
     base::Optional<uint32_t> wakee_pid = base::StringToUInt32(args["pid"]);
diff --git a/src/trace_processor/metadata.h b/src/trace_processor/metadata.h
index 8965052..aad028f 100644
--- a/src/trace_processor/metadata.h
+++ b/src/trace_processor/metadata.h
@@ -19,7 +19,7 @@
 
 #include <stddef.h>
 
-#include "src/trace_processor/string_pool.h"
+#include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/variadic.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/process_tracker_unittest.cc b/src/trace_processor/process_tracker_unittest.cc
index ad971a5..1122a99 100644
--- a/src/trace_processor/process_tracker_unittest.cc
+++ b/src/trace_processor/process_tracker_unittest.cc
@@ -37,9 +37,6 @@
     context.args_tracker.reset(new ArgsTracker(&context));
     context.process_tracker.reset(new ProcessTracker(&context));
     context.event_tracker.reset(new EventTracker(&context));
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
-    context.sched_tracker.reset(new SchedEventTracker(&context));
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
   }
 
  protected:
@@ -97,13 +94,14 @@
   static const char kCommProc1[] = "process1";
   static const char kCommProc2[] = "process2";
   int32_t prio = 1024;
+  SchedEventTracker* sched_tracker = SchedEventTracker::GetOrCreate(&context);
 
-  context.sched_tracker->PushSchedSwitch(cpu, timestamp, /*tid=*/1, kCommProc2,
-                                         prio, prev_state,
-                                         /*tid=*/4, kCommProc1, prio);
-  context.sched_tracker->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/4,
-                                         kCommProc1, prio, prev_state,
-                                         /*tid=*/1, kCommProc2, prio);
+  sched_tracker->PushSchedSwitch(cpu, timestamp, /*tid=*/1, kCommProc2, prio,
+                                 prev_state,
+                                 /*tid=*/4, kCommProc1, prio);
+  sched_tracker->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/4, kCommProc1,
+                                 prio, prev_state,
+                                 /*tid=*/1, kCommProc2, prio);
 
   context.process_tracker->SetProcessMetadata(2, base::nullopt, "test");
   context.process_tracker->UpdateThread(4, 2);
diff --git a/src/trace_processor/register_additional_modules.cc b/src/trace_processor/register_additional_modules.cc
index cb2b68b..bc9d6ef 100644
--- a/src/trace_processor/register_additional_modules.cc
+++ b/src/trace_processor/register_additional_modules.cc
@@ -15,13 +15,19 @@
  */
 
 #include "src/trace_processor/register_additional_modules.h"
+#include "src/trace_processor/importers/proto/android_probes_module.h"
 #include "src/trace_processor/importers/proto/graphics_event_module.h"
+#include "src/trace_processor/importers/proto/heap_graph_module.h"
+#include "src/trace_processor/importers/proto/system_probes_module.h"
 
 namespace perfetto {
 namespace trace_processor {
 
 void RegisterAdditionalModules(TraceProcessorContext* context) {
+  context->modules.emplace_back(new AndroidProbesModule(context));
   context->modules.emplace_back(new GraphicsEventModule(context));
+  context->modules.emplace_back(new HeapGraphModule(context));
+  context->modules.emplace_back(new SystemProbesModule(context));
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/sched_slice_table_unittest.cc b/src/trace_processor/sched_slice_table_unittest.cc
index 655033a..f9131ae 100644
--- a/src/trace_processor/sched_slice_table_unittest.cc
+++ b/src/trace_processor/sched_slice_table_unittest.cc
@@ -44,7 +44,7 @@
     context_.args_tracker.reset(new ArgsTracker(&context_));
     context_.process_tracker.reset(new ProcessTracker(&context_));
     context_.event_tracker.reset(new EventTracker(&context_));
-    context_.sched_tracker.reset(new SchedEventTracker(&context_));
+    sched_tracker_ = SchedEventTracker::GetOrCreate(&context_);
 
     SchedSliceTable::RegisterTable(db_.get(), context_.storage.get());
   }
@@ -61,6 +61,7 @@
   TraceProcessorContext context_;
   ScopedDb db_;
   ScopedStmt stmt_;
+  SchedEventTracker* sched_tracker_;
 };
 
 TEST_F(SchedSliceTableTest, RowsReturnedInCorrectOrderWithinCpu) {
@@ -72,18 +73,14 @@
   static const char kCommProc2[] = "process2";
   uint32_t pid_2 = 4;
   int32_t prio = 1024;
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp, pid_1, kCommProc2,
-                                          prio, prev_state, pid_2, kCommProc1,
-                                          prio);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 3, pid_2, kCommProc1,
-                                          prio, prev_state, pid_1, kCommProc2,
-                                          prio);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 4, pid_1, kCommProc2,
-                                          prio, prev_state, pid_2, kCommProc1,
-                                          prio);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 10, pid_2,
-                                          kCommProc1, prio, prev_state, pid_1,
-                                          kCommProc2, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp, pid_1, kCommProc2, prio,
+                                  prev_state, pid_2, kCommProc1, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp + 3, pid_2, kCommProc1, prio,
+                                  prev_state, pid_1, kCommProc2, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp + 4, pid_1, kCommProc2, prio,
+                                  prev_state, pid_2, kCommProc1, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp + 10, pid_2, kCommProc1, prio,
+                                  prev_state, pid_1, kCommProc2, prio);
 
   PrepareValidStatement(
       "SELECT dur, ts, cpu FROM sched where dur != 0 ORDER BY dur");
@@ -117,24 +114,18 @@
   static const char kCommProc2[] = "process2";
   uint32_t pid_2 = 4;
   int32_t prio = 1024;
-  context_.sched_tracker->PushSchedSwitch(cpu_3, timestamp - 2, pid_1,
-                                          kCommProc2, prio, prev_state, pid_2,
-                                          kCommProc1, prio);
-  context_.sched_tracker->PushSchedSwitch(cpu_3, timestamp - 1, pid_2,
-                                          kCommProc1, prio, prev_state, pid_1,
-                                          kCommProc2, prio);
-  context_.sched_tracker->PushSchedSwitch(cpu_1, timestamp, pid_1, kCommProc2,
-                                          prio, prev_state, pid_2, kCommProc1,
-                                          prio);
-  context_.sched_tracker->PushSchedSwitch(cpu_2, timestamp + 3, pid_2,
-                                          kCommProc1, prio, prev_state, pid_1,
-                                          kCommProc2, prio);
-  context_.sched_tracker->PushSchedSwitch(cpu_1, timestamp + 4, pid_2,
-                                          kCommProc1, prio, prev_state, pid_1,
-                                          kCommProc2, prio);
-  context_.sched_tracker->PushSchedSwitch(cpu_2, timestamp + 10, pid_1,
-                                          kCommProc2, prio, prev_state, pid_2,
-                                          kCommProc1, prio);
+  sched_tracker_->PushSchedSwitch(cpu_3, timestamp - 2, pid_1, kCommProc2, prio,
+                                  prev_state, pid_2, kCommProc1, prio);
+  sched_tracker_->PushSchedSwitch(cpu_3, timestamp - 1, pid_2, kCommProc1, prio,
+                                  prev_state, pid_1, kCommProc2, prio);
+  sched_tracker_->PushSchedSwitch(cpu_1, timestamp, pid_1, kCommProc2, prio,
+                                  prev_state, pid_2, kCommProc1, prio);
+  sched_tracker_->PushSchedSwitch(cpu_2, timestamp + 3, pid_2, kCommProc1, prio,
+                                  prev_state, pid_1, kCommProc2, prio);
+  sched_tracker_->PushSchedSwitch(cpu_1, timestamp + 4, pid_2, kCommProc1, prio,
+                                  prev_state, pid_1, kCommProc2, prio);
+  sched_tracker_->PushSchedSwitch(cpu_2, timestamp + 10, pid_1, kCommProc2,
+                                  prio, prev_state, pid_2, kCommProc1, prio);
 
   PrepareValidStatement(
       "SELECT dur, ts, cpu FROM sched where dur != 0 ORDER BY dur desc");
@@ -167,18 +158,14 @@
   static const char kCommProc2[] = "process2";
   uint32_t pid_2 = 4;
   int32_t prio = 1024;
-  context_.sched_tracker->PushSchedSwitch(cpu_1, timestamp, pid_1, kCommProc2,
-                                          prio, prev_state, pid_2, kCommProc1,
-                                          prio);
-  context_.sched_tracker->PushSchedSwitch(cpu_2, timestamp + 3, pid_2,
-                                          kCommProc1, prio, prev_state, pid_1,
-                                          kCommProc2, prio);
-  context_.sched_tracker->PushSchedSwitch(cpu_1, timestamp + 4, pid_2,
-                                          kCommProc1, prio, prev_state, pid_1,
-                                          kCommProc2, prio);
-  context_.sched_tracker->PushSchedSwitch(cpu_2, timestamp + 10, pid_1,
-                                          kCommProc2, prio, prev_state, pid_2,
-                                          kCommProc1, prio);
+  sched_tracker_->PushSchedSwitch(cpu_1, timestamp, pid_1, kCommProc2, prio,
+                                  prev_state, pid_2, kCommProc1, prio);
+  sched_tracker_->PushSchedSwitch(cpu_2, timestamp + 3, pid_2, kCommProc1, prio,
+                                  prev_state, pid_1, kCommProc2, prio);
+  sched_tracker_->PushSchedSwitch(cpu_1, timestamp + 4, pid_2, kCommProc1, prio,
+                                  prev_state, pid_1, kCommProc2, prio);
+  sched_tracker_->PushSchedSwitch(cpu_2, timestamp + 10, pid_1, kCommProc2,
+                                  prio, prev_state, pid_2, kCommProc1, prio);
 
   PrepareValidStatement(
       "SELECT dur, ts, cpu FROM sched WHERE dur != 0 and cpu = 3");
@@ -200,18 +187,14 @@
   static const char kCommProc2[] = "process2";
   uint32_t pid_2 = 4;
   int32_t prio = 1024;
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp, pid_1, kCommProc2,
-                                          prio, prev_state, pid_2, kCommProc1,
-                                          prio);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 3, pid_2, kCommProc1,
-                                          prio, prev_state, pid_1, kCommProc2,
-                                          prio);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 4, pid_1, kCommProc2,
-                                          prio, prev_state, pid_2, kCommProc1,
-                                          prio);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 10, pid_2,
-                                          kCommProc1, prio, prev_state, pid_1,
-                                          kCommProc2, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp, pid_1, kCommProc2, prio,
+                                  prev_state, pid_2, kCommProc1, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp + 3, pid_2, kCommProc1, prio,
+                                  prev_state, pid_1, kCommProc2, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp + 4, pid_1, kCommProc2, prio,
+                                  prev_state, pid_2, kCommProc1, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp + 10, pid_2, kCommProc1, prio,
+                                  prev_state, pid_1, kCommProc2, prio);
 
   PrepareValidStatement("SELECT utid FROM sched where dur != 0 ORDER BY utid");
 
@@ -238,12 +221,12 @@
   // Fill |cpu_5| and |cpu_7) with one sched switch per time unit starting,
   // respectively, @ T=50 and T=70.
   for (int64_t i = 0; i <= 11; i++) {
-    context_.sched_tracker->PushSchedSwitch(cpu_5, 50 + i, pid_1, "pid_1", prio,
-                                            prev_state, pid_1, "pid_1", prio);
+    sched_tracker_->PushSchedSwitch(cpu_5, 50 + i, pid_1, "pid_1", prio,
+                                    prev_state, pid_1, "pid_1", prio);
   }
   for (int64_t i = 0; i <= 11; i++) {
-    context_.sched_tracker->PushSchedSwitch(cpu_7, 70 + i, pid_2, "pid_2", prio,
-                                            prev_state, pid_2, "pid_2", prio);
+    sched_tracker_->PushSchedSwitch(cpu_7, 70 + i, pid_2, "pid_2", prio,
+                                    prev_state, pid_2, "pid_2", prio);
   }
 
   auto query = [this](const std::string& where_clauses) {
diff --git a/src/trace_processor/slice_tracker_unittest.cc b/src/trace_processor/slice_tracker_unittest.cc
index f4f3c64..386a15c 100644
--- a/src/trace_processor/slice_tracker_unittest.cc
+++ b/src/trace_processor/slice_tracker_unittest.cc
@@ -44,7 +44,7 @@
 
 std::vector<SliceInfo> ToSliceInfo(const tables::SliceTable& slices) {
   std::vector<SliceInfo> infos;
-  for (uint32_t i = 0; i < slices.size(); i++) {
+  for (uint32_t i = 0; i < slices.row_count(); i++) {
     infos.emplace_back(SliceInfo{slices.ts()[i], slices.dur()[i]});
   }
   return infos;
@@ -60,7 +60,7 @@
   tracker.End(10 /*ts*/, track, 0 /*cat*/, 1 /*name*/);
 
   const auto& slices = context.storage->slice_table();
-  EXPECT_EQ(slices.size(), 1u);
+  EXPECT_EQ(slices.row_count(), 1u);
   EXPECT_EQ(slices.ts()[0], 2);
   EXPECT_EQ(slices.dur()[0], 8);
   EXPECT_EQ(slices.track_id()[0], track);
@@ -88,7 +88,7 @@
               });
 
   const auto& slices = context.storage->slice_table();
-  EXPECT_EQ(slices.size(), 1u);
+  EXPECT_EQ(slices.row_count(), 1u);
   EXPECT_EQ(slices.ts()[0], 2);
   EXPECT_EQ(slices.dur()[0], 8);
   EXPECT_EQ(slices.track_id()[0], track);
@@ -121,7 +121,7 @@
 
   const auto& slices = context.storage->slice_table();
 
-  EXPECT_EQ(slices.size(), 2u);
+  EXPECT_EQ(slices.row_count(), 2u);
 
   uint32_t idx = 0;
   EXPECT_EQ(slices.ts()[idx], 2);
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index a531b92..6e0df8b 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -192,7 +192,7 @@
   // end of filtering. Note that |current_row_count| should always be at least 1
   // unless we are absolutely certain that we will return no rows as otherwise
   // SQLite can make some bad choices.
-  uint32_t current_row_count = table.size();
+  uint32_t current_row_count = table.row_count();
 
   // If the table is empty, any constraint set only pays the fixed cost. Also we
   // can return 0 as the row count as we are certain that we will return no
diff --git a/src/trace_processor/sqlite/db_sqlite_table_unittest.cc b/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
index d88a046..4f39839 100644
--- a/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
@@ -24,9 +24,9 @@
 
 class TestTable : public Table {
  public:
-  TestTable(uint32_t size) : Table(&pool_, nullptr) {
-    row_maps_.emplace_back(RowMap(0, size));
-    size_ = size;
+  TestTable(uint32_t row_count) : Table(&pool_, nullptr) {
+    row_maps_.emplace_back(RowMap(0, row_count));
+    row_count_ = row_count;
 
     columns_.emplace_back(Column::IdColumn(this, 0u, 0u));
     columns_.emplace_back(
diff --git a/src/trace_processor/storage_minimal_smoke_test.cc b/src/trace_processor/storage_minimal_smoke_test.cc
new file mode 100644
index 0000000..1aa037d
--- /dev/null
+++ b/src/trace_processor/storage_minimal_smoke_test.cc
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio>
+#include <string>
+
+#include <json/reader.h>
+#include <json/value.h>
+
+#include "perfetto/ext/trace_processor/export_json.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/trace_processor_storage.h"
+#include "src/base/test/utils.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+class JsonStringOutputWriter : public json::OutputWriter {
+ public:
+  util::Status AppendString(const std::string& string) override {
+    buffer += string;
+    return util::OkStatus();
+  }
+  std::string buffer;
+};
+
+class StorageMinimalSmokeTest : public ::testing::Test {
+ public:
+  StorageMinimalSmokeTest()
+      : storage_(TraceProcessorStorage::CreateInstance(Config())) {}
+
+ protected:
+  std::unique_ptr<TraceProcessorStorage> storage_;
+};
+
+TEST_F(StorageMinimalSmokeTest, GraphicEventsIgnored) {
+  const size_t MAX_SIZE = 1 << 20;
+  auto f = fopen(base::GetTestDataPath("test/data/gpu_trace.pb").c_str(), "rb");
+  std::unique_ptr<uint8_t[]> buf(new uint8_t[MAX_SIZE]);
+  auto rsize = fread(reinterpret_cast<char*>(buf.get()), 1, MAX_SIZE, f);
+  storage_->Parse(std::move(buf), rsize);
+  storage_->NotifyEndOfFile();
+
+  JsonStringOutputWriter output_writer;
+  auto status = json::ExportJson(storage_.get(), &output_writer);
+  Json::Reader reader;
+  Json::Value result;
+  reader.parse(output_writer.buffer, result);
+
+  ASSERT_EQ(result["traceEvents"].size(), 0u);
+}
+
+TEST_F(StorageMinimalSmokeTest, TrackEventsImported) {
+  const size_t MAX_SIZE = 1 << 20;
+  auto f = fopen("test/trace_processor/track_event_typed_args.pb", "rb");
+  std::unique_ptr<uint8_t[]> buf(new uint8_t[MAX_SIZE]);
+  auto rsize = fread(reinterpret_cast<char*>(buf.get()), 1, MAX_SIZE, f);
+  storage_->Parse(std::move(buf), rsize);
+  storage_->NotifyEndOfFile();
+
+  JsonStringOutputWriter output_writer;
+  auto status = json::ExportJson(storage_.get(), &output_writer);
+  Json::Reader reader;
+  Json::Value result;
+  reader.parse(output_writer.buffer, result);
+
+  ASSERT_EQ(result["traceEvents"].size(), 4u);
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/syscall_tracker.h b/src/trace_processor/syscall_tracker.h
index 09cf772..1dba57d 100644
--- a/src/trace_processor/syscall_tracker.h
+++ b/src/trace_processor/syscall_tracker.h
@@ -21,6 +21,7 @@
 #include <tuple>
 
 #include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/destructible.h"
 #include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/trace_storage.h"
@@ -39,12 +40,17 @@
   kX86_64,
 };
 
-class SyscallTracker {
+class SyscallTracker : public Destructible {
  public:
-  explicit SyscallTracker(TraceProcessorContext*);
   SyscallTracker(const SyscallTracker&) = delete;
   SyscallTracker& operator=(const SyscallTracker&) = delete;
   virtual ~SyscallTracker();
+  static SyscallTracker* GetOrCreate(TraceProcessorContext* context) {
+    if (!context->syscall_tracker) {
+      context->syscall_tracker.reset(new SyscallTracker(context));
+    }
+    return static_cast<SyscallTracker*>(context->syscall_tracker.get());
+  }
 
   void SetArchitecture(Architecture architecture);
 
@@ -65,6 +71,8 @@
   }
 
  private:
+  explicit SyscallTracker(TraceProcessorContext*);
+
   TraceProcessorContext* const context_;
 
   inline StringId SyscallNumberToStringId(uint32_t syscall_num) {
diff --git a/src/trace_processor/syscall_tracker_unittest.cc b/src/trace_processor/syscall_tracker_unittest.cc
index a659a64..1d9f513 100644
--- a/src/trace_processor/syscall_tracker_unittest.cc
+++ b/src/trace_processor/syscall_tracker_unittest.cc
@@ -54,7 +54,6 @@
     context.track_tracker.reset(track_tracker);
     slice_tracker = new MockSliceTracker(&context);
     context.slice_tracker.reset(slice_tracker);
-    context.syscall_tracker.reset(new SyscallTracker(&context));
   }
 
  protected:
@@ -72,19 +71,21 @@
   EXPECT_CALL(*slice_tracker, End(110, track, kNullStringId, _, _))
       .WillOnce(DoAll(SaveArg<3>(&end_name), Return(base::nullopt)));
 
-  context.syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 57 /*sys_read*/);
-  context.syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 57 /*sys_read*/);
+  SyscallTracker* syscall_tracker = SyscallTracker::GetOrCreate(&context);
+  syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 57 /*sys_read*/);
+  syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 57 /*sys_read*/);
   EXPECT_EQ(context.storage->GetString(begin_name), "sys_57");
   EXPECT_EQ(context.storage->GetString(end_name), "sys_57");
 }
 
 TEST_F(SyscallTrackerTest, IgnoreWriteSyscalls) {
-  context.syscall_tracker->SetArchitecture(kAarch64);
+  SyscallTracker* syscall_tracker = SyscallTracker::GetOrCreate(&context);
+  syscall_tracker->SetArchitecture(kAarch64);
   EXPECT_CALL(*slice_tracker, Begin(_, _, _, _, _)).Times(0);
   EXPECT_CALL(*slice_tracker, End(_, _, _, _, _)).Times(0);
 
-  context.syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 64 /*sys_write*/);
-  context.syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 64 /*sys_write*/);
+  syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 64 /*sys_write*/);
+  syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 64 /*sys_write*/);
 }
 
 TEST_F(SyscallTrackerTest, Aarch64) {
@@ -96,9 +97,10 @@
   EXPECT_CALL(*slice_tracker, End(110, track, kNullStringId, _, _))
       .WillOnce(DoAll(SaveArg<3>(&end_name), Return(base::nullopt)));
 
-  context.syscall_tracker->SetArchitecture(kAarch64);
-  context.syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 63 /*sys_read*/);
-  context.syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 63 /*sys_read*/);
+  SyscallTracker* syscall_tracker = SyscallTracker::GetOrCreate(&context);
+  syscall_tracker->SetArchitecture(kAarch64);
+  syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 63 /*sys_read*/);
+  syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 63 /*sys_read*/);
   EXPECT_EQ(context.storage->GetString(begin_name), "sys_read");
   EXPECT_EQ(context.storage->GetString(end_name), "sys_read");
 }
@@ -112,9 +114,10 @@
   EXPECT_CALL(*slice_tracker, End(110, track, kNullStringId, _, _))
       .WillOnce(DoAll(SaveArg<3>(&end_name), Return(base::nullopt)));
 
-  context.syscall_tracker->SetArchitecture(kX86_64);
-  context.syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 0 /*sys_read*/);
-  context.syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 0 /*sys_read*/);
+  SyscallTracker* syscall_tracker = SyscallTracker::GetOrCreate(&context);
+  syscall_tracker->SetArchitecture(kX86_64);
+  syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 0 /*sys_read*/);
+  syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 0 /*sys_read*/);
   EXPECT_EQ(context.storage->GetString(begin_name), "sys_read");
   EXPECT_EQ(context.storage->GetString(end_name), "sys_read");
 }
@@ -122,9 +125,11 @@
 TEST_F(SyscallTrackerTest, SyscallNumberTooLarge) {
   EXPECT_CALL(*slice_tracker, Begin(_, _, _, _, _)).Times(0);
   EXPECT_CALL(*slice_tracker, End(_, _, _, _, _)).Times(0);
-  context.syscall_tracker->SetArchitecture(kAarch64);
-  context.syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 9999);
-  context.syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 9999);
+
+  SyscallTracker* syscall_tracker = SyscallTracker::GetOrCreate(&context);
+  syscall_tracker->SetArchitecture(kAarch64);
+  syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 9999);
+  syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 9999);
 }
 
 }  // namespace
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index 3b9fa44..b87d011 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -24,7 +24,6 @@
     "track_tables.h",
   ]
   deps = [
-    "..:common",
     "../../../gn:default_deps",
     "../db:lib",
   ]
diff --git a/src/trace_processor/tables/macros_benchmark.cc b/src/trace_processor/tables/macros_benchmark.cc
index 008bcab..2e7bfbb 100644
--- a/src/trace_processor/tables/macros_benchmark.cc
+++ b/src/trace_processor/tables/macros_benchmark.cc
@@ -108,7 +108,7 @@
 }
 BENCHMARK(BM_TableIteratorChild)->Apply(TableFilterArgs);
 
-static void BM_TableFilterIdColumn(benchmark::State& state) {
+static void BM_TableFilterRootId(benchmark::State& state) {
   StringPool pool;
   RootTestTable root(&pool, nullptr);
 
@@ -120,7 +120,66 @@
     benchmark::DoNotOptimize(root.Filter({root.id().eq(30)}));
   }
 }
-BENCHMARK(BM_TableFilterIdColumn)->Apply(TableFilterArgs);
+BENCHMARK(BM_TableFilterRootId)->Apply(TableFilterArgs);
+
+static void BM_TableFilterRootIdAndOther(benchmark::State& state) {
+  StringPool pool;
+  RootTestTable root(&pool, nullptr);
+
+  uint32_t size = static_cast<uint32_t>(state.range(0));
+
+  for (uint32_t i = 0; i < size; ++i) {
+    RootTestTable::Row root_row;
+    root_row.root_non_null = i * 4;
+    root.Insert(root_row);
+  }
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(root.Filter(
+        {root.id().eq(root.row_count() - 1), root.root_non_null().gt(100)}));
+  }
+}
+BENCHMARK(BM_TableFilterRootIdAndOther)->Apply(TableFilterArgs);
+
+static void BM_TableFilterChildId(benchmark::State& state) {
+  StringPool pool;
+  RootTestTable root(&pool, nullptr);
+  ChildTestTable child(&pool, &root);
+
+  uint32_t size = static_cast<uint32_t>(state.range(0));
+  for (uint32_t i = 0; i < size; ++i) {
+    root.Insert({});
+    child.Insert({});
+  }
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(child.Filter({child.id().eq(30)}));
+  }
+}
+BENCHMARK(BM_TableFilterChildId)->Apply(TableFilterArgs);
+
+static void BM_TableFilterChildIdAndSortedInRoot(benchmark::State& state) {
+  StringPool pool;
+  RootTestTable root(&pool, nullptr);
+  ChildTestTable child(&pool, &root);
+
+  uint32_t size = static_cast<uint32_t>(state.range(0));
+  for (uint32_t i = 0; i < size; ++i) {
+    RootTestTable::Row root_row;
+    root_row.root_sorted = i * 2;
+    root.Insert(root_row);
+
+    ChildTestTable::Row child_row;
+    child_row.root_sorted = i * 2 + 1;
+    child.Insert(child_row);
+  }
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(
+        child.Filter({child.id().eq(30), child.root_sorted().gt(1024)}));
+  }
+}
+BENCHMARK(BM_TableFilterChildIdAndSortedInRoot)->Apply(TableFilterArgs);
 
 static void BM_TableFilterRootNonNullEqMatchMany(benchmark::State& state) {
   StringPool pool;
@@ -320,25 +379,6 @@
 }
 BENCHMARK(BM_TableFilterChildSortedEqInParent)->Apply(TableFilterArgs);
 
-static void BM_TableFilterIdAndOtherParent(benchmark::State& state) {
-  StringPool pool;
-  RootTestTable root(&pool, nullptr);
-
-  uint32_t size = static_cast<uint32_t>(state.range(0));
-
-  for (uint32_t i = 0; i < size; ++i) {
-    RootTestTable::Row root_row;
-    root_row.root_non_null = i * 4;
-    root.Insert(root_row);
-  }
-
-  for (auto _ : state) {
-    benchmark::DoNotOptimize(root.Filter(
-        {root.id().eq(root.size() - 1), root.root_non_null().gt(100)}));
-  }
-}
-BENCHMARK(BM_TableFilterIdAndOtherParent)->Apply(TableFilterArgs);
-
 static void BM_TableSortRootNonNull(benchmark::State& state) {
   StringPool pool;
   RootTestTable root(&pool, nullptr);
diff --git a/src/trace_processor/tables/macros_internal.h b/src/trace_processor/tables/macros_internal.h
index 1f65ec7..ff172e7 100644
--- a/src/trace_processor/tables/macros_internal.h
+++ b/src/trace_processor/tables/macros_internal.h
@@ -76,7 +76,7 @@
     }
     // Also add the index of the new row to the identity row map and increment
     // the size.
-    row_maps_.back().Insert(size_++);
+    row_maps_.back().Insert(row_count_++);
   }
 
   // Stores the most specific "derived" type of this row in the table.
@@ -267,7 +267,7 @@
     uint32_t Insert(const Row& row) {                                         \
       uint32_t id;                                                            \
       if (parent_ == nullptr) {                                               \
-        id = size();                                                          \
+        id = row_count();                                                     \
         type_.Append(string_pool_->InternString(row.type()));                 \
       } else {                                                                \
         id = parent_->Insert(row);                                            \
diff --git a/src/trace_processor/tables/macros_unittest.cc b/src/trace_processor/tables/macros_unittest.cc
index 46e03da..c7c7e82 100644
--- a/src/trace_processor/tables/macros_unittest.cc
+++ b/src/trace_processor/tables/macros_unittest.cc
@@ -144,47 +144,47 @@
 
   Table out = slice_.Filter({slice_.dur().is_null()});
   const auto* dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_EQ(dur->Get(0).type, SqlValue::kNull);
   ASSERT_EQ(dur->Get(1).type, SqlValue::kNull);
 
   out = slice_.Filter({slice_.dur().is_not_null()});
   dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 3u);
+  ASSERT_EQ(out.row_count(), 3u);
   ASSERT_EQ(dur->Get(0).long_value, 100);
   ASSERT_EQ(dur->Get(1).long_value, 101);
   ASSERT_EQ(dur->Get(2).long_value, 200);
 
   out = slice_.Filter({slice_.dur().lt(101)});
   dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_EQ(dur->Get(0).long_value, 100);
 
   out = slice_.Filter({slice_.dur().eq(101)});
   dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_EQ(dur->Get(0).long_value, 101);
 
   out = slice_.Filter({slice_.dur().gt(101)});
   dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_EQ(dur->Get(0).long_value, 200);
 
   out = slice_.Filter({slice_.dur().ne(100)});
   dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_EQ(dur->Get(0).long_value, 101);
   ASSERT_EQ(dur->Get(1).long_value, 200);
 
   out = slice_.Filter({slice_.dur().le(101)});
   dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_EQ(dur->Get(0).long_value, 100);
   ASSERT_EQ(dur->Get(1).long_value, 101);
 
   out = slice_.Filter({slice_.dur().ge(101)});
   dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_EQ(dur->Get(0).long_value, 101);
   ASSERT_EQ(dur->Get(1).long_value, 200);
 }
@@ -204,24 +204,24 @@
 
   Table out = slice_.Filter({slice_.dur().eq_value(SqlValue::Double(100.0))});
   const Column* dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_EQ(dur->Get(0).long_value, 100);
 
   out = slice_.Filter({slice_.dur().le_value(SqlValue::Double(99.9999))});
   dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_EQ(dur->Get(0).long_value, std::numeric_limits<int64_t>::min());
 
   out = slice_.Filter({slice_.dur().ge_value(SqlValue::Double(99.9999))});
   dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_EQ(dur->Get(0).long_value, 100);
   ASSERT_EQ(dur->Get(1).long_value, std::numeric_limits<int64_t>::max());
 
   out = slice_.Filter({slice_.dur().eq_value(
       SqlValue::Double(std::numeric_limits<int64_t>::min()))});
   dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_EQ(dur->Get(0).long_value, std::numeric_limits<int64_t>::min());
 }
 
@@ -241,10 +241,10 @@
   slice_.Insert({});
 
   Table out = slice_.Filter({slice_.dur().ne_value(SqlValue())});
-  ASSERT_EQ(out.size(), 0u);
+  ASSERT_EQ(out.row_count(), 0u);
 
   out = slice_.Filter({slice_.dur().eq_value(SqlValue::String("100"))});
-  ASSERT_EQ(out.size(), 0u);
+  ASSERT_EQ(out.row_count(), 0u);
 }
 
 TEST_F(TableMacrosUnittest, NullableDoubleComparision) {
@@ -264,47 +264,47 @@
 
   Table out = counter_.Filter({counter_.value().is_null()});
   const auto* value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_EQ(value->Get(0).type, SqlValue::kNull);
   ASSERT_EQ(value->Get(1).type, SqlValue::kNull);
 
   out = counter_.Filter({counter_.value().is_not_null()});
   value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 3u);
+  ASSERT_EQ(out.row_count(), 3u);
   ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
   ASSERT_DOUBLE_EQ(value->Get(1).double_value, 101);
   ASSERT_DOUBLE_EQ(value->Get(2).double_value, 200);
 
   out = counter_.Filter({counter_.value().lt(101)});
   value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
 
   out = counter_.Filter({counter_.value().eq(101)});
   value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
 
   out = counter_.Filter({counter_.value().gt(101)});
   value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_DOUBLE_EQ(value->Get(0).double_value, 200);
 
   out = counter_.Filter({counter_.value().ne(100)});
   value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
   ASSERT_DOUBLE_EQ(value->Get(1).double_value, 200);
 
   out = counter_.Filter({counter_.value().le(101)});
   value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
   ASSERT_DOUBLE_EQ(value->Get(1).double_value, 101);
 
   out = counter_.Filter({counter_.value().ge(101)});
   value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
   ASSERT_DOUBLE_EQ(value->Get(1).double_value, 200);
 }
@@ -324,12 +324,12 @@
 
   Table out = counter_.Filter({counter_.value().eq_value(SqlValue::Long(100))});
   const Column* value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100.0);
 
   out = counter_.Filter({counter_.value().lt_value(SqlValue::Long(100))});
   value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_DOUBLE_EQ(value->Get(0).double_value, 99.9999);
   ASSERT_DOUBLE_EQ(value->Get(1).double_value,
                    std::numeric_limits<int64_t>::min());
@@ -337,7 +337,7 @@
   out = counter_.Filter({counter_.value().eq_value(
       SqlValue::Long(std::numeric_limits<int64_t>::min()))});
   value = out.GetColumnByName("value");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_DOUBLE_EQ(value->Get(0).double_value,
                    std::numeric_limits<int64_t>::min());
 }
@@ -356,45 +356,45 @@
 
   Table out = cpu_slice_.Filter({cpu_slice_.end_state().is_null()});
   const auto* end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_EQ(end_state->Get(0).type, SqlValue::kNull);
   ASSERT_EQ(end_state->Get(1).type, SqlValue::kNull);
 
   out = cpu_slice_.Filter({cpu_slice_.end_state().is_not_null()});
   end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_STREQ(end_state->Get(0).string_value, "R");
   ASSERT_STREQ(end_state->Get(1).string_value, "D");
 
   out = cpu_slice_.Filter({cpu_slice_.end_state().lt("R")});
   end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_STREQ(end_state->Get(0).string_value, "D");
 
   out = cpu_slice_.Filter({cpu_slice_.end_state().eq("D")});
   end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_STREQ(end_state->Get(0).string_value, "D");
 
   out = cpu_slice_.Filter({cpu_slice_.end_state().gt("D")});
   end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_STREQ(end_state->Get(0).string_value, "R");
 
   out = cpu_slice_.Filter({cpu_slice_.end_state().ne("D")});
   end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_STREQ(end_state->Get(0).string_value, "R");
 
   out = cpu_slice_.Filter({cpu_slice_.end_state().le("R")});
   end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_STREQ(end_state->Get(0).string_value, "R");
   ASSERT_STREQ(end_state->Get(1).string_value, "D");
 
   out = cpu_slice_.Filter({cpu_slice_.end_state().ge("D")});
   end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.size(), 2u);
+  ASSERT_EQ(out.row_count(), 2u);
   ASSERT_STREQ(end_state->Get(0).string_value, "R");
   ASSERT_STREQ(end_state->Get(1).string_value, "D");
 }
@@ -414,7 +414,7 @@
   const auto* end_state = out.GetColumnByName("end_state");
   const auto* cpu = out.GetColumnByName("cpu");
 
-  ASSERT_EQ(out.size(), 1u);
+  ASSERT_EQ(out.row_count(), 1u);
   ASSERT_EQ(cpu->Get(0).long_value, 1u);
   ASSERT_STREQ(end_state->Get(0).string_value, "D");
 }
diff --git a/src/trace_processor/tables/profiler_tables.h b/src/trace_processor/tables/profiler_tables.h
index 13f3330..d00c5e7 100644
--- a/src/trace_processor/tables/profiler_tables.h
+++ b/src/trace_processor/tables/profiler_tables.h
@@ -42,6 +42,17 @@
 
 PERFETTO_TP_TABLE(PERFETTO_TP_SYMBOL_DEF);
 
+#define PERFETTO_TP_HEAP_PROFILE_ALLOCATION_DEF(NAME, PARENT, C) \
+  NAME(HeapProfileAllocationTable, "heap_profile_allocation")    \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                              \
+  C(int64_t, ts, Column::Flag::kSorted)                          \
+  C(uint32_t, upid)                                              \
+  C(int64_t, callsite_id)                                        \
+  C(int64_t, count)                                              \
+  C(int64_t, size)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_HEAP_PROFILE_ALLOCATION_DEF);
+
 #define PERFETTO_TP_HEAP_GRAPH_OBJECT_DEF(NAME, PARENT, C)  \
   NAME(HeapGraphObjectTable, "heap_graph_object")           \
   PERFETTO_TP_ROOT_TABLE(PARENT, C)                         \
diff --git a/src/trace_processor/tables/track_tables.h b/src/trace_processor/tables/track_tables.h
index 9048d66..29c3041 100644
--- a/src/trace_processor/tables/track_tables.h
+++ b/src/trace_processor/tables/track_tables.h
@@ -17,7 +17,6 @@
 #ifndef SRC_TRACE_PROCESSOR_TABLES_TRACK_TABLES_H_
 #define SRC_TRACE_PROCESSOR_TABLES_TRACK_TABLES_H_
 
-#include "src/trace_processor/string_pool.h"
 #include "src/trace_processor/tables/macros.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/thread_table_unittest.cc b/src/trace_processor/thread_table_unittest.cc
index 6d5427c..03c6647 100644
--- a/src/trace_processor/thread_table_unittest.cc
+++ b/src/trace_processor/thread_table_unittest.cc
@@ -41,7 +41,7 @@
     context_.args_tracker.reset(new ArgsTracker(&context_));
     context_.process_tracker.reset(new ProcessTracker(&context_));
     context_.event_tracker.reset(new EventTracker(&context_));
-    context_.sched_tracker.reset(new SchedEventTracker(&context_));
+    sched_tracker_ = SchedEventTracker::GetOrCreate(&context_);
 
     ThreadTable::RegisterTable(db_.get(), context_.storage.get());
     ProcessTable::RegisterTable(db_.get(), context_.storage.get());
@@ -63,6 +63,7 @@
   TraceProcessorContext context_;
   ScopedDb db_;
   ScopedStmt stmt_;
+  SchedEventTracker* sched_tracker_;
 };
 
 TEST_F(ThreadTableUnittest, Select) {
@@ -73,12 +74,12 @@
   static const char kThreadName2[] = "thread2";
   int32_t prio = 1024;
 
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp, /*tid=*/1,
-                                          kThreadName2, prio, prev_state,
-                                          /*tid=*/4, kThreadName1, prio);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/4,
-                                          kThreadName1, prio, prev_state,
-                                          /*tid=*/1, kThreadName2, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp, /*tid=*/1, kThreadName2, prio,
+                                  prev_state,
+                                  /*tid=*/4, kThreadName1, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/4, kThreadName1,
+                                  prio, prev_state,
+                                  /*tid=*/1, kThreadName2, prio);
 
   context_.process_tracker->SetProcessMetadata(2, base::nullopt, "test");
   context_.process_tracker->UpdateThread(4 /*tid*/, 2 /*pid*/);
@@ -101,15 +102,15 @@
   static const char kThreadName2[] = "thread2";
   int32_t prio = 1024;
 
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp, /*tid=*/1,
-                                          kThreadName2, prio, prev_state,
-                                          /*tid=*/4, kThreadName1, prio);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/4,
-                                          kThreadName1, prio, prev_state,
-                                          /*tid=*/1, kThreadName2, prio);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 2, /*tid=*/1,
-                                          kThreadName2, prio, prev_state,
-                                          /*tid=*/4, kThreadName1, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp, /*tid=*/1, kThreadName2, prio,
+                                  prev_state,
+                                  /*tid=*/4, kThreadName1, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/4, kThreadName1,
+                                  prio, prev_state,
+                                  /*tid=*/1, kThreadName2, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp + 2, /*tid=*/1, kThreadName2,
+                                  prio, prev_state,
+                                  /*tid=*/4, kThreadName1, prio);
 
   context_.process_tracker->SetProcessMetadata(2, base::nullopt, "test");
   context_.process_tracker->UpdateThread(4 /*tid*/, 2 /*pid*/);
@@ -134,12 +135,12 @@
   static const char kThreadName2[] = "thread2";
   int32_t prio = 1024;
 
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp, /*tid=*/1,
-                                          kThreadName2, prio, prev_state,
-                                          /*tid=*/4, kThreadName1, prio);
-  context_.sched_tracker->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/4,
-                                          kThreadName1, prio, prev_state,
-                                          /*tid=*/1, kThreadName2, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp, /*tid=*/1, kThreadName2, prio,
+                                  prev_state,
+                                  /*tid=*/4, kThreadName1, prio);
+  sched_tracker_->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/4, kThreadName1,
+                                  prio, prev_state,
+                                  /*tid=*/1, kThreadName2, prio);
 
   // Also create a process for which we haven't seen any thread.
   context_.process_tracker->SetProcessMetadata(7, base::nullopt, "pid7");
diff --git a/src/trace_processor/trace_blob_view.h b/src/trace_processor/trace_blob_view.h
index 46cc424..7a7889b 100644
--- a/src/trace_processor/trace_blob_view.h
+++ b/src/trace_processor/trace_blob_view.h
@@ -51,7 +51,7 @@
   TraceBlobView(const TraceBlobView&) = delete;
   TraceBlobView& operator=(const TraceBlobView&) = delete;
 
-  TraceBlobView slice(size_t offset, size_t length) {
+  TraceBlobView slice(size_t offset, size_t length) const {
     PERFETTO_DCHECK(offset + length <= offset_ + length_);
     return TraceBlobView(shbuf_, offset, length);
   }
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index a6b1c03..dbefd62 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -17,25 +17,17 @@
 #include "src/trace_processor/trace_processor_context.h"
 
 #include "src/trace_processor/args_tracker.h"
-#include "src/trace_processor/binder_tracker.h"
 #include "src/trace_processor/chunked_trace_reader.h"
 #include "src/trace_processor/clock_tracker.h"
 #include "src/trace_processor/event_tracker.h"
 #include "src/trace_processor/heap_profile_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
-#include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 #include "src/trace_processor/importers/json/json_trace_parser.h"
-#include "src/trace_processor/importers/proto/android_probes_module.h"
-#include "src/trace_processor/importers/proto/heap_graph_module.h"
-#include "src/trace_processor/importers/proto/heap_graph_tracker.h"
 #include "src/trace_processor/importers/proto/proto_trace_parser.h"
-#include "src/trace_processor/importers/proto/system_probes_module.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
-#include "src/trace_processor/importers/systrace/systrace_parser.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/stack_profile_tracker.h"
-#include "src/trace_processor/syscall_tracker.h"
 #include "src/trace_processor/trace_sorter.h"
 #include "src/trace_processor/track_tracker.h"
 
diff --git a/src/trace_processor/trace_processor_context.h b/src/trace_processor/trace_processor_context.h
index 3eab980..6d5b75c 100644
--- a/src/trace_processor/trace_processor_context.h
+++ b/src/trace_processor/trace_processor_context.h
@@ -21,13 +21,13 @@
 #include <vector>
 
 #include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/destructible.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 
 namespace perfetto {
 namespace trace_processor {
 
 class ArgsTracker;
-class BinderTracker;
 class ChunkedTraceReader;
 class ClockTracker;
 class EventTracker;
@@ -35,10 +35,7 @@
 class HeapGraphTracker;
 class HeapProfileTracker;
 class ProcessTracker;
-class SchedEventTracker;
 class SliceTracker;
-class SyscallTracker;
-class SystraceParser;
 class TraceParser;
 class TraceSorter;
 class TraceStorage;
@@ -56,17 +53,21 @@
   std::unique_ptr<ArgsTracker> args_tracker;
   std::unique_ptr<SliceTracker> slice_tracker;
   std::unique_ptr<ProcessTracker> process_tracker;
-  std::unique_ptr<SyscallTracker> syscall_tracker;
   std::unique_ptr<EventTracker> event_tracker;
-  std::unique_ptr<SchedEventTracker> sched_tracker;
   std::unique_ptr<ClockTracker> clock_tracker;
   std::unique_ptr<TraceParser> parser;
   std::unique_ptr<TraceSorter> sorter;
   std::unique_ptr<ChunkedTraceReader> chunk_reader;
   std::unique_ptr<HeapProfileTracker> heap_profile_tracker;
-  std::unique_ptr<SystraceParser> systrace_parser;
-  std::unique_ptr<HeapGraphTracker> heap_graph_tracker;
-  std::unique_ptr<BinderTracker> binder_tracker;
+
+  // These fields are stored as pointers to Destructible objects rather than
+  // their actual type (a subclass of Destructible), as the concrete subclass
+  // type is only available in the storage_full target. To access these fields,
+  // use the GetOrCreate() method on their subclass type,
+  // e.g. SyscallTracker::GetOrCreate(context).
+  std::unique_ptr<Destructible> syscall_tracker;  // SyscallTracker
+  std::unique_ptr<Destructible> sched_tracker;    // SchedEventTracker
+  std::unique_ptr<Destructible> systrace_parser;  // SystraceParser
 
   // The module at the index N is registered to handle field id N in
   // TracePacket.
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index e47806d..039b1bd 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -26,7 +26,6 @@
 #include "src/trace_processor/android_logs_table.h"
 #include "src/trace_processor/args_table.h"
 #include "src/trace_processor/cpu_profile_stack_sample_table.h"
-#include "src/trace_processor/heap_profile_allocation_table.h"
 #include "src/trace_processor/instants_table.h"
 #include "src/trace_processor/metadata_table.h"
 #include "src/trace_processor/process_table.h"
@@ -382,7 +381,6 @@
   StatsTable::RegisterTable(*db_, context_.storage.get());
   AndroidLogsTable::RegisterTable(*db_, context_.storage.get());
   RawTable::RegisterTable(*db_, context_.storage.get());
-  HeapProfileAllocationTable::RegisterTable(*db_, context_.storage.get());
   CpuProfileStackSampleTable::RegisterTable(*db_, context_.storage.get());
   StackProfileFrameTable::RegisterTable(*db_, context_.storage.get());
   StackProfileMappingTable::RegisterTable(*db_, context_.storage.get());
@@ -435,6 +433,9 @@
   DbSqliteTable::RegisterTable(*db_, &storage->symbol_table(),
                                storage->symbol_table().table_name());
   DbSqliteTable::RegisterTable(
+      *db_, &storage->heap_profile_allocation_table(),
+      storage->heap_profile_allocation_table().table_name());
+  DbSqliteTable::RegisterTable(
       *db_, &storage->stack_profile_callsite_table(),
       storage->stack_profile_callsite_table().table_name());
 
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 50877fa..54855c5 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -127,34 +127,34 @@
   }
 }
 
-void FreeLine(char* line) {
-  linenoiseHistoryAdd(line);
-  linenoiseHistorySave(GetHistoryPath().c_str());
-  linenoiseFree(line);
-}
+struct LineDeleter {
+  void operator()(char* p) const {
+    linenoiseHistoryAdd(p);
+    linenoiseHistorySave(GetHistoryPath().c_str());
+    linenoiseFree(p);
+  }
+};
 
-char* GetLine(const char* prompt) {
-  return linenoise(prompt);
+using ScopedLine = std::unique_ptr<char, LineDeleter>;
+
+ScopedLine GetLine(const char* prompt) {
+  return ScopedLine(linenoise(prompt));
 }
 
 #else
 
 void SetupLineEditor() {}
 
-void FreeLine(char* line) {
-  delete[] line;
-}
+using ScopedLine = std::unique_ptr<char>;
 
-char* GetLine(const char* prompt) {
+ScopedLine GetLine(const char* prompt) {
   printf("\r%80s\r%s", "", prompt);
   fflush(stdout);
-  char* line = new char[1024];
-  if (!fgets(line, 1024 - 1, stdin)) {
-    FreeLine(line);
+  ScopedLine line(new char[1024]);
+  if (!fgets(line.get(), 1024 - 1, stdin))
     return nullptr;
-  }
-  if (strlen(line) > 0)
-    line[strlen(line) - 1] = 0;
+  if (strlen(line.get()) > 0)
+    line.get()[strlen(line.get()) - 1] = 0;
   return line;
 }
 
@@ -452,15 +452,15 @@
   SetupLineEditor();
 
   for (;;) {
-    char* line = GetLine("> ");
+    ScopedLine line = GetLine("> ");
     if (!line)
       break;
-    if (strcmp(line, "") == 0)
+    if (strcmp(line.get(), "") == 0)
       continue;
-    if (line[0] == '.') {
+    if (line.get()[0] == '.') {
       char command[32] = {};
       char arg[1024] = {};
-      sscanf(line + 1, "%31s %1023s", command, arg);
+      sscanf(line.get() + 1, "%31s %1023s", command, arg);
       if (strcmp(command, "quit") == 0 || strcmp(command, "q") == 0) {
         break;
       } else if (strcmp(command, "help") == 0) {
@@ -477,10 +477,8 @@
     }
 
     base::TimeNanos t_start = base::GetWallTimeNs();
-    auto it = g_tp->ExecuteQuery(line);
+    auto it = g_tp->ExecuteQuery(line.get());
     PrintQueryResultInteractively(&it, t_start, column_width);
-
-    FreeLine(line);
   }
   return 0;
 }
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 2ceb370..46aefbf 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -18,26 +18,18 @@
 
 #include "perfetto/base/logging.h"
 #include "src/trace_processor/args_tracker.h"
-#include "src/trace_processor/binder_tracker.h"
 #include "src/trace_processor/clock_tracker.h"
 #include "src/trace_processor/event_tracker.h"
 #include "src/trace_processor/forwarding_trace_parser.h"
 #include "src/trace_processor/heap_profile_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
-#include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
-#include "src/trace_processor/importers/proto/android_probes_module.h"
-#include "src/trace_processor/importers/proto/heap_graph_module.h"
-#include "src/trace_processor/importers/proto/heap_graph_tracker.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
-#include "src/trace_processor/importers/proto/system_probes_module.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
-#include "src/trace_processor/importers/systrace/systrace_parser.h"
 #include "src/trace_processor/importers/systrace/systrace_trace_parser.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/stack_profile_tracker.h"
-#include "src/trace_processor/syscall_tracker.h"
 #include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_sorter.h"
 #include "src/trace_processor/track_tracker.h"
@@ -53,16 +45,8 @@
   context_.slice_tracker.reset(new SliceTracker(&context_));
   context_.event_tracker.reset(new EventTracker(&context_));
   context_.process_tracker.reset(new ProcessTracker(&context_));
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_SYSCALLS)
-  context_.syscall_tracker.reset(new SyscallTracker(&context_));
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_SYSCALLS)
   context_.clock_tracker.reset(new ClockTracker(&context_));
   context_.heap_profile_tracker.reset(new HeapProfileTracker(&context_));
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
-  context_.sched_tracker.reset(new SchedEventTracker(&context_));
-  context_.systrace_parser.reset(new SystraceParser(&context_));
-  context_.binder_tracker.reset(new BinderTracker(&context_));
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
 
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
   context_.modules.emplace_back(new FtraceModuleImpl(&context_));
@@ -74,15 +58,6 @@
   context_.ftrace_module =
       static_cast<FtraceModule*>(context_.modules.back().get());
 
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_HEAP_GRAPHS)
-  context_.modules.emplace_back(new HeapGraphModule(&context_));
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_HEAP_GRAPHS)
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_ANDROID_PROBES)
-  context_.modules.emplace_back(new AndroidProbesModule(&context_));
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_ANDROID_PROBES)
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
-  context_.modules.emplace_back(new SystemProbesModule(&context_));
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_SYSTEM_PROBES)
   context_.modules.emplace_back(new TrackEventModule(&context_));
 }
 
@@ -112,7 +87,7 @@
   if (context_.sorter)
     context_.sorter->ExtractEventsForced();
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
-  context_.sched_tracker->FlushPendingEvents();
+  SchedEventTracker::GetOrCreate(&context_)->FlushPendingEvents();
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_FTRACE)
   context_.event_tracker->FlushPendingEvents();
   context_.slice_tracker->FlushPendingSlices();
diff --git a/src/trace_processor/trace_storage.cc b/src/trace_processor/trace_storage.cc
index 2f4e872..c3c4077 100644
--- a/src/trace_processor/trace_storage.cc
+++ b/src/trace_processor/trace_storage.cc
@@ -132,12 +132,11 @@
                     android_log_.timestamps().end(), &start_ns, &end_ns);
   MaybeUpdateMinMax(raw_events_.timestamps().begin(),
                     raw_events_.timestamps().end(), &start_ns, &end_ns);
-  MaybeUpdateMinMax(heap_profile_allocations_.timestamps().begin(),
-                    heap_profile_allocations_.timestamps().end(), &start_ns,
-                    &end_ns);
 
   DbTableMaybeUpdateMinMax(counter_table_.ts(), &start_ns, &end_ns);
   DbTableMaybeUpdateMinMax(slice_table_.ts(), &start_ns, &end_ns);
+  DbTableMaybeUpdateMinMax(heap_profile_allocation_table_.ts(), &start_ns,
+                           &end_ns);
 
   if (start_ns == std::numeric_limits<int64_t>::max()) {
     return std::make_pair(0, 0);
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index 56c92a0..b27d5f1 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -32,10 +32,10 @@
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/ftrace_utils.h"
 #include "src/trace_processor/metadata.h"
 #include "src/trace_processor/stats.h"
-#include "src/trace_processor/string_pool.h"
 #include "src/trace_processor/tables/counter_tables.h"
 #include "src/trace_processor/tables/profiler_tables.h"
 #include "src/trace_processor/tables/slice_tables.h"
@@ -773,40 +773,6 @@
         index_;
   };
 
-  class HeapProfileAllocations {
-   public:
-    struct Row {
-      int64_t timestamp;
-      UniquePid upid;
-      int64_t callsite_id;
-      int64_t count;
-      int64_t size;
-    };
-
-    uint32_t size() const { return static_cast<uint32_t>(timestamps_.size()); }
-
-    void Insert(const Row& row) {
-      timestamps_.emplace_back(row.timestamp);
-      upids_.emplace_back(row.upid);
-      callsite_ids_.emplace_back(row.callsite_id);
-      counts_.emplace_back(row.count);
-      sizes_.emplace_back(row.size);
-    }
-
-    const std::deque<int64_t>& timestamps() const { return timestamps_; }
-    const std::deque<UniquePid>& upids() const { return upids_; }
-    const std::deque<int64_t>& callsite_ids() const { return callsite_ids_; }
-    const std::deque<int64_t>& counts() const { return counts_; }
-    const std::deque<int64_t>& sizes() const { return sizes_; }
-
-   private:
-    std::deque<int64_t> timestamps_;
-    std::deque<UniquePid> upids_;
-    std::deque<int64_t> callsite_ids_;
-    std::deque<int64_t> counts_;
-    std::deque<int64_t> sizes_;
-  };
-
   class CpuProfileStackSamples {
    public:
     struct Row {
@@ -1106,11 +1072,12 @@
     return &stack_profile_callsite_table_;
   }
 
-  const HeapProfileAllocations& heap_profile_allocations() const {
-    return heap_profile_allocations_;
+  const tables::HeapProfileAllocationTable& heap_profile_allocation_table()
+      const {
+    return heap_profile_allocation_table_;
   }
-  HeapProfileAllocations* mutable_heap_profile_allocations() {
-    return &heap_profile_allocations_;
+  tables::HeapProfileAllocationTable* mutable_heap_profile_allocation_table() {
+    return &heap_profile_allocation_table_;
   }
   const CpuProfileStackSamples& cpu_profile_stack_samples() const {
     return cpu_profile_stack_samples_;
@@ -1267,7 +1234,8 @@
   StackProfileFrames stack_profile_frames_;
   tables::StackProfileCallsiteTable stack_profile_callsite_table_{&string_pool_,
                                                                   nullptr};
-  HeapProfileAllocations heap_profile_allocations_;
+  tables::HeapProfileAllocationTable heap_profile_allocation_table_{
+      &string_pool_, nullptr};
   CpuProfileStackSamples cpu_profile_stack_samples_;
 
   // Symbol tables (mappings from frames to symbol names)
diff --git a/src/trace_processor/variadic.h b/src/trace_processor/variadic.h
index 6df011e..0444721 100644
--- a/src/trace_processor/variadic.h
+++ b/src/trace_processor/variadic.h
@@ -17,7 +17,7 @@
 #ifndef SRC_TRACE_PROCESSOR_VARIADIC_H_
 #define SRC_TRACE_PROCESSOR_VARIADIC_H_
 
-#include "src/trace_processor/string_pool.h"
+#include "src/trace_processor/containers/string_pool.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/test/ci/linux_tests.sh b/test/ci/linux_tests.sh
index 11f60a0..62bf1c7 100755
--- a/test/ci/linux_tests.sh
+++ b/test/ci/linux_tests.sh
@@ -23,6 +23,7 @@
 
 ${OUT_PATH}/perfetto_unittests
 ${OUT_PATH}/perfetto_integrationtests
+${OUT_PATH}/trace_processor_minimal_smoke_tests
 
 # If this is a split host+target build, use the trace_processoer_shell binary
 # from the host directory. In some cases (e.g. lsan x86 builds) the host binary
diff --git a/test/trace_processor/clock_sync.out b/test/trace_processor/clock_sync.out
index 77ee53f..aca328a 100644
--- a/test/trace_processor/clock_sync.out
+++ b/test/trace_processor/clock_sync.out
@@ -4,4 +4,7 @@
 1003,7
 1005,9
 2006,11
-3007,13
+2010,12
+2013,13
+3007,14
+3010,15
diff --git a/test/trace_processor/clock_sync.py b/test/trace_processor/clock_sync.py
index 9dded25..e097024 100644
--- a/test/trace_processor/clock_sync.py
+++ b/test/trace_processor/clock_sync.py
@@ -68,10 +68,25 @@
 
 # This counter should be translated @ BOOTTIME : 3000 + 7
 trace.add_gpu_counter(
-    ts=7, clock_id=SEQ_CLOCK1, counter_id=42, value=13, seq_id=3)
+    ts=7, clock_id=SEQ_CLOCK1, counter_id=42, value=14, seq_id=3)
 
 # This counter should be translated @ BOOTTIME : 2000 + 6
 trace.add_gpu_counter(
     ts=6, clock_id=SEQ_CLOCK1, seq_id=2, counter_id=42, value=11)
 
+# Set default clock for sequence 2.
+defaults_packet = trace.add_packet()
+defaults_packet.trusted_packet_sequence_id = 2
+defaults_packet.trace_packet_defaults.timestamp_clock_id = SEQ_CLOCK1
+
+# This counter should be translated @ BOOTTIME : 2000 + 10
+trace.add_gpu_counter(ts=10, seq_id=2, counter_id=42, value=12)
+
+# Manually specified clock_id overrides the default clock.
+trace.add_gpu_counter(
+    ts=2013, clock_id=CLOCK_BOOTTIME, seq_id=2, counter_id=42, value=13)
+
+# Other sequence's default clock isn't changed, so this should be in BOOTTIME.
+trace.add_gpu_counter(ts=3010, counter_id=42, value=15, seq_id=3)
+
 print(trace.trace.SerializeToString())
diff --git a/test/trace_processor/heap_graph_interleaved_object.out b/test/trace_processor/heap_graph_interleaved_object.out
index c64ab68..82dc981 100644
--- a/test/trace_processor/heap_graph_interleaved_object.out
+++ b/test/trace_processor/heap_graph_interleaved_object.out
@@ -1,6 +1,6 @@
 "id","type","upid","graph_sample_ts","object_id","self_size","retained_size","unique_retained_size","reference_set_id","reachable","type_name","deobfuscated_type_name","root_type"
-0,"heap_graph_object",3,10,1,64,64,64,0,1,"FactoryProducerDelegateImplActor","[NULL]","ROOT_JAVA_FRAME"
-1,"heap_graph_object",2,10,1,64,96,96,0,1,"FactoryProducerDelegateImplActor","[NULL]","ROOT_JAVA_FRAME"
-2,"heap_graph_object",2,10,2,32,32,32,1,1,"Foo","[NULL]","[NULL]"
+0,"heap_graph_object",3,10,1,64,-1,-1,0,1,"FactoryProducerDelegateImplActor","[NULL]","ROOT_JAVA_FRAME"
+1,"heap_graph_object",2,10,1,64,-1,-1,0,1,"FactoryProducerDelegateImplActor","[NULL]","ROOT_JAVA_FRAME"
+2,"heap_graph_object",2,10,2,32,-1,-1,1,1,"Foo","[NULL]","[NULL]"
 3,"heap_graph_object",2,10,3,128,-1,-1,1,0,"Foo","[NULL]","[NULL]"
 4,"heap_graph_object",2,10,4,256,-1,-1,1,0,"a","DeobfuscatedA","[NULL]"
diff --git a/test/trace_processor/heap_graph_object.out b/test/trace_processor/heap_graph_object.out
index 70e31f1..a25b67b 100644
--- a/test/trace_processor/heap_graph_object.out
+++ b/test/trace_processor/heap_graph_object.out
@@ -1,5 +1,5 @@
 "id","type","upid","graph_sample_ts","object_id","self_size","retained_size","unique_retained_size","reference_set_id","reachable","type_name","deobfuscated_type_name","root_type"
-0,"heap_graph_object",2,10,1,64,96,96,0,1,"FactoryProducerDelegateImplActor","[NULL]","ROOT_JAVA_FRAME"
-1,"heap_graph_object",2,10,2,32,32,32,1,1,"Foo","[NULL]","[NULL]"
+0,"heap_graph_object",2,10,1,64,-1,-1,0,1,"FactoryProducerDelegateImplActor","[NULL]","ROOT_JAVA_FRAME"
+1,"heap_graph_object",2,10,2,32,-1,-1,1,1,"Foo","[NULL]","[NULL]"
 2,"heap_graph_object",2,10,3,128,-1,-1,1,0,"Foo","[NULL]","[NULL]"
 3,"heap_graph_object",2,10,4,256,-1,-1,1,0,"a","DeobfuscatedA","[NULL]"
diff --git a/test/trace_processor/index b/test/trace_processor/index
index 6793acd..1d6dee0 100644
--- a/test/trace_processor/index
+++ b/test/trace_processor/index
@@ -151,6 +151,7 @@
 track_event_same_tids.textproto track_event_slices.sql track_event_same_tids_slices.out
 track_event_typed_args.textproto track_event_slices.sql track_event_typed_args_slices.out
 track_event_typed_args.textproto track_event_args.sql track_event_typed_args_args.out
+track_event_tracks.textproto track_event_slices.sql track_event_tracks_slices.out
 
 # Parsing of an html file with systrace data inside
 ../data/systrace.html systrace_html.sql systrace_html.out
diff --git a/test/trace_processor/track_event_same_tids_slices.out b/test/trace_processor/track_event_same_tids_slices.out
index 350acf9..48fa77b 100644
--- a/test/trace_processor/track_event_same_tids_slices.out
+++ b/test/trace_processor/track_event_same_tids_slices.out
@@ -1,3 +1,3 @@
-"ts","dur","category","name","arg_set_id"
-1000,0,"cat","name1",0
-2000,0,"cat","name2",0
+"thread","ts","dur","category","name","arg_set_id"
+"t1",1000,0,"cat","name1",0
+"t2",2000,0,"cat","name2",0
diff --git a/test/trace_processor/track_event_slices.sql b/test/trace_processor/track_event_slices.sql
index 68772b4..42f7b69 100644
--- a/test/trace_processor/track_event_slices.sql
+++ b/test/trace_processor/track_event_slices.sql
@@ -13,4 +13,14 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
-select ts, dur, category, name, arg_set_id from slice order by ts asc;
\ No newline at end of file
+select
+  thread.name as thread,
+  slice.ts,
+  slice.dur,
+  slice.category,
+  slice.name,
+  slice.arg_set_id
+from slice
+left join thread_track on slice.track_id = thread_track.id
+left join thread on thread_track.utid = thread.utid
+order by ts asc;
diff --git a/test/trace_processor/track_event_tracks.textproto b/test/trace_processor/track_event_tracks.textproto
new file mode 100644
index 0000000..fd8cdc8
--- /dev/null
+++ b/test/trace_processor/track_event_tracks.textproto
@@ -0,0 +1,69 @@
+# Sequence 1 defaults to track for "t1".
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_descriptor {
+    uuid: 1
+    thread {
+      pid: 5
+      tid: 1
+      thread_name: "t1"
+    }
+  }
+  trace_packet_defaults {
+    track_event_defaults {
+      track_uuid: 1
+    }
+  }
+}
+# Sequence 2 defaults to track for "t2".
+packet {
+  trusted_packet_sequence_id: 2
+  timestamp: 0
+  incremental_state_cleared: true
+  track_descriptor {
+    uuid: 2
+    thread {
+      pid: 5
+      tid: 2
+      thread_name: "t2"
+    }
+  }
+  trace_packet_defaults {
+    track_event_defaults {
+      track_uuid: 2
+    }
+  }
+}
+# Should appear on default track "t1".
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 1000
+  track_event {
+    categories: "cat"
+    name: "name1"
+    type: 3
+  }
+}
+# Should appear on default track "t2".
+packet {
+  trusted_packet_sequence_id: 2
+  timestamp: 2000
+  track_event {
+    categories: "cat"
+    name: "name2"
+    type: 3
+  }
+}
+# Should appear on overridden track "t2".
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 3000
+  track_event {
+    track_uuid: 2
+    categories: "cat"
+    name: "name3"
+    type: 3
+  }
+}
diff --git a/test/trace_processor/track_event_tracks_slices.out b/test/trace_processor/track_event_tracks_slices.out
new file mode 100644
index 0000000..93c7186
--- /dev/null
+++ b/test/trace_processor/track_event_tracks_slices.out
@@ -0,0 +1,4 @@
+"thread","ts","dur","category","name","arg_set_id"
+"t1",1000,0,"cat","name1",0
+"t2",2000,0,"cat","name2",0
+"t2",3000,0,"cat","name3",0
diff --git a/test/trace_processor/track_event_typed_args.pb b/test/trace_processor/track_event_typed_args.pb
new file mode 100644
index 0000000..c478cb0
--- /dev/null
+++ b/test/trace_processor/track_event_typed_args.pb
Binary files differ
diff --git a/test/trace_processor/track_event_typed_args_slices.out b/test/trace_processor/track_event_typed_args_slices.out
index d280266..6a9a969 100644
--- a/test/trace_processor/track_event_typed_args_slices.out
+++ b/test/trace_processor/track_event_typed_args_slices.out
@@ -1,4 +1,4 @@
-"ts","dur","category","name","arg_set_id"
-1000,0,"cat","name1",2
-2000,0,"cat","name2",3
-3000,0,"cat","name3",4
+"thread","ts","dur","category","name","arg_set_id"
+"t1",1000,0,"cat","name1",2
+"t1",2000,0,"cat","name2",3
+"t1",3000,0,"cat","name3",4
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index c00e697..ebc440a 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -158,11 +158,17 @@
         ('static_libs', {'libasync_safe'}),
         ('header_libs', {'bionic_libc_platform_headers'}),
     ],
-    'perfetto_unittests': [('data', set(enumerate_data_deps())),],
+    'perfetto_unittests': [
+        ('data', set(enumerate_data_deps())),
+        ('include_dirs', {'bionic/libc/kernel'}),
+    ],
     'traced_probes': [
         ('required', {'libperfetto_android_internal', 'trigger_perfetto'}),
     ],
     'libperfetto_android_internal': [('static_libs', {'libhealthhalutils'}),],
+    'traced_perf': [
+        ('include_dirs', {'bionic/libc/kernel'}),
+    ],
     'trace_processor_shell': [
       ('dist', {'targets': ['sdk_repo']}),
       ('stl', 'libc++_static'),
diff --git a/tools/heap_profile b/tools/heap_profile
index 967299e..735fc58 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -167,12 +167,12 @@
   parser.add_argument(
       "--no-running",
       action="store_true",
-      help="Do not target already running processes.")
+      help="Do not target already running processes. Requires Android 11.")
   parser.add_argument(
       "--no-startup",
       action="store_true",
       help="Do not target processes that start during "
-      "the profile.")
+      "the profile. Requires Android 11.")
   parser.add_argument(
       "--shmem-size",
       help="Size of buffer between client and "
diff --git a/tools/trace_to_text/BUILD.gn b/tools/trace_to_text/BUILD.gn
index 4b2044c..41c844d 100644
--- a/tools/trace_to_text/BUILD.gn
+++ b/tools/trace_to_text/BUILD.gn
@@ -38,7 +38,6 @@
 
 source_set("utils") {
   deps = [
-    ":symbolizer",
     "../../gn:default_deps",
     "../../include/perfetto/profiling:deobfuscator",
     "../../include/perfetto/protozero",
@@ -47,12 +46,13 @@
     "../../protos/perfetto/trace/interned_data:zero",
     "../../protos/perfetto/trace/profiling:zero",
     "../../src/profiling:deobfuscator",
+    "../../src/profiling/symbolizer",
+    "../../src/profiling/symbolizer:symbolize_database",
   ]
   public_deps = [
     "../../gn:zlib",
     "../../include/perfetto/ext/base",
     "../../include/perfetto/profiling:deobfuscator",
-    "../../include/perfetto/profiling:symbolizer",
   ]
   sources = [
     "utils.cc",
@@ -60,45 +60,21 @@
   ]
 }
 
-source_set("local_symbolizer") {
-  public_deps = [
-    "../../include/perfetto/ext/base",
-    "../../include/perfetto/profiling:symbolizer",
-  ]
-  deps = [
-    ":symbolizer",
-    ":utils",
-    "../../gn:default_deps",
-    "../../include/perfetto/protozero",
-  ]
-  sources = [
-    "local_symbolizer.cc",
-    "local_symbolizer.h",
-  ]
-}
-
-source_set("symbolizer") {
-  deps = [
-    "../../gn:default_deps",
-    "../../include/perfetto/profiling:symbolizer",
-  ]
-  sources = [
-    "symbolizer.cc",
-  ]
-}
-
 source_set("pprofbuilder") {
+  public_deps = [
+    "../../include/perfetto/profiling:pprof_builder",
+  ]
   deps = [
-    ":symbolizer",
     ":utils",
     "../../gn:default_deps",
     "../../include/perfetto/base",
-    "../../include/perfetto/profiling:symbolizer",
     "../../include/perfetto/protozero",
     "../../include/perfetto/trace_processor",
     "../../protos/perfetto/trace:zero",
     "../../protos/perfetto/trace/profiling:zero",
     "../../protos/third_party/pprof:zero",
+    "../../src/profiling/symbolizer",
+    "../../src/profiling/symbolizer:symbolize_database",
   ]
   sources = [
     "pprof_builder.cc",
@@ -117,7 +93,6 @@
 # executable) and by the "lite" version (the WASM module for the UI).
 source_set("common") {
   deps = [
-    ":local_symbolizer",
     ":pprofbuilder",
     ":utils",
     "../../gn:default_deps",
@@ -126,6 +101,8 @@
     "../../include/perfetto/profiling:deobfuscator",
     "../../include/perfetto/protozero",
     "../../protos/perfetto/trace:zero",
+    "../../src/profiling/symbolizer",
+    "../../src/profiling/symbolizer:symbolize_database",
     "../../src/trace_processor:lib",
   ]
   sources = [
diff --git a/tools/trace_to_text/pprof_builder.cc b/tools/trace_to_text/pprof_builder.cc
index 77dc8615..1b1014d 100644
--- a/tools/trace_to_text/pprof_builder.cc
+++ b/tools/trace_to_text/pprof_builder.cc
@@ -31,11 +31,13 @@
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/utils.h"
-#include "perfetto/profiling/symbolizer.h"
 #include "perfetto/protozero/packed_repeated_fields.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/trace_processor/trace_processor.h"
 
+#include "src/profiling/symbolizer/symbolize_database.h"
+#include "src/profiling/symbolizer/symbolizer.h"
+
 #include "protos/perfetto/trace/trace.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "protos/third_party/pprof/profile.pbzero.h"
@@ -497,7 +499,7 @@
 
 bool TraceToPprof(std::istream* input,
                   std::vector<SerializedProfile>* output,
-                  Symbolizer* symbolizer,
+                  profiling::Symbolizer* symbolizer,
                   uint64_t pid,
                   const std::vector<uint64_t>& timestamps) {
   trace_processor::Config config;
@@ -513,26 +515,27 @@
 
 bool TraceToPprof(trace_processor::TraceProcessor* tp,
                   std::vector<SerializedProfile>* output,
-                  Symbolizer* symbolizer,
+                  profiling::Symbolizer* symbolizer,
                   uint64_t pid,
                   const std::vector<uint64_t>& timestamps) {
   if (symbolizer) {
-    SymbolizeDatabase(tp, symbolizer, [&tp](const std::string& packet_proto) {
-      std::unique_ptr<uint8_t[]> buf(new uint8_t[11 + packet_proto.size()]);
-      uint8_t* wptr = &buf[0];
-      *(wptr++) =
-          MakeTagLengthDelimited(protos::pbzero::Trace::kPacketFieldNumber);
-      wptr = WriteVarInt(packet_proto.size(), wptr);
-      memcpy(wptr, packet_proto.data(), packet_proto.size());
-      wptr += packet_proto.size();
-      size_t buf_size = static_cast<size_t>(wptr - &buf[0]);
-      auto status = tp->Parse(std::move(buf), buf_size);
-      if (!status.ok()) {
-        PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
-                                status.message().c_str());
-        return;
-      }
-    });
+    profiling::SymbolizeDatabase(
+        tp, symbolizer, [&tp](const std::string& packet_proto) {
+          std::unique_ptr<uint8_t[]> buf(new uint8_t[11 + packet_proto.size()]);
+          uint8_t* wptr = &buf[0];
+          *(wptr++) =
+              MakeTagLengthDelimited(protos::pbzero::Trace::kPacketFieldNumber);
+          wptr = WriteVarInt(packet_proto.size(), wptr);
+          memcpy(wptr, packet_proto.data(), packet_proto.size());
+          wptr += packet_proto.size();
+          size_t buf_size = static_cast<size_t>(wptr - &buf[0]);
+          auto status = tp->Parse(std::move(buf), buf_size);
+          if (!status.ok()) {
+            PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
+                                    status.message().c_str());
+            return;
+          }
+        });
   }
 
   tp->NotifyEndOfFile();
diff --git a/tools/trace_to_text/symbolize_profile.cc b/tools/trace_to_text/symbolize_profile.cc
index b354572..7cf1e10 100644
--- a/tools/trace_to_text/symbolize_profile.cc
+++ b/tools/trace_to_text/symbolize_profile.cc
@@ -19,11 +19,13 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
-#include "perfetto/profiling/symbolizer.h"
 #include "perfetto/trace_processor/trace_processor.h"
 
+#include "src/profiling/symbolizer/symbolize_database.h"
+#include "src/profiling/symbolizer/symbolizer.h"
+
 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
-#include "tools/trace_to_text/local_symbolizer.h"
+#include "src/profiling/symbolizer/local_symbolizer.h"
 #endif
 
 #include "protos/perfetto/trace/trace.pbzero.h"
@@ -36,11 +38,11 @@
 // Ingest profile, and emit a symbolization table for each sequence. This can
 // be prepended to the profile to attach the symbol information.
 int SymbolizeProfile(std::istream* input, std::ostream* output) {
-  std::unique_ptr<Symbolizer> symbolizer;
+  std::unique_ptr<profiling::Symbolizer> symbolizer;
   auto binary_path = GetPerfettoBinaryPath();
   if (!binary_path.empty()) {
 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
-    symbolizer.reset(new LocalSymbolizer(GetPerfettoBinaryPath()));
+    symbolizer.reset(new profiling::LocalSymbolizer(GetPerfettoBinaryPath()));
 #else
     PERFETTO_FATAL("This build does not support local symbolization.");
 #endif
diff --git a/tools/trace_to_text/trace_to_profile.cc b/tools/trace_to_text/trace_to_profile.cc
index 4f5f1ec..ae9a2a5 100644
--- a/tools/trace_to_text/trace_to_profile.cc
+++ b/tools/trace_to_text/trace_to_profile.cc
@@ -22,7 +22,7 @@
 #include "perfetto/base/build_config.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
-#include "tools/trace_to_text/local_symbolizer.h"
+#include "src/profiling/symbolizer/local_symbolizer.h"
 #endif
 #include "tools/trace_to_text/utils.h"
 
@@ -31,7 +31,7 @@
 #include "perfetto/ext/base/temp_file.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/profiling/pprof_builder.h"
-#include "perfetto/profiling/symbolizer.h"
+#include "src/profiling/symbolizer/symbolizer.h"
 
 namespace {
 
@@ -53,11 +53,11 @@
                    std::ostream* output,
                    uint64_t pid,
                    std::vector<uint64_t> timestamps) {
-  std::unique_ptr<Symbolizer> symbolizer;
+  std::unique_ptr<profiling::Symbolizer> symbolizer;
   auto binary_path = GetPerfettoBinaryPath();
   if (!binary_path.empty()) {
 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
-    symbolizer.reset(new LocalSymbolizer(GetPerfettoBinaryPath()));
+    symbolizer.reset(new profiling::LocalSymbolizer(GetPerfettoBinaryPath()));
 #else
     PERFETTO_ELOG(
         "This build does not support local symbolization. "
diff --git a/tools/trace_to_text/utils.cc b/tools/trace_to_text/utils.cc
index 7b9b358..efc05f5 100644
--- a/tools/trace_to_text/utils.cc
+++ b/tools/trace_to_text/utils.cc
@@ -40,61 +40,9 @@
 
 using Iterator = trace_processor::TraceProcessor::Iterator;
 
-constexpr const char* kQueryUnsymbolized =
-    "select spm.name, spm.build_id, spf.rel_pc "
-    "from stack_profile_frame spf "
-    "join stack_profile_mapping spm "
-    "on spf.mapping = spm.id "
-    "where spm.build_id != '' and spf.symbol_set_id == 0";
 
 constexpr size_t kCompressionBufferSize = 500 * 1024;
 
-std::string FromHex(const char* str, size_t size) {
-  if (size % 2) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
-    return "";
-  }
-  std::string result(size / 2, '\0');
-  for (size_t i = 0; i < size; i += 2) {
-    char hex_byte[3];
-    hex_byte[0] = str[i];
-    hex_byte[1] = str[i + 1];
-    hex_byte[2] = '\0';
-    char* end;
-    long int byte = strtol(hex_byte, &end, 16);
-    if (*end != '\0') {
-      PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
-      return "";
-    }
-    result[i / 2] = static_cast<char>(byte);
-  }
-  return result;
-}
-
-std::string FromHex(const std::string& str) {
-  return FromHex(str.c_str(), str.size());
-}
-
-using NameAndBuildIdPair = std::pair<std::string, std::string>;
-
-std::map<NameAndBuildIdPair, std::vector<uint64_t>> GetUnsymbolizedFrames(
-    trace_processor::TraceProcessor* tp) {
-  std::map<std::pair<std::string, std::string>, std::vector<uint64_t>> res;
-  Iterator it = tp->ExecuteQuery(kQueryUnsymbolized);
-  while (it.Next()) {
-    auto name_and_buildid =
-        std::make_pair(it.Get(0).string_value, FromHex(it.Get(1).string_value));
-    int64_t rel_pc = it.Get(2).long_value;
-    res[name_and_buildid].emplace_back(rel_pc);
-  }
-  if (!it.Status().ok()) {
-    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
-                            it.Status().message().c_str());
-    return {};
-  }
-  return res;
-}
-
 std::map<std::string, std::set<std::string>> GetHeapGraphClasses(
     trace_processor::TraceProcessor* tp) {
   std::map<std::string, std::set<std::string>> res;
@@ -242,37 +190,6 @@
   return true;
 }
 
-void SymbolizeDatabase(trace_processor::TraceProcessor* tp,
-                       Symbolizer* symbolizer,
-                       std::function<void(const std::string&)> callback) {
-  PERFETTO_CHECK(symbolizer);
-  auto unsymbolized = GetUnsymbolizedFrames(tp);
-  for (auto it = unsymbolized.cbegin(); it != unsymbolized.cend(); ++it) {
-    const auto& name_and_buildid = it->first;
-    const std::vector<uint64_t>& rel_pcs = it->second;
-    auto res = symbolizer->Symbolize(name_and_buildid.first,
-                                     name_and_buildid.second, rel_pcs);
-    if (res.empty())
-      continue;
-
-    protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> packet;
-    auto* module_symbols = packet->set_module_symbols();
-    module_symbols->set_path(name_and_buildid.first);
-    module_symbols->set_build_id(name_and_buildid.second);
-    PERFETTO_DCHECK(res.size() == rel_pcs.size());
-    for (size_t i = 0; i < res.size(); ++i) {
-      auto* address_symbols = module_symbols->add_address_symbols();
-      address_symbols->set_address(rel_pcs[i]);
-      for (const SymbolizedFrame& frame : res[i]) {
-        auto* line = address_symbols->add_lines();
-        line->set_function_name(frame.function_name);
-        line->set_source_file_name(frame.file_name);
-        line->set_line_number(frame.line);
-      }
-    }
-    callback(packet.SerializeAsString());
-  }
-}
 
 void DeobfuscateDatabase(
     trace_processor::TraceProcessor* tp,
diff --git a/tools/trace_to_text/utils.h b/tools/trace_to_text/utils.h
index b2e2c0d..46c86f9 100644
--- a/tools/trace_to_text/utils.h
+++ b/tools/trace_to_text/utils.h
@@ -32,7 +32,6 @@
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/paged_memory.h"
 #include "perfetto/profiling/deobfuscator.h"
-#include "perfetto/profiling/symbolizer.h"
 
 namespace perfetto {
 
@@ -65,12 +64,6 @@
 
 void WriteTracePacket(const std::string& str, std::ostream* output);
 
-// Generate ModuleSymbol protos for all unsymbolized frames in the database.
-// Wrap them in proto-encoded TracePackets messages and call callback.
-void SymbolizeDatabase(trace_processor::TraceProcessor* tp,
-                       Symbolizer* symbolizer,
-                       std::function<void(const std::string&)> callback);
-
 // Generate ObfuscationMapping protos for all obfuscated java names in the
 // database.
 // Wrap them in proto-encoded TracePackets messages and call callback.
diff --git a/ui/src/frontend/legacy_trace_viewer.ts b/ui/src/frontend/legacy_trace_viewer.ts
index 4dafe4c..c55451d 100644
--- a/ui/src/frontend/legacy_trace_viewer.ts
+++ b/ui/src/frontend/legacy_trace_viewer.ts
@@ -22,7 +22,8 @@
   fileName = fileName.toLowerCase();
   return (
       fileName.endsWith('.json') || fileName.endsWith('.json.gz') ||
-      fileName.endsWith('.zip') || fileName.endsWith('.ctrace'));
+      fileName.endsWith('.zip') || fileName.endsWith('.ctrace') ||
+      fileName.endsWith('.html'));
 }
 
 export function openFileWithLegacyTraceViewer(file: File) {