Merge changes I442d4291,I239ea5e4

* changes:
  android builds (incl. standalone): switch from Oz to O2
  O2-friendly GetStaticEventInfo
diff --git a/.gitignore b/.gitignore
index 821445d..655f094 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 .android_config
+.ccls-cache
 .cproject
 .deps_sha1
 .DS_Store
diff --git a/Android.bp b/Android.bp
index e6fe23b..a013fa9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -426,6 +426,7 @@
     "liblog",
     "libprotobuf-cpp-lite",
   ],
+  host_supported: true,
   export_include_dirs: [
     "include",
     "include/perfetto/base/build_configs/android_tree",
@@ -4900,11 +4901,9 @@
     "tools/trace_to_text/local_symbolizer.cc",
     "tools/trace_to_text/main.cc",
     "tools/trace_to_text/pprof_builder.cc",
-    "tools/trace_to_text/profile_visitor.cc",
     "tools/trace_to_text/proto_full_utils.cc",
     "tools/trace_to_text/symbolize_profile.cc",
     "tools/trace_to_text/symbolizer.cc",
-    "tools/trace_to_text/trace_symbol_table.cc",
     "tools/trace_to_text/trace_to_profile.cc",
     "tools/trace_to_text/trace_to_systrace.cc",
     "tools/trace_to_text/trace_to_text.cc",
diff --git a/BUILD b/BUILD
index b931137..a101de8 100644
--- a/BUILD
+++ b/BUILD
@@ -367,11 +367,13 @@
         "include/perfetto/tracing/internal/data_source_internal.h",
         "include/perfetto/tracing/internal/tracing_muxer.h",
         "include/perfetto/tracing/internal/tracing_tls.h",
+        "include/perfetto/tracing/internal/track_event_data_source.h",
         "include/perfetto/tracing/locked_handle.h",
         "include/perfetto/tracing/platform.h",
         "include/perfetto/tracing/trace_writer_base.h",
         "include/perfetto/tracing/tracing.h",
         "include/perfetto/tracing/tracing_backend.h",
+        "include/perfetto/tracing/track_event.h",
     ],
 )
 
@@ -874,6 +876,24 @@
     ],
 )
 
+# GN target: //src/tracing:client_api
+filegroup(
+    name = "src_tracing_client_api",
+    srcs = [
+        "src/tracing/data_source.cc",
+        "src/tracing/internal/in_process_tracing_backend.cc",
+        "src/tracing/internal/in_process_tracing_backend.h",
+        "src/tracing/internal/system_tracing_backend.cc",
+        "src/tracing/internal/system_tracing_backend.h",
+        "src/tracing/internal/tracing_muxer_impl.cc",
+        "src/tracing/internal/tracing_muxer_impl.h",
+        "src/tracing/platform.cc",
+        "src/tracing/tracing.cc",
+        "src/tracing/track_event.cc",
+        "src/tracing/virtual_destructors.cc",
+    ],
+)
+
 # GN target: //src/tracing:common
 filegroup(
     name = "src_tracing_common",
@@ -910,6 +930,14 @@
     ],
 )
 
+# GN target: //src/tracing:platform_posix
+filegroup(
+    name = "src_tracing_platform_posix",
+    srcs = [
+        "src/tracing/platform_posix.cc",
+    ],
+)
+
 # GN target: //src/tracing:tracing
 filegroup(
     name = "src_tracing_tracing",
@@ -996,8 +1024,6 @@
     name = "tools_trace_to_text_symbolizer",
     srcs = [
         "tools/trace_to_text/symbolizer.cc",
-        "tools/trace_to_text/trace_symbol_table.cc",
-        "tools/trace_to_text/trace_symbol_table.h",
     ],
 )
 
@@ -1005,8 +1031,6 @@
 filegroup(
     name = "tools_trace_to_text_utils",
     srcs = [
-        "tools/trace_to_text/profile_visitor.cc",
-        "tools/trace_to_text/profile_visitor.h",
         "tools/trace_to_text/utils.cc",
         "tools/trace_to_text/utils.h",
     ],
@@ -1273,6 +1297,14 @@
     ],
 )
 
+# GN target: //protos/perfetto/config:merged_config
+perfetto_cc_proto_library(
+    name = "protos_perfetto_config_merged_config",
+    deps = [
+        ":protos_perfetto_config_merged_config_protos",
+    ],
+)
+
 # GN target: //protos/perfetto/common:lite
 perfetto_cc_proto_library(
     name = "protos_perfetto_common_lite",
@@ -1281,6 +1313,17 @@
     ],
 )
 
+# GN target: //protos/perfetto/config:merged_config
+perfetto_proto_library(
+    name = "protos_perfetto_config_merged_config_protos",
+    srcs = [
+        "protos/perfetto/config/perfetto_config.proto",
+    ],
+    visibility = [
+        "//visibility:public",
+    ],
+)
+
 # GN target: //protos/perfetto/trace/ftrace:lite
 perfetto_cc_proto_library(
     name = "protos_perfetto_trace_ftrace_lite",
@@ -1690,6 +1733,9 @@
     srcs = [
         "protos/perfetto/trace/perfetto_trace.proto",
     ],
+    visibility = [
+        "//visibility:public",
+    ],
 )
 
 # GN target: //protos/perfetto/config/profiling:zero
@@ -1846,6 +1892,9 @@
     srcs = [
         "protos/perfetto/metrics/metrics.proto",
     ],
+    visibility = [
+        "//visibility:public",
+    ],
     deps = [
         ":protos_perfetto_metrics_android_protos",
     ],
@@ -1931,6 +1980,76 @@
 # Public targets
 # ##############################################################################
 
+# GN target: //:libperfetto_client_experimental
+perfetto_cc_library(
+    name = "libperfetto_client_experimental",
+    srcs = [
+        "include/perfetto/tracing.h",
+        ":src_base_base",
+        ":src_base_unix_socket",
+        ":src_ipc_ipc",
+        ":src_protozero_protozero",
+        ":src_tracing_client_api",
+        ":src_tracing_common",
+        ":src_tracing_ipc",
+        ":src_tracing_platform_posix",
+        ":src_tracing_tracing",
+    ],
+    hdrs = [
+        ":include_perfetto_base_base",
+        ":include_perfetto_ext_base_base",
+        ":include_perfetto_ext_ipc_ipc",
+        ":include_perfetto_ext_tracing_core_core",
+        ":include_perfetto_ext_tracing_ipc_ipc",
+        ":include_perfetto_protozero_protozero",
+        ":include_perfetto_tracing_core_core",
+        ":include_perfetto_tracing_tracing",
+    ],
+    visibility = [
+        "//visibility:public",
+    ],
+    deps = [
+        ":protos_perfetto_common_lite",
+        ":protos_perfetto_common_zero",
+        ":protos_perfetto_config_android_lite",
+        ":protos_perfetto_config_android_zero",
+        ":protos_perfetto_config_ftrace_lite",
+        ":protos_perfetto_config_ftrace_zero",
+        ":protos_perfetto_config_gpu_lite",
+        ":protos_perfetto_config_gpu_zero",
+        ":protos_perfetto_config_inode_file_lite",
+        ":protos_perfetto_config_inode_file_zero",
+        ":protos_perfetto_config_lite",
+        ":protos_perfetto_config_power_lite",
+        ":protos_perfetto_config_power_zero",
+        ":protos_perfetto_config_process_stats_lite",
+        ":protos_perfetto_config_process_stats_zero",
+        ":protos_perfetto_config_profiling_lite",
+        ":protos_perfetto_config_profiling_zero",
+        ":protos_perfetto_config_sys_stats_lite",
+        ":protos_perfetto_config_sys_stats_zero",
+        ":protos_perfetto_config_zero",
+        ":protos_perfetto_ipc_ipc",
+        ":protos_perfetto_ipc_wire_protocol",
+        ":protos_perfetto_trace_android_zero",
+        ":protos_perfetto_trace_chrome_zero",
+        ":protos_perfetto_trace_filesystem_zero",
+        ":protos_perfetto_trace_ftrace_zero",
+        ":protos_perfetto_trace_gpu_zero",
+        ":protos_perfetto_trace_interned_data_zero",
+        ":protos_perfetto_trace_minimal_lite",
+        ":protos_perfetto_trace_minimal_zero",
+        ":protos_perfetto_trace_non_minimal_zero",
+        ":protos_perfetto_trace_perfetto_zero",
+        ":protos_perfetto_trace_power_zero",
+        ":protos_perfetto_trace_profiling_zero",
+        ":protos_perfetto_trace_ps_zero",
+        ":protos_perfetto_trace_sys_stats_zero",
+        ":protos_perfetto_trace_track_event_zero",
+        ":protos_perfetto_trace_trusted_lite",
+    ] + PERFETTO_CONFIG.deps.protobuf_lite,
+)
+
 # GN target: //src/perfetto_cmd:perfetto
 perfetto_cc_binary(
     name = "perfetto",
diff --git a/BUILD.gn b/BUILD.gn
index 0a10260..0ba5807 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -210,6 +210,7 @@
     configs += [ "//build/config/compiler:no_chromium_code" ]
     public_deps = [
       "include/perfetto/ext/tracing/core",
+      "protos/perfetto/common:zero",
       "protos/perfetto/trace:zero",
       "protos/perfetto/trace/chrome:zero",
       "protos/perfetto/trace/interned_data:zero",
diff --git a/bazel/BUILD b/bazel/BUILD
index 069748d..1bf33a4 100644
--- a/bazel/BUILD
+++ b/bazel/BUILD
@@ -17,3 +17,16 @@
     values = {"cpu": "darwin"},
     visibility = ["//visibility:public"],
 )
+
+config_setting(
+    name = "os_linux",
+    values = {"cpu": "k8"},
+    visibility = ["//visibility:public"],
+)
+
+# Note this config does not imply MSVC.
+config_setting(
+    name = "os_windows",
+    values = {"cpu": "x64_windows"},
+    visibility = ["//visibility:public"],
+)
diff --git a/bazel/jsoncpp.BUILD b/bazel/jsoncpp.BUILD
index 8a33eb8..bef06a1 100644
--- a/bazel/jsoncpp.BUILD
+++ b/bazel/jsoncpp.BUILD
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
+
 cc_library(
     name = "jsoncpp",
     srcs = [
@@ -38,7 +40,7 @@
     ],
     copts = [
         "-Isrc/lib_json",
-    ],
+    ] + PERFETTO_CONFIG.deps_copts.jsoncpp,
     defines = [
         "JSON_USE_EXCEPTION=0",
     ],
diff --git a/bazel/linenoise.BUILD b/bazel/linenoise.BUILD
index d2b6053..050e7c5 100644
--- a/bazel/linenoise.BUILD
+++ b/bazel/linenoise.BUILD
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
+
 cc_library(
     name = "linenoise",
     srcs = [
@@ -23,5 +25,6 @@
     includes = [
         ".",
     ],
+    copts = PERFETTO_CONFIG.deps_copts.linenoise,
     visibility = ["//visibility:public"],
 )
diff --git a/bazel/rules.bzl b/bazel/rules.bzl
index 87c48c7..8713984 100644
--- a/bazel/rules.bzl
+++ b/bazel/rules.bzl
@@ -21,12 +21,14 @@
 
 def default_cc_args():
     return {
-        "deps": [PERFETTO_CONFIG.root + ":build_config_hdr"],
+        "deps": PERFETTO_CONFIG.deps.build_config,
         "copts": [],
         "includes": ["include"],
         "linkopts": select({
+            "@perfetto//bazel:os_linux": ["-ldl", "-lrt"],
             "@perfetto//bazel:os_osx": [],
-            "//conditions:default": ["-ldl", "-lrt"],
+            "@perfetto//bazel:os_windows": [],
+            "//conditions:default": ["-ldl"],
         }),
     }
 
diff --git a/bazel/sqlite.BUILD b/bazel/sqlite.BUILD
index e028f1f..8b02c6d 100644
--- a/bazel/sqlite.BUILD
+++ b/bazel/sqlite.BUILD
@@ -49,7 +49,7 @@
     "-DSQLITE_TEMP_STORE=3",
     "-DSQLITE_OMIT_LOAD_EXTENSION",
     "-DSQLITE_OMIT_RANDOMNESS",
-]
+] + PERFETTO_CONFIG.deps_copts.sqlite
 
 cc_library(
     name = "sqlite",
diff --git a/bazel/standalone/perfetto_cfg.bzl b/bazel/standalone/perfetto_cfg.bzl
index e89c7a0..1ee8e52 100644
--- a/bazel/standalone/perfetto_cfg.bzl
+++ b/bazel/standalone/perfetto_cfg.bzl
@@ -28,6 +28,12 @@
     # to allow perfetto embedders (e.g. gapid) and google internal builds to
     # override paths and target names to their own third_party.
     deps = struct(
+        # Target exposing the build config header. It should be a valid
+        # cc_library dependency as it will become a dependency of every
+        # perfetto_cc_library target. It needs to expose a
+        # "perfetto_build_flags.h" file that can be included via:
+        # #include "perfetto_build_flags.h".
+        build_config = ["//:build_config_hdr"],
         zlib = ["@perfetto_dep_zlib//:zlib"],
         jsoncpp = ["@perfetto_dep_jsoncpp//:jsoncpp"],
         linenoise = ["@perfetto_dep_linenoise//:linenoise"],
@@ -39,6 +45,16 @@
         protobuf_full = ["@com_google_protobuf//:protobuf"],
     ),
 
+    # This struct allows embedders to customize the cc_opts for Perfetto
+    # 3rd party dependencies. They only have an effect if the dependencies are
+    # initialized with the Perfetto build files (i.e. via perfetto_deps()).
+    deps_copts = struct(
+        zlib = [],
+        jsoncpp = [],
+        linenoise = [],
+        sqlite = [],
+    ),
+
     # This struct allows the embedder to customize copts and other args passed
     # to rules like cc_binary. Prefixed rules (e.g. perfetto_cc_binary) will
     # look into this struct before falling back on native.cc_binary().
diff --git a/bazel/zlib.BUILD b/bazel/zlib.BUILD
index 1557352..5c723ce 100644
--- a/bazel/zlib.BUILD
+++ b/bazel/zlib.BUILD
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
+
 cc_library(
     name = "zlib",
     srcs = [
@@ -48,7 +50,7 @@
     copts = [
         "-DHAVE_HIDDEN",
         "-Isrc",
-    ],
+    ] + PERFETTO_CONFIG.deps_copts.zlib,
     includes = ["zlib"],
     visibility = ["//visibility:public"],
 )
diff --git a/include/perfetto/ext/base/scoped_file.h b/include/perfetto/ext/base/scoped_file.h
index 2fee6a1..24c8970 100644
--- a/include/perfetto/ext/base/scoped_file.h
+++ b/include/perfetto/ext/base/scoped_file.h
@@ -88,7 +88,8 @@
                                   mode_t mode = kInvalidMode) {
   PERFETTO_DCHECK((flags & O_CREAT) == 0 || mode != kInvalidMode);
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-  ScopedFile fd(open(path.c_str(), flags, mode));
+  // Always use O_BINARY on Windows, to avoid silly EOL translations.
+  ScopedFile fd(open(path.c_str(), flags | O_BINARY, mode));
 #else
   // Always open a ScopedFile with O_CLOEXEC so we can safely fork and exec.
   ScopedFile fd(open(path.c_str(), flags | O_CLOEXEC, mode));
diff --git a/include/perfetto/ext/base/string_utils.h b/include/perfetto/ext/base/string_utils.h
index 977673f..df1ed7c 100644
--- a/include/perfetto/ext/base/string_utils.h
+++ b/include/perfetto/ext/base/string_utils.h
@@ -20,6 +20,8 @@
 #include <string>
 #include <vector>
 
+#include "perfetto/ext/base/string_view.h"
+
 namespace perfetto {
 namespace base {
 
@@ -34,6 +36,7 @@
 bool StartsWith(const std::string& str, const std::string& prefix);
 bool EndsWith(const std::string& str, const std::string& suffix);
 bool Contains(const std::string& haystack, const std::string& needle);
+size_t Find(const StringView& needle, const StringView& haystack);
 bool CaseInsensitiveEqual(const std::string& first, const std::string& second);
 std::string Join(const std::vector<std::string>& parts,
                  const std::string& delim);
diff --git a/include/perfetto/profiling/BUILD.gn b/include/perfetto/profiling/BUILD.gn
index a897783..23e19c9 100644
--- a/include/perfetto/profiling/BUILD.gn
+++ b/include/perfetto/profiling/BUILD.gn
@@ -21,3 +21,9 @@
     "symbolizer.h",
   ]
 }
+
+source_set("normalize") {
+  sources = [
+    "normalize.h",
+  ]
+}
diff --git a/src/profiling/memory/ext.h b/include/perfetto/profiling/normalize.h
similarity index 91%
rename from src/profiling/memory/ext.h
rename to include/perfetto/profiling/normalize.h
index b353e98..7b7da1d 100644
--- a/src/profiling/memory/ext.h
+++ b/include/perfetto/profiling/normalize.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_PROFILING_MEMORY_EXT_H_
-#define SRC_PROFILING_MEMORY_EXT_H_
+#ifndef INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
+#define INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
 
 // Header only code that gets used in other projects.
 // This is currently used in
@@ -63,4 +63,4 @@
 }  // namespace profiling
 }  // namespace perfetto
 
-#endif  // SRC_PROFILING_MEMORY_EXT_H_
+#endif  // INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
diff --git a/include/perfetto/profiling/pprof_builder.h b/include/perfetto/profiling/pprof_builder.h
index db6f57b..2ec9515 100644
--- a/include/perfetto/profiling/pprof_builder.h
+++ b/include/perfetto/profiling/pprof_builder.h
@@ -21,6 +21,8 @@
 #include <string>
 #include <vector>
 
+#include "perfetto/trace_processor/trace_processor.h"
+
 // TODO(135923303): do not depend on anything in this file as it will be
 // changed heavily as part of fixing b/135923303.
 namespace perfetto {
@@ -33,11 +35,22 @@
   std::string serialized;
 };
 
+bool TraceToPprof(trace_processor::TraceProcessor*,
+                  std::vector<SerializedProfile>* output,
+                  Symbolizer* symbolizer,
+                  uint64_t pid = 0,
+                  const std::vector<uint64_t>& timestamps = {});
+
 bool TraceToPprof(std::istream* input,
                   std::vector<SerializedProfile>* output,
-                  Symbolizer* symbolizer);
+                  Symbolizer* symbolizer,
+                  uint64_t pid = 0,
+                  const std::vector<uint64_t>& timestamps = {});
 
-bool TraceToPprof(std::istream* input, std::vector<SerializedProfile>* output);
+bool TraceToPprof(std::istream* input,
+                  std::vector<SerializedProfile>* output,
+                  uint64_t pid = 0,
+                  const std::vector<uint64_t>& timestamps = {});
 
 }  // namespace trace_to_text
 }  // namespace perfetto
diff --git a/include/perfetto/trace_processor/basic_types.h b/include/perfetto/trace_processor/basic_types.h
index 8bd946d..4c0f951 100644
--- a/include/perfetto/trace_processor/basic_types.h
+++ b/include/perfetto/trace_processor/basic_types.h
@@ -66,8 +66,8 @@
 
   int Compare(const SqlValue& value) const {
     // TODO(lalitm): this is almost the same as what SQLite does with the
-    // exception of comparisons between long and double - we choose (for
-    // performance reasons) to omit comparisons between them.
+    // exception of comparisions between long and double - we choose (for
+    // performance reasons) to omit comparisions between them.
     if (type != value.type)
       return type - value.type;
 
@@ -75,9 +75,11 @@
       case Type::kNull:
         return 0;
       case Type::kLong:
-        return signbit(long_value - value.long_value);
-      case Type::kDouble:
-        return signbit(double_value - value.double_value);
+        return static_cast<int>(long_value - value.long_value);
+      case Type::kDouble: {
+        double diff = double_value - value.double_value;
+        return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
+      }
       case Type::kString:
         return strcmp(string_value, value.string_value);
       case Type::kBytes: {
@@ -85,7 +87,7 @@
         int ret = memcmp(bytes_value, value.bytes_value, bytes);
         if (ret != 0)
           return ret;
-        return signbit(bytes_count - value.bytes_count);
+        return static_cast<int>(bytes_count - value.bytes_count);
       }
     }
     PERFETTO_FATAL("For GCC");
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index 2385d90..776876c 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -16,6 +16,7 @@
   public_deps = [
     "../../../gn:default_deps",
     "../../../protos/perfetto/trace:zero",
+    "../../../protos/perfetto/trace/track_event:zero",
     "../base",
     "../protozero",
   ]
@@ -26,10 +27,12 @@
     "internal/data_source_internal.h",
     "internal/tracing_muxer.h",
     "internal/tracing_tls.h",
+    "internal/track_event_data_source.h",
     "locked_handle.h",
     "platform.h",
     "trace_writer_base.h",
     "tracing.h",
     "tracing_backend.h",
+    "track_event.h",
   ]
 }
diff --git a/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto b/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
index 7cd0594..e61377a 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
@@ -42,7 +42,7 @@
     // bundle. The first is absolute, each next one is relative to its
     // predecessor.
     repeated uint64 switch_timestamp = 1 [packed = true];
-    repeated int32 switch_prev_state = 2 [packed = true];
+    repeated int64 switch_prev_state = 2 [packed = true];
     repeated int32 switch_next_pid = 3 [packed = true];
     repeated int32 switch_next_prio = 4 [packed = true];
 
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 315a489..60832da 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -2320,7 +2320,7 @@
     // bundle. The first is absolute, each next one is relative to its
     // predecessor.
     repeated uint64 switch_timestamp = 1 [packed = true];
-    repeated int32 switch_prev_state = 2 [packed = true];
+    repeated int64 switch_prev_state = 2 [packed = true];
     repeated int32 switch_next_pid = 3 [packed = true];
     repeated int32 switch_next_prio = 4 [packed = true];
 
@@ -3112,8 +3112,32 @@
 message Mapping {
   optional uint64 iid = 1;       // Interning key.
   optional uint64 build_id = 2;  // Interning key.
-  // TODO(fmayer): Document difference between those two.
-  optional uint64 exact_offset = 8;
+
+  // The linker may create multiple memory mappings for the same shared
+  // library.
+  // This is so that the ELF header is mapped as read only, while the
+  // executable memory is mapped as executable only.
+  // The details of this depend on the linker, a possible mapping of an ELF
+  // file is this:
+  //         +----------------------+
+  // ELF     |xxxxxxxxxyyyyyyyyyyyyy|
+  //         +---------+------------+
+  //         |         |
+  //         | read    | executable
+  //         v mapping v mapping
+  //         +----------------------+
+  // Memory  |xxxxxxxxx|yyyyyyyyyyyy|
+  //         +------------------+---+
+  //         ^         ^        ^
+  //         +         +        +
+  //       start     exact    relpc
+  //       offset   offset    0x1800
+  //       0x0000   0x1000
+  //
+  // exact_offset is the offset into the library file of this mapping.
+  // start_offset is the offset into the library file of the first mapping
+  // for that library. For native libraries (.so files) this should be 0.
+  optional uint64 exact_offset = 8;  // This is not set on Android 10.
   optional uint64 start_offset = 3;
   optional uint64 start = 4;
   optional uint64 end = 5;
diff --git a/protos/perfetto/trace/profiling/profile_common.proto b/protos/perfetto/trace/profiling/profile_common.proto
index a9e3bf7..9b88de2 100644
--- a/protos/perfetto/trace/profiling/profile_common.proto
+++ b/protos/perfetto/trace/profiling/profile_common.proto
@@ -87,8 +87,32 @@
 message Mapping {
   optional uint64 iid = 1;       // Interning key.
   optional uint64 build_id = 2;  // Interning key.
-  // TODO(fmayer): Document difference between those two.
-  optional uint64 exact_offset = 8;
+
+  // The linker may create multiple memory mappings for the same shared
+  // library.
+  // This is so that the ELF header is mapped as read only, while the
+  // executable memory is mapped as executable only.
+  // The details of this depend on the linker, a possible mapping of an ELF
+  // file is this:
+  //         +----------------------+
+  // ELF     |xxxxxxxxxyyyyyyyyyyyyy|
+  //         +---------+------------+
+  //         |         |
+  //         | read    | executable
+  //         v mapping v mapping
+  //         +----------------------+
+  // Memory  |xxxxxxxxx|yyyyyyyyyyyy|
+  //         +------------------+---+
+  //         ^         ^        ^
+  //         +         +        +
+  //       start     exact    relpc
+  //       offset   offset    0x1800
+  //       0x0000   0x1000
+  //
+  // exact_offset is the offset into the library file of this mapping.
+  // start_offset is the offset into the library file of the first mapping
+  // for that library. For native libraries (.so files) this should be 0.
+  optional uint64 exact_offset = 8;  // This is not set on Android 10.
   optional uint64 start_offset = 3;
   optional uint64 start = 4;
   optional uint64 end = 5;
diff --git a/src/base/event_fd.cc b/src/base/event_fd.cc
index b8f75bf..976db19 100644
--- a/src/base/event_fd.cc
+++ b/src/base/event_fd.cc
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include "perfetto/base/build_config.h"
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
 #include <stdint.h>
 #include <unistd.h>
 
@@ -74,3 +77,5 @@
 
 }  // namespace base
 }  // namespace perfetto
+
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc
index 74c985a..b8020ad 100644
--- a/src/base/file_utils.cc
+++ b/src/base/file_utils.cc
@@ -22,7 +22,8 @@
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/utils.h"
 
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) || \
+    PERFETTO_BUILDFLAG(PERFETTO_COMPILER_GCC)
 #include <unistd.h>
 #else
 #include <corecrt_io.h>
diff --git a/src/base/pipe.cc b/src/base/pipe.cc
index 5c9c3d7..c61492f 100644
--- a/src/base/pipe.cc
+++ b/src/base/pipe.cc
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include "perfetto/base/build_config.h"
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
 #include "perfetto/ext/base/pipe.h"
 
 #include <sys/types.h>
@@ -54,3 +57,5 @@
 
 }  // namespace base
 }  // namespace perfetto
+
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/string_utils.cc b/src/base/string_utils.cc
index c042290..5dc1bc8 100644
--- a/src/base/string_utils.cc
+++ b/src/base/string_utils.cc
@@ -39,6 +39,18 @@
   return haystack.find(needle) != std::string::npos;
 }
 
+size_t Find(const StringView& needle, const StringView& haystack) {
+  if (needle.size() == 0)
+    return 0;
+  if (needle.size() > haystack.size())
+    return std::string::npos;
+  for (size_t i = 0; i < haystack.size() - (needle.size() - 1); ++i) {
+    if (strncmp(haystack.data() + i, needle.data(), needle.size()) == 0)
+      return i;
+  }
+  return std::string::npos;
+}
+
 bool CaseInsensitiveEqual(const std::string& first, const std::string& second) {
   return first.size() == second.size() &&
          std::equal(
diff --git a/src/base/string_utils_unittest.cc b/src/base/string_utils_unittest.cc
index c14415c..c38f746 100644
--- a/src/base/string_utils_unittest.cc
+++ b/src/base/string_utils_unittest.cc
@@ -108,6 +108,38 @@
   EXPECT_EQ(StripChars("foobar", "froab", '_'), "______");
 }
 
+TEST(StringUtilsTest, Contains) {
+  EXPECT_TRUE(Contains("", ""));
+  EXPECT_TRUE(Contains("abc", ""));
+  EXPECT_TRUE(Contains("abc", "a"));
+  EXPECT_TRUE(Contains("abc", "b"));
+  EXPECT_TRUE(Contains("abc", "c"));
+  EXPECT_TRUE(Contains("abc", "ab"));
+  EXPECT_TRUE(Contains("abc", "bc"));
+  EXPECT_TRUE(Contains("abc", "abc"));
+  EXPECT_FALSE(Contains("abc", "d"));
+  EXPECT_FALSE(Contains("abc", "ac"));
+  EXPECT_FALSE(Contains("abc", "abcd"));
+  EXPECT_FALSE(Contains("", "a"));
+  EXPECT_FALSE(Contains("", "abc"));
+}
+
+TEST(StringUtilsTest, Find) {
+  EXPECT_EQ(Find("", ""), 0u);
+  EXPECT_EQ(Find("", "abc"), 0u);
+  EXPECT_EQ(Find("a", "abc"), 0u);
+  EXPECT_EQ(Find("b", "abc"), 1u);
+  EXPECT_EQ(Find("c", "abc"), 2u);
+  EXPECT_EQ(Find("ab", "abc"), 0u);
+  EXPECT_EQ(Find("bc", "abc"), 1u);
+  EXPECT_EQ(Find("abc", "abc"), 0u);
+  EXPECT_EQ(Find("d", "abc"), std::string::npos);
+  EXPECT_EQ(Find("ac", "abc"), std::string::npos);
+  EXPECT_EQ(Find("abcd", "abc"), std::string::npos);
+  EXPECT_EQ(Find("a", ""), std::string::npos);
+  EXPECT_EQ(Find("abc", ""), std::string::npos);
+}
+
 }  // namespace
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/temp_file.cc b/src/base/temp_file.cc
index d57d383..d1e77e9 100644
--- a/src/base/temp_file.cc
+++ b/src/base/temp_file.cc
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include "perfetto/base/build_config.h"
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
 #include "perfetto/ext/base/temp_file.h"
 
 #include <stdlib.h>
@@ -90,3 +93,6 @@
 
 }  // namespace base
 }  // namespace perfetto
+
+
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/thread_task_runner.cc b/src/base/thread_task_runner.cc
index d0a1036..0576ee9 100644
--- a/src/base/thread_task_runner.cc
+++ b/src/base/thread_task_runner.cc
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include "perfetto/base/build_config.h"
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
 #include "perfetto/ext/base/thread_task_runner.h"
 
 #include <condition_variable>
@@ -79,3 +82,5 @@
 
 }  // namespace base
 }  // namespace perfetto
+
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/unix_task_runner.cc b/src/base/unix_task_runner.cc
index 6ef71aa..422947f 100644
--- a/src/base/unix_task_runner.cc
+++ b/src/base/unix_task_runner.cc
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-#include "perfetto/ext/base/unix_task_runner.h"
-
 #include "perfetto/base/build_config.h"
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+#include "perfetto/ext/base/unix_task_runner.h"
 
 #include <errno.h>
 #include <stdlib.h>
@@ -229,3 +230,5 @@
 
 }  // namespace base
 }  // namespace perfetto
+
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index a384fe8..10af659 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -93,6 +93,7 @@
 source_set("proc_utils") {
   deps = [
     "../../../gn:default_deps",
+    "../../../include/perfetto/profiling:normalize",
     "../../base",
   ]
   sources = [
@@ -212,6 +213,7 @@
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
     "../../../gn:libunwindstack",
+    "../../../include/perfetto/profiling:normalize",
     "../../base",
     "../../base:test_support",
     "../../tracing",
diff --git a/src/profiling/memory/bookkeeping_dump.cc b/src/profiling/memory/bookkeeping_dump.cc
index 13e24bc..ddd569f 100644
--- a/src/profiling/memory/bookkeeping_dump.cc
+++ b/src/profiling/memory/bookkeeping_dump.cc
@@ -84,38 +84,47 @@
 }
 
 void DumpState::WriteBuildIDString(const Interned<std::string>& str) {
-  bool inserted;
-  std::tie(std::ignore, inserted) =
-      intern_state_->dumped_strings_.emplace(str.id());
-  if (inserted) {
+  auto it_and_inserted = intern_state_->dumped_strings_.emplace(str.id(), 0);
+  auto it = it_and_inserted.first;
+  // This is for the rare case that the same string is used as two different
+  // types (e.g. a function name that matches a path segment). In that case
+  // we need to emit the string as all of its types.
+  if ((it->second & kDumpedBuildID) == 0) {
     auto interned_string = GetCurrentInternedData()->add_build_ids();
     interned_string->set_iid(str.id());
     interned_string->set_str(reinterpret_cast<const uint8_t*>(str->c_str()),
                              str->size());
+    it->second |= kDumpedBuildID;
   }
 }
 
 void DumpState::WriteMappingPathString(const Interned<std::string>& str) {
-  bool inserted;
-  std::tie(std::ignore, inserted) =
-      intern_state_->dumped_strings_.emplace(str.id());
-  if (inserted) {
+  auto it_and_inserted = intern_state_->dumped_strings_.emplace(str.id(), 0);
+  auto it = it_and_inserted.first;
+  // This is for the rare case that the same string is used as two different
+  // types (e.g. a function name that matches a path segment). In that case
+  // we need to emit the string as all of its types.
+  if ((it->second & kDumpedMappingPath) == 0) {
     auto interned_string = GetCurrentInternedData()->add_mapping_paths();
     interned_string->set_iid(str.id());
     interned_string->set_str(reinterpret_cast<const uint8_t*>(str->c_str()),
                              str->size());
+    it->second |= kDumpedMappingPath;
   }
 }
 
 void DumpState::WriteFunctionNameString(const Interned<std::string>& str) {
-  bool inserted;
-  std::tie(std::ignore, inserted) =
-      intern_state_->dumped_strings_.emplace(str.id());
-  if (inserted) {
+  auto it_and_inserted = intern_state_->dumped_strings_.emplace(str.id(), 0);
+  auto it = it_and_inserted.first;
+  // This is for the rare case that the same string is used as two different
+  // types (e.g. a function name that matches a path segment). In that case
+  // we need to emit the string as all of its types.
+  if ((it->second & kDumpedFunctionName) == 0) {
     auto interned_string = GetCurrentInternedData()->add_function_names();
     interned_string->set_iid(str.id());
     interned_string->set_str(reinterpret_cast<const uint8_t*>(str->c_str()),
                              str->size());
+    it->second |= kDumpedFunctionName;
   }
 }
 
diff --git a/src/profiling/memory/bookkeeping_dump.h b/src/profiling/memory/bookkeeping_dump.h
index bb09914..517d02c 100644
--- a/src/profiling/memory/bookkeeping_dump.h
+++ b/src/profiling/memory/bookkeeping_dump.h
@@ -37,13 +37,17 @@
 
 void WriteFixedInternings(TraceWriter* trace_writer);
 
+constexpr int kDumpedBuildID = 1 << 0;
+constexpr int kDumpedMappingPath = 1 << 1;
+constexpr int kDumpedFunctionName = 1 << 2;
+
 class DumpState {
  public:
   class InternState {
    private:
     friend class DumpState;
 
-    std::set<InternID> dumped_strings_;
+    std::map<InternID, int> dumped_strings_;
     std::set<InternID> dumped_frames_;
     std::set<InternID> dumped_mappings_;
     std::set<uint64_t> dumped_callstacks_;
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
index c9c7767..d1024cc 100644
--- a/src/profiling/memory/heapprofd_producer.cc
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -308,8 +308,7 @@
     return;
   }
 
-  auto it = data_sources_.find(id);
-  if (it != data_sources_.end()) {
+  if (data_sources_.find(id) != data_sources_.end()) {
     PERFETTO_DFATAL_OR_ELOG(
         "Received duplicated data source instance id: %" PRIu64, id);
     return;
diff --git a/src/profiling/memory/proc_utils.cc b/src/profiling/memory/proc_utils.cc
index 07f85cd..6d0b6f3 100644
--- a/src/profiling/memory/proc_utils.cc
+++ b/src/profiling/memory/proc_utils.cc
@@ -21,7 +21,7 @@
 #include <unistd.h>
 
 #include "perfetto/ext/base/file_utils.h"
-#include "src/profiling/memory/ext.h"
+#include "perfetto/profiling/normalize.h"
 
 namespace perfetto {
 namespace profiling {
diff --git a/src/profiling/memory/proc_utils_unittest.cc b/src/profiling/memory/proc_utils_unittest.cc
index b1881bb..6c0065c 100644
--- a/src/profiling/memory/proc_utils_unittest.cc
+++ b/src/profiling/memory/proc_utils_unittest.cc
@@ -15,7 +15,7 @@
  */
 
 #include "src/profiling/memory/proc_utils.h"
-#include "src/profiling/memory/ext.h"
+#include "perfetto/profiling/normalize.h"
 
 #include "perfetto/ext/base/utils.h"
 #include "test/gtest_and_gmock.h"
diff --git a/src/trace_processor/args_tracker.cc b/src/trace_processor/args_tracker.cc
index 79da621..c21f586 100644
--- a/src/trace_processor/args_tracker.cc
+++ b/src/trace_processor/args_tracker.cc
@@ -85,6 +85,10 @@
         storage->mutable_metadata()->OverwriteMetadata(
             row, Variadic::Integer(set_id));
         break;
+      case TableId::kTrack:
+        storage->mutable_track_table()->mutable_source_arg_set_id()->Set(
+            row, set_id);
+        break;
       default:
         PERFETTO_FATAL("Unsupported table to insert args into");
     }
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index b09dc86..7360779 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -60,6 +60,8 @@
     ]
     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/db/bit_vector.cc
index 5e2db89..edf1af6 100644
--- a/src/trace_processor/db/bit_vector.cc
+++ b/src/trace_processor/db/bit_vector.cc
@@ -19,12 +19,19 @@
 namespace perfetto {
 namespace trace_processor {
 
-BitVector::BitVector(uint32_t count, bool value) : inner_(count, value) {}
+BitVector::BitVector() = default;
 
-BitVector::BitVector(std::vector<bool> inner) : inner_(std::move(inner)) {}
+BitVector::BitVector(uint32_t count, bool value) {
+  Resize(count, value);
+}
+
+BitVector::BitVector(std::vector<Block> blocks,
+                     std::vector<uint32_t> counts,
+                     uint32_t size)
+    : size_(size), counts_(std::move(counts)), blocks_(std::move(blocks)) {}
 
 BitVector BitVector::Copy() const {
-  return BitVector(inner_);
+  return BitVector(blocks_, counts_, size_);
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/db/bit_vector.h b/src/trace_processor/db/bit_vector.h
index 4bddd75..bd85b76 100644
--- a/src/trace_processor/db/bit_vector.h
+++ b/src/trace_processor/db/bit_vector.h
@@ -18,8 +18,10 @@
 #define SRC_TRACE_PROCESSOR_DB_BIT_VECTOR_H_
 
 #include <stdint.h>
+#include <stdio.h>
 
 #include <algorithm>
+#include <array>
 #include <vector>
 
 #include "perfetto/base/logging.h"
@@ -29,17 +31,15 @@
 
 // A bitvector which compactly stores a vector of bools using a single bit
 // for each bool.
-// TODO(lalitm): currently this is just a thin wrapper around std::vector<bool>
-// but in the future, we plan to add quite a few optimizations around ranges
-// of set bits.
 class BitVector {
  public:
   // Creates an empty bitvector.
-  BitVector() = default;
+  BitVector();
 
   // Creates a bitvector of |count| size filled with |value|.
   BitVector(uint32_t count, bool value = false);
 
+  // Enable moving bitvectors as they have no unmovable state.
   BitVector(BitVector&&) noexcept = default;
   BitVector& operator=(BitVector&&) = default;
 
@@ -47,21 +47,14 @@
   BitVector Copy() const;
 
   // Returns the size of the bitvector.
-  uint32_t size() const { return static_cast<uint32_t>(inner_.size()); }
+  uint32_t size() const { return static_cast<uint32_t>(size_); }
 
   // Returns whether the bit at |idx| is set.
   bool IsSet(uint32_t idx) const {
     PERFETTO_DCHECK(idx < size());
-    return inner_[idx];
-  }
 
-  // Returns the index of the next set bit at or after index |idx|.
-  // If there is no other set bits, returns |size()|.
-  uint32_t NextSet(uint32_t idx) const {
-    PERFETTO_DCHECK(idx <= inner_.size());
-    auto it = std::find(inner_.begin() + static_cast<ptrdiff_t>(idx),
-                        inner_.end(), true);
-    return static_cast<uint32_t>(std::distance(inner_.begin(), it));
+    Address a = IndexToAddress(idx);
+    return blocks_[a.block_idx].IsSet(a.block_offset);
   }
 
   // Returns the number of set bits in the bitvector.
@@ -70,35 +63,184 @@
   // Returns the number of set bits between the start of the bitvector
   // (inclusive) and the index |end| (exclusive).
   uint32_t GetNumBitsSet(uint32_t end) const {
-    return static_cast<uint32_t>(std::count(
-        inner_.begin(), inner_.begin() + static_cast<ptrdiff_t>(end), true));
+    if (end == 0)
+      return 0;
+
+    // Although the external interface we present uses an exclusive |end|,
+    // internally it's a lot nicer to work with an inclusive |end| (mainly
+    // because we get block rollovers on exclusive ends which means we need
+    // to have if checks to ensure we don't overflow the number of blocks).
+    Address addr = IndexToAddress(end - 1);
+    uint32_t idx = addr.block_idx;
+
+    // Add the number of set bits until the start of the block to the number
+    // of set bits until the end address inside the block.
+    return counts_[idx] + blocks_[idx].GetNumBitsSet(addr.block_offset);
   }
 
-  // Returns the index of the |n|'th set bit.
+  // Returns the index of the |n|th set bit. Should only be called with |n| <
+  // GetNumBitsSet().
   uint32_t IndexOfNthSet(uint32_t n) const {
-    // TODO(lalitm): improve the performance of this method by investigating
-    // AVX instructions.
-    uint32_t offset = 0;
-    for (uint32_t i = NextSet(0); i < size(); i = NextSet(i + 1), ++offset) {
-      if (offset == n)
-        return i;
+    PERFETTO_DCHECK(n < GetNumBitsSet());
+
+    // First search for the block which, up until the start of it, has more than
+    // n bits set. Note that this should never return |counts.begin()| as
+    // that should always be 0.
+    // TODO(lalitm): investigate whether we can make this faster with small
+    // binary search followed by a linear search instead of binary searching the
+    // full way.
+    auto it = std::upper_bound(counts_.begin(), counts_.end(), n);
+    PERFETTO_DCHECK(it != counts_.begin());
+
+    // Go back one block to find the block which has the bit we are looking for.
+    uint16_t block_idx =
+        static_cast<uint16_t>(std::distance(counts_.begin(), it) - 1);
+
+    // Figure out how many set bits forward we are looking inside the block
+    // by taking away the number of bits at the start of the block from n.
+    uint32_t set_in_block = n - counts_[block_idx];
+
+    // Compute the address of the bit in the block then convert the full
+    // address back to an index.
+    BlockOffset block_offset = blocks_[block_idx].IndexOfNthSet(set_in_block);
+    return AddressToIndex(Address{block_idx, block_offset});
+  }
+
+  // Sets the bit at index |idx| to true.
+  void Set(uint32_t idx) {
+    // Set the bit to the correct value inside the block but store the old
+    // bit to help fix the counts.
+    auto addr = IndexToAddress(idx);
+    bool old_value = blocks_[addr.block_idx].IsSet(addr.block_offset);
+
+    // If the old value was unset, set the bit and add one to the count.
+    if (PERFETTO_LIKELY(!old_value)) {
+      blocks_[addr.block_idx].Set(addr.block_offset);
+
+      uint32_t size = static_cast<uint32_t>(counts_.size());
+      for (uint32_t i = addr.block_idx + 1; i < size; ++i) {
+        counts_[i]++;
+      }
     }
-    PERFETTO_FATAL("Index out of bounds");
   }
 
-  // Sets the value at index |idx| to |value|.
-  void Set(uint32_t idx, bool value) {
-    PERFETTO_DCHECK(idx < size());
-    inner_[idx] = value;
+  // Sets the bit at index |idx| to false.
+  void Clear(uint32_t idx) {
+    // Set the bit to the correct value inside the block but store the old
+    // bit to help fix the counts.
+    auto addr = IndexToAddress(idx);
+    bool old_value = blocks_[addr.block_idx].IsSet(addr.block_offset);
+
+    // If the old value was set, clear the bit and subtract one from all the
+    // counts.
+    if (PERFETTO_LIKELY(old_value)) {
+      blocks_[addr.block_idx].Clear(addr.block_offset);
+
+      uint32_t size = static_cast<uint32_t>(counts_.size());
+      for (uint32_t i = addr.block_idx + 1; i < size; ++i) {
+        counts_[i]--;
+      }
+    }
   }
 
-  // Appends |value| to the bitvector.
-  void Append(bool value) { inner_.push_back(value); }
+  // Appends true to the bitvector.
+  void AppendTrue() {
+    Address addr = IndexToAddress(size_);
+    uint32_t old_blocks_size = static_cast<uint32_t>(blocks_.size());
+    uint32_t new_blocks_size = addr.block_idx + 1;
+
+    if (PERFETTO_UNLIKELY(new_blocks_size > old_blocks_size)) {
+      uint32_t t = GetNumBitsSet();
+      blocks_.emplace_back();
+      counts_.emplace_back(t);
+    }
+
+    size_++;
+    blocks_[addr.block_idx].Set(addr.block_offset);
+  }
+
+  // Appends false to the bitvector.
+  void AppendFalse() {
+    Address addr = IndexToAddress(size_);
+    uint32_t old_blocks_size = static_cast<uint32_t>(blocks_.size());
+    uint32_t new_blocks_size = addr.block_idx + 1;
+
+    if (PERFETTO_UNLIKELY(new_blocks_size > old_blocks_size)) {
+      uint32_t t = GetNumBitsSet();
+      blocks_.emplace_back();
+      counts_.emplace_back(t);
+    }
+
+    size_++;
+    // We don't need to clear the bit as we ensure that anything after
+    // size_ is always set to false.
+  }
 
   // Resizes the BitVector to the given |size|.
   // Truncates the BitVector if |size| < |size()| or fills the new space with
-  // |value| if |size| > |size()|.
-  void Resize(uint32_t size, bool value = false) { inner_.resize(size, value); }
+  // |value| if |size| > |size()|. Calling this method is a noop if |size| ==
+  // |size()|.
+  void Resize(uint32_t size, bool value = false) {
+    uint32_t old_size = size_;
+    if (size == old_size)
+      return;
+
+    // Empty bitvectors should be memory efficient so we don't keep any data
+    // around in the bitvector.
+    if (size == 0) {
+      blocks_.clear();
+      counts_.clear();
+      size_ = 0;
+      return;
+    }
+
+    // Compute the address of the new last bit in the bitvector.
+    Address last_addr = IndexToAddress(size - 1);
+    uint32_t old_blocks_size = static_cast<uint32_t>(counts_.size());
+    uint32_t new_blocks_size = last_addr.block_idx + 1;
+
+    // Then, resize the block and count vectors to have the correct
+    // number of entries.
+    blocks_.resize(new_blocks_size);
+    counts_.resize(new_blocks_size);
+
+    if (new_blocks_size > old_blocks_size) {
+      // If we've increased the number of blocks, we need to fix the
+      // counts vector by setting the newly added counts to the
+      // count of the bitvector. This matches the empty blocks we just
+      // added. Below, we will actually set the bits in the newly added
+      // blocks and as we do that, we will update the counts.
+      //
+      // Note: as we haven't updated |size_| yet GetNumBitsSet() won't take into
+      // account the newly added blocks yet.
+      uint32_t count = GetNumBitsSet();
+      for (uint32_t i = old_blocks_size; i < new_blocks_size; ++i) {
+        counts_[i] = count;
+      }
+    }
+
+    // Actually update the size before we call |Set| below to ensure Set's
+    // invariants make sense.
+    size_ = size;
+    if (size > old_size) {
+      // Just go through all the newly added bits and set them to the true - we
+      // don't need to do this if !value because we always expect the newly
+      // added bits to be zeroed (this is ensured by the else branch).
+      // TODO(lalitm): this is clearly non optimal. Try and have a more
+      // optimized version of this based on setting whole blocks
+      // to 1.
+      if (value) {
+        for (uint32_t i = old_size; i < size; ++i) {
+          Set(i);
+        }
+      }
+    } else {
+      // Throw away all the bits after the new last bit. We do this to make
+      // future lookup, append and resize operations not have to worrying about
+      // trailing garbage bits in the last block.
+      blocks_[last_addr.block_idx].ClearAfter(last_addr.block_offset);
+    }
+  }
 
   // Updates the ith set bit of this bitvector with the value of
   // |other.IsSet(i)|.
@@ -116,20 +258,303 @@
   void UpdateSetBits(const BitVector& other) {
     PERFETTO_DCHECK(other.size() == GetNumBitsSet());
 
-    uint32_t offset = 0;
-    for (uint32_t i = NextSet(0); i < size(); i = NextSet(i + 1), ++offset) {
-      if (!other.IsSet(offset))
-        Set(i, false);
+    // Go through each set bit and if |other| has it unset, then unset the
+    // bit taking care to update the index we consider to take into account
+    // the bits we just unset.
+    // TODO(lalitm): we add an iterator implementation to remove this
+    // inefficient loop.
+    uint32_t removed = 0;
+    for (uint32_t i = 0, size = other.size(); i < size; ++i) {
+      if (!other.IsSet(i)) {
+        Clear(IndexOfNthSet(i - removed++));
+      }
     }
   }
 
  private:
-  BitVector(std::vector<bool>);
+  // Represents the offset of a bit within a block.
+  struct BlockOffset {
+    uint16_t word_idx;
+    uint16_t bit_idx;
+  };
+
+  // Represents the address of a bit within the bitvector.
+  struct Address {
+    uint32_t block_idx;
+    BlockOffset block_offset;
+  };
+
+  // Represents the smallest collection of bits we can refer to as
+  // one unit.
+  //
+  // Currently, this is implemented as a 64 bit integer as this is the
+  // largest type which we can assume to be present on all platforms.
+  class BitWord {
+   public:
+    static constexpr uint32_t kBits = 64;
+
+    // Returns whether the bit at the given index is set.
+    bool IsSet(uint32_t idx) const {
+      PERFETTO_DCHECK(idx < kBits);
+      return (word >> idx) & 1ull;
+    }
+
+    // Sets the bit at the given index to true.
+    void Set(uint32_t idx) {
+      PERFETTO_DCHECK(idx < kBits);
+
+      // Or the value for the true shifted up to |idx| with the word.
+      word |= 1ull << idx;
+    }
+
+    // Sets the bit at the given index to false.
+    void Clear(uint32_t idx) {
+      PERFETTO_DCHECK(idx < kBits);
+
+      // And the integer of all bits set apart from |idx| with the word.
+      word &= ~(1ull << idx);
+    }
+
+    // Clears all the bits (i.e. sets the atom to zero).
+    void ClearAll() { word = 0; }
+
+    // Returns the index of the nth set bit.
+    // Undefined if |n| >= |GetNumBitsSet()|.
+    uint16_t IndexOfNthSet(uint32_t n) const {
+      PERFETTO_DCHECK(n < kBits);
+
+      // The below code is very dense but essentially computes the nth set
+      // bit inside |atom| in the "broadword" style of programming (sometimes
+      // referred to as "SIMD within a register").
+      //
+      // Instead of treating a uint64 as an individual unit, broadword
+      // algorithms treat them as a packed vector of uint8. By doing this, they
+      // allow branchless algorithms when considering bits of a uint64.
+      //
+      // In benchmarks, this algorithm has found to be the fastest, portable
+      // way of computing the nth set bit (if we were only targetting new
+      // versions of x64, we could also use pdep + ctz but unfortunately
+      // this would fail on WASM - this about 2.5-3x faster on x64).
+      //
+      // The code below was taken from the paper
+      // http://vigna.di.unimi.it/ftp/papers/Broadword.pdf
+      uint64_t s = word - ((word & 0xAAAAAAAAAAAAAAAA) >> 1);
+      s = (s & 0x3333333333333333) + ((s >> 2) & 0x3333333333333333);
+      s = ((s + (s >> 4)) & 0x0F0F0F0F0F0F0F0F) * L8;
+
+      uint64_t b = (BwLessThan(s, n * L8) >> 7) * L8 >> 53 & ~7ull;
+      uint64_t l = n - ((s << 8) >> b & 0xFF);
+      s = (BwGtZero(((word >> b & 0xFF) * L8) & 0x8040201008040201) >> 7) * L8;
+
+      uint64_t ret = b + ((BwLessThan(s, l * L8) >> 7) * L8 >> 56);
+
+      return static_cast<uint16_t>(ret);
+    }
+
+    // Returns the number of set bits.
+    uint32_t GetNumBitsSet() const {
+      // We use __builtin_popcountll here as it's available natively for the two
+      // targets we care most about (x64 and WASM).
+      return static_cast<uint32_t>(__builtin_popcountll(word));
+    }
+
+    // Returns the number of set bits up to and including the bit at |idx|.
+    uint32_t GetNumBitsSet(uint32_t idx) const {
+      PERFETTO_DCHECK(idx < kBits);
+
+      // We use __builtin_popcountll here as it's available natively for the two
+      // targets we care most about (x64 and WASM).
+      return static_cast<uint32_t>(__builtin_popcountll(WordUntil(idx)));
+    }
+
+    // Retains all bits up to and including the bit at |idx| and clears
+    // all bits after this point.
+    void ClearAfter(uint32_t idx) {
+      PERFETTO_DCHECK(idx < kBits);
+      word = WordUntil(idx);
+    }
+
+   private:
+    // Constant with all the low bit of every byte set.
+    static constexpr uint64_t L8 = 0x0101010101010101;
+
+    // Constant with all the high bit of every byte set.
+    static constexpr uint64_t H8 = 0x8080808080808080;
+
+    // Returns a packed uint64 encoding whether each byte of x is less
+    // than each corresponding byte of y.
+    // This is computed in the "broadword" style of programming; see
+    // IndexOfNthSet for details on this.
+    static uint64_t BwLessThan(uint64_t x, uint64_t y) {
+      return (((y | H8) - (x & ~H8)) ^ x ^ y) & H8;
+    }
+
+    // Returns a packed uint64 encoding whether each byte of x is greater
+    // than or equal zero.
+    // This is computed in the "broadword" style of programming; see
+    // IndexOfNthSet for details on this.
+    static uint64_t BwGtZero(uint64_t x) { return (((x | H8) - L8) | x) & H8; }
+
+    // Returns the bits up to and including the bit at |idx|.
+    uint64_t WordUntil(uint32_t idx) const {
+      PERFETTO_DCHECK(idx < kBits);
+
+      // To understand what is happeninng here, consider an example.
+      // Suppose we want to all the bits up to the 7th bit in the atom
+      //               7th
+      //                |
+      //                v
+      // atom: 01010101011111000
+      //
+      // The easiest way to do this would be if we had a mask with only
+      // the bottom 7 bits set:
+      // mask: 00000000001111111
+      //
+      // Start with 1 and shift it up (idx + 1) bits we get:
+      // top : 00000000010000000
+      uint64_t top = 1ull << ((idx + 1ull) % kBits);
+
+      // We need to handle the case where idx == 63. In this case |top| will be
+      // zero because 1 << ((idx + 1) % 64) == 1 << (64 % 64) == 1.
+      // In this case, we actually want top == 0. We can do this by shifting
+      // down by (idx + 1) / kBits - this will be a noop for every index other
+      // than idx == 63. This should also be free on intel because of the mod
+      // instruction above.
+      top = top >> ((idx + 1) / kBits);
+
+      // Then if we take away 1, we get precisely the mask we want.
+      uint64_t mask = top - 1u;
+
+      // Finish up by anding the the atom with the computed msk.
+      return word & mask;
+    }
+
+    uint64_t word = 0;
+  };
+
+  // Represents a group of bits with a bitcount such that it is
+  // efficient to work on these bits.
+  //
+  // On x86 architectures we generally target for trace processor, the
+  // size of a cache line is 64 bytes (or 512 bits). For this reason,
+  // we make the size of the block contain 8 atoms as 8 * 64 == 512.
+  //
+  // TODO(lalitm): investigate whether we should tune this value for
+  // WASM and ARM.
+  class Block {
+   public:
+    // See class documentation for how these constants are chosen.
+    static constexpr uint32_t kWords = 8;
+    static constexpr uint32_t kBits = kWords * BitWord::kBits;
+
+    // Returns whether the bit at the given address is set.
+    bool IsSet(const BlockOffset& addr) const {
+      PERFETTO_DCHECK(addr.word_idx < kWords);
+
+      return words_[addr.word_idx].IsSet(addr.bit_idx);
+    }
+
+    // Sets the bit at the given address to true.
+    void Set(const BlockOffset& addr) {
+      PERFETTO_DCHECK(addr.word_idx < kWords);
+
+      words_[addr.word_idx].Set(addr.bit_idx);
+    }
+
+    // Sets the bit at the given address to false.
+    void Clear(const BlockOffset& addr) {
+      PERFETTO_DCHECK(addr.word_idx < kWords);
+
+      words_[addr.word_idx].Clear(addr.bit_idx);
+    }
+
+    // Gets the offset of the nth set bit in this block.
+    BlockOffset IndexOfNthSet(uint32_t n) const {
+      uint32_t count = 0;
+      for (uint16_t i = 0; i < kWords; ++i) {
+        // Keep a running count of all the set bits in the atom.
+        uint32_t value = count + words_[i].GetNumBitsSet();
+        if (value <= n) {
+          count = value;
+          continue;
+        }
+
+        // The running count of set bits is more than |n|. That means this atom
+        // contains the bit we are looking for.
+
+        // Take away the number of set bits to the start of this atom from |n|.
+        uint32_t set_in_atom = n - count;
+
+        // Figure out the index of the set bit inside the atom and create the
+        // address of this bit from that.
+        uint16_t bit_idx = words_[i].IndexOfNthSet(set_in_atom);
+        PERFETTO_DCHECK(bit_idx < 64);
+        return BlockOffset{i, bit_idx};
+      }
+      PERFETTO_FATAL("Index out of bounds");
+    }
+
+    // Gets the number of set bits within a block up to and including the bit
+    // at the given address.
+    uint32_t GetNumBitsSet(const BlockOffset& addr) const {
+      PERFETTO_DCHECK(addr.word_idx < kWords);
+
+      // Count all the set bits in the atom until we reach the last atom
+      // index.
+      uint32_t count = 0;
+      for (uint32_t i = 0; i < addr.word_idx; ++i) {
+        count += words_[i].GetNumBitsSet();
+      }
+
+      // For the last atom, only count the bits upto and including the bit
+      // index.
+      return count + words_[addr.word_idx].GetNumBitsSet(addr.bit_idx);
+    }
+
+    // Retains all bits up to and including the bit at |addr| and clears
+    // all bits after this point.
+    void ClearAfter(const BlockOffset& offset) {
+      PERFETTO_DCHECK(offset.word_idx < kWords);
+
+      // In the first atom, keep the bits until the address specified.
+      words_[offset.word_idx].ClearAfter(offset.bit_idx);
+
+      // For all subsequent atoms, we just clear the whole atom.
+      for (uint32_t i = offset.word_idx + 1; i < kWords; ++i) {
+        words_[i].ClearAll();
+      }
+    }
+
+   private:
+    std::array<BitWord, kWords> words_{};
+  };
+
+  BitVector(std::vector<Block> blocks,
+            std::vector<uint32_t> counts,
+            uint32_t size);
 
   BitVector(const BitVector&) = delete;
   BitVector& operator=(const BitVector&) = delete;
 
-  std::vector<bool> inner_;
+  static Address IndexToAddress(uint32_t idx) {
+    Address a;
+    a.block_idx = idx / Block::kBits;
+
+    uint16_t bit_idx_inside_block = idx % Block::kBits;
+    a.block_offset.word_idx = bit_idx_inside_block / BitWord::kBits;
+    a.block_offset.bit_idx = bit_idx_inside_block % BitWord::kBits;
+    return a;
+  }
+
+  static uint32_t AddressToIndex(Address addr) {
+    return addr.block_idx * Block::kBits +
+           addr.block_offset.word_idx * BitWord::kBits +
+           addr.block_offset.bit_idx;
+  }
+
+  uint32_t size_ = 0;
+  std::vector<uint32_t> counts_;
+  std::vector<Block> blocks_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/db/bit_vector_benchmark.cc b/src/trace_processor/db/bit_vector_benchmark.cc
index d6ed930..50e9baf 100644
--- a/src/trace_processor/db/bit_vector_benchmark.cc
+++ b/src/trace_processor/db/bit_vector_benchmark.cc
@@ -18,68 +18,119 @@
 
 #include "src/trace_processor/db/bit_vector.h"
 
-static void BM_BitVectorAppend(benchmark::State& state) {
-  static constexpr uint32_t kPoolSize = 1024 * 1024;
-  std::vector<bool> bit_pool(kPoolSize);
+namespace {
 
-  static constexpr uint32_t kRandomSeed = 42;
-  std::minstd_rand0 rnd_engine(kRandomSeed);
-  for (uint32_t i = 0; i < kPoolSize; ++i) {
-    bit_pool[i] = rnd_engine() % 2;
-  }
+using perfetto::trace_processor::BitVector;
 
-  perfetto::trace_processor::BitVector bv;
-  uint32_t pool_idx = 0;
+}
+
+static void BM_BitVectorAppendTrue(benchmark::State& state) {
+  BitVector bv;
   for (auto _ : state) {
-    bv.Append(bit_pool[pool_idx]);
-    pool_idx = (pool_idx + 1) % kPoolSize;
+    bv.AppendTrue();
     benchmark::ClobberMemory();
   }
 }
-BENCHMARK(BM_BitVectorAppend);
+BENCHMARK(BM_BitVectorAppendTrue);
+
+static void BM_BitVectorAppendFalse(benchmark::State& state) {
+  BitVector bv;
+  for (auto _ : state) {
+    bv.AppendFalse();
+    benchmark::ClobberMemory();
+  }
+}
+BENCHMARK(BM_BitVectorAppendFalse);
 
 static void BM_BitVectorSet(benchmark::State& state) {
+  static constexpr uint32_t kRandomSeed = 42;
+  std::minstd_rand0 rnd_engine(kRandomSeed);
+
+  uint32_t size = static_cast<uint32_t>(state.range(0));
+
+  BitVector bv;
+  for (uint32_t i = 0; i < size; ++i) {
+    if (rnd_engine() % 2) {
+      bv.AppendTrue();
+    } else {
+      bv.AppendFalse();
+    }
+  }
+
   static constexpr uint32_t kPoolSize = 1024 * 1024;
   std::vector<bool> bit_pool(kPoolSize);
   std::vector<uint32_t> row_pool(kPoolSize);
-
-  static constexpr uint32_t kSize = 123456;
-  perfetto::trace_processor::BitVector bv;
-
-  static constexpr uint32_t kRandomSeed = 42;
-  std::minstd_rand0 rnd_engine(kRandomSeed);
   for (uint32_t i = 0; i < kPoolSize; ++i) {
     bit_pool[i] = rnd_engine() % 2;
-    row_pool[i] = rnd_engine() % kSize;
-  }
-
-  for (uint32_t i = 0; i < kSize; ++i) {
-    bv.Append(rnd_engine() % 2);
+    row_pool[i] = rnd_engine() % size;
   }
 
   uint32_t pool_idx = 0;
   for (auto _ : state) {
-    bv.Set(row_pool[pool_idx], bit_pool[pool_idx]);
+    bv.Set(row_pool[pool_idx]);
     pool_idx = (pool_idx + 1) % kPoolSize;
     benchmark::ClobberMemory();
   }
 }
-BENCHMARK(BM_BitVectorSet);
+BENCHMARK(BM_BitVectorSet)
+    ->Arg(64)
+    ->Arg(512)
+    ->Arg(8192)
+    ->Arg(123456)
+    ->Arg(1234567);
 
-static void BM_BitVectorIndexOfNthSet(benchmark::State& state) {
-  static constexpr uint32_t kPoolSize = 1024 * 1024;
-  std::vector<uint32_t> row_pool(kPoolSize);
-
-  static constexpr uint32_t kSize = 123456;
-  perfetto::trace_processor::BitVector bv;
-
+static void BM_BitVectorClear(benchmark::State& state) {
   static constexpr uint32_t kRandomSeed = 42;
   std::minstd_rand0 rnd_engine(kRandomSeed);
-  for (uint32_t i = 0; i < kSize; ++i) {
-    bool value = rnd_engine() % 2;
-    bv.Append(value);
+
+  uint32_t size = static_cast<uint32_t>(state.range(0));
+
+  BitVector bv;
+  for (uint32_t i = 0; i < size; ++i) {
+    if (rnd_engine() % 2) {
+      bv.AppendTrue();
+    } else {
+      bv.AppendFalse();
+    }
   }
 
+  static constexpr uint32_t kPoolSize = 1024 * 1024;
+  std::vector<uint32_t> row_pool(kPoolSize);
+  for (uint32_t i = 0; i < kPoolSize; ++i) {
+    row_pool[i] = rnd_engine() % size;
+  }
+
+  uint32_t pool_idx = 0;
+  for (auto _ : state) {
+    bv.Clear(row_pool[pool_idx]);
+    pool_idx = (pool_idx + 1) % kPoolSize;
+    benchmark::ClobberMemory();
+  }
+}
+BENCHMARK(BM_BitVectorClear)
+    ->Arg(64)
+    ->Arg(512)
+    ->Arg(8192)
+    ->Arg(123456)
+    ->Arg(1234567);
+
+static void BM_BitVectorIndexOfNthSet(benchmark::State& state) {
+  static constexpr uint32_t kRandomSeed = 42;
+  std::minstd_rand0 rnd_engine(kRandomSeed);
+
+  uint32_t size = static_cast<uint32_t>(state.range(0));
+
+  BitVector bv;
+  for (uint32_t i = 0; i < size; ++i) {
+    if (rnd_engine() % 2) {
+      bv.AppendTrue();
+    } else {
+      bv.AppendFalse();
+    }
+  }
+
+  static constexpr uint32_t kPoolSize = 1024 * 1024;
+  std::vector<uint32_t> row_pool(kPoolSize);
   uint32_t set_bit_count = bv.GetNumBitsSet();
   for (uint32_t i = 0; i < kPoolSize; ++i) {
     row_pool[i] = rnd_engine() % set_bit_count;
@@ -91,18 +142,28 @@
     pool_idx = (pool_idx + 1) % kPoolSize;
   }
 }
-BENCHMARK(BM_BitVectorIndexOfNthSet);
+BENCHMARK(BM_BitVectorIndexOfNthSet)
+    ->Arg(64)
+    ->Arg(512)
+    ->Arg(8192)
+    ->Arg(123456)
+    ->Arg(1234567);
 
 static void BM_BitVectorGetNumBitsSet(benchmark::State& state) {
-  static constexpr uint32_t kSize = 123456;
-  perfetto::trace_processor::BitVector bv;
-  uint32_t count = 0;
-
   static constexpr uint32_t kRandomSeed = 42;
   std::minstd_rand0 rnd_engine(kRandomSeed);
-  for (uint32_t i = 0; i < kSize; ++i) {
+
+  uint32_t size = static_cast<uint32_t>(state.range(0));
+
+  uint32_t count = 0;
+  BitVector bv;
+  for (uint32_t i = 0; i < size; ++i) {
     bool value = rnd_engine() % 2;
-    bv.Append(value);
+    if (value) {
+      bv.AppendTrue();
+    } else {
+      bv.AppendFalse();
+    }
 
     if (value)
       count++;
@@ -114,4 +175,9 @@
   }
   PERFETTO_CHECK(res == count);
 }
-BENCHMARK(BM_BitVectorGetNumBitsSet);
+BENCHMARK(BM_BitVectorGetNumBitsSet)
+    ->Arg(64)
+    ->Arg(512)
+    ->Arg(8192)
+    ->Arg(123456)
+    ->Arg(1234567);
diff --git a/src/trace_processor/db/bit_vector_unittest.cc b/src/trace_processor/db/bit_vector_unittest.cc
index 371b4fe..7e7543b 100644
--- a/src/trace_processor/db/bit_vector_unittest.cc
+++ b/src/trace_processor/db/bit_vector_unittest.cc
@@ -16,97 +16,210 @@
 
 #include "src/trace_processor/db/bit_vector.h"
 
+#include <random>
+
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace {
 
-TEST(BitVectorUnittest, Set) {
-  BitVector bv(3, true);
-  bv.Set(0, false);
-  bv.Set(1, true);
+TEST(BitVectorUnittest, CreateAllTrue) {
+  BitVector bv(2049, true);
 
-  ASSERT_EQ(bv.size(), 3u);
-  ASSERT_FALSE(bv.IsSet(0));
+  // Ensure that a selection of interesting bits are set.
+  ASSERT_TRUE(bv.IsSet(0));
   ASSERT_TRUE(bv.IsSet(1));
-  ASSERT_TRUE(bv.IsSet(2));
+  ASSERT_TRUE(bv.IsSet(511));
+  ASSERT_TRUE(bv.IsSet(512));
+  ASSERT_TRUE(bv.IsSet(2047));
+  ASSERT_TRUE(bv.IsSet(2048));
 }
 
-TEST(BitVectorUnittest, Append) {
+TEST(BitVectorUnittest, CreateAllFalse) {
+  BitVector bv(2049, false);
+
+  // Ensure that a selection of interesting bits are cleared.
+  ASSERT_FALSE(bv.IsSet(0));
+  ASSERT_FALSE(bv.IsSet(1));
+  ASSERT_FALSE(bv.IsSet(511));
+  ASSERT_FALSE(bv.IsSet(512));
+  ASSERT_FALSE(bv.IsSet(2047));
+  ASSERT_FALSE(bv.IsSet(2048));
+}
+
+TEST(BitVectorUnittest, Set) {
+  BitVector bv(2049, false);
+  bv.Set(0);
+  bv.Set(1);
+  bv.Set(511);
+  bv.Set(512);
+  bv.Set(2047);
+
+  // Ensure the bits we touched are set.
+  ASSERT_TRUE(bv.IsSet(0));
+  ASSERT_TRUE(bv.IsSet(1));
+  ASSERT_TRUE(bv.IsSet(511));
+  ASSERT_TRUE(bv.IsSet(512));
+  ASSERT_TRUE(bv.IsSet(2047));
+
+  // Ensure that a selection of other interestinng bits are
+  // still cleared.
+  ASSERT_FALSE(bv.IsSet(2));
+  ASSERT_FALSE(bv.IsSet(63));
+  ASSERT_FALSE(bv.IsSet(64));
+  ASSERT_FALSE(bv.IsSet(510));
+  ASSERT_FALSE(bv.IsSet(513));
+  ASSERT_FALSE(bv.IsSet(1023));
+  ASSERT_FALSE(bv.IsSet(1024));
+  ASSERT_FALSE(bv.IsSet(2046));
+  ASSERT_FALSE(bv.IsSet(2048));
+  ASSERT_FALSE(bv.IsSet(2048));
+}
+
+TEST(BitVectorUnittest, Clear) {
+  BitVector bv(2049, true);
+  bv.Clear(0);
+  bv.Clear(1);
+  bv.Clear(511);
+  bv.Clear(512);
+  bv.Clear(2047);
+
+  // Ensure the bits we touched are cleared.
+  ASSERT_FALSE(bv.IsSet(0));
+  ASSERT_FALSE(bv.IsSet(1));
+  ASSERT_FALSE(bv.IsSet(511));
+  ASSERT_FALSE(bv.IsSet(512));
+  ASSERT_FALSE(bv.IsSet(2047));
+
+  // Ensure that a selection of other interestinng bits are
+  // still set.
+  ASSERT_TRUE(bv.IsSet(2));
+  ASSERT_TRUE(bv.IsSet(63));
+  ASSERT_TRUE(bv.IsSet(64));
+  ASSERT_TRUE(bv.IsSet(510));
+  ASSERT_TRUE(bv.IsSet(513));
+  ASSERT_TRUE(bv.IsSet(1023));
+  ASSERT_TRUE(bv.IsSet(1024));
+  ASSERT_TRUE(bv.IsSet(2046));
+  ASSERT_TRUE(bv.IsSet(2048));
+}
+
+TEST(BitVectorUnittest, AppendToEmpty) {
   BitVector bv;
-  bv.Append(true);
-  bv.Append(false);
+  bv.AppendTrue();
+  bv.AppendFalse();
 
   ASSERT_EQ(bv.size(), 2u);
   ASSERT_TRUE(bv.IsSet(0));
   ASSERT_FALSE(bv.IsSet(1));
 }
 
-TEST(BitVectorUnittest, NextSet) {
-  BitVector bv(6, false);
-  bv.Set(1, true);
-  bv.Set(2, true);
-  bv.Set(4, true);
+TEST(BitVectorUnittest, AppendToExisting) {
+  BitVector bv(2046, false);
+  bv.AppendTrue();
+  bv.AppendFalse();
+  bv.AppendTrue();
+  bv.AppendTrue();
 
-  ASSERT_EQ(bv.NextSet(0), 1u);
-  ASSERT_EQ(bv.NextSet(1), 1u);
-  ASSERT_EQ(bv.NextSet(2), 2u);
-  ASSERT_EQ(bv.NextSet(3), 4u);
-  ASSERT_EQ(bv.NextSet(4), 4u);
-  ASSERT_EQ(bv.NextSet(5), 6u);
+  ASSERT_EQ(bv.size(), 2050u);
+  ASSERT_TRUE(bv.IsSet(2046));
+  ASSERT_FALSE(bv.IsSet(2047));
+  ASSERT_TRUE(bv.IsSet(2048));
+  ASSERT_TRUE(bv.IsSet(2049));
 }
 
 TEST(BitVectorUnittest, GetNumBitsSet) {
-  BitVector bv(6, false);
-  bv.Set(1, true);
-  bv.Set(2, true);
-  bv.Set(4, true);
+  BitVector bv(2049, false);
+  bv.Set(0);
+  bv.Set(1);
+  bv.Set(511);
+  bv.Set(512);
+  bv.Set(2047);
+  bv.Set(2048);
 
-  ASSERT_EQ(bv.GetNumBitsSet(), 3u);
+  ASSERT_EQ(bv.GetNumBitsSet(), 6u);
 
   ASSERT_EQ(bv.GetNumBitsSet(0), 0u);
-  ASSERT_EQ(bv.GetNumBitsSet(1), 0u);
-  ASSERT_EQ(bv.GetNumBitsSet(2), 1u);
+  ASSERT_EQ(bv.GetNumBitsSet(1), 1u);
+  ASSERT_EQ(bv.GetNumBitsSet(2), 2u);
   ASSERT_EQ(bv.GetNumBitsSet(3), 2u);
-  ASSERT_EQ(bv.GetNumBitsSet(4), 2u);
-  ASSERT_EQ(bv.GetNumBitsSet(5), 3u);
-  ASSERT_EQ(bv.GetNumBitsSet(6), 3u);
+  ASSERT_EQ(bv.GetNumBitsSet(511), 2u);
+  ASSERT_EQ(bv.GetNumBitsSet(512), 3u);
+  ASSERT_EQ(bv.GetNumBitsSet(1023), 4u);
+  ASSERT_EQ(bv.GetNumBitsSet(1024), 4u);
+  ASSERT_EQ(bv.GetNumBitsSet(2047), 4u);
+  ASSERT_EQ(bv.GetNumBitsSet(2048), 5u);
+  ASSERT_EQ(bv.GetNumBitsSet(2049), 6u);
 }
 
 TEST(BitVectorUnittest, IndexOfNthSet) {
-  BitVector bv(6, false);
-  bv.Set(1, true);
-  bv.Set(2, true);
-  bv.Set(4, true);
+  BitVector bv(2050, false);
+  bv.Set(0);
+  bv.Set(1);
+  bv.Set(511);
+  bv.Set(512);
+  bv.Set(2047);
+  bv.Set(2048);
 
-  ASSERT_EQ(bv.IndexOfNthSet(0), 1u);
-  ASSERT_EQ(bv.IndexOfNthSet(1), 2u);
-  ASSERT_EQ(bv.IndexOfNthSet(2), 4u);
+  ASSERT_EQ(bv.IndexOfNthSet(0), 0u);
+  ASSERT_EQ(bv.IndexOfNthSet(1), 1u);
+  ASSERT_EQ(bv.IndexOfNthSet(2), 511u);
+  ASSERT_EQ(bv.IndexOfNthSet(3), 512u);
+  ASSERT_EQ(bv.IndexOfNthSet(4), 2047u);
+  ASSERT_EQ(bv.IndexOfNthSet(5), 2048u);
 }
 
 TEST(BitVectorUnittest, Resize) {
   BitVector bv(1, false);
+
   bv.Resize(2, true);
-  bv.Resize(3, false);
-
-  ASSERT_EQ(bv.IsSet(1), true);
-  ASSERT_EQ(bv.IsSet(2), false);
-
-  bv.Resize(2, false);
-
   ASSERT_EQ(bv.size(), 2u);
   ASSERT_EQ(bv.IsSet(1), true);
+
+  bv.Resize(2049, false);
+  ASSERT_EQ(bv.size(), 2049u);
+  ASSERT_EQ(bv.IsSet(2), false);
+  ASSERT_EQ(bv.IsSet(2047), false);
+  ASSERT_EQ(bv.IsSet(2048), false);
+
+  // Set these two bits; the first should be preserved and the
+  // second should disappear.
+  bv.Set(512);
+  bv.Set(513);
+
+  bv.Resize(513, false);
+  ASSERT_EQ(bv.size(), 513u);
+  ASSERT_EQ(bv.IsSet(1), true);
+  ASSERT_EQ(bv.IsSet(512), true);
+  ASSERT_EQ(bv.GetNumBitsSet(), 2u);
+
+  // When we resize up, we need to be sure that the set bit from
+  // before we resized down is not still present as a garbage bit.
+  bv.Resize(514, false);
+  ASSERT_EQ(bv.size(), 514u);
+  ASSERT_EQ(bv.IsSet(513), false);
+  ASSERT_EQ(bv.GetNumBitsSet(), 2u);
+}
+
+TEST(BitVectorUnittest, AppendAfterResizeDown) {
+  BitVector bv(2049, false);
+  bv.Set(2048);
+
+  bv.Resize(2048);
+  bv.AppendFalse();
+  ASSERT_EQ(bv.IsSet(2048), false);
+  ASSERT_EQ(bv.GetNumBitsSet(), 0u);
 }
 
 TEST(BitVectorUnittest, UpdateSetBits) {
   BitVector bv(6, false);
-  bv.Set(1, true);
-  bv.Set(2, true);
-  bv.Set(4, true);
+  bv.Set(1);
+  bv.Set(2);
+  bv.Set(4);
 
   BitVector picker(3u, true);
-  picker.Set(1, false);
+  picker.Clear(1);
 
   bv.UpdateSetBits(picker);
 
@@ -115,6 +228,37 @@
   ASSERT_TRUE(bv.IsSet(4));
 }
 
+TEST(BitVectorUnittest, QueryStressTest) {
+  BitVector bv;
+  std::vector<bool> bool_vec;
+  std::vector<uint32_t> int_vec;
+
+  static constexpr uint32_t kCount = 4096;
+  std::minstd_rand0 rand;
+  for (uint32_t i = 0; i < kCount; ++i) {
+    bool res = rand() % 2u;
+    if (res) {
+      bv.AppendTrue();
+    } else {
+      bv.AppendFalse();
+    }
+    bool_vec.push_back(res);
+    if (res)
+      int_vec.emplace_back(i);
+  }
+
+  for (uint32_t i = 0; i < kCount; ++i) {
+    uint32_t count = static_cast<uint32_t>(std::count(
+        bool_vec.begin(), bool_vec.begin() + static_cast<int32_t>(i), true));
+    ASSERT_EQ(bv.IsSet(i), bool_vec[i]);
+    ASSERT_EQ(bv.GetNumBitsSet(i), count);
+  }
+
+  for (uint32_t i = 0; i < int_vec.size(); ++i) {
+    ASSERT_EQ(bv.IndexOfNthSet(i), int_vec[i]);
+  }
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/column.cc b/src/trace_processor/db/column.cc
index 9e46036..91b22ef 100644
--- a/src/trace_processor/db/column.cc
+++ b/src/trace_processor/db/column.cc
@@ -66,6 +66,9 @@
     case FilterOp::kGt:
       iv->RemoveIf([this, value](uint32_t row) { return Get(row) <= value; });
       break;
+    case FilterOp::kNeq:
+      iv->RemoveIf([this, value](uint32_t row) { return Get(row) == value; });
+      break;
   }
 }
 
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 8930ba8..b545e10 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -30,8 +30,9 @@
 namespace trace_processor {
 
 // Represents the possible filter operations on a column.
-enum FilterOp {
+enum class FilterOp {
   kEq,
+  kNeq,
   kGt,
   kLt,
 };
@@ -161,6 +162,9 @@
   // given filter constraint.
   void FilterInto(FilterOp, SqlValue value, RowMap*) const;
 
+  // Returns true if this column is considered an id column.
+  bool IsId() const { return (flags_ & Flag::kId) != 0; }
+
   const RowMap& row_map() const;
   const char* name() const { return name_; }
   SqlValue::Type type() const {
@@ -186,6 +190,9 @@
   Constraint lt(SqlValue value) const {
     return Constraint{col_idx_, FilterOp::kLt, value};
   }
+  Constraint neq(SqlValue value) const {
+    return Constraint{col_idx_, FilterOp::kNeq, value};
+  }
 
   // Returns an Order for each Order type for this Column.
   Order ascending() const { return Order{col_idx_, false}; }
diff --git a/src/trace_processor/db/row_map.cc b/src/trace_processor/db/row_map.cc
index 29c493e..b9a3a9c 100644
--- a/src/trace_processor/db/row_map.cc
+++ b/src/trace_processor/db/row_map.cc
@@ -29,28 +29,31 @@
   return compact_ ? RowMap(bit_vector_.Copy()) : RowMap(index_vector_);
 }
 
-void RowMap::SelectRows(const RowMap& picker) {
+RowMap RowMap::SelectRows(const RowMap& picker) const {
   if (compact_ && picker.compact_) {
-    bit_vector_.UpdateSetBits(picker.bit_vector_);
+    BitVector bv = bit_vector_.Copy();
+    bv.UpdateSetBits(picker.bit_vector_);
+    return RowMap(std::move(bv));
   } else if (compact_ && !picker.compact_) {
-    index_vector_.resize(picker.index_vector_.size());
+    std::vector<uint32_t> iv(picker.index_vector_.size());
     for (uint32_t i = 0; i < picker.index_vector_.size(); ++i) {
       // TODO(lalitm): this is pretty inefficient.
-      index_vector_[i] = bit_vector_.IndexOfNthSet(picker.index_vector_[i]);
+      iv[i] = bit_vector_.IndexOfNthSet(picker.index_vector_[i]);
     }
-    compact_ = false;
-    bit_vector_ = BitVector();
+    return RowMap(std::move(iv));
   } else if (!compact_ && picker.compact_) {
+    RowMap rm = Copy();
     uint32_t idx = 0;
-    RemoveIf(
+    rm.RemoveIf(
         [&idx, &picker](uint32_t) { return !picker.bit_vector_.IsSet(idx++); });
+    return rm;
   } else /* (!compact_ && !picker.compact_) */ {
-    std::vector<uint32_t> old_idx_vector = std::move(index_vector_);
-    index_vector_ = std::vector<uint32_t>(picker.index_vector_.size());
+    std::vector<uint32_t> iv(picker.index_vector_.size());
     for (uint32_t i = 0; i < picker.index_vector_.size(); ++i) {
-      PERFETTO_DCHECK(picker.index_vector_[i] < old_idx_vector.size());
-      index_vector_[i] = old_idx_vector[picker.index_vector_[i]];
+      PERFETTO_DCHECK(picker.index_vector_[i] < index_vector_.size());
+      iv[i] = index_vector_[picker.index_vector_[i]];
     }
+    return RowMap(std::move(iv));
   }
 }
 
diff --git a/src/trace_processor/db/row_map.h b/src/trace_processor/db/row_map.h
index 6dcdbc7..01006fb 100644
--- a/src/trace_processor/db/row_map.h
+++ b/src/trace_processor/db/row_map.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/optional.h"
 #include "src/trace_processor/db/bit_vector.h"
 
 namespace perfetto {
@@ -68,12 +69,17 @@
   }
 
   // Returns the first index of the given |row| in the RowMap.
-  uint32_t IndexOf(uint32_t row) const {
+  base::Optional<uint32_t> IndexOf(uint32_t row) const {
     if (compact_) {
-      return bit_vector_.GetNumBitsSet(row);
+      return row < bit_vector_.size() && bit_vector_.IsSet(row)
+                 ? base::make_optional(bit_vector_.GetNumBitsSet(row))
+                 : base::nullopt;
     } else {
       auto it = std::find(index_vector_.begin(), index_vector_.end(), row);
-      return static_cast<uint32_t>(std::distance(index_vector_.begin(), it));
+      return it != index_vector_.end()
+                 ? base::make_optional(static_cast<uint32_t>(
+                       std::distance(index_vector_.begin(), it)))
+                 : base::nullopt;
     }
   }
 
@@ -82,7 +88,7 @@
     if (compact_) {
       if (row >= bit_vector_.size())
         bit_vector_.Resize(row + 1, false);
-      bit_vector_.Set(row, true);
+      bit_vector_.Set(row);
     } else {
       index_vector_.emplace_back(row);
     }
@@ -98,17 +104,24 @@
   // this  : [0, 10, 11, 11, 4]
   //
   // Conceptually, we are performing the following algorithm:
+  // RowMap rm = Copy()
   // for (idx : picker)
-  //   this[i++] = this[idx]
-  void SelectRows(const RowMap& picker);
+  //   rm[i++] = this[idx]
+  // return rm;
+  RowMap SelectRows(const RowMap& picker) const;
 
   // Removes any row where |p(row)| returns false from this RowMap.
   template <typename Predicate>
   void RemoveIf(Predicate p) {
     if (compact_) {
       const auto& bv = bit_vector_;
-      for (uint32_t i = bv.NextSet(0); i < bv.size(); i = bv.NextSet(i + 1)) {
-        bit_vector_.Set(i, !p(i));
+      uint32_t removed = 0;
+      for (uint32_t i = 0, size = bv.GetNumBitsSet(); i < size; ++i) {
+        uint32_t idx = bv.IndexOfNthSet(i - removed);
+        if (p(idx)) {
+          removed++;
+          bit_vector_.Clear(idx);
+        }
       }
     } else {
       auto it = std::remove_if(index_vector_.begin(), index_vector_.end(), p);
diff --git a/src/trace_processor/db/row_map_benchmark.cc b/src/trace_processor/db/row_map_benchmark.cc
new file mode 100644
index 0000000..89f41a7
--- /dev/null
+++ b/src/trace_processor/db/row_map_benchmark.cc
@@ -0,0 +1,158 @@
+// 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 <random>
+
+#include <benchmark/benchmark.h>
+
+#include "src/trace_processor/db/row_map.h"
+
+using perfetto::trace_processor::BitVector;
+using perfetto::trace_processor::RowMap;
+
+namespace {
+
+static constexpr uint32_t kPoolSize = 100000;
+static constexpr uint32_t kSize = 123456;
+
+std::vector<uint32_t> CreateRandomIndexVector(uint32_t size, uint32_t mod) {
+  static constexpr uint32_t kRandomSeed = 476;
+  std::minstd_rand0 rnd_engine(kRandomSeed);
+  std::vector<uint32_t> rows(size);
+  for (uint32_t i = 0; i < size; ++i) {
+    rows[i] = rnd_engine() % mod;
+  }
+  return rows;
+}
+
+BitVector CreateRandomBitVector(uint32_t size) {
+  static constexpr uint32_t kRandomSeed = 42;
+  std::minstd_rand0 rnd_engine(kRandomSeed);
+  BitVector bv;
+  for (uint32_t i = 0; i < size; ++i) {
+    if (rnd_engine() % 2) {
+      bv.AppendTrue();
+    } else {
+      bv.AppendFalse();
+    }
+  }
+  return bv;
+}
+
+}  // namespace
+
+static void BM_RowMapBitVectorGet(benchmark::State& state) {
+  RowMap rm(CreateRandomBitVector(kSize));
+  auto pool_vec = CreateRandomIndexVector(kPoolSize, rm.size());
+
+  uint32_t pool_idx = 0;
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(rm.Get(pool_vec[pool_idx]));
+    pool_idx = (pool_idx + 1) % kPoolSize;
+  }
+}
+BENCHMARK(BM_RowMapBitVectorGet);
+
+static void BM_RowMapIndexVectorGet(benchmark::State& state) {
+  RowMap rm(CreateRandomIndexVector(kSize, kSize));
+  auto pool_vec = CreateRandomIndexVector(kPoolSize, kSize);
+
+  uint32_t pool_idx = 0;
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(rm.Get(pool_vec[pool_idx]));
+    pool_idx = (pool_idx + 1) % kPoolSize;
+  }
+}
+BENCHMARK(BM_RowMapIndexVectorGet);
+
+// TODO(lalitm): add benchmarks for IndexOf after BitVector is made faster.
+// We can't add them right now because they are just too slow to run.
+
+static void BM_RowMapBitVectorAdd(benchmark::State& state) {
+  auto pool_vec = CreateRandomIndexVector(kPoolSize, kSize);
+
+  RowMap rm(BitVector{});
+  uint32_t pool_idx = 0;
+  for (auto _ : state) {
+    rm.Add(pool_vec[pool_idx]);
+    pool_idx = (pool_idx + 1) % kPoolSize;
+    benchmark::ClobberMemory();
+  }
+}
+BENCHMARK(BM_RowMapBitVectorAdd);
+
+static void BM_RowMapIndexVectorAdd(benchmark::State& state) {
+  auto pool_vec = CreateRandomIndexVector(kPoolSize, kSize);
+
+  RowMap rm(std::vector<uint32_t>{});
+  uint32_t pool_idx = 0;
+  for (auto _ : state) {
+    rm.Add(pool_vec[pool_idx]);
+    pool_idx = (pool_idx + 1) % kPoolSize;
+    benchmark::ClobberMemory();
+  }
+}
+BENCHMARK(BM_RowMapIndexVectorAdd);
+
+static void BM_RowMapBvSelectBv(benchmark::State& state) {
+  RowMap rm(CreateRandomBitVector(kSize));
+  RowMap selector(CreateRandomBitVector(rm.size()));
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(rm.SelectRows(selector));
+  }
+}
+BENCHMARK(BM_RowMapBvSelectBv);
+
+// TODO(lalitm): add benchmarks for BvSelectIv after BitVector is made faster.
+// We can't add them right now because they are just too slow to run.
+
+static void BM_RowMapIvSelectBv(benchmark::State& state) {
+  RowMap rm(CreateRandomIndexVector(kSize, kSize));
+  RowMap selector(CreateRandomBitVector(rm.size()));
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(rm.SelectRows(selector));
+  }
+}
+BENCHMARK(BM_RowMapIvSelectBv);
+
+static void BM_RowMapIvSelectIv(benchmark::State& state) {
+  RowMap rm(CreateRandomIndexVector(kSize, kSize));
+  RowMap selector(CreateRandomIndexVector(rm.size(), rm.size()));
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(rm.SelectRows(selector));
+  }
+}
+BENCHMARK(BM_RowMapIvSelectIv);
+
+static void BM_RowMapBvSelectSingleRow(benchmark::State& state) {
+  // This benchmark tests the performance of selecting just a single
+  // row of a RowMap. We specially test this case as it occurs on every join
+  // based on id originating from SQLite; nested subqueries will be performed
+  // on the id column and will select just a single row.
+  RowMap rm(CreateRandomBitVector(kSize));
+
+  static constexpr uint32_t kRandomSeed = 123;
+  std::minstd_rand0 rnd_engine(kRandomSeed);
+  BitVector bv(rm.size(), false);
+  bv.Set(rnd_engine() % bv.size());
+  RowMap selector(std::move(bv));
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(rm.SelectRows(selector));
+  }
+}
+BENCHMARK(BM_RowMapBvSelectSingleRow);
diff --git a/src/trace_processor/db/row_map_unittest.cc b/src/trace_processor/db/row_map_unittest.cc
index 0556e9b..4c62c79 100644
--- a/src/trace_processor/db/row_map_unittest.cc
+++ b/src/trace_processor/db/row_map_unittest.cc
@@ -27,12 +27,12 @@
 
 std::shared_ptr<RowMap> BitVectorRowMap() {
   BitVector bv;
-  bv.Append(true);
-  bv.Append(false);
-  bv.Append(true);
-  bv.Append(true);
-  bv.Append(false);
-  bv.Append(true);
+  bv.AppendTrue();
+  bv.AppendFalse();
+  bv.AppendTrue();
+  bv.AppendTrue();
+  bv.AppendFalse();
+  bv.AppendTrue();
   return std::shared_ptr<RowMap>(new RowMap(std::move(bv)));
 }
 
@@ -64,37 +64,38 @@
   ASSERT_EQ(row_map.IndexOf(3u), 2u);
   ASSERT_EQ(row_map.IndexOf(5u), 3u);
   ASSERT_EQ(row_map.IndexOf(10u), 4u);
+  ASSERT_FALSE(row_map.IndexOf(6u));
 }
 
 TEST_P(RowMapUnittest, SelectRowsBitVector) {
   RowMap row_map = GetParam()->Copy();
 
   BitVector picker_bv;
-  picker_bv.Append(true);
-  picker_bv.Append(false);
-  picker_bv.Append(false);
-  picker_bv.Append(true);
+  picker_bv.AppendTrue();
+  picker_bv.AppendFalse();
+  picker_bv.AppendFalse();
+  picker_bv.AppendTrue();
   RowMap picker(std::move(picker_bv));
 
-  row_map.SelectRows(picker);
+  auto res = row_map.SelectRows(picker);
 
-  ASSERT_EQ(row_map.size(), 2u);
-  ASSERT_EQ(row_map.Get(0u), 0u);
-  ASSERT_EQ(row_map.Get(1u), 5u);
+  ASSERT_EQ(res.size(), 2u);
+  ASSERT_EQ(res.Get(0u), 0u);
+  ASSERT_EQ(res.Get(1u), 5u);
 }
 
 TEST_P(RowMapUnittest, SelectRowsRowVector) {
   RowMap row_map = GetParam()->Copy();
   RowMap picker(std::vector<uint32_t>{1u, 0u, 3u, 0u, 0u});
 
-  row_map.SelectRows(picker);
+  auto res = row_map.SelectRows(picker);
 
-  ASSERT_EQ(row_map.size(), 5u);
-  ASSERT_EQ(row_map.Get(0u), 2u);
-  ASSERT_EQ(row_map.Get(1u), 0u);
-  ASSERT_EQ(row_map.Get(2u), 5u);
-  ASSERT_EQ(row_map.Get(3u), 0u);
-  ASSERT_EQ(row_map.Get(4u), 0u);
+  ASSERT_EQ(res.size(), 5u);
+  ASSERT_EQ(res.Get(0u), 2u);
+  ASSERT_EQ(res.Get(1u), 0u);
+  ASSERT_EQ(res.Get(2u), 5u);
+  ASSERT_EQ(res.Get(3u), 0u);
+  ASSERT_EQ(res.Get(4u), 0u);
 }
 
 TEST_P(RowMapUnittest, RemoveIf) {
diff --git a/src/trace_processor/db/sparse_vector.h b/src/trace_processor/db/sparse_vector.h
index 12a2a60..c2472ac 100644
--- a/src/trace_processor/db/sparse_vector.h
+++ b/src/trace_processor/db/sparse_vector.h
@@ -51,11 +51,11 @@
   // Adds the given value to the SparseVector.
   void Append(T val) {
     data_.emplace_back(val);
-    valid_.Append(true);
+    valid_.AppendTrue();
   }
 
   // Adds a null value to the SparseVector.
-  void AppendNull() { valid_.Append(false); }
+  void AppendNull() { valid_.AppendFalse(); }
 
   // Adds the given optional value to the SparseVector.
   void Append(base::Optional<T> val) {
@@ -76,7 +76,7 @@
       data_[data_idx] = val;
     } else {
       data_.insert(data_.begin() + static_cast<ptrdiff_t>(data_idx), val);
-      valid_.Set(idx, true);
+      valid_.Set(idx);
     }
   }
 
diff --git a/src/trace_processor/db/sparse_vector_benchmark.cc b/src/trace_processor/db/sparse_vector_benchmark.cc
new file mode 100644
index 0000000..f9403dc
--- /dev/null
+++ b/src/trace_processor/db/sparse_vector_benchmark.cc
@@ -0,0 +1,39 @@
+// 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 <random>
+
+#include <benchmark/benchmark.h>
+
+#include "src/trace_processor/db/sparse_vector.h"
+
+static void BM_SparseVectorAppend(benchmark::State& state) {
+  static constexpr uint32_t kPoolSize = 123456;
+  std::vector<uint8_t> data_pool(kPoolSize);
+
+  static constexpr uint32_t kRandomSeed = 42;
+  std::minstd_rand0 rnd_engine(kRandomSeed);
+  for (uint32_t i = 0; i < kPoolSize; ++i) {
+    data_pool[i] = rnd_engine() % std::numeric_limits<uint8_t>::max();
+  }
+
+  perfetto::trace_processor::SparseVector<uint8_t> sv;
+  uint32_t pool_idx = 0;
+  for (auto _ : state) {
+    sv.Append(data_pool[pool_idx]);
+    pool_idx = (pool_idx + 1) % kPoolSize;
+    benchmark::ClobberMemory();
+  }
+}
+BENCHMARK(BM_SparseVectorAppend);
diff --git a/src/trace_processor/db/table.cc b/src/trace_processor/db/table.cc
index 1bafb4f..90afc19 100644
--- a/src/trace_processor/db/table.cc
+++ b/src/trace_processor/db/table.cc
@@ -31,18 +31,6 @@
     columns_.emplace_back(col, this, columns_.size(), col.row_map_idx_);
 }
 
-Table& Table::operator=(const Table& other) {
-  size_ = other.size_;
-  string_pool_ = other.string_pool_;
-
-  for (const RowMap& rm : other.row_maps_) {
-    row_maps_.emplace_back(rm.Copy());
-  }
-  for (const Column& col : other.columns_)
-    columns_.emplace_back(col, this, columns_.size(), col.row_map_idx_);
-  return *this;
-}
-
 Table& Table::operator=(Table&& other) noexcept {
   size_ = other.size_;
   string_pool_ = other.string_pool_;
@@ -55,12 +43,29 @@
   return *this;
 }
 
+Table Table::Copy() const {
+  Table table = CopyExceptRowMaps();
+  for (const RowMap& rm : row_maps_) {
+    table.row_maps_.emplace_back(rm.Copy());
+  }
+  return table;
+}
+
+Table Table::CopyExceptRowMaps() const {
+  Table table(string_pool_, nullptr);
+  table.size_ = size_;
+  for (const Column& col : columns_) {
+    table.columns_.emplace_back(col, &table, col.col_idx_, col.row_map_idx_);
+  }
+  return table;
+}
+
 Table Table::Filter(const std::vector<Constraint>& cs) const {
   // TODO(lalitm): we can add optimizations here depending on whether this is
   // an lvalue or rvalue.
 
   if (cs.empty())
-    return *this;
+    return Copy();
 
   // Create a RowMap indexing all rows and filter this down to the rows which
   // meet all the constraints.
@@ -71,11 +76,12 @@
 
   // Return a copy of this table with the RowMaps using the computed filter
   // RowMap and with the updated size.
-  Table table(*this);
-  for (RowMap& map : table.row_maps_) {
-    map.SelectRows(rm);
-  }
+  Table table = CopyExceptRowMaps();
   table.size_ = 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());
+  }
   return table;
 }
 
@@ -84,7 +90,7 @@
   // an lvalue or rvalue.
 
   if (od.empty())
-    return *this;
+    return Copy();
 
   // Build an index vector with all the indices for the first |size_| rows.
   std::vector<uint32_t> idx(size_);
@@ -104,11 +110,11 @@
 
   // Return a copy of this table with the RowMaps using the computed ordered
   // RowMap.
+  Table table = CopyExceptRowMaps();
   RowMap rm(std::move(idx));
-  Table table(*this);
-  for (RowMap& map : table.row_maps_) {
-    map.SelectRows(rm);
-    PERFETTO_DCHECK(map.size() == table.size());
+  for (const RowMap& map : row_maps_) {
+    table.row_maps_.emplace_back(map.SelectRows(rm));
+    PERFETTO_DCHECK(table.row_maps_.back().size() == table.size());
   }
   return table;
 }
@@ -124,7 +130,7 @@
 
   for (const Column& col : columns_) {
     // We skip id columns as they are misleading on join tables.
-    if (strcmp(col.name_, "id") == 0)
+    if ((col.flags_ & Column::kId) != 0)
       continue;
     table.columns_.emplace_back(col, &table, table.columns_.size(),
                                 col.row_map_idx_);
@@ -148,14 +154,13 @@
   // join table as we go.
   RowMap rm(std::move(indices));
   for (const RowMap& ot : other.row_maps_) {
-    table.row_maps_.emplace_back(ot.Copy());
-    table.row_maps_.back().SelectRows(rm);
+    table.row_maps_.emplace_back(ot.SelectRows(rm));
   }
 
   uint32_t left_row_maps_size = static_cast<uint32_t>(row_maps_.size());
   for (const Column& col : other.columns_) {
     // We skip id columns as they are misleading on join tables.
-    if (strcmp(col.name_, "id") == 0)
+    if ((col.flags_ & Column::kId) != 0)
       continue;
 
     // Ensure that we offset the RowMap index by the number of RowMaps in the
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index 51ad822..f2ecc8b 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -115,10 +115,8 @@
  private:
   friend class Column;
 
-  // We explicitly define the copy constructor here because we need to change
-  // the Table pointer in each column to the Table being copied into.
-  Table(const Table& other) { *this = other; }
-  Table& operator=(const Table& other);
+  Table Copy() const;
+  Table CopyExceptRowMaps() const;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/event_tracker.cc b/src/trace_processor/event_tracker.cc
index 8b30809..79757a9 100644
--- a/src/trace_processor/event_tracker.cc
+++ b/src/trace_processor/event_tracker.cc
@@ -66,33 +66,20 @@
   prev_timestamp_ = ts;
   PERFETTO_DCHECK(cpu < base::kMaxCpus);
 
-  auto* slices = context_->storage->mutable_slices();
-
   StringId next_comm_id = context_->storage->InternString(next_comm);
   auto next_utid =
       context_->process_tracker->UpdateThreadName(next_pid, next_comm_id);
 
   // First use this data to close the previous slice.
   bool prev_pid_match_prev_next_pid = false;
-  auto* prev_slice = &pending_sched_per_cpu_[cpu];
-  size_t slice_idx = prev_slice->storage_index;
-  if (slice_idx < std::numeric_limits<size_t>::max()) {
-    prev_pid_match_prev_next_pid = prev_pid == prev_slice->next_pid;
+  auto* pending_sched = &pending_sched_per_cpu_[cpu];
+  size_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
+  if (pending_slice_idx < std::numeric_limits<size_t>::max()) {
+    prev_pid_match_prev_next_pid = prev_pid == pending_sched->last_pid;
     if (PERFETTO_LIKELY(prev_pid_match_prev_next_pid)) {
-      int64_t duration = ts - slices->start_ns()[slice_idx];
-      slices->set_duration(slice_idx, duration);
-
-      // We store the state as a uint16 as we only consider values up to 2048
-      // when unpacking the information inside; this allows savings of 48 bits
-      // per slice.
-      auto task_state =
-          ftrace_utils::TaskState(static_cast<uint16_t>(prev_state));
-      if (!task_state.is_valid()) {
-        context_->storage->IncrementStats(stats::task_state_invalid);
-      }
-      slices->set_end_state(slice_idx, task_state);
+      ClosePendingSlice(pending_slice_idx, ts, prev_state);
     } else {
-      // If the pids ae not consistent, make a note of this.
+      // If the pids are not consistent, make a note of this.
       context_->storage->IncrementStats(stats::mismatched_sched_switch_tids);
     }
   }
@@ -104,6 +91,92 @@
   UniqueTid prev_utid =
       context_->process_tracker->UpdateThreadName(prev_pid, prev_comm_id);
 
+  auto new_slice_idx = AddRawEventAndStartSlice(
+      cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio, prev_state,
+      next_utid, next_pid, next_comm_id, next_prio);
+
+  // Finally, update the info for the next sched switch on this CPU.
+  pending_sched->pending_slice_storage_idx = new_slice_idx;
+  pending_sched->last_pid = next_pid;
+  pending_sched->last_utid = next_utid;
+  pending_sched->last_prio = next_prio;
+}
+
+void EventTracker::PushSchedSwitchCompact(uint32_t cpu,
+                                          int64_t ts,
+                                          int64_t prev_state,
+                                          uint32_t next_pid,
+                                          int32_t next_prio,
+                                          StringId next_comm_id) {
+  // At this stage all events should be globally timestamp ordered.
+  if (ts < prev_timestamp_) {
+    PERFETTO_ELOG("sched_switch event out of order by %.4f ms, skipping",
+                  (prev_timestamp_ - ts) / 1e6);
+    context_->storage->IncrementStats(stats::sched_switch_out_of_order);
+    return;
+  }
+  prev_timestamp_ = ts;
+  PERFETTO_DCHECK(cpu < base::kMaxCpus);
+
+  auto next_utid =
+      context_->process_tracker->UpdateThreadName(next_pid, next_comm_id);
+
+  auto* pending_sched = &pending_sched_per_cpu_[cpu];
+
+  // If we're processing the first compact event for this cpu, don't start a
+  // slice since we're missing the "prev_*" fields. The successive events will
+  // create slices as normal, but the first per-cpu switch is effectively
+  // discarded.
+  if (pending_sched->last_utid == std::numeric_limits<UniqueTid>::max()) {
+    pending_sched->last_pid = next_pid;
+    pending_sched->last_utid = next_utid;
+    pending_sched->last_prio = next_prio;
+    // Note: no pending slice, so leave |pending_slice_storage_idx| in its
+    // invalid state.
+    return;
+  }
+
+  // Close the pending slice if any (we won't have one when processing the first
+  // two compact events for a given cpu).
+  size_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
+  if (pending_slice_idx < std::numeric_limits<size_t>::max())
+    ClosePendingSlice(pending_slice_idx, ts, prev_state);
+
+  // Use the previous event's values to infer this event's "prev_*" fields.
+  // There are edge cases, but this assumption should still produce sensible
+  // results in the absence of data loss.
+  UniqueTid prev_utid = pending_sched->last_utid;
+  uint32_t prev_pid = pending_sched->last_pid;
+  int32_t prev_prio = pending_sched->last_prio;
+
+  // Do a fresh task name lookup in case it was updated by a task_rename while
+  // scheduled.
+  const auto& prev_thread = context_->storage->GetThread(prev_utid);
+  StringId prev_comm_id = prev_thread.name_id;
+
+  auto new_slice_idx = AddRawEventAndStartSlice(
+      cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio, prev_state,
+      next_utid, next_pid, next_comm_id, next_prio);
+
+  // Finally, update the info for the next sched switch on this CPU.
+  pending_sched->pending_slice_storage_idx = new_slice_idx;
+  pending_sched->last_pid = next_pid;
+  pending_sched->last_utid = next_utid;
+  pending_sched->last_prio = next_prio;
+}
+
+PERFETTO_ALWAYS_INLINE
+size_t EventTracker::AddRawEventAndStartSlice(uint32_t cpu,
+                                              int64_t ts,
+                                              UniqueTid prev_utid,
+                                              uint32_t prev_pid,
+                                              StringId prev_comm_id,
+                                              int32_t prev_prio,
+                                              int64_t prev_state,
+                                              UniqueTid next_utid,
+                                              uint32_t next_pid,
+                                              StringId next_comm_id,
+                                              int32_t next_prio) {
   // Push the raw event - this is done as the raw ftrace event codepath does
   // not insert sched_switch.
   auto rid = context_->storage->mutable_raw_events()->AddRawEvent(
@@ -125,13 +198,30 @@
   add_raw_arg(rid, SS::kNextPidFieldNumber, Variadic::Integer(next_pid));
   add_raw_arg(rid, SS::kNextPrioFieldNumber, Variadic::Integer(next_prio));
 
-  // Add the slice for the "next" slice.
-  auto next_idx = slices->AddSlice(cpu, ts, 0 /* duration */, next_utid,
-                                   ftrace_utils::TaskState(), next_prio);
+  // Open a new scheduling slice, corresponding to the task that was
+  // just switched to.
+  return context_->storage->mutable_slices()->AddSlice(
+      cpu, ts, 0 /* duration */, next_utid, ftrace_utils::TaskState(),
+      next_prio);
+}
 
-  // Finally, update the info for the next sched switch on this CPU.
-  prev_slice->storage_index = next_idx;
-  prev_slice->next_pid = next_pid;
+PERFETTO_ALWAYS_INLINE
+void EventTracker::ClosePendingSlice(size_t pending_slice_idx,
+                                     int64_t ts,
+                                     int64_t prev_state) {
+  auto* slices = context_->storage->mutable_slices();
+
+  int64_t duration = ts - slices->start_ns()[pending_slice_idx];
+  slices->set_duration(pending_slice_idx, duration);
+
+  // We store the state as a uint16 as we only consider values up to 2048
+  // when unpacking the information inside; this allows savings of 48 bits
+  // per slice.
+  auto task_state = ftrace_utils::TaskState(static_cast<uint16_t>(prev_state));
+  if (!task_state.is_valid()) {
+    context_->storage->IncrementStats(stats::task_state_invalid);
+  }
+  slices->set_end_state(pending_slice_idx, task_state);
 }
 
 RowId EventTracker::PushCounter(int64_t timestamp,
@@ -208,7 +298,7 @@
   int64_t end_ts = context_->storage->GetTraceTimestampBoundsNs().second;
   auto* slices = context_->storage->mutable_slices();
   for (const auto& pending_sched : pending_sched_per_cpu_) {
-    size_t row = pending_sched.storage_index;
+    size_t row = pending_sched.pending_slice_storage_idx;
     if (row == std::numeric_limits<size_t>::max())
       continue;
 
diff --git a/src/trace_processor/event_tracker.h b/src/trace_processor/event_tracker.h
index 8c56e6d..c743263 100644
--- a/src/trace_processor/event_tracker.h
+++ b/src/trace_processor/event_tracker.h
@@ -38,7 +38,7 @@
   EventTracker& operator=(const EventTracker&) = delete;
   virtual ~EventTracker();
 
-  // This method is called when a sched switch event is seen in the trace.
+  // This method is called when a sched_switch event is seen in the trace.
   virtual void PushSchedSwitch(uint32_t cpu,
                                int64_t timestamp,
                                uint32_t prev_pid,
@@ -49,6 +49,15 @@
                                base::StringView next_comm,
                                int32_t next_prio);
 
+  // This method is called when parsing a sched_switch encoded in the compact
+  // format.
+  void PushSchedSwitchCompact(uint32_t cpu,
+                              int64_t ts,
+                              int64_t prev_state,
+                              uint32_t next_pid,
+                              int32_t next_prio,
+                              StringId next_comm_id);
+
   // This method is called when a counter event is seen in the trace.
   virtual RowId PushCounter(int64_t timestamp,
                             double value,
@@ -75,10 +84,18 @@
   void FlushPendingEvents();
 
  private:
-  // Represents a slice which is currently pending.
-  struct PendingSchedSlice {
-    size_t storage_index = std::numeric_limits<size_t>::max();
-    uint32_t next_pid = 0;
+  // Infromation retained from the preceding sched_switch seen on a given cpu.
+  struct PendingSchedInfo {
+    // The pending scheduling slice that the next event will complete.
+    size_t pending_slice_storage_idx = std::numeric_limits<size_t>::max();
+
+    // pid/utid/prio corresponding to the last sched_switch seen on this cpu
+    // (its "next_*" fields). There is some duplication with respect to the
+    // slices storage, but we don't always have a slice when decoding events in
+    // the compact format.
+    uint32_t last_pid = std::numeric_limits<uint32_t>::max();
+    UniqueTid last_utid = std::numeric_limits<UniqueTid>::max();
+    int32_t last_prio = std::numeric_limits<int32_t>::max();
   };
 
   // Represents a counter event which is currently pending upid resolution.
@@ -94,8 +111,22 @@
     UniqueTid utid = 0;
   };
 
-  // Store pending sched slices for each CPU.
-  std::array<PendingSchedSlice, base::kMaxCpus> pending_sched_per_cpu_{};
+  size_t AddRawEventAndStartSlice(uint32_t cpu,
+                                  int64_t ts,
+                                  UniqueTid prev_utid,
+                                  uint32_t prev_pid,
+                                  StringId prev_comm_id,
+                                  int32_t prev_prio,
+                                  int64_t prev_state,
+                                  UniqueTid next_utid,
+                                  uint32_t next_pid,
+                                  StringId next_comm_id,
+                                  int32_t next_prio);
+
+  void ClosePendingSlice(size_t slice_idx, int64_t ts, int64_t prev_state);
+
+  // Infromation retained from the preceding sched_switch seen on a given cpu.
+  std::array<PendingSchedInfo, base::kMaxCpus> pending_sched_per_cpu_{};
 
   // Store the rows in the counters table which need upids resolved.
   std::vector<PendingUpidResolutionCounter> pending_upid_resolution_counter_;
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index bc3f3ef..2ff1f6e 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
 #include <inttypes.h>
 #include <json/reader.h>
 #include <json/value.h>
@@ -292,14 +295,18 @@
     }
 
     if (slices.types()[i] == RefType::kRefTrack) {  // Async event.
-      const auto& async_track = storage->chrome_async_track_table();
       TrackId track_id = static_cast<TrackId>(slices.refs()[i]);
-      uint32_t async_row = *async_track.id().IndexOf(SqlValue::Long(track_id));
 
-      auto opt_upid = storage->chrome_async_track_table().upid()[async_row];
-      if (opt_upid.has_value()) {
+      // TODO(lalitm): add a check here for looking up source_arg_set_id
+      // and checking that this track originated from Chrome.
+
+      const auto& process_track = storage->process_track_table();
+      auto opt_process_row =
+          process_track.id().IndexOf(SqlValue::Long(track_id));
+      if (opt_process_row.has_value()) {
+        uint32_t upid = process_track.upid()[*opt_process_row];
         event["id2"]["local"] = PrintUint64(track_id);
-        event["pid"] = storage->GetProcess(*opt_upid).pid;
+        event["pid"] = storage->GetProcess(upid).pid;
       } else {
         event["id2"]["global"] = PrintUint64(track_id);
       }
@@ -844,3 +851,5 @@
 }  // namespace json
 }  // namespace trace_processor
 }  // namespace perfetto
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index 3afe4cc..5e9cd14 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -67,13 +67,15 @@
   const int64_t kThreadID = 100;
   const char* kCategory = "cat";
   const char* kName = "name";
+  constexpr TrackId track = 22;
 
   TraceStorage storage;
   UniqueTid utid = storage.AddEmptyThread(kThreadID);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  storage.mutable_nestable_slices()->AddSlice(
-      kTimestamp, kDuration, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+  storage.mutable_nestable_slices()->AddSlice(kTimestamp, kDuration, track,
+                                              utid, RefType::kRefUtid, cat_id,
+                                              name_id, 0, 0, 0);
   storage.mutable_thread_slices()->AddThreadSlice(
       0, kThreadTimestamp, kThreadDuration, kThreadInstructionCount,
       kThreadInstructionDelta);
@@ -112,13 +114,15 @@
   const int64_t kThreadID = 100;
   const char* kCategory = "cat";
   const char* kName = "name";
+  constexpr TrackId track = 22;
 
   TraceStorage storage;
   UniqueTid utid = storage.AddEmptyThread(kThreadID);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  storage.mutable_nestable_slices()->AddSlice(
-      kTimestamp, kDuration, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+  storage.mutable_nestable_slices()->AddSlice(kTimestamp, kDuration, track,
+                                              utid, RefType::kRefUtid, cat_id,
+                                              name_id, 0, 0, 0);
   storage.mutable_thread_slices()->AddThreadSlice(
       0, kThreadTimestamp, kThreadDuration, kThreadInstructionCount,
       kThreadInstructionDelta);
@@ -176,11 +180,13 @@
 }
 
 TEST(ExportJsonTest, WrongRefType) {
+  constexpr TrackId track = 22;
+
   TraceStorage storage;
   StringId cat_id = storage.InternString("cat");
   StringId name_id = storage.InternString("name");
-  storage.mutable_nestable_slices()->AddSlice(0, 0, 0, RefType::kRefCpuId,
-                                              cat_id, name_id, 0, 0, 0);
+  storage.mutable_nestable_slices()->AddSlice(
+      0, 0, track, 0, RefType::kRefCpuId, cat_id, name_id, 0, 0, 0);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -339,13 +345,14 @@
   const char* kCategory = "cat";
   const char* kName = "name";
   const char* kSrc = "source_file.cc";
+  constexpr TrackId track = 22;
 
   TraceStorage storage;
   UniqueTid utid = storage.AddEmptyThread(0);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
-                                              cat_id, name_id, 0, 0, 0);
+  storage.mutable_nestable_slices()->AddSlice(
+      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
 
   StringId arg_key_id =
       storage.InternString(base::StringView("task.posted_from.file_name"));
@@ -378,13 +385,14 @@
   const char* kCategory = "cat";
   const char* kName = "name";
   double kValues[] = {1.234, 2.345};
+  constexpr TrackId track = 22;
 
   TraceStorage storage;
   UniqueTid utid = storage.AddEmptyThread(0);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
-                                              cat_id, name_id, 0, 0, 0);
+  storage.mutable_nestable_slices()->AddSlice(
+      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
 
   StringId arg_flat_key_id =
       storage.InternString(base::StringView("debug.draw_duration_ms"));
@@ -427,13 +435,14 @@
   const char* kName = "name";
   uint64_t kValue0 = 1;
   uint64_t kValue1 = std::numeric_limits<uint64_t>::max();
+  constexpr TrackId track = 22;
 
   TraceStorage storage;
   UniqueTid utid = storage.AddEmptyThread(0);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
-                                              cat_id, name_id, 0, 0, 0);
+  storage.mutable_nestable_slices()->AddSlice(
+      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
 
   StringId arg_key0_id = storage.InternString(base::StringView("arg0"));
   StringId arg_key1_id = storage.InternString(base::StringView("arg1"));
@@ -470,13 +479,14 @@
   const char* kCategory = "cat";
   const char* kName = "name";
   int kValues[] = {123, 234};
+  constexpr TrackId track = 22;
 
   TraceStorage storage;
   UniqueTid utid = storage.AddEmptyThread(0);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
-                                              cat_id, name_id, 0, 0, 0);
+  storage.mutable_nestable_slices()->AddSlice(
+      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
 
   StringId arg_flat_key_id = storage.InternString(base::StringView("a.b"));
   StringId arg_key0_id = storage.InternString(base::StringView("a[0].b"));
@@ -515,13 +525,14 @@
   const char* kCategory = "cat";
   const char* kName = "name";
   int kValues[] = {123, 234};
+  constexpr TrackId track = 22;
 
   TraceStorage storage;
   UniqueTid utid = storage.AddEmptyThread(0);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
-                                              cat_id, name_id, 0, 0, 0);
+  storage.mutable_nestable_slices()->AddSlice(
+      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
 
   StringId arg_flat_key_id = storage.InternString(base::StringView("a"));
   StringId arg_key0_id = storage.InternString(base::StringView("a[0][0]"));
@@ -560,13 +571,14 @@
 TEST(ExportJsonTest, StorageWithLegacyJsonArgs) {
   const char* kCategory = "cat";
   const char* kName = "name";
+  constexpr TrackId track = 22;
 
   TraceStorage storage;
   UniqueTid utid = storage.AddEmptyThread(0);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
-                                              cat_id, name_id, 0, 0, 0);
+  storage.mutable_nestable_slices()->AddSlice(
+      0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
 
   StringId arg_key_id = storage.InternString(base::StringView("a"));
   StringId arg_value_id = storage.InternString(base::StringView("{\"b\":123}"));
@@ -598,12 +610,13 @@
   const int64_t kTimestamp = 10000000;
   const char* kCategory = "cat";
   const char* kName = "name";
+  constexpr TrackId track = 22;
 
   TraceStorage storage;
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
   storage.mutable_nestable_slices()->AddSlice(
-      kTimestamp, 0, 0, RefType::kRefNoRef, cat_id, name_id, 0, 0, 0);
+      kTimestamp, 0, track, 0, RefType::kRefNoRef, cat_id, name_id, 0, 0, 0);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -638,13 +651,13 @@
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
 
-  tables::ChromeAsyncTrackTable::Row track(name_id);
+  tables::ProcessTrackTable::Row track(name_id);
   track.upid = upid;
-  TrackId track_id = storage.mutable_chrome_async_track_table()->Insert(track);
+  TrackId track_id = storage.mutable_process_track_table()->Insert(track);
 
   storage.mutable_nestable_slices()->AddSlice(kTimestamp, kDuration, track_id,
-                                              RefType::kRefTrack, cat_id,
-                                              name_id, 0, 0, 0);
+                                              track_id, RefType::kRefTrack,
+                                              cat_id, name_id, 0, 0, 0);
   StringId arg_key_id = storage.InternString(base::StringView(kArgName));
   TraceStorage::Args::Arg arg;
   arg.flat_key = arg_key_id;
@@ -701,13 +714,13 @@
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
 
-  tables::ChromeAsyncTrackTable::Row track(name_id);
+  tables::ProcessTrackTable::Row track(name_id);
   track.upid = upid;
-  TrackId track_id = storage.mutable_chrome_async_track_table()->Insert(track);
+  TrackId track_id = storage.mutable_process_track_table()->Insert(track);
 
   auto slice_id = storage.mutable_nestable_slices()->AddSlice(
-      kTimestamp, kDuration, track_id, RefType::kRefTrack, cat_id, name_id, 0,
-      0, 0);
+      kTimestamp, kDuration, track_id, track_id, RefType::kRefTrack, cat_id,
+      name_id, 0, 0, 0);
   storage.mutable_virtual_track_slices()->AddVirtualTrackSlice(
       slice_id, kThreadTimestamp, kThreadDuration, 0, 0);
 
@@ -758,13 +771,13 @@
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
 
-  tables::ChromeAsyncTrackTable::Row track(name_id);
+  tables::ProcessTrackTable::Row track(name_id);
   track.upid = upid;
-  TrackId track_id = storage.mutable_chrome_async_track_table()->Insert(track);
+  TrackId track_id = storage.mutable_process_track_table()->Insert(track);
 
   auto slice_id = storage.mutable_nestable_slices()->AddSlice(
-      kTimestamp, kDuration, track_id, RefType::kRefTrack, cat_id, name_id, 0,
-      0, 0);
+      kTimestamp, kDuration, track_id, track_id, RefType::kRefTrack, cat_id,
+      name_id, 0, 0, 0);
   storage.mutable_virtual_track_slices()->AddVirtualTrackSlice(
       slice_id, kThreadTimestamp, kThreadDuration, 0, 0);
 
@@ -803,12 +816,13 @@
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
 
-  tables::ChromeAsyncTrackTable::Row track(name_id);
+  tables::ProcessTrackTable::Row track(name_id);
   track.upid = upid;
-  TrackId track_id = storage.mutable_chrome_async_track_table()->Insert(track);
+  TrackId track_id = storage.mutable_process_track_table()->Insert(track);
 
-  storage.mutable_nestable_slices()->AddSlice(
-      kTimestamp, 0, track_id, RefType::kRefTrack, cat_id, name_id, 0, 0, 0);
+  storage.mutable_nestable_slices()->AddSlice(kTimestamp, 0, track_id, track_id,
+                                              RefType::kRefTrack, cat_id,
+                                              name_id, 0, 0, 0);
   StringId arg_key_id = storage.InternString(base::StringView("arg_name"));
   TraceStorage::Args::Arg arg;
   arg.flat_key = arg_key_id;
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index b3aced7..5f6a99f 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -143,14 +143,14 @@
       base::StartsWith(start, "<html>"))
     return kSystraceTraceType;
 
+  // Ctrace is deflate'ed systrace.
+  if (start.find("TRACE:") != std::string::npos)
+    return kCtraceTraceType;
+
   // Systrace with no header or leading HTML.
   if (base::StartsWith(start, " "))
     return kSystraceTraceType;
 
-  // Ctrace is deflate'ed systrace.
-  if (base::StartsWith(start, "TRACE:"))
-    return kCtraceTraceType;
-
   // gzip'ed trace containing one of the other formats.
   if (base::StartsWith(start, "\x1f\x8b"))
     return kGzipTraceType;
diff --git a/src/trace_processor/ftrace_utils.cc b/src/trace_processor/ftrace_utils.cc
index 081ba94..b58fd73 100644
--- a/src/trace_processor/ftrace_utils.cc
+++ b/src/trace_processor/ftrace_utils.cc
@@ -97,6 +97,8 @@
       state_ |= Atom::kParked;
     else if (c == 'N')
       state_ |= Atom::kNoLoad;
+    else if (c == '|')
+      continue;
     else {
       invalid_char = true;
       break;
@@ -202,10 +204,13 @@
   }
 
   int64_t padding = 16 - static_cast<int64_t>(name.size());
-  if (PERFETTO_LIKELY(padding > 0)) {
+  if (padding > 0) {
     writer->AppendChar(' ', static_cast<size_t>(padding));
   }
-  writer->AppendString(name);
+  for (size_t i = 0; i < name.size(); ++i) {
+    char c = name.data()[i];
+    writer->AppendChar(c == '-' ? '_' : c);
+  }
   writer->AppendChar('-');
 
   size_t pre_pid_pos = writer->pos();
diff --git a/src/trace_processor/ftrace_utils_unittest.cc b/src/trace_processor/ftrace_utils_unittest.cc
index dbfe0e4..3675fb2 100644
--- a/src/trace_processor/ftrace_utils_unittest.cc
+++ b/src/trace_processor/ftrace_utils_unittest.cc
@@ -53,6 +53,9 @@
   ASSERT_STREQ(TaskState(4096).ToString().data(), "R+");
   ASSERT_STREQ(TaskState(130).ToString().data(), "DK");
   ASSERT_STREQ(TaskState(258).ToString().data(), "DW");
+
+  ASSERT_EQ(TaskState("D|K").raw_state(), 130);
+  ASSERT_EQ(TaskState("D|W").raw_state(), 258);
 }
 
 }  // namespace
diff --git a/src/trace_processor/fuchsia_trace_parser.cc b/src/trace_processor/fuchsia_trace_parser.cc
index a755bc6..74df7f3 100644
--- a/src/trace_processor/fuchsia_trace_parser.cc
+++ b/src/trace_processor/fuchsia_trace_parser.cc
@@ -257,7 +257,7 @@
                   ts, counter_value,
                   context_->storage->InternString(
                       base::StringView(counter_name_str)),
-                  utid, kRefUtid);
+                  utid, RefType::kRefUtid);
             }
           }
           break;
@@ -266,17 +266,19 @@
           UniqueTid utid =
               procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
                                   static_cast<uint32_t>(tinfo.pid));
-          slices->Begin(ts, utid, RefType::kRefUtid, cat, name);
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+          slices->Begin(ts, track_id, utid, RefType::kRefUtid, cat, name);
           break;
         }
         case kDurationEnd: {
           UniqueTid utid =
               procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
                                   static_cast<uint32_t>(tinfo.pid));
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
           // TODO(b/131181693): |cat| and |name| are not passed here so that
           // if two slices end at the same timestep, the slices get closed in
           // the correct order regardless of which end event is processed first.
-          slices->End(ts, utid, RefType::kRefUtid);
+          slices->End(ts, track_id);
           break;
         }
         case kDurationComplete: {
@@ -285,27 +287,25 @@
           UniqueTid utid =
               procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
                                   static_cast<uint32_t>(tinfo.pid));
-          slices->Scoped(ts, utid, RefType::kRefUtid, cat, name, end_ts - ts);
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+          slices->Scoped(ts, track_id, utid, RefType::kRefUtid, cat, name,
+                         end_ts - ts);
           break;
         }
         case kAsyncBegin: {
-          tables::FuchsiaAsyncTrackTable::Row track(name);
-          track.correlation_id = static_cast<int64_t>(*current++);
-
-          TrackId track_id =
-              context_->track_tracker->InternFuchsiaAsyncTrack(track);
-          slices->Begin(ts, track_id, RefType::kRefTrack, cat, name);
+          int64_t correlation_id = static_cast<int64_t>(*current++);
+          TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
+              name, correlation_id);
+          slices->Begin(ts, track_id, track_id, RefType::kRefTrack, cat, name);
           break;
         }
         case kAsyncInstant: {
           // TODO(eseckler): Consider storing these instants as 0-duration
           // slices instead, so that they get nested underneath begin/end
           // slices.
-          tables::FuchsiaAsyncTrackTable::Row track(name);
-          track.correlation_id = static_cast<int64_t>(*current++);
-
-          TrackId track_id =
-              context_->track_tracker->InternFuchsiaAsyncTrack(track);
+          int64_t correlation_id = static_cast<int64_t>(*current++);
+          TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
+              name, correlation_id);
           RowId row = context_->event_tracker->PushInstant(
               ts, name, 0, track_id, RefType::kRefTrack);
           for (const Arg& arg : args) {
@@ -317,12 +317,10 @@
           break;
         }
         case kAsyncEnd: {
-          tables::FuchsiaAsyncTrackTable::Row track(name);
-          track.correlation_id = static_cast<int64_t>(*current++);
-
-          TrackId track_id =
-              context_->track_tracker->InternFuchsiaAsyncTrack(track);
-          slices->End(ts, track_id, RefType::kRefTrack, cat, name);
+          int64_t correlation_id = static_cast<int64_t>(*current++);
+          TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
+              name, correlation_id);
+          slices->End(ts, track_id, cat, name);
           break;
         }
       }
diff --git a/src/trace_processor/graphics_event_parser.cc b/src/trace_processor/graphics_event_parser.cc
index 6044d2f..f6b8eae 100644
--- a/src/trace_processor/graphics_event_parser.cc
+++ b/src/trace_processor/graphics_event_parser.cc
@@ -192,9 +192,10 @@
       snprintf(buffer, 64, "render stage(%zu)", stage_id);
       stage_name = context_->storage->InternString(buffer);
     }
+    TrackId track_id =
+        gpu_hw_queue_ids_[static_cast<size_t>(event.hw_queue_id())];
     const auto slice_id = context_->slice_tracker->Scoped(
-        ts, gpu_hw_queue_ids_[static_cast<size_t>(event.hw_queue_id())],
-        RefType::kRefTrack, 0 /* cat */, stage_name,
+        ts, track_id, track_id, RefType::kRefTrack, 0 /* cat */, stage_name,
         static_cast<int64_t>(event.duration()), args_callback);
 
     context_->storage->mutable_gpu_slice_table()->Insert(
@@ -270,8 +271,9 @@
   TrackId track_id = context_->track_tracker->InternGpuTrack(track);
 
   const auto slice_id = context_->slice_tracker->Scoped(
-      timestamp, track_id, RefType::kRefTrack, 0 /* cat */, event_name_id,
-      duration, [this, layer_name_id](ArgsTracker* args_tracker, RowId row_id) {
+      timestamp, track_id, track_id, RefType::kRefTrack, 0 /* cat */,
+      event_name_id, duration,
+      [this, layer_name_id](ArgsTracker* args_tracker, RowId row_id) {
         args_tracker->AddArg(row_id, layer_name_key_id_, layer_name_key_id_,
                              Variadic::String(layer_name_id));
       });
diff --git a/src/trace_processor/gzip_trace_parser.cc b/src/trace_processor/gzip_trace_parser.cc
index a374019..4dfce0b 100644
--- a/src/trace_processor/gzip_trace_parser.cc
+++ b/src/trace_processor/gzip_trace_parser.cc
@@ -16,9 +16,13 @@
 
 #include "src/trace_processor/gzip_trace_parser.h"
 
+#include <string>
+
 #include <zlib.h>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
 #include "src/trace_processor/forwarding_trace_parser.h"
 
 namespace perfetto {
@@ -45,13 +49,15 @@
   if (!inner_) {
     inner_.reset(new ForwardingTraceParser(context_));
 
-    // .ctrace files begin with "TRACE:" strip this if present.
-    static const char kSystraceFileHeader[] = "TRACE:\n";
-    if (size >= strlen(kSystraceFileHeader) &&
-        strncmp(reinterpret_cast<char*>(start), kSystraceFileHeader,
-                strlen(kSystraceFileHeader)) == 0) {
-      start += strlen(kSystraceFileHeader);
-      len -= strlen(kSystraceFileHeader);
+    // .ctrace files begin with: "TRACE:\n" or "done. TRACE:\n" strip this if
+    // present.
+    base::StringView beginning(reinterpret_cast<char*>(start), size);
+
+    static const char* kSystraceFileHeader = "TRACE:\n";
+    size_t offset = Find(kSystraceFileHeader, beginning);
+    if (offset != std::string::npos) {
+      start += strlen(kSystraceFileHeader) + offset;
+      len -= strlen(kSystraceFileHeader) + offset;
     }
   }
 
diff --git a/src/trace_processor/json_trace_parser.cc b/src/trace_processor/json_trace_parser.cc
index 5dc2816..54a548f 100644
--- a/src/trace_processor/json_trace_parser.cc
+++ b/src/trace_processor/json_trace_parser.cc
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
 #include "src/trace_processor/json_trace_parser.h"
 
 #include <inttypes.h>
@@ -23,7 +26,6 @@
 #include <limits>
 #include <string>
 
-#include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/utils.h"
@@ -31,10 +33,7 @@
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/trace_processor_context.h"
-
-#if !PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
-#error JSON parsing and exporting is not supported in this build configuration
-#endif
+#include "src/trace_processor/track_tracker.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -88,11 +87,14 @@
 
   switch (phase) {
     case 'B': {  // TRACE_EVENT_BEGIN.
-      slice_tracker->Begin(timestamp, utid, RefType::kRefUtid, cat_id, name_id);
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      slice_tracker->Begin(timestamp, track_id, utid, RefType::kRefUtid, cat_id,
+                           name_id);
       break;
     }
     case 'E': {  // TRACE_EVENT_END.
-      slice_tracker->End(timestamp, utid, RefType::kRefUtid, cat_id, name_id);
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      slice_tracker->End(timestamp, track_id, cat_id, name_id);
       break;
     }
     case 'X': {  // TRACE_EVENT (scoped event).
@@ -100,8 +102,9 @@
           json_trace_utils::CoerceToNs(value["dur"]);
       if (!opt_dur.has_value())
         return;
-      slice_tracker->Scoped(timestamp, utid, RefType::kRefUtid, cat_id, name_id,
-                            opt_dur.value());
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      slice_tracker->Scoped(timestamp, track_id, utid, RefType::kRefUtid,
+                            cat_id, name_id, opt_dur.value());
       break;
     }
     case 'M': {  // Metadata events (process and thread names).
@@ -124,3 +127,5 @@
 
 }  // namespace trace_processor
 }  // namespace perfetto
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/json_trace_tokenizer.cc b/src/trace_processor/json_trace_tokenizer.cc
index 274d908..120cc69 100644
--- a/src/trace_processor/json_trace_tokenizer.cc
+++ b/src/trace_processor/json_trace_tokenizer.cc
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
 #include "src/trace_processor/json_trace_tokenizer.h"
 
 #include <json/reader.h>
@@ -147,3 +150,5 @@
 
 }  // namespace trace_processor
 }  // namespace perfetto
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/json_trace_utils.cc b/src/trace_processor/json_trace_utils.cc
index 0d6ba70..c4db237 100644
--- a/src/trace_processor/json_trace_utils.cc
+++ b/src/trace_processor/json_trace_utils.cc
@@ -14,17 +14,14 @@
  * limitations under the License.
  */
 
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
 #include "src/trace_processor/json_trace_utils.h"
 
 #include <json/value.h>
 #include <limits>
 
-#include "perfetto/base/build_config.h"
-
-#if !PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
-#error JSON parsing and exporting is not supported in this build configuration
-#endif
-
 namespace perfetto {
 namespace trace_processor {
 namespace json_trace_utils {
@@ -83,3 +80,5 @@
 }  // namespace json_trace_utils
 }  // namespace trace_processor
 }  // namespace perfetto
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/metrics/android/heap_profile_callsite_stats.sql b/src/trace_processor/metrics/android/heap_profile_callsite_stats.sql
index 716b17c..f81b3d3 100644
--- a/src/trace_processor/metrics/android/heap_profile_callsite_stats.sql
+++ b/src/trace_processor/metrics/android/heap_profile_callsite_stats.sql
@@ -37,6 +37,21 @@
 SELECT *
 FROM callsite_parser;
 
+-- Join frames with symbols
+CREATE TABLE symbolized_frame AS
+SELECT
+  spf.id AS id,
+  spf.mapping AS mapping,
+  IFNULL(
+    (SELECT name FROM stack_profile_symbol symbol
+      WHERE symbol.symbol_set_id = spf.symbol_set_id
+      LIMIT 1),
+    spf.name
+  ) AS name
+FROM stack_profile_frame spf;
+
+CREATE UNIQUE INDEX symbolized_frame_idx ON symbolized_frame(id);
+
 -- Join with the frames table to get the symbol names.
 -- Output order for position matters (as will be the order in the subsequent aggregate operations).
 -- We use the cross join to force the join order between virtual and non-virtual tables.
@@ -45,16 +60,12 @@
   callsite_id,
   position,
   HeapProfileCallsiteStats_Frame(
-    'name', IFNULL(
-      (SELECT name FROM stack_profile_symbol symbol
-        WHERE symbol.symbol_set_id = spf.symbol_set_id
-        LIMIT 1),
-      spf.name),
+    'name', spf.name,
     'mapping_name', stack_profile_mapping.name
   ) AS frame_proto
 FROM flattened_callsite
 CROSS JOIN stack_profile_callsite
-CROSS JOIN stack_profile_frame spf
+CROSS JOIN symbolized_frame spf
 CROSS JOIN stack_profile_mapping
 WHERE
   flattened_callsite.current_id = stack_profile_callsite.id
diff --git a/src/trace_processor/proto_incremental_state.h b/src/trace_processor/proto_incremental_state.h
index 408b2fd..2bfc910 100644
--- a/src/trace_processor/proto_incremental_state.h
+++ b/src/trace_processor/proto_incremental_state.h
@@ -82,6 +82,11 @@
 
 }  // namespace proto_incremental_state_internal
 
+struct DefaultFieldName;
+struct BuildIdFieldName;
+struct MappingPathsFieldName;
+struct FunctionNamesFieldName;
+
 // Stores per-packet-sequence incremental state during trace parsing, such as
 // reference timestamps for delta timestamp calculation and interned messages.
 class ProtoIncrementalState {
@@ -115,26 +120,26 @@
   class PacketSequenceState {
    public:
     int64_t IncrementAndGetTrackEventTimeNs(int64_t delta_ns) {
-      PERFETTO_DCHECK(IsTrackEventStateValid());
+      PERFETTO_DCHECK(track_event_timestamps_valid());
       track_event_timestamp_ns_ += delta_ns;
       return track_event_timestamp_ns_;
     }
 
     int64_t IncrementAndGetTrackEventThreadTimeNs(int64_t delta_ns) {
-      PERFETTO_DCHECK(IsTrackEventStateValid());
+      PERFETTO_DCHECK(track_event_timestamps_valid());
       track_event_thread_timestamp_ns_ += delta_ns;
       return track_event_thread_timestamp_ns_;
     }
 
     int64_t IncrementAndGetTrackEventThreadInstructionCount(int64_t delta) {
-      PERFETTO_DCHECK(IsTrackEventStateValid());
+      PERFETTO_DCHECK(track_event_timestamps_valid());
       track_event_thread_instruction_count_ += delta;
       return track_event_thread_instruction_count_;
     }
 
     void OnPacketLoss() {
       packet_loss_ = true;
-      thread_descriptor_seen_ = false;
+      track_event_timestamps_valid_ = false;
     }
 
     void OnIncrementalStateCleared() { packet_loss_ = false; }
@@ -144,7 +149,8 @@
                              int64_t timestamp_ns,
                              int64_t thread_timestamp_ns,
                              int64_t thread_instruction_count) {
-      thread_descriptor_seen_ = true;
+      track_event_timestamps_valid_ = true;
+      pid_and_tid_valid_ = true;
       pid_ = pid;
       tid_ = tid;
       track_event_timestamp_ns_ = timestamp_ns;
@@ -154,14 +160,18 @@
 
     bool IsIncrementalStateValid() const { return !packet_loss_; }
 
-    bool IsTrackEventStateValid() const {
-      return IsIncrementalStateValid() && thread_descriptor_seen_;
+    bool track_event_timestamps_valid() const {
+      return track_event_timestamps_valid_;
     }
 
+    bool pid_and_tid_valid() const { return pid_and_tid_valid_; }
+
     int32_t pid() const { return pid_; }
     int32_t tid() const { return tid_; }
 
-    template <typename MessageType>
+    // Use DefaultFieldName only if there is a single field in InternedData of
+    // the MessageType.
+    template <typename MessageType, typename FieldName = DefaultFieldName>
     InternedDataMap<MessageType>* GetInternedDataMap();
 
    private:
@@ -172,11 +182,15 @@
 
     // We can only consider TrackEvent delta timestamps to be correct after we
     // have observed a thread descriptor (since the last packet loss).
-    bool thread_descriptor_seen_ = false;
+    bool track_event_timestamps_valid_ = false;
 
-    // Process/thread ID of the packet sequence. Used as default values for
-    // TrackEvents that don't specify a pid/tid override. Only valid while
-    // |seen_thread_descriptor_| is true.
+    // |pid_| and |tid_| are only valid after we parsed at least one
+    // ThreadDescriptor packet on the sequence.
+    bool pid_and_tid_valid_ = false;
+
+    // Process/thread ID of the packet sequence set by a ThreadDescriptor
+    // packet. Used as default values for TrackEvents that don't specify a
+    // pid/tid override. Only valid after |pid_and_tid_valid_| is set to true.
     int32_t pid_ = 0;
     int32_t tid_ = 0;
 
@@ -191,7 +205,9 @@
     InternedDataMap<protos::pbzero::DebugAnnotationName>
         debug_annotation_names_;
     InternedDataMap<protos::pbzero::SourceLocation> source_locations_;
-    InternedDataMap<protos::pbzero::InternedString> interned_strings_;
+    InternedDataMap<protos::pbzero::InternedString> build_ids_;
+    InternedDataMap<protos::pbzero::InternedString> mapping_paths_;
+    InternedDataMap<protos::pbzero::InternedString> function_names_;
     InternedDataMap<protos::pbzero::LogMessageBody> interned_log_messages_;
     InternedDataMap<protos::pbzero::Mapping> mappings_;
     InternedDataMap<protos::pbzero::Frame> frames_;
@@ -253,9 +269,25 @@
 
 template <>
 inline ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
+ProtoIncrementalState::PacketSequenceState::
+    GetInternedDataMap<protos::pbzero::InternedString, BuildIdFieldName>() {
+  return &build_ids_;
+}
+
+template <>
+inline ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
 ProtoIncrementalState::PacketSequenceState::GetInternedDataMap<
-    protos::pbzero::InternedString>() {
-  return &interned_strings_;
+    protos::pbzero::InternedString,
+    MappingPathsFieldName>() {
+  return &mapping_paths_;
+}
+
+template <>
+inline ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
+ProtoIncrementalState::PacketSequenceState::GetInternedDataMap<
+    protos::pbzero::InternedString,
+    FunctionNamesFieldName>() {
+  return &function_names_;
 }
 
 template <>
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index 241b1f3..5b4e3be 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -23,9 +23,9 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/metatrace_events.h"
-#include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/string_writer.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/traced/sys_stats_counters.h"
 #include "perfetto/protozero/proto_decoder.h"
@@ -36,13 +36,14 @@
 #include "src/trace_processor/heap_profile_tracker.h"
 #include "src/trace_processor/metadata.h"
 #include "src/trace_processor/process_tracker.h"
+#include "src/trace_processor/proto_incremental_state.h"
+#include "src/trace_processor/stack_profile_tracker.h"
 #include "src/trace_processor/syscall_tracker.h"
 #include "src/trace_processor/systrace_parser.h"
 #include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/track_tracker.h"
 #include "src/trace_processor/variadic.h"
 
-#include "perfetto/ext/base/string_writer.h"
 #include "protos/perfetto/common/android_log_constants.pbzero.h"
 #include "protos/perfetto/common/trace_stats.pbzero.h"
 #include "protos/perfetto/trace/android/android_log.pbzero.h"
@@ -132,9 +133,24 @@
       : seq_state_(seq_state) {}
 
   base::Optional<base::StringView> GetString(
-      StackProfileTracker::SourceStringId iid) const override {
-    auto* map =
-        seq_state_->GetInternedDataMap<protos::pbzero::InternedString>();
+      StackProfileTracker::SourceStringId iid,
+      StackProfileTracker::InternedStringType type) const override {
+    ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
+        map = nullptr;
+    switch (type) {
+      case StackProfileTracker::InternedStringType::kBuildId:
+        map = seq_state_->GetInternedDataMap<protos::pbzero::InternedString,
+                                             BuildIdFieldName>();
+        break;
+      case StackProfileTracker::InternedStringType::kFunctionName:
+        map = seq_state_->GetInternedDataMap<protos::pbzero::InternedString,
+                                             FunctionNamesFieldName>();
+        break;
+      case StackProfileTracker::InternedStringType::kMappingPath:
+        map = seq_state_->GetInternedDataMap<protos::pbzero::InternedString,
+                                             MappingPathsFieldName>();
+        break;
+    }
     auto it = map->find(iid);
     if (it == map->end()) {
       PERFETTO_DLOG("Did not find string %" PRIu64 " in %zu elems", iid,
@@ -657,6 +673,18 @@
     int64_t ts,
     TraceSorter::TimestampedTracePiece ttp) {
   PERFETTO_DCHECK(ttp.json_value == nullptr);
+
+  // Handle the (optional) alternative encoding format for sched_switch.
+  if (ttp.inline_event.type == TraceSorter::InlineEvent::Type::kSchedSwitch) {
+    const auto& event = ttp.inline_event.sched_switch;
+    context_->event_tracker->PushSchedSwitchCompact(
+        cpu, ts, event.prev_state, static_cast<uint32_t>(event.next_pid),
+        event.next_prio, event.next_comm);
+
+    context_->args_tracker->Flush();
+    return;
+  }
+
   const TraceBlobView& ftrace = ttp.blob_view;
 
   ProtoDecoder decoder(ftrace.data(), ftrace.length());
@@ -1576,32 +1604,27 @@
     int64_t ticount,
     ProtoIncrementalState::PacketSequenceState* sequence_state,
     ConstBytes blob) {
+  using LegacyEvent = protos::pbzero::TrackEvent::LegacyEvent;
+
   protos::pbzero::TrackEvent::Decoder event(blob.data, blob.size);
 
   const auto legacy_event_blob = event.legacy_event();
-  protos::pbzero::TrackEvent::LegacyEvent::Decoder legacy_event(
-      legacy_event_blob.data, legacy_event_blob.size);
+  LegacyEvent::Decoder legacy_event(legacy_event_blob.data,
+                                    legacy_event_blob.size);
 
   // TODO(eseckler): This legacy event field will eventually be replaced by
   // fields in TrackEvent itself.
   if (PERFETTO_UNLIKELY(!event.type() && !legacy_event.has_phase())) {
     context_->storage->IncrementStats(stats::track_event_parser_errors);
-    PERFETTO_ELOG("TrackEvent without type or phase");
+    PERFETTO_DLOG("TrackEvent without type or phase");
     return;
   }
 
   ProcessTracker* procs = context_->process_tracker.get();
   TraceStorage* storage = context_->storage.get();
+  TrackTracker* track_tracker = context_->track_tracker.get();
   SliceTracker* slice_tracker = context_->slice_tracker.get();
 
-  uint32_t pid = static_cast<uint32_t>(sequence_state->pid());
-  uint32_t tid = static_cast<uint32_t>(sequence_state->tid());
-  if (legacy_event.has_pid_override())
-    pid = static_cast<uint32_t>(legacy_event.pid_override());
-  if (legacy_event.has_tid_override())
-    tid = static_cast<uint32_t>(legacy_event.tid_override());
-  UniqueTid utid = procs->UpdateThread(tid, pid);
-
   std::vector<uint64_t> category_iids;
   for (auto it = event.category_iids(); it; ++it) {
     category_iids.push_back(it->as_uint64());
@@ -1620,8 +1643,8 @@
         sequence_state->GetInternedDataMap<protos::pbzero::EventCategory>();
     auto cat_view_it = map->find(category_iids[0]);
     if (cat_view_it == map->end()) {
-      context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-      PERFETTO_ELOG("Could not find category interning entry for ID %" PRIu64,
+      storage->IncrementStats(stats::track_event_tokenizer_errors);
+      PERFETTO_DLOG("Could not find category interning entry for ID %" PRIu64,
                     category_iids[0]);
     } else {
       // If the name is already in the pool, no need to decode it again.
@@ -1648,8 +1671,8 @@
     for (uint64_t iid : category_iids) {
       auto cat_view_it = map->find(iid);
       if (cat_view_it == map->end()) {
-        context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-        PERFETTO_ELOG("Could not find category interning entry for ID %" PRIu64,
+        storage->IncrementStats(stats::track_event_tokenizer_errors);
+        PERFETTO_DLOG("Could not find category interning entry for ID %" PRIu64,
                       iid);
         continue;
       }
@@ -1666,9 +1689,6 @@
     }
     if (!categories.empty())
       category_id = storage->InternString(base::StringView(categories));
-  } else {
-    context_->storage->IncrementStats(stats::track_event_parser_errors);
-    PERFETTO_ELOG("TrackEvent without category");
   }
 
   StringId name_id = 0;
@@ -1681,8 +1701,8 @@
     auto* map = sequence_state->GetInternedDataMap<protos::pbzero::EventName>();
     auto name_view_it = map->find(name_iid);
     if (name_view_it == map->end()) {
-      context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-      PERFETTO_ELOG("Could not find event name interning entry for ID %" PRIu64,
+      storage->IncrementStats(stats::track_event_tokenizer_errors);
+      PERFETTO_DLOG("Could not find event name interning entry for ID %" PRIu64,
                     name_iid);
     } else {
       // If the name is already in the pool, no need to decode it again.
@@ -1701,6 +1721,152 @@
     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;
+  TrackId track_id;
+  base::Optional<UniqueTid> utid;
+  base::Optional<UniqueTid> upid;
+
+  // Determine track from track_uuid specified in either TrackEvent or
+  // TrackEventDefaults. If none is set, fall back to the track specified by the
+  // sequence's (or event's) pid + tid or a default track.
+  if (track_uuid) {
+    base::Optional<TrackId> opt_track_id =
+        track_tracker->GetDescriptorTrack(track_uuid);
+    if (!opt_track_id) {
+      storage->IncrementStats(stats::track_event_parser_errors);
+      PERFETTO_DLOG("TrackEvent with unknown track_uuid %" PRIu64, track_uuid);
+      return;
+    }
+    track_id = *opt_track_id;
+
+    auto thread_track_row =
+        context_->storage->thread_track_table().id().IndexOf(
+            SqlValue::Long(track_id));
+    if (thread_track_row) {
+      utid = storage->thread_track_table().utid()[*thread_track_row];
+      upid = storage->GetThread(*utid).upid;
+    } else {
+      auto process_track_row =
+          context_->storage->process_track_table().id().IndexOf(
+              SqlValue::Long(track_id));
+      if (process_track_row)
+        upid = storage->process_track_table().upid()[*process_track_row];
+    }
+  } else if (sequence_state->pid_and_tid_valid() ||
+             (legacy_event.has_pid_override() &&
+              legacy_event.has_tid_override())) {
+    uint32_t pid = static_cast<uint32_t>(sequence_state->pid());
+    uint32_t tid = static_cast<uint32_t>(sequence_state->tid());
+    if (legacy_event.has_pid_override())
+      pid = static_cast<uint32_t>(legacy_event.pid_override());
+    if (legacy_event.has_tid_override())
+      tid = static_cast<uint32_t>(legacy_event.tid_override());
+
+    utid = procs->UpdateThread(tid, pid);
+    upid = storage->GetThread(*utid).upid;
+    track_id = track_tracker->GetOrCreateDescriptorTrackForThread(*utid);
+  } else {
+    track_id = track_tracker->GetOrCreateDefaultDescriptorTrack();
+  }
+
+  // TODO(eseckler): Replace phase with type and remove handling of
+  // legacy_event.phase() once it is no longer used by producers.
+  int32_t phase = 0;
+  if (legacy_event.has_phase()) {
+    phase = legacy_event.phase();
+
+    switch (phase) {
+      case 'b':
+      case 'e':
+      case 'n': {
+        // Intern tracks for legacy async events based on legacy event ids.
+        base::Optional<UniquePid> event_upid;
+        int64_t source_id = 0;
+        if (legacy_event.has_unscoped_id()) {
+          source_id = static_cast<int64_t>(legacy_event.unscoped_id());
+        } else if (legacy_event.has_global_id()) {
+          source_id = static_cast<int64_t>(legacy_event.global_id());
+        } else if (legacy_event.has_local_id()) {
+          if (!upid) {
+            storage->IncrementStats(stats::track_event_parser_errors);
+            PERFETTO_DLOG(
+                "TrackEvent with local_id without process association");
+            return;
+          }
+
+          source_id = static_cast<int64_t>(legacy_event.local_id());
+          event_upid = upid;
+        } else {
+          storage->IncrementStats(stats::track_event_parser_errors);
+          PERFETTO_DLOG("Async LegacyEvent without ID");
+          return;
+        }
+
+        StringId id_scope = 0;
+        if (legacy_event.has_id_scope()) {
+          id_scope = storage->InternString(legacy_event.id_scope());
+        }
+
+        track_id = context_->track_tracker->InternLegacyChromeAsyncTrack(
+            name_id, event_upid, source_id, id_scope);
+        break;
+      }
+      case 'i':
+      case 'I': {
+        // Intern tracks for global or process-scoped legacy instant events.
+        switch (legacy_event.instant_event_scope()) {
+          case LegacyEvent::SCOPE_UNSPECIFIED:
+          case LegacyEvent::SCOPE_THREAD:
+            // Thread-scoped legacy instant events already have the right track
+            // based on the tid/pid of the sequence.
+            if (!utid) {
+              storage->IncrementStats(stats::track_event_parser_errors);
+              PERFETTO_DLOG(
+                  "Thread-scoped instant event without thread association");
+              return;
+            }
+            break;
+          case LegacyEvent::SCOPE_GLOBAL:
+            track_id = context_->track_tracker
+                           ->GetOrCreateLegacyChromeGlobalInstantTrack();
+            break;
+          case LegacyEvent::SCOPE_PROCESS:
+            if (!upid) {
+              storage->IncrementStats(stats::track_event_parser_errors);
+              PERFETTO_DLOG(
+                  "Process-scoped instant event without process association");
+              return;
+            }
+
+            track_id =
+                context_->track_tracker->InternLegacyChromeProcessInstantTrack(
+                    *upid);
+            break;
+        }
+        break;
+      }
+      default:
+        break;
+    }
+  } else {
+    switch (event.type()) {
+      case protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN:
+        phase = utid ? 'B' : 'b';
+        break;
+      case protos::pbzero::TrackEvent::TYPE_SLICE_END:
+        phase = utid ? 'E' : 'e';
+        break;
+      case protos::pbzero::TrackEvent::TYPE_INSTANT:
+        phase = utid ? 'i' : 'n';
+        break;
+      default:
+        PERFETTO_FATAL("unexpected event type %d", event.type());
+        return;
+    }
+  }
+
   auto args_callback = [this, &event, &sequence_state, ts, utid](
                            ArgsTracker* args_tracker, RowId row_id) {
     for (auto it = event.debug_annotations(); it; ++it) {
@@ -1719,47 +1885,17 @@
     }
   };
 
-  using LegacyEvent = protos::pbzero::TrackEvent::LegacyEvent;
-
-  tables::ChromeAsyncTrackTable::Row track(name_id);
-  if (legacy_event.has_unscoped_id()) {
-    track.async_id = static_cast<int64_t>(legacy_event.unscoped_id());
-  } else if (legacy_event.has_global_id()) {
-    track.async_id = static_cast<int64_t>(legacy_event.global_id());
-  } else if (legacy_event.has_local_id()) {
-    track.upid = procs->GetOrCreateProcess(pid);
-    track.async_id = static_cast<int64_t>(legacy_event.local_id());
-  }
-
-  StringId id_scope = 0;
-  if (legacy_event.has_id_scope()) {
-    id_scope = storage->InternString(legacy_event.id_scope());
-  }
-
-  int32_t phase = 0;
-  if (legacy_event.has_phase()) {
-    phase = legacy_event.phase();
-  } else {
-    switch (event.type()) {
-      case protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN:
-        phase = 'B';
-        break;
-      case protos::pbzero::TrackEvent::TYPE_SLICE_END:
-        phase = 'E';
-        break;
-      case protos::pbzero::TrackEvent::TYPE_INSTANT:
-        phase = 'i';
-        break;
-      default:
-        PERFETTO_FATAL("unexpected event type %d", event.type());
-        return;
-    }
-  }
-
   switch (static_cast<char>(phase)) {
     case 'B': {  // TRACE_EVENT_PHASE_BEGIN.
-      auto opt_slice_id = slice_tracker->Begin(
-          ts, utid, RefType::kRefUtid, category_id, name_id, args_callback);
+      if (!utid) {
+        storage->IncrementStats(stats::track_event_parser_errors);
+        PERFETTO_DLOG("TrackEvent with phase B without thread association");
+        return;
+      }
+
+      auto opt_slice_id =
+          slice_tracker->Begin(ts, track_id, *utid, RefType::kRefUtid,
+                               category_id, name_id, args_callback);
       if (opt_slice_id.has_value()) {
         auto* thread_slices = storage->mutable_thread_slices();
         PERFETTO_DCHECK(!thread_slices->slice_count() ||
@@ -1772,8 +1908,14 @@
       break;
     }
     case 'E': {  // TRACE_EVENT_PHASE_END.
-      auto opt_slice_id = slice_tracker->End(
-          ts, utid, RefType::kRefUtid, category_id, name_id, args_callback);
+      if (!utid) {
+        storage->IncrementStats(stats::track_event_parser_errors);
+        PERFETTO_DLOG("TrackEvent with phase E without thread association");
+        return;
+      }
+
+      auto opt_slice_id =
+          slice_tracker->End(ts, track_id, category_id, name_id, args_callback);
       if (opt_slice_id.has_value()) {
         auto* thread_slices = storage->mutable_thread_slices();
         thread_slices->UpdateThreadDeltasForSliceId(opt_slice_id.value(), tts,
@@ -1782,12 +1924,18 @@
       break;
     }
     case 'X': {  // TRACE_EVENT_PHASE_COMPLETE.
+      if (!utid) {
+        storage->IncrementStats(stats::track_event_parser_errors);
+        PERFETTO_DLOG("TrackEvent with phase X without thread association");
+        return;
+      }
+
       auto duration_ns = legacy_event.duration_us() * 1000;
       if (duration_ns < 0)
         return;
-      auto opt_slice_id =
-          slice_tracker->Scoped(ts, utid, RefType::kRefUtid, category_id,
-                                name_id, duration_ns, args_callback);
+      auto opt_slice_id = slice_tracker->Scoped(
+          ts, track_id, *utid, RefType::kRefUtid, category_id, name_id,
+          duration_ns, args_callback);
       if (opt_slice_id.has_value()) {
         auto* thread_slices = storage->mutable_thread_slices();
         PERFETTO_DCHECK(!thread_slices->slice_count() ||
@@ -1810,9 +1958,10 @@
       switch (legacy_event.instant_event_scope()) {
         case LegacyEvent::SCOPE_UNSPECIFIED:
         case LegacyEvent::SCOPE_THREAD: {
-          auto opt_slice_id =
-              slice_tracker->Scoped(ts, utid, RefType::kRefUtid, category_id,
-                                    name_id, duration_ns, args_callback);
+          // TODO(lalitm): Associate thread slices with track instead.
+          auto opt_slice_id = slice_tracker->Scoped(
+              ts, track_id, *utid, RefType::kRefUtid, category_id, name_id,
+              duration_ns, args_callback);
           if (opt_slice_id.has_value()) {
             auto* thread_slices = storage->mutable_thread_slices();
             PERFETTO_DCHECK(!thread_slices->slice_count() ||
@@ -1824,14 +1973,15 @@
           break;
         }
         case LegacyEvent::SCOPE_GLOBAL: {
-          slice_tracker->Scoped(ts, /*ref=*/0, RefType::kRefNoRef, category_id,
-                                name_id, duration_ns, args_callback);
+          slice_tracker->Scoped(ts, track_id, /*ref=*/0, RefType::kRefNoRef,
+                                category_id, name_id, duration_ns,
+                                args_callback);
           break;
         }
         case LegacyEvent::SCOPE_PROCESS: {
-          slice_tracker->Scoped(ts, procs->GetOrCreateProcess(pid),
-                                RefType::kRefUpid, category_id, name_id,
-                                duration_ns, args_callback);
+          slice_tracker->Scoped(ts, track_id, *upid, RefType::kRefUpid,
+                                category_id, name_id, duration_ns,
+                                args_callback);
           break;
         }
         default: {
@@ -1843,11 +1993,9 @@
       break;
     }
     case 'b': {  // TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN
-      TrackId track_id =
-          context_->track_tracker->InternChromeAsyncTrack(track, id_scope);
       auto opt_slice_id =
-          slice_tracker->Begin(ts, track_id, RefType::kRefTrack, category_id,
-                               name_id, args_callback);
+          slice_tracker->Begin(ts, track_id, track_id, RefType::kRefTrack,
+                               category_id, name_id, args_callback);
       // For the time beeing, we only create vtrack slice rows if we need to
       // store thread timestamps/counters.
       if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
@@ -1862,11 +2010,8 @@
       break;
     }
     case 'e': {  // TRACE_EVENT_PHASE_NESTABLE_ASYNC_END
-      TrackId track_id =
-          context_->track_tracker->InternChromeAsyncTrack(track, id_scope);
       auto opt_slice_id =
-          slice_tracker->End(ts, track_id, RefType::kRefTrack, category_id,
-                             name_id, args_callback);
+          slice_tracker->End(ts, track_id, category_id, name_id, args_callback);
       if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
         auto* vtrack_slices = storage->mutable_virtual_track_slices();
         vtrack_slices->UpdateThreadDeltasForSliceId(opt_slice_id.value(), tts,
@@ -1879,11 +2024,9 @@
       // nested underneath their parent slices.
       int64_t duration_ns = 0;
       int64_t tidelta = 0;
-      TrackId track_id =
-          context_->track_tracker->InternChromeAsyncTrack(track, id_scope);
-      auto opt_slice_id =
-          slice_tracker->Scoped(ts, track_id, RefType::kRefTrack, category_id,
-                                name_id, duration_ns, args_callback);
+      auto opt_slice_id = slice_tracker->Scoped(
+          ts, track_id, track_id, RefType::kRefTrack, category_id, name_id,
+          duration_ns, args_callback);
       if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
         auto* vtrack_slices = storage->mutable_virtual_track_slices();
         PERFETTO_DCHECK(!vtrack_slices->slice_count() ||
@@ -1900,6 +2043,13 @@
       NullTermStringView event_name = storage->GetString(name_id);
       PERFETTO_DCHECK(event_name.data());
       if (strcmp(event_name.c_str(), "thread_name") == 0) {
+        if (!utid) {
+          storage->IncrementStats(stats::track_event_parser_errors);
+          PERFETTO_DLOG(
+              "thread_name metadata event without thread association");
+          return;
+        }
+
         auto it = event.debug_annotations();
         if (!it)
           break;
@@ -1908,11 +2058,18 @@
         auto thread_name = annotation.string_value();
         if (!thread_name.size)
           break;
-        auto thread_name_id = context_->storage->InternString(thread_name);
-        procs->UpdateThreadName(tid, thread_name_id);
+        auto thread_name_id = storage->InternString(thread_name);
+        procs->UpdateThreadName(storage->GetThread(*utid).tid, thread_name_id);
         break;
       }
       if (strcmp(event_name.c_str(), "process_name") == 0) {
+        if (!upid) {
+          storage->IncrementStats(stats::track_event_parser_errors);
+          PERFETTO_DLOG(
+              "process_name metadata event without process association");
+          return;
+        }
+
         auto it = event.debug_annotations();
         if (!it)
           break;
@@ -1921,7 +2078,8 @@
         auto process_name = annotation.string_value();
         if (!process_name.size)
           break;
-        procs->SetProcessMetadata(pid, base::nullopt, process_name);
+        procs->SetProcessMetadata(storage->GetProcess(*upid).pid, base::nullopt,
+                                  process_name);
         break;
       }
       // Other metadata events are proxied via the raw table for JSON export.
@@ -1941,15 +2099,21 @@
     int64_t ts,
     int64_t tts,
     int64_t ticount,
-    UniqueTid utid,
+    base::Optional<UniqueTid> utid,
     StringId category_id,
     StringId name_id,
     const protos::pbzero::TrackEvent::LegacyEvent::Decoder& legacy_event,
     SliceTracker::SetArgsCallback args_callback) {
   using LegacyEvent = protos::pbzero::TrackEvent::LegacyEvent;
 
+  if (!utid) {
+    context_->storage->IncrementStats(stats::track_event_parser_errors);
+    PERFETTO_DLOG("raw legacy event without thread association");
+    return;
+  }
+
   RowId row_id = context_->storage->mutable_raw_events()->AddRawEvent(
-      ts, raw_legacy_event_id_, 0, utid);
+      ts, raw_legacy_event_id_, 0, *utid);
   ArgsTracker args(context_);
   args.AddArg(row_id, legacy_event_category_key_id_,
               legacy_event_category_key_id_, Variadic::String(category_id));
@@ -2080,7 +2244,7 @@
     auto name_view_it = map->find(name_iid);
     if (name_view_it == map->end()) {
       context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-      PERFETTO_ELOG(
+      PERFETTO_DLOG(
           "Could not find debug annotation name interning entry for ID "
           "%" PRIu64,
           name_iid);
@@ -2106,7 +2270,7 @@
     name_id = storage->InternString(annotation.name());
   } else {
     context_->storage->IncrementStats(stats::track_event_parser_errors);
-    PERFETTO_ELOG("Debug annotation without name");
+    PERFETTO_DLOG("Debug annotation without name");
     return;
   }
 
@@ -2213,7 +2377,7 @@
   auto location_view_it = map->find(iid);
   if (location_view_it == map->end()) {
     context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-    PERFETTO_ELOG(
+    PERFETTO_DLOG(
         "Could not find source location interning entry for ID %" PRIu64, iid);
     return;
   }
@@ -2256,9 +2420,15 @@
     ConstBytes blob,
     ProtoIncrementalState::PacketSequenceState* sequence_state,
     int64_t ts,
-    uint32_t utid,
+    base::Optional<UniqueTid> utid,
     ArgsTracker* args_tracker,
     RowId row) {
+  if (!utid) {
+    context_->storage->IncrementStats(stats::track_event_parser_errors);
+    PERFETTO_DLOG("LogMessage without thread association");
+    return;
+  }
+
   protos::pbzero::LogMessage::Decoder message(blob.data, blob.size);
 
   TraceStorage* storage = context_->storage.get();
@@ -2270,7 +2440,7 @@
   auto message_body_entry = map->find(message.body_iid());
   if (message_body_entry == map->end()) {
     context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-    PERFETTO_ELOG(
+    PERFETTO_DLOG(
         "Could not find source location interning entry for ID %" PRIu64,
         message.body_iid());
     return;
@@ -2290,7 +2460,7 @@
   // TODO(nicomazz): LogMessage also contains the source of the message (file
   // and line number). Android logs doesn't support this so far.
   context_->storage->mutable_android_log()->AddLogEvent(
-      ts, utid,
+      ts, *utid,
       /*priority*/ 0,
       /*tag_id*/ 0,  // TODO(nicomazz): Abuse tag_id to display
                      // "file_name:line_number".
@@ -2423,8 +2593,9 @@
       sprintf(fallback, "Event %d", eid);
       name_id = context_->storage->InternString(fallback);
     }
-    context_->slice_tracker->Scoped(ts, utid, RefType::kRefUtid, cat_id,
-                                    name_id, event.event_duration_ns());
+    TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+    context_->slice_tracker->Scoped(ts, track_id, utid, RefType::kRefUtid,
+                                    cat_id, name_id, event.event_duration_ns());
   } else if (event.has_counter_id()) {
     auto cid = event.counter_id();
     if (cid < metatrace::COUNTERS_MAX) {
diff --git a/src/trace_processor/proto_trace_parser.h b/src/trace_processor/proto_trace_parser.h
index c613b11..31ecd38 100644
--- a/src/trace_processor/proto_trace_parser.h
+++ b/src/trace_processor/proto_trace_parser.h
@@ -22,6 +22,7 @@
 #include <array>
 #include <memory>
 
+#include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/protozero/field.h"
 #include "src/trace_processor/ftrace_descriptors.h"
@@ -109,7 +110,7 @@
       int64_t ts,
       int64_t tts,
       int64_t ticount,
-      UniqueTid utid,
+      base::Optional<UniqueTid> utid,
       StringId category_id,
       StringId name_id,
       const protos::pbzero::TrackEvent::LegacyEvent::Decoder& legacy_event,
@@ -136,7 +137,7 @@
   void ParseLogMessage(ConstBytes,
                        ProtoIncrementalState::PacketSequenceState*,
                        int64_t,
-                       uint32_t,
+                       base::Optional<UniqueTid>,
                        ArgsTracker*,
                        RowId);
   void ParseModuleSymbols(ConstBytes);
diff --git a/src/trace_processor/proto_trace_parser_unittest.cc b/src/trace_processor/proto_trace_parser_unittest.cc
index 88ad474..70552cf 100644
--- a/src/trace_processor/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/proto_trace_parser_unittest.cc
@@ -50,6 +50,7 @@
 #include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
 #include "protos/perfetto/trace/track_event/task_execution.pbzero.h"
 #include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
 namespace perfetto {
@@ -142,10 +143,19 @@
     ON_CALL(*this, GetString(_)).WillByDefault(Invoke([this](StringId id) {
       return TraceStorage::GetString(id);
     }));
+
+    ON_CALL(*this, GetThread(_))
+        .WillByDefault(Invoke(this, &MockTraceStorage::GetThreadImpl));
+  }
+
+  const Thread& GetThreadImpl(UniqueTid utid) {
+    return TraceStorage::GetThread(utid);
   }
 
   MOCK_METHOD1(InternString, StringId(base::StringView));
   MOCK_CONST_METHOD1(GetString, NullTermStringView(StringId));
+
+  MOCK_CONST_METHOD1(GetThread, const Thread&(UniqueTid));
 };
 
 class MockArgsTracker : public ArgsTracker {
@@ -161,22 +171,23 @@
  public:
   MockSliceTracker(TraceProcessorContext* context) : SliceTracker(context) {}
 
-  MOCK_METHOD6(Begin,
+  MOCK_METHOD7(Begin,
                base::Optional<uint32_t>(int64_t timestamp,
+                                        TrackId track_id,
                                         int64_t ref,
                                         RefType ref_type,
                                         StringId cat,
                                         StringId name,
                                         SetArgsCallback args_callback));
-  MOCK_METHOD6(End,
+  MOCK_METHOD5(End,
                base::Optional<uint32_t>(int64_t timestamp,
-                                        int64_t ref,
-                                        RefType ref_type,
+                                        TrackId track_id,
                                         StringId cat,
                                         StringId name,
                                         SetArgsCallback args_callback));
-  MOCK_METHOD7(Scoped,
+  MOCK_METHOD8(Scoped,
                base::Optional<uint32_t>(int64_t timestamp,
+                                        TrackId track_id,
                                         int64_t ref,
                                         RefType ref_type,
                                         StringId cat,
@@ -712,25 +723,31 @@
       .Times(3)
       .WillRepeatedly(Return(1));
 
+  TraceStorage::Thread thread(16);
+  thread.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(1))
+      .Times(3)
+      .WillRepeatedly(testing::ReturnRef(thread));
+
   MockArgsTracker args(&context_);
 
+  constexpr TrackId track = 0u;
   InSequence in_sequence;  // Below slices should be sorted by timestamp.
-  EXPECT_CALL(*slice_, Scoped(1005000, 1, RefType::kRefUtid, kNullStringId,
-                              kNullStringId, 23000, _))
+  EXPECT_CALL(*slice_, Scoped(1005000, track, 1, RefType::kRefUtid,
+                              kNullStringId, kNullStringId, 23000, _))
       .WillOnce(DoAll(
-          InvokeArgument<6>(
+          InvokeArgument<7>(
               &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 0u)),
           Return(0u)));
-  EXPECT_CALL(*slice_, Begin(1010000, 1, RefType::kRefUtid, kNullStringId,
-                             kNullStringId, _))
+  EXPECT_CALL(*slice_, Begin(1010000, track, 1, RefType::kRefUtid,
+                             kNullStringId, kNullStringId, _))
       .WillOnce(DoAll(
-          InvokeArgument<5>(
+          InvokeArgument<6>(
               &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
           Return(1u)));
-  EXPECT_CALL(*slice_, End(1020000, 1, RefType::kRefUtid, kNullStringId,
-                           kNullStringId, _))
+  EXPECT_CALL(*slice_, End(1020000, track, kNullStringId, kNullStringId, _))
       .WillOnce(DoAll(
-          InvokeArgument<5>(
+          InvokeArgument<4>(
               &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
           Return(1u)));
 
@@ -799,25 +816,31 @@
       .Times(3)
       .WillRepeatedly(Return(1));
 
+  TraceStorage::Thread thread(16);
+  thread.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(1))
+      .Times(3)
+      .WillRepeatedly(testing::ReturnRef(thread));
+
   MockArgsTracker args(&context_);
 
+  constexpr TrackId track = 0u;
   InSequence in_sequence;  // Below slices should be sorted by timestamp.
-  EXPECT_CALL(*slice_, Begin(1010000, 1, RefType::kRefUtid, kNullStringId,
-                             kNullStringId, _))
-      .WillOnce(DoAll(
-          InvokeArgument<5>(
-              &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 0u)),
-          Return(0u)));
-  EXPECT_CALL(*slice_, Scoped(1015000, 1, RefType::kRefUtid, kNullStringId,
-                              kNullStringId, 0, _))
+  EXPECT_CALL(*slice_, Begin(1010000, track, 1, RefType::kRefUtid,
+                             kNullStringId, kNullStringId, _))
       .WillOnce(DoAll(
           InvokeArgument<6>(
+              &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 0u)),
+          Return(0u)));
+  EXPECT_CALL(*slice_, Scoped(1015000, track, 1, RefType::kRefUtid,
+                              kNullStringId, kNullStringId, 0, _))
+      .WillOnce(DoAll(
+          InvokeArgument<7>(
               &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
           Return(1u)));
-  EXPECT_CALL(*slice_, End(1020000, 1, RefType::kRefUtid, kNullStringId,
-                           kNullStringId, _))
+  EXPECT_CALL(*slice_, End(1020000, track, kNullStringId, kNullStringId, _))
       .WillOnce(DoAll(
-          InvokeArgument<5>(
+          InvokeArgument<4>(
               &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 0u)),
           Return(0u)));
 
@@ -937,20 +960,27 @@
       .Times(5)
       .WillRepeatedly(Return(1));
 
-  EXPECT_CALL(*process_, GetOrCreateProcess(15)).WillOnce(Return(2));
+  TraceStorage::Thread thread(16);
+  thread.upid = 2u;
+  EXPECT_CALL(*storage_, GetThread(1))
+      .Times(5)
+      .WillRepeatedly(testing::ReturnRef(thread));
 
   MockArgsTracker args(&context_);
 
+  constexpr TrackId thread_1_track = 0u;
+  constexpr TrackId process_2_track = 1u;
+
   InSequence in_sequence;  // Below slices should be sorted by timestamp.
 
   EXPECT_CALL(*storage_, InternString(base::StringView("cat2,cat3")))
       .WillOnce(Return(1));
   EXPECT_CALL(*storage_, InternString(base::StringView("ev2")))
       .WillOnce(Return(2));
-  EXPECT_CALL(*slice_, Scoped(1005000, 1, RefType::kRefUtid, StringId(1),
-                              StringId(2), 23000, _))
+  EXPECT_CALL(*slice_, Scoped(1005000, thread_1_track, 1, RefType::kRefUtid,
+                              StringId(1), StringId(2), 23000, _))
       .WillOnce(DoAll(
-          InvokeArgument<6>(
+          InvokeArgument<7>(
               &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 0u)),
           Return(0u)));
 
@@ -958,31 +988,31 @@
       .WillOnce(Return(3));
   EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
       .WillOnce(Return(4));
-  EXPECT_CALL(*slice_,
-              Begin(1010000, 1, RefType::kRefUtid, StringId(3), StringId(4), _))
-      .WillOnce(DoAll(
-          InvokeArgument<5>(
-              &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
-          Return(1u)));
-
-  EXPECT_CALL(*slice_,
-              End(1020000, 1, RefType::kRefUtid, StringId(3), StringId(4), _))
-      .WillOnce(DoAll(
-          InvokeArgument<5>(
-              &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
-          Return(1u)));
-
-  EXPECT_CALL(*slice_, Scoped(1040000, 1, RefType::kRefUtid, StringId(3),
-                              StringId(4), 0, _))
+  EXPECT_CALL(*slice_, Begin(1010000, thread_1_track, 1, RefType::kRefUtid,
+                             StringId(3), StringId(4), _))
       .WillOnce(DoAll(
           InvokeArgument<6>(
+              &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
+          Return(1u)));
+
+  EXPECT_CALL(*slice_,
+              End(1020000, thread_1_track, StringId(3), StringId(4), _))
+      .WillOnce(DoAll(
+          InvokeArgument<4>(
+              &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
+          Return(1u)));
+
+  EXPECT_CALL(*slice_, Scoped(1040000, thread_1_track, 1, RefType::kRefUtid,
+                              StringId(3), StringId(4), 0, _))
+      .WillOnce(DoAll(
+          InvokeArgument<7>(
               &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 2u)),
           Return(2u)));
 
-  EXPECT_CALL(*slice_, Scoped(1050000, 2, RefType::kRefUpid, StringId(3),
-                              StringId(4), 0, _))
+  EXPECT_CALL(*slice_, Scoped(1050000, process_2_track, 2, RefType::kRefUpid,
+                              StringId(3), StringId(4), 0, _))
       .WillOnce(DoAll(
-          InvokeArgument<6>(
+          InvokeArgument<7>(
               &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 3u)),
           Return(3u)));
 
@@ -1094,7 +1124,12 @@
   EXPECT_CALL(*process_, UpdateThread(16, 15))
       .Times(4)
       .WillRepeatedly(Return(1));
-  EXPECT_CALL(*process_, GetOrCreateProcess(15)).WillOnce(Return(1));
+
+  TraceStorage::Thread thread(16);
+  thread.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(1))
+      .Times(4)
+      .WillRepeatedly(testing::ReturnRef(thread));
 
   InSequence in_sequence;  // Below slices should be sorted by timestamp.
 
@@ -1102,7 +1137,7 @@
       .WillOnce(Return(1));
   EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
       .WillOnce(Return(2));
-  EXPECT_CALL(*slice_, Begin(1010000, 0, RefType::kRefTrack, StringId(1),
+  EXPECT_CALL(*slice_, Begin(1010000, 1, 1, RefType::kRefTrack, StringId(1),
                              StringId(2), _))
       .WillOnce(Return(0u));
 
@@ -1110,25 +1145,26 @@
       .WillOnce(Return(3));
   EXPECT_CALL(*storage_, InternString(base::StringView("ev2")))
       .WillOnce(Return(4));
-  EXPECT_CALL(*slice_, Scoped(1015000, 0, RefType::kRefTrack, StringId(3),
+  EXPECT_CALL(*slice_, Scoped(1015000, 1, 1, RefType::kRefTrack, StringId(3),
                               StringId(4), 0, _));
 
-  EXPECT_CALL(*slice_,
-              End(1020000, 0, RefType::kRefTrack, StringId(1), StringId(2), _))
+  EXPECT_CALL(*slice_, End(1020000, 1, StringId(1), StringId(2), _))
       .WillOnce(Return(0u));
 
   EXPECT_CALL(*storage_, InternString(base::StringView("scope1")))
       .WillOnce(Return(5));
-  EXPECT_CALL(*slice_, Scoped(1030000, 1, RefType::kRefTrack, StringId(3),
+  EXPECT_CALL(*slice_, Scoped(1030000, 2, 2, RefType::kRefTrack, StringId(3),
                               StringId(4), 0, _));
 
   context_.sorter->ExtractEventsForced();
 
-  EXPECT_EQ(storage_->chrome_async_track_table().size(), 2u);
-  EXPECT_EQ(storage_->chrome_async_track_table().name()[0], 2u);
-  EXPECT_EQ(storage_->chrome_async_track_table().name()[1], 4u);
-  EXPECT_EQ(storage_->chrome_async_track_table().upid()[0], base::nullopt);
-  EXPECT_EQ(storage_->chrome_async_track_table().upid()[1], 1u);
+  // First track is for the thread; second and third are the async event tracks.
+  EXPECT_EQ(storage_->track_table().size(), 3u);
+  EXPECT_EQ(storage_->track_table().name()[1], 2u);
+  EXPECT_EQ(storage_->track_table().name()[2], 4u);
+
+  EXPECT_EQ(storage_->process_track_table().size(), 1u);
+  EXPECT_EQ(storage_->process_track_table().upid()[0], 1u);
 
   EXPECT_EQ(storage_->virtual_track_slices().slice_count(), 1u);
   EXPECT_EQ(storage_->virtual_track_slices().slice_ids()[0], 0u);
@@ -1140,6 +1176,216 @@
             20);
 }
 
+// TODO(eseckler): Also test instant events on separate tracks.
+TEST_F(ProtoTraceParserTest, TrackEventWithTrackDescriptors) {
+  context_.sorter.reset(new TraceSorter(
+      &context_, std::numeric_limits<int64_t>::max() /*window size*/));
+
+  // Sequence 1.
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    packet->set_incremental_state_cleared(true);
+    auto* track_desc = packet->set_track_descriptor();
+    track_desc->set_uuid(1234);
+    track_desc->set_name("Thread track 1");
+    auto* thread_desc = track_desc->set_thread();
+    thread_desc->set_pid(15);
+    thread_desc->set_tid(16);
+  }
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    auto* track_desc = packet->set_track_descriptor();
+    track_desc->set_uuid(5678);
+    track_desc->set_name("Async track 1");
+  }
+  {
+    // Async event started on "Async track 1".
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    packet->set_timestamp(1010000);
+    auto* event = packet->set_track_event();
+    event->set_track_uuid(5678);
+    event->set_thread_time_absolute_us(2005);
+    event->set_thread_instruction_count_absolute(3020);
+    event->add_category_iids(1);
+    event->set_name_iid(1);
+    event->set_type(protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN);
+    auto* legacy_event = event->set_legacy_event();
+    legacy_event->set_use_async_tts(true);
+
+    auto* interned_data = packet->set_interned_data();
+    auto cat1 = interned_data->add_event_categories();
+    cat1->set_iid(1);
+    cat1->set_name("cat1");
+    auto ev1 = interned_data->add_event_names();
+    ev1->set_iid(1);
+    ev1->set_name("ev1");
+  }
+  {
+    // Instant event on "Thread track 1".
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    packet->set_timestamp(1015000);
+    auto* event = packet->set_track_event();
+    event->set_track_uuid(1234);
+    event->set_thread_time_absolute_us(2007);
+    event->add_category_iids(2);
+    event->set_name_iid(2);
+    event->set_type(protos::pbzero::TrackEvent::TYPE_INSTANT);
+
+    auto* interned_data = packet->set_interned_data();
+    auto cat1 = interned_data->add_event_categories();
+    cat1->set_iid(2);
+    cat1->set_name("cat2");
+    auto ev1 = interned_data->add_event_names();
+    ev1->set_iid(2);
+    ev1->set_name("ev2");
+  }
+
+  // Sequence 2.
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(2);
+    packet->set_incremental_state_cleared(true);
+    auto* track_desc = packet->set_track_descriptor();
+    track_desc->set_uuid(4321);
+    track_desc->set_name("Thread track 2");
+    auto* thread_desc = track_desc->set_thread();
+    thread_desc->set_pid(15);
+    thread_desc->set_tid(17);
+  }
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(2);
+    auto* track_desc = packet->set_track_descriptor();
+    track_desc->set_uuid(5678);  // "Async track 1" defined on sequence 1.
+  }
+  {
+    // Async event completed on "Async track 1".
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(2);
+    packet->set_timestamp(1020000);
+    auto* event = packet->set_track_event();
+    event->set_track_uuid(5678);
+    event->set_thread_time_absolute_us(2010);
+    event->set_thread_instruction_count_absolute(3040);
+    event->set_type(protos::pbzero::TrackEvent::TYPE_SLICE_END);
+    auto* legacy_event = event->set_legacy_event();
+    legacy_event->set_use_async_tts(true);
+  }
+  {
+    // Instant event on "Thread track 2".
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(2);
+    packet->set_timestamp(1016000);
+    auto* event = packet->set_track_event();
+    event->set_track_uuid(4321);
+    event->set_thread_time_absolute_us(2008);
+    event->add_category_iids(1);
+    event->set_name_iid(1);
+    event->set_type(protos::pbzero::TrackEvent::TYPE_INSTANT);
+
+    auto* interned_data = packet->set_interned_data();
+    auto cat1 = interned_data->add_event_categories();
+    cat1->set_iid(1);
+    cat1->set_name("cat3");
+    auto ev1 = interned_data->add_event_names();
+    ev1->set_iid(1);
+    ev1->set_name("ev3");
+  }
+
+  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillRepeatedly(Return(1));
+  EXPECT_CALL(*process_, UpdateThread(17, 15)).WillRepeatedly(Return(2));
+
+  TraceStorage::Thread thread1(16);
+  thread1.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(1))
+      .WillRepeatedly(testing::ReturnRef(thread1));
+  TraceStorage::Thread thread2(16);
+  thread2.upid = 2u;
+  EXPECT_CALL(*storage_, GetThread(2))
+      .WillRepeatedly(testing::ReturnRef(thread2));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("Thread track 1")))
+      .WillOnce(Return(10));
+  EXPECT_CALL(*storage_, InternString(base::StringView("Async track 1")))
+      .WillOnce(Return(11));
+  EXPECT_CALL(*storage_, InternString(base::StringView("Thread track 2")))
+      .WillOnce(Return(12));
+  EXPECT_CALL(*storage_, InternString(base::StringView("")))
+      .WillOnce(Return(0));
+
+  Tokenize();
+
+  // 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().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().utid()[0], 1u);
+  EXPECT_EQ(storage_->thread_track_table().utid()[1], 2u);
+
+  InSequence in_sequence;  // Below slices should be sorted by timestamp.
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
+      .WillOnce(Return(1));
+  EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
+      .WillOnce(Return(2));
+  EXPECT_CALL(*slice_, Begin(1010000, 1, 1, RefType::kRefTrack, StringId(1),
+                             StringId(2), _))
+      .WillOnce(Return(0u));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("cat2")))
+      .WillOnce(Return(3));
+  EXPECT_CALL(*storage_, InternString(base::StringView("ev2")))
+      .WillOnce(Return(4));
+  EXPECT_CALL(*slice_, Scoped(1015000, 0, 1, RefType::kRefUtid, StringId(3),
+                              StringId(4), 0, _))
+      .WillOnce(Return(1u));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("cat3")))
+      .WillOnce(Return(5));
+  EXPECT_CALL(*storage_, InternString(base::StringView("ev3")))
+      .WillOnce(Return(6));
+  EXPECT_CALL(*slice_, Scoped(1016000, 2, 2, RefType::kRefUtid, StringId(5),
+                              StringId(6), 0, _))
+      .WillOnce(Return(2u));
+
+  EXPECT_CALL(*slice_, End(1020000, 1, StringId(0), StringId(0), _))
+      .WillOnce(Return(0u));
+
+  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_->virtual_track_slices().slice_count(), 1u);
+  EXPECT_EQ(storage_->virtual_track_slices().slice_ids()[0], 0u);
+  EXPECT_EQ(storage_->virtual_track_slices().thread_timestamp_ns()[0], 2005000);
+  EXPECT_EQ(storage_->virtual_track_slices().thread_duration_ns()[0], 5000);
+  EXPECT_EQ(storage_->virtual_track_slices().thread_instruction_counts()[0],
+            3020);
+  EXPECT_EQ(storage_->virtual_track_slices().thread_instruction_deltas()[0],
+            20);
+
+  EXPECT_EQ(storage_->thread_slices().slice_count(), 2u);
+  EXPECT_EQ(storage_->thread_slices().slice_ids()[0], 1u);
+  EXPECT_EQ(storage_->thread_slices().thread_timestamp_ns()[0], 2007000);
+  EXPECT_EQ(storage_->thread_slices().thread_duration_ns()[0], 0);
+  EXPECT_EQ(storage_->thread_slices().thread_instruction_counts()[0], 0);
+  EXPECT_EQ(storage_->thread_slices().thread_instruction_deltas()[0], 0);
+  EXPECT_EQ(storage_->thread_slices().slice_ids()[1], 2u);
+  EXPECT_EQ(storage_->thread_slices().thread_timestamp_ns()[1], 2008000);
+  EXPECT_EQ(storage_->thread_slices().thread_duration_ns()[1], 0);
+  EXPECT_EQ(storage_->thread_slices().thread_instruction_counts()[1], 0);
+  EXPECT_EQ(storage_->thread_slices().thread_instruction_deltas()[1], 0);
+}
+
 TEST_F(ProtoTraceParserTest, TrackEventWithoutIncrementalStateReset) {
   context_.sorter.reset(new TraceSorter(
       &context_, std::numeric_limits<int64_t>::max() /*window size*/));
@@ -1168,7 +1414,7 @@
 
   Tokenize();
 
-  EXPECT_CALL(*slice_, Begin(_, _, _, _, _, _)).Times(0);
+  EXPECT_CALL(*slice_, Begin(_, _, _, _, _, _, _)).Times(0);
   context_.sorter->ExtractEventsForced();
 }
 
@@ -1192,7 +1438,7 @@
 
   Tokenize();
 
-  EXPECT_CALL(*slice_, Begin(_, _, _, _, _, _)).Times(0);
+  EXPECT_CALL(*slice_, Begin(_, _, _, _, _, _, _)).Times(0);
   context_.sorter->ExtractEventsForced();
 }
 
@@ -1286,11 +1532,17 @@
       .Times(2)
       .WillRepeatedly(Return(1));
 
+  TraceStorage::Thread thread(16);
+  thread.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(1))
+      .Times(2)
+      .WillRepeatedly(testing::ReturnRef(thread));
+
+  constexpr TrackId track = 0u;
   InSequence in_sequence;  // Below slices should be sorted by timestamp.
-  EXPECT_CALL(*slice_, Begin(1010000, 1, RefType::kRefUtid, kNullStringId,
-                             kNullStringId, _));
-  EXPECT_CALL(*slice_, End(2010000, 1, RefType::kRefUtid, kNullStringId,
-                           kNullStringId, _));
+  EXPECT_CALL(*slice_, Begin(1010000, track, 1, RefType::kRefUtid,
+                             kNullStringId, kNullStringId, _));
+  EXPECT_CALL(*slice_, End(2010000, track, kNullStringId, kNullStringId, _));
 
   context_.sorter->ExtractEventsForced();
 }
@@ -1389,6 +1641,20 @@
       .Times(2)
       .WillRepeatedly(Return(2));
 
+  TraceStorage::Thread thread(16);
+  thread.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(1))
+      .Times(2)
+      .WillRepeatedly(testing::ReturnRef(thread));
+
+  TraceStorage::Thread thread2(17);
+  thread2.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(2))
+      .Times(2)
+      .WillRepeatedly(testing::ReturnRef(thread2));
+
+  constexpr TrackId thread_2_track = 0u;
+  constexpr TrackId thread_1_track = 1u;
   InSequence in_sequence;  // Below slices should be sorted by timestamp.
 
   EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
@@ -1396,20 +1662,20 @@
   EXPECT_CALL(*storage_, InternString(base::StringView("ev2")))
       .WillOnce(Return(2));
 
-  EXPECT_CALL(*slice_, Begin(1005000, 2, RefType::kRefUtid, StringId(1),
-                             StringId(2), _));
+  EXPECT_CALL(*slice_, Begin(1005000, thread_2_track, 2, RefType::kRefUtid,
+                             StringId(1), StringId(2), _));
 
   EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
       .WillOnce(Return(1));
   EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
       .WillOnce(Return(3));
 
-  EXPECT_CALL(*slice_, Begin(1010000, 1, RefType::kRefUtid, StringId(1),
-                             StringId(3), _));
+  EXPECT_CALL(*slice_, Begin(1010000, thread_1_track, 1, RefType::kRefUtid,
+                             StringId(1), StringId(3), _));
   EXPECT_CALL(*slice_,
-              End(1015000, 2, RefType::kRefUtid, StringId(1), StringId(2), _));
+              End(1015000, thread_2_track, StringId(1), StringId(2), _));
   EXPECT_CALL(*slice_,
-              End(1020000, 1, RefType::kRefUtid, StringId(1), StringId(3), _));
+              End(1020000, thread_1_track, StringId(1), StringId(3), _));
 
   context_.sorter->ExtractEventsForced();
 }
@@ -1538,15 +1804,22 @@
       .Times(2)
       .WillRepeatedly(Return(1));
 
+  TraceStorage::Thread thread(16);
+  thread.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(1))
+      .Times(2)
+      .WillRepeatedly(testing::ReturnRef(thread));
+
+  constexpr TrackId track = 0u;
   InSequence in_sequence;  // Below slices should be sorted by timestamp.
 
   EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
       .WillOnce(Return(1));
   EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
       .WillOnce(Return(2));
-  EXPECT_CALL(*slice_,
-              Begin(1010000, 1, RefType::kRefUtid, StringId(1), StringId(2), _))
-      .WillOnce(DoAll(InvokeArgument<5>(&args, 1u), Return(1u)));
+  EXPECT_CALL(*slice_, Begin(1010000, track, 1, RefType::kRefUtid, StringId(1),
+                             StringId(2), _))
+      .WillOnce(DoAll(InvokeArgument<6>(&args, 1u), Return(1u)));
   EXPECT_CALL(*storage_, InternString(base::StringView("debug.an1")))
       .WillOnce(Return(3));
   EXPECT_CALL(args, AddArg(1u, StringId(3), StringId(3),
@@ -1583,9 +1856,8 @@
   EXPECT_CALL(args,
               AddArg(1u, StringId(6), StringId(10), Variadic::Integer(23)));
 
-  EXPECT_CALL(*slice_,
-              End(1020000, 1, RefType::kRefUtid, StringId(1), StringId(2), _))
-      .WillOnce(DoAll(InvokeArgument<5>(&args, 1u), Return(1u)));
+  EXPECT_CALL(*slice_, End(1020000, track, StringId(1), StringId(2), _))
+      .WillOnce(DoAll(InvokeArgument<4>(&args, 1u), Return(1u)));
 
   EXPECT_CALL(*storage_, InternString(base::StringView("debug.an3")))
       .WillOnce(Return(11));
@@ -1664,15 +1936,20 @@
 
   EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
 
+  TraceStorage::Thread thread(16);
+  thread.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(1)).WillOnce(testing::ReturnRef(thread));
+
+  constexpr TrackId track = 0u;
   InSequence in_sequence;  // Below slices should be sorted by timestamp.
 
   EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
       .WillOnce(Return(1));
   EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
       .WillOnce(Return(2));
-  EXPECT_CALL(*slice_,
-              Begin(1010000, 1, RefType::kRefUtid, StringId(1), StringId(2), _))
-      .WillOnce(DoAll(InvokeArgument<5>(&args, 1u), Return(1u)));
+  EXPECT_CALL(*slice_, Begin(1010000, track, 1, RefType::kRefUtid, StringId(1),
+                             StringId(2), _))
+      .WillOnce(DoAll(InvokeArgument<6>(&args, 1u), Return(1u)));
   EXPECT_CALL(*storage_, InternString(base::StringView("file1")))
       .WillOnce(Return(3));
   EXPECT_CALL(*storage_, InternString(base::StringView("func1")))
@@ -1739,6 +2016,11 @@
 
   EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
 
+  TraceStorage::Thread thread(16);
+  thread.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(1)).WillOnce(testing::ReturnRef(thread));
+
+  constexpr TrackId track = 0;
   InSequence in_sequence;  // Below slices should be sorted by timestamp.
 
   EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
@@ -1746,9 +2028,9 @@
   EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
       .WillOnce(Return(2));
 
-  EXPECT_CALL(*slice_, Scoped(1010000, 1, RefType::kRefUtid, StringId(1),
+  EXPECT_CALL(*slice_, Scoped(1010000, track, 1, RefType::kRefUtid, StringId(1),
                               StringId(2), 0, _))
-      .WillOnce(DoAll(InvokeArgument<6>(&args, 1u), Return(1u)));
+      .WillOnce(DoAll(InvokeArgument<7>(&args, 1u), Return(1u)));
 
   EXPECT_CALL(*storage_, InternString(base::StringView("body1")))
       .WillOnce(Return(3));
@@ -1819,12 +2101,16 @@
 
   EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
 
+  TraceStorage::Thread thread(16);
+  thread.upid = 1u;
+  EXPECT_CALL(*storage_, GetThread(1)).WillOnce(testing::ReturnRef(thread));
+
   EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
       .WillOnce(Return(1));
   EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
       .WillOnce(Return(2));
   EXPECT_CALL(*storage_, InternString(base::StringView("scope1")))
-      .Times(2)
+      .Times(1)
       .WillRepeatedly(Return(3));
   EXPECT_CALL(*storage_, InternString(base::StringView("?")))
       .WillOnce(Return(4));
@@ -1845,7 +2131,7 @@
   EXPECT_EQ(raw_events.utids()[0], 1u);
   EXPECT_EQ(raw_events.arg_set_ids()[0], 1u);
 
-  EXPECT_EQ(storage_->args().args_count(), 13u);
+  EXPECT_GE(storage_->args().args_count(), 13u);
 
   EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.category"),
                      Variadic::String(1u)));
diff --git a/src/trace_processor/proto_trace_tokenizer.cc b/src/trace_processor/proto_trace_tokenizer.cc
index e47ff6b..8366e57 100644
--- a/src/trace_processor/proto_trace_tokenizer.cc
+++ b/src/trace_processor/proto_trace_tokenizer.cc
@@ -28,10 +28,12 @@
 #include "src/trace_processor/clock_tracker.h"
 #include "src/trace_processor/event_tracker.h"
 #include "src/trace_processor/process_tracker.h"
+#include "src/trace_processor/proto_incremental_state.h"
 #include "src/trace_processor/stats.h"
 #include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_sorter.h"
 #include "src/trace_processor/trace_storage.h"
+#include "src/trace_processor/track_tracker.h"
 
 #include "protos/perfetto/config/trace_config.pbzero.h"
 #include "protos/perfetto/trace/clock_snapshot.pbzero.h"
@@ -40,9 +42,11 @@
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/source_location.pbzero.h"
 #include "protos/perfetto/trace/track_event/task_execution.pbzero.h"
 #include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
 namespace perfetto {
@@ -58,7 +62,7 @@
 constexpr uint8_t kTracePacketTag =
     MakeTagLengthDelimited(protos::pbzero::Trace::kPacketFieldNumber);
 
-template <typename MessageType>
+template <typename MessageType, typename FieldName = DefaultFieldName>
 void InternMessage(TraceProcessorContext* context,
                    ProtoIncrementalState::PacketSequenceState* state,
                    TraceBlobView message) {
@@ -77,7 +81,7 @@
   }
   iid = field.as_uint64();
 
-  auto res = state->GetInternedDataMap<MessageType>()->emplace(
+  auto res = state->GetInternedDataMap<MessageType, FieldName>()->emplace(
       iid,
       ProtoIncrementalState::InternedDataView<MessageType>(std::move(message)));
   // If a message with this ID is already interned, its data should not have
@@ -224,12 +228,13 @@
                        ? static_cast<int64_t>(decoder.timestamp())
                        : latest_timestamp_;
 
+  const uint32_t seq_id = decoder.trusted_packet_sequence_id();
+
   // If the TracePacket specifies a non-zero clock-id, translate the timestamp
   // into the trace-time clock domain.
   if (decoder.timestamp_clock_id()) {
     PERFETTO_DCHECK(decoder.has_timestamp());
     ClockTracker::ClockId clock_id = decoder.timestamp_clock_id();
-    const uint32_t seq_id = decoder.trusted_packet_sequence_id();
     bool is_seq_scoped = ClockTracker::IsReservedSeqScopedClockId(clock_id);
     if (is_seq_scoped) {
       if (!seq_id) {
@@ -237,7 +242,7 @@
             "TracePacket specified a sequence-local clock id (%" PRIu32
             ") but the TraceWriter's sequence_id is zero (the service is "
             "probably too old)",
-            seq_id);
+            decoder.timestamp_clock_id());
       }
       clock_id = ClockTracker::SeqScopedClockIdToGlobal(
           seq_id, decoder.timestamp_clock_id());
@@ -260,17 +265,41 @@
   }
   latest_timestamp_ = std::max(timestamp, latest_timestamp_);
 
-  if (decoder.incremental_state_cleared()) {
+  auto* state = GetIncrementalStateForPacketSequence(
+      decoder.trusted_packet_sequence_id());
+
+  uint32_t sequence_flags = decoder.sequence_flags();
+
+  if (decoder.incremental_state_cleared() ||
+      sequence_flags &
+          protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
     HandleIncrementalStateCleared(decoder);
   } else if (decoder.previous_packet_dropped()) {
     HandlePreviousPacketDropped(decoder);
   }
 
+  if (decoder.sequence_flags() &
+      protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE) {
+    if (!seq_id) {
+      return util::ErrStatus(
+          "TracePacket specified SEQ_NEEDS_INCREMENTAL_STATE but the "
+          "TraceWriter's sequence_id is zero (the service is "
+          "probably too old)");
+    }
+
+    if (!state->IsIncrementalStateValid()) {
+      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+      return util::OkStatus();
+    }
+  }
+
   if (decoder.has_clock_snapshot()) {
     return ParseClockSnapshot(decoder.clock_snapshot(),
                               decoder.trusted_packet_sequence_id());
   }
 
+  // TODO(eseckler): Parse TracePacketDefaults.
+
   if (decoder.has_interned_data()) {
     auto field = decoder.interned_data();
     const size_t offset = packet.offset_of(field.data);
@@ -284,11 +313,18 @@
     return util::OkStatus();
   }
 
-  if (decoder.has_track_event()) {
-    ParseTrackEventPacket(decoder, std::move(packet));
+  if (decoder.has_track_descriptor()) {
+    ParseTrackDescriptorPacket(decoder);
     return util::OkStatus();
   }
 
+  if (decoder.has_track_event()) {
+    ParseTrackEventPacket(decoder, std::move(packet), timestamp);
+    return util::OkStatus();
+  }
+
+  // TODO(eseckler): Remove this once Chrome has switched fully over to
+  // TrackDescriptors.
   if (decoder.has_thread_descriptor()) {
     ParseThreadDescriptorPacket(decoder);
     return util::OkStatus();
@@ -347,9 +383,6 @@
     }
   }
 
-  auto* state = GetIncrementalStateForPacketSequence(
-      decoder.trusted_packet_sequence_id());
-
   // Use parent data and length because we want to parse this again
   // later to get the exact type of the packet.
   context_->sorter->PushTracePacket(timestamp, state, std::move(packet));
@@ -424,17 +457,17 @@
 
   for (auto it = interned_data_decoder.build_ids(); it; ++it) {
     size_t offset = interned_data.offset_of(it->data());
-    InternMessage<protos::pbzero::InternedString>(
+    InternMessage<protos::pbzero::InternedString, BuildIdFieldName>(
         context_, state, interned_data.slice(offset, it->size()));
   }
   for (auto it = interned_data_decoder.mapping_paths(); it; ++it) {
     size_t offset = interned_data.offset_of(it->data());
-    InternMessage<protos::pbzero::InternedString>(
+    InternMessage<protos::pbzero::InternedString, MappingPathsFieldName>(
         context_, state, interned_data.slice(offset, it->size()));
   }
   for (auto it = interned_data_decoder.function_names(); it; ++it) {
     size_t offset = interned_data.offset_of(it->data());
-    InternMessage<protos::pbzero::InternedString>(
+    InternMessage<protos::pbzero::InternedString, FunctionNamesFieldName>(
         context_, state, interned_data.slice(offset, it->size()));
   }
 
@@ -461,6 +494,51 @@
   }
 }
 
+void ProtoTraceTokenizer::ParseTrackDescriptorPacket(
+    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
+  auto track_descriptor_field = packet_decoder.track_descriptor();
+  protos::pbzero::TrackDescriptor::Decoder track_descriptor_decoder(
+      track_descriptor_field.data, track_descriptor_field.size);
+
+  if (!track_descriptor_decoder.has_uuid()) {
+    PERFETTO_ELOG("TrackDescriptor packet without trusted_packet_sequence_id");
+    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+    return;
+  }
+
+  base::Optional<UniquePid> upid;
+  base::Optional<UniqueTid> utid;
+
+  if (track_descriptor_decoder.has_process()) {
+    auto process_descriptor_field = track_descriptor_decoder.process();
+    protos::pbzero::ProcessDescriptor::Decoder process_descriptor_decoder(
+        process_descriptor_field.data, process_descriptor_field.size);
+
+    // TODO(eseckler): Also parse process name / type here.
+
+    upid = context_->process_tracker->GetOrCreateProcess(
+        static_cast<uint32_t>(process_descriptor_decoder.pid()));
+  }
+
+  if (track_descriptor_decoder.has_thread()) {
+    auto thread_descriptor_field = track_descriptor_decoder.thread();
+    protos::pbzero::ThreadDescriptor::Decoder thread_descriptor_decoder(
+        thread_descriptor_field.data, thread_descriptor_field.size);
+
+    ParseThreadDescriptor(thread_descriptor_decoder);
+    utid = context_->process_tracker->UpdateThread(
+        static_cast<uint32_t>(thread_descriptor_decoder.tid()),
+        static_cast<uint32_t>(thread_descriptor_decoder.pid()));
+    upid = *context_->storage->GetThread(*utid).upid;
+  }
+
+  StringId name_id =
+      context_->storage->InternString(track_descriptor_decoder.name());
+
+  context_->track_tracker->UpdateDescriptorTrack(
+      track_descriptor_decoder.uuid(), name_id, upid, utid);
+}
+
 void ProtoTraceTokenizer::ParseThreadDescriptorPacket(
     const protos::pbzero::TracePacket::Decoder& packet_decoder) {
   if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
@@ -478,8 +556,7 @@
   // incorrectly once we move out of the packet loss state. Instead, wait until
   // the first subsequent descriptor after incremental state is cleared.
   if (!state->IsIncrementalStateValid()) {
-    context_->storage->IncrementStats(
-        stats::track_event_tokenizer_skipped_packets);
+    context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
     return;
   }
 
@@ -493,6 +570,12 @@
       thread_descriptor_decoder.reference_thread_time_us() * 1000,
       thread_descriptor_decoder.reference_thread_instruction_count());
 
+  ParseThreadDescriptor(thread_descriptor_decoder);
+}
+
+void ProtoTraceTokenizer::ParseThreadDescriptor(
+    const protos::pbzero::ThreadDescriptor::Decoder&
+        thread_descriptor_decoder) {
   base::StringView name;
   if (thread_descriptor_decoder.has_thread_name()) {
     name = thread_descriptor_decoder.thread_name();
@@ -576,7 +659,8 @@
 
 void ProtoTraceTokenizer::ParseTrackEventPacket(
     const protos::pbzero::TracePacket::Decoder& packet_decoder,
-    TraceBlobView packet) {
+    TraceBlobView packet,
+    int64_t packet_timestamp) {
   constexpr auto kTimestampDeltaUsFieldNumber =
       protos::pbzero::TrackEvent::kTimestampDeltaUsFieldNumber;
   constexpr auto kTimestampAbsoluteUsFieldNumber =
@@ -599,11 +683,11 @@
   auto* state = GetIncrementalStateForPacketSequence(
       packet_decoder.trusted_packet_sequence_id());
 
-  // TrackEvents can only be parsed correctly while incremental state for their
-  // sequence is valid and after a ThreadDescriptor has been parsed.
-  if (!state->IsTrackEventStateValid()) {
-    context_->storage->IncrementStats(
-        stats::track_event_tokenizer_skipped_packets);
+  // TODO(eseckler): For now, TrackEvents can only be parsed correctly while
+  // incremental state for their sequence is valid, because chromium doesn't set
+  // SEQ_NEEDS_INCREMENTAL_STATE yet. Remove this once it does.
+  if (!state->IsIncrementalStateValid()) {
+    context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
     return;
   }
 
@@ -614,14 +698,26 @@
   int64_t thread_timestamp = 0;
   int64_t thread_instructions = 0;
 
+  // TODO(eseckler): Remove handling of timestamps relative to ThreadDescriptors
+  // once all producers have switched to clock-domain timestamps (e.g.
+  // TracePacket's timestamp).
+
   if (auto ts_delta_field =
           event_decoder.FindField(kTimestampDeltaUsFieldNumber)) {
+    // Delta timestamps require a valid ThreadDescriptor packet since the last
+    // packet loss.
+    if (!state->track_event_timestamps_valid()) {
+      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+      return;
+    }
     timestamp = state->IncrementAndGetTrackEventTimeNs(
         ts_delta_field.as_int64() * 1000);
   } else if (auto ts_absolute_field =
                  event_decoder.FindField(kTimestampAbsoluteUsFieldNumber)) {
     // One-off absolute timestamps don't affect delta computation.
     timestamp = ts_absolute_field.as_int64() * 1000;
+  } else if (packet_decoder.has_timestamp()) {
+    timestamp = packet_timestamp;
   } else {
     PERFETTO_ELOG("TrackEvent without timestamp");
     context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
@@ -632,6 +728,12 @@
 
   if (auto tt_delta_field =
           event_decoder.FindField(kThreadTimeDeltaUsFieldNumber)) {
+    // Delta timestamps require a valid ThreadDescriptor packet since the last
+    // packet loss.
+    if (!state->track_event_timestamps_valid()) {
+      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+      return;
+    }
     thread_timestamp = state->IncrementAndGetTrackEventThreadTimeNs(
         tt_delta_field.as_int64() * 1000);
   } else if (auto tt_absolute_field =
@@ -642,6 +744,12 @@
 
   if (auto ti_delta_field =
           event_decoder.FindField(kThreadInstructionCountDeltaFieldNumber)) {
+    // Delta timestamps require a valid ThreadDescriptor packet since the last
+    // packet loss.
+    if (!state->track_event_timestamps_valid()) {
+      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+      return;
+    }
     thread_instructions =
         state->IncrementAndGetTrackEventThreadInstructionCount(
             ti_delta_field.as_int64());
@@ -673,6 +781,11 @@
     return;
   }
 
+  if (decoder.has_compact_sched()) {
+    ParseFtraceCompactSched(cpu, decoder.compact_sched().data,
+                            decoder.compact_sched().size);
+  }
+
   for (auto it = decoder.event(); it; ++it) {
     size_t off = bundle.offset_of(it->data());
     ParseFtraceEvent(cpu, bundle.slice(off, it->size()));
@@ -680,6 +793,60 @@
   context_->sorter->FinalizeFtraceEventBatch(cpu);
 }
 
+void ProtoTraceTokenizer::ParseFtraceCompactSched(uint32_t cpu,
+                                                  const uint8_t* data,
+                                                  size_t size) {
+  protos::pbzero::FtraceEventBundle_CompactSched::Decoder compact(data, size);
+
+  // Build the interning table for next_comm fields.
+  std::vector<StringId> string_table;
+  string_table.reserve(512);
+  for (auto it = compact.switch_next_comm_table(); it; it++) {
+    StringId value = context_->storage->InternString(it->as_string());
+    string_table.push_back(value);
+  }
+
+  // Accumulator for timestamp deltas.
+  int64_t timestamp_acc = 0;
+
+  // The events' fields are stored in a structure-of-arrays style, using packed
+  // repeated fields. Walk each repeated field in step to recover individual
+  // events.
+  bool parse_error = false;
+  auto timestamp_it = compact.switch_timestamp(&parse_error);
+  auto pstate_it = compact.switch_prev_state(&parse_error);
+  auto npid_it = compact.switch_next_pid(&parse_error);
+  auto nprio_it = compact.switch_next_prio(&parse_error);
+  auto comm_it = compact.switch_next_comm_index(&parse_error);
+  for (; timestamp_it && pstate_it && npid_it && nprio_it && comm_it;
+       ++timestamp_it, ++pstate_it, ++npid_it, ++nprio_it, ++comm_it) {
+    TraceSorter::InlineSchedSwitch event{};
+
+    // delta-encoded timestamp
+    timestamp_acc += static_cast<int64_t>(*timestamp_it);
+    int64_t event_timestamp = timestamp_acc;
+
+    // index into the interned string table
+    PERFETTO_DCHECK(*comm_it < string_table.size());
+    event.next_comm = string_table[*comm_it];
+
+    event.prev_state = *pstate_it;
+    event.next_pid = *npid_it;
+    event.next_prio = *nprio_it;
+
+    context_->sorter->PushInlineFtraceEvent(
+        cpu, event_timestamp, TraceSorter::InlineEvent::SchedSwitch(event));
+  }
+
+  // Check that all packed buffers were decoded correctly, and fully.
+  bool sizes_match =
+      !timestamp_it && !pstate_it && !npid_it && !nprio_it && !comm_it;
+  if (parse_error || !sizes_match)
+    context_->storage->IncrementStats(stats::compact_sched_has_parse_errors);
+
+  latest_timestamp_ = std::max(timestamp_acc, latest_timestamp_);
+}
+
 PERFETTO_ALWAYS_INLINE
 void ProtoTraceTokenizer::ParseFtraceEvent(uint32_t cpu, TraceBlobView event) {
   constexpr auto kTimestampFieldNumber =
diff --git a/src/trace_processor/proto_trace_tokenizer.h b/src/trace_processor/proto_trace_tokenizer.h
index 907bd4a..485effd 100644
--- a/src/trace_processor/proto_trace_tokenizer.h
+++ b/src/trace_processor/proto_trace_tokenizer.h
@@ -27,6 +27,7 @@
 #include "src/trace_processor/trace_processor_impl.h"
 
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
 
 namespace protozero {
 struct ConstBytes;
@@ -64,11 +65,15 @@
   void HandlePreviousPacketDropped(const protos::pbzero::TracePacket::Decoder&);
   void ParseInternedData(const protos::pbzero::TracePacket::Decoder&,
                          TraceBlobView interned_data);
+  void ParseTrackDescriptorPacket(const protos::pbzero::TracePacket::Decoder&);
   void ParseThreadDescriptorPacket(const protos::pbzero::TracePacket::Decoder&);
+  void ParseThreadDescriptor(const protos::pbzero::ThreadDescriptor::Decoder&);
   void ParseTrackEventPacket(const protos::pbzero::TracePacket::Decoder&,
-                             TraceBlobView packet);
+                             TraceBlobView packet,
+                             int64_t packet_timestamp);
   void ParseFtraceBundle(TraceBlobView);
   void ParseFtraceEvent(uint32_t cpu, TraceBlobView);
+  void ParseFtraceCompactSched(uint32_t cpu, const uint8_t* data, size_t size);
 
   ProtoIncrementalState::PacketSequenceState*
   GetIncrementalStateForPacketSequence(uint32_t sequence_id) {
diff --git a/src/trace_processor/slice_table.cc b/src/trace_processor/slice_table.cc
index 70a96f2..d2dca83 100644
--- a/src/trace_processor/slice_table.cc
+++ b/src/trace_processor/slice_table.cc
@@ -34,6 +34,7 @@
       .AddGenericNumericColumn("slice_id", RowAccessor())
       .AddOrderedNumericColumn("ts", &slices.start_ns())
       .AddNumericColumn("dur", &slices.durations())
+      .AddNumericColumn("track_id", &slices.track_id())
       .AddNumericColumn("ref", &slices.refs())
       .AddStringColumn("ref_type", &slices.types(), &GetRefTypeStringMap())
       .AddStringColumn("category", &slices.categories(),
diff --git a/src/trace_processor/slice_tracker.cc b/src/trace_processor/slice_tracker.cc
index 70083da..0ab1183 100644
--- a/src/trace_processor/slice_tracker.cc
+++ b/src/trace_processor/slice_tracker.cc
@@ -23,6 +23,7 @@
 #include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/trace_storage.h"
+#include "src/trace_processor/track_tracker.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -45,10 +46,13 @@
   UniqueTid utid =
       context_->process_tracker->UpdateThread(ftrace_tid, atrace_tgid);
   ftrace_to_atrace_tgid_[ftrace_tid] = atrace_tgid;
-  return Begin(timestamp, utid, RefType::kRefUtid, category, name);
+
+  TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+  return Begin(timestamp, track_id, utid, RefType::kRefUtid, category, name);
 }
 
 base::Optional<uint32_t> SliceTracker::Begin(int64_t timestamp,
+                                             TrackId track_id,
                                              int64_t ref,
                                              RefType ref_type,
                                              StringId category,
@@ -61,12 +65,13 @@
   }
   prev_timestamp_ = timestamp;
 
-  MaybeCloseStack(timestamp, &stacks_[{ref, ref_type}]);
-  return StartSlice(timestamp, kPendingDuration, ref, ref_type, category, name,
-                    args_callback);
+  MaybeCloseStack(timestamp, &stacks_[track_id]);
+  return StartSlice(timestamp, kPendingDuration, track_id, ref, ref_type,
+                    category, name, args_callback);
 }
 
 base::Optional<uint32_t> SliceTracker::Scoped(int64_t timestamp,
+                                              TrackId track_id,
                                               int64_t ref,
                                               RefType ref_type,
                                               StringId category,
@@ -81,20 +86,21 @@
   prev_timestamp_ = timestamp;
 
   PERFETTO_DCHECK(duration >= 0);
-  MaybeCloseStack(timestamp, &stacks_[{ref, ref_type}]);
-  return StartSlice(timestamp, duration, ref, ref_type, category, name,
-                    args_callback);
+  MaybeCloseStack(timestamp, &stacks_[track_id]);
+  return StartSlice(timestamp, duration, track_id, ref, ref_type, category,
+                    name, args_callback);
 }
 
 base::Optional<uint32_t> SliceTracker::StartSlice(
     int64_t timestamp,
     int64_t duration,
+    TrackId track_id,
     int64_t ref,
     RefType ref_type,
     StringId category,
     StringId name,
     SetArgsCallback args_callback) {
-  auto* stack = &stacks_[{ref, ref_type}];
+  auto* stack = &stacks_[track_id];
   auto* slices = context_->storage->mutable_nestable_slices();
 
   const uint8_t depth = static_cast<uint8_t>(stack->size());
@@ -105,8 +111,8 @@
   int64_t parent_stack_id =
       depth == 0 ? 0 : slices->stack_ids()[stack->back().first];
   uint32_t slice_idx =
-      slices->AddSlice(timestamp, duration, ref, ref_type, category, name,
-                       depth, 0, parent_stack_id);
+      slices->AddSlice(timestamp, duration, track_id, ref, ref_type, category,
+                       name, depth, 0, parent_stack_id);
   stack->emplace_back(std::make_pair(slice_idx, ArgsTracker(context_)));
 
   if (args_callback) {
@@ -137,12 +143,12 @@
   }
   UniqueTid utid =
       context_->process_tracker->UpdateThread(ftrace_tid, actual_tgid);
-  return End(timestamp, utid, RefType::kRefUtid);
+  TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+  return End(timestamp, track_id);
 }
 
 base::Optional<uint32_t> SliceTracker::End(int64_t timestamp,
-                                           int64_t ref,
-                                           RefType ref_type,
+                                           TrackId track_id,
                                            StringId category,
                                            StringId name,
                                            SetArgsCallback args_callback) {
@@ -153,10 +159,9 @@
   }
   prev_timestamp_ = timestamp;
 
-  StackMapKey stack_key = {ref, ref_type};
-  MaybeCloseStack(timestamp, &stacks_[stack_key]);
+  MaybeCloseStack(timestamp, &stacks_[track_id]);
 
-  auto& stack = stacks_[stack_key];
+  auto& stack = stacks_[track_id];
   if (stack.empty())
     return base::nullopt;
 
@@ -179,7 +184,7 @@
         TraceStorage::CreateRowId(TableId::kNestableSlices, slice_idx));
   }
 
-  return CompleteSlice(stack_key);
+  return CompleteSlice(track_id);
   // TODO(primiano): auto-close B slices left open at the end.
 }
 
@@ -194,8 +199,8 @@
   stacks_.clear();
 }
 
-base::Optional<uint32_t> SliceTracker::CompleteSlice(StackMapKey stack_key) {
-  auto& stack = stacks_[stack_key];
+base::Optional<uint32_t> SliceTracker::CompleteSlice(TrackId track_id) {
+  auto& stack = stacks_[track_id];
   uint32_t slice_idx = stack.back().first;
   stack.pop_back();
   return slice_idx;
diff --git a/src/trace_processor/slice_tracker.h b/src/trace_processor/slice_tracker.h
index 351aea0..658d32c 100644
--- a/src/trace_processor/slice_tracker.h
+++ b/src/trace_processor/slice_tracker.h
@@ -43,6 +43,7 @@
   // virtual for testing
   virtual base::Optional<uint32_t> Begin(
       int64_t timestamp,
+      TrackId track_id,
       int64_t ref,
       RefType ref_type,
       StringId category,
@@ -52,6 +53,7 @@
   // virtual for testing
   virtual base::Optional<uint32_t> Scoped(
       int64_t timestamp,
+      TrackId track_id,
       int64_t ref,
       RefType ref_type,
       StringId category,
@@ -66,8 +68,7 @@
   // virtual for testing
   virtual base::Optional<uint32_t> End(
       int64_t timestamp,
-      int64_t ref,
-      RefType ref_type,
+      TrackId track_id,
       StringId opt_category = {},
       StringId opt_name = {},
       SetArgsCallback args_callback = SetArgsCallback());
@@ -76,35 +77,17 @@
 
  private:
   using SlicesStack = std::vector<std::pair<uint32_t /* row */, ArgsTracker>>;
-
-  struct StackMapKey {
-    int64_t ref;
-    RefType type;
-
-    bool operator==(const StackMapKey& rhs) const {
-      return std::tie(ref, type) == std::tie(rhs.ref, rhs.type);
-    }
-  };
-
-  struct StackMapHash {
-    size_t operator()(const StackMapKey& p) const {
-      base::Hash hash;
-      hash.Update(p.ref);
-      hash.Update(p.type);
-      return static_cast<size_t>(hash.digest());
-    }
-  };
-
-  using StackMap = std::unordered_map<StackMapKey, SlicesStack, StackMapHash>;
+  using StackMap = std::unordered_map<TrackId, SlicesStack>;
 
   base::Optional<uint32_t> StartSlice(int64_t timestamp,
                                       int64_t duration,
+                                      TrackId track_id,
                                       int64_t ref,
                                       RefType ref_type,
                                       StringId category,
                                       StringId name,
                                       SetArgsCallback args_callback);
-  base::Optional<uint32_t> CompleteSlice(StackMapKey stack_key);
+  base::Optional<uint32_t> CompleteSlice(TrackId track_id);
 
   void MaybeCloseStack(int64_t end_ts, SlicesStack*);
   int64_t GetStackHash(const SlicesStack&);
diff --git a/src/trace_processor/slice_tracker_unittest.cc b/src/trace_processor/slice_tracker_unittest.cc
index 203941e..b547ad1 100644
--- a/src/trace_processor/slice_tracker_unittest.cc
+++ b/src/trace_processor/slice_tracker_unittest.cc
@@ -55,17 +55,20 @@
   context.storage.reset(new TraceStorage());
   SliceTracker tracker(&context);
 
-  tracker.Begin(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/);
-  tracker.End(10 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/);
+  constexpr TrackId track = 22u;
+  tracker.Begin(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+                1 /*name*/);
+  tracker.End(10 /*ts*/, track, 0 /*cat*/, 1 /*name*/);
 
   auto slices = context.storage->nestable_slices();
   EXPECT_EQ(slices.slice_count(), 1u);
   EXPECT_EQ(slices.start_ns()[0], 2);
   EXPECT_EQ(slices.durations()[0], 8);
+  EXPECT_EQ(slices.track_id()[0], track);
   EXPECT_EQ(slices.categories()[0], 0u);
   EXPECT_EQ(slices.names()[0], 1u);
   EXPECT_EQ(slices.refs()[0], 42);
-  EXPECT_EQ(slices.types()[0], kRefUtid);
+  EXPECT_EQ(slices.types()[0], RefType::kRefUtid);
   EXPECT_EQ(slices.depths()[0], 0);
   EXPECT_EQ(slices.arg_set_ids()[0], kInvalidArgSetId);
 }
@@ -75,12 +78,13 @@
   context.storage.reset(new TraceStorage());
   SliceTracker tracker(&context);
 
-  tracker.Begin(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/,
-                [](ArgsTracker* args_tracker, RowId row) {
+  constexpr TrackId track = 22u;
+  tracker.Begin(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+                1 /*name*/, [](ArgsTracker* args_tracker, RowId row) {
                   args_tracker->AddArg(row, /*flat_key=*/1, /*key=*/2,
                                        /*value=*/Variadic::Integer(10));
                 });
-  tracker.End(10 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/,
+  tracker.End(10 /*ts*/, track, 0 /*cat*/, 1 /*name*/,
               [](ArgsTracker* args_tracker, RowId row) {
                 args_tracker->AddArg(row, /*flat_key=*/3, /*key=*/4,
                                      /*value=*/Variadic::Integer(20));
@@ -90,10 +94,11 @@
   EXPECT_EQ(slices.slice_count(), 1u);
   EXPECT_EQ(slices.start_ns()[0], 2);
   EXPECT_EQ(slices.durations()[0], 8);
+  EXPECT_EQ(slices.track_id()[0], track);
   EXPECT_EQ(slices.categories()[0], 0u);
   EXPECT_EQ(slices.names()[0], 1u);
   EXPECT_EQ(slices.refs()[0], 42);
-  EXPECT_EQ(slices.types()[0], kRefUtid);
+  EXPECT_EQ(slices.types()[0], RefType::kRefUtid);
   EXPECT_EQ(slices.depths()[0], 0);
   auto set_id = slices.arg_set_ids()[0];
 
@@ -113,10 +118,13 @@
   context.storage.reset(new TraceStorage());
   SliceTracker tracker(&context);
 
-  tracker.Begin(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/);
-  tracker.Begin(3 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 2 /*name*/);
-  tracker.End(5 /*ts*/, 42 /*ref*/, RefType::kRefUtid);
-  tracker.End(10 /*ts*/, 42 /*ref*/, RefType::kRefUtid);
+  constexpr TrackId track = 22u;
+  tracker.Begin(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+                1 /*name*/);
+  tracker.Begin(3 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+                2 /*name*/);
+  tracker.End(5 /*ts*/, track);
+  tracker.End(10 /*ts*/, track);
 
   auto slices = context.storage->nestable_slices();
 
@@ -125,18 +133,20 @@
   size_t idx = 0;
   EXPECT_EQ(slices.start_ns()[idx], 2);
   EXPECT_EQ(slices.durations()[idx], 8);
+  EXPECT_EQ(slices.track_id()[idx], track);
   EXPECT_EQ(slices.categories()[idx], 0u);
   EXPECT_EQ(slices.names()[idx], 1u);
   EXPECT_EQ(slices.refs()[idx], 42);
-  EXPECT_EQ(slices.types()[idx], kRefUtid);
+  EXPECT_EQ(slices.types()[idx], RefType::kRefUtid);
   EXPECT_EQ(slices.depths()[idx++], 0);
 
   EXPECT_EQ(slices.start_ns()[idx], 3);
   EXPECT_EQ(slices.durations()[idx], 2);
+  EXPECT_EQ(slices.track_id()[idx], track);
   EXPECT_EQ(slices.categories()[idx], 0u);
   EXPECT_EQ(slices.names()[idx], 2u);
   EXPECT_EQ(slices.refs()[idx], 42);
-  EXPECT_EQ(slices.types()[idx], kRefUtid);
+  EXPECT_EQ(slices.types()[idx], RefType::kRefUtid);
   EXPECT_EQ(slices.depths()[idx], 1);
 
   EXPECT_EQ(slices.parent_stack_ids()[0], 0);
@@ -149,11 +159,12 @@
   context.storage.reset(new TraceStorage());
   SliceTracker tracker(&context);
 
-  tracker.Begin(0 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0, 0);
-  tracker.Begin(1 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0, 0);
-  tracker.Scoped(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0, 0, 6);
-  tracker.End(9 /*ts*/, 42 /*ref*/, RefType::kRefUtid);
-  tracker.End(10 /*ts*/, 42 /*ref*/, RefType::kRefUtid);
+  constexpr TrackId track = 22u;
+  tracker.Begin(0 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0, 0);
+  tracker.Begin(1 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0, 0);
+  tracker.Scoped(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0, 0, 6);
+  tracker.End(9 /*ts*/, track);
+  tracker.End(10 /*ts*/, track);
 
   auto slices = ToSliceInfo(context.storage->nestable_slices());
   EXPECT_THAT(slices,
@@ -165,10 +176,12 @@
   context.storage.reset(new TraceStorage());
   SliceTracker tracker(&context);
 
-  tracker.Begin(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/);
-  tracker.End(3 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 1 /*cat*/, 1 /*name*/);
-  tracker.End(4 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 2 /*name*/);
-  tracker.End(5 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/);
+  constexpr TrackId track = 22u;
+  tracker.Begin(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+                1 /*name*/);
+  tracker.End(3 /*ts*/, track, 1 /*cat*/, 1 /*name*/);
+  tracker.End(4 /*ts*/, track, 0 /*cat*/, 2 /*name*/);
+  tracker.End(5 /*ts*/, track, 0 /*cat*/, 1 /*name*/);
 
   auto slices = ToSliceInfo(context.storage->nestable_slices());
   EXPECT_THAT(slices, ElementsAre(SliceInfo{2, 3}));
@@ -182,13 +195,14 @@
   // Bug scenario: the second zero-length scoped slice prevents the first slice
   // from being closed, leading to an inconsistency when we try to insert the
   // final slice and it doesn't intersect with the still pending first slice.
-  tracker.Scoped(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/,
-                 10 /* dur */);
-  tracker.Scoped(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/,
-                 0 /* dur */);
-  tracker.Scoped(12 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+  constexpr TrackId track = 22u;
+  tracker.Scoped(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+                 1 /*name*/, 10 /* dur */);
+  tracker.Scoped(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+                 1 /*name*/, 0 /* dur */);
+  tracker.Scoped(12 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
                  1 /*name*/, 1 /* dur */);
-  tracker.Scoped(13 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+  tracker.Scoped(13 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
                  1 /*name*/, 1 /* dur */);
 
   auto slices = ToSliceInfo(context.storage->nestable_slices());
@@ -196,24 +210,26 @@
                                   SliceInfo{12, 1}, SliceInfo{13, 1}));
 }
 
-TEST(SliceTrackerTest, DifferentRefTypes) {
+TEST(SliceTrackerTest, DifferentTracks) {
   TraceProcessorContext context;
   context.storage.reset(new TraceStorage());
   SliceTracker tracker(&context);
 
-  tracker.Begin(0 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0, 0);
-  tracker.Scoped(2 /*ts*/, 42 /*ref*/, RefType::kRefUpid, 0, 0, 6);
-  tracker.Scoped(3 /*ts*/, 42 /*ref*/, RefType::kRefUpid, 0, 0, 4);
-  tracker.End(10 /*ts*/, 42 /*ref*/, RefType::kRefUtid);
+  constexpr TrackId track_a = 22u;
+  constexpr TrackId track_b = 23u;
+  tracker.Begin(0 /*ts*/, track_a, 42 /*ref*/, RefType::kRefUtid, 0, 0);
+  tracker.Scoped(2 /*ts*/, track_b, 42 /*ref*/, RefType::kRefUtid, 0, 0, 6);
+  tracker.Scoped(3 /*ts*/, track_b, 42 /*ref*/, RefType::kRefUtid, 0, 0, 4);
+  tracker.End(10 /*ts*/, track_a);
   tracker.FlushPendingSlices();
 
   auto slices = ToSliceInfo(context.storage->nestable_slices());
   EXPECT_THAT(slices,
               ElementsAre(SliceInfo{0, 10}, SliceInfo{2, 6}, SliceInfo{3, 4}));
 
-  EXPECT_EQ(context.storage->nestable_slices().types()[0], RefType::kRefUtid);
-  EXPECT_EQ(context.storage->nestable_slices().types()[1], RefType::kRefUpid);
-  EXPECT_EQ(context.storage->nestable_slices().types()[2], RefType::kRefUpid);
+  EXPECT_EQ(context.storage->nestable_slices().track_id()[0], track_a);
+  EXPECT_EQ(context.storage->nestable_slices().track_id()[1], track_b);
+  EXPECT_EQ(context.storage->nestable_slices().track_id()[2], track_b);
   EXPECT_EQ(context.storage->nestable_slices().depths()[0], 0);
   EXPECT_EQ(context.storage->nestable_slices().depths()[1], 0);
   EXPECT_EQ(context.storage->nestable_slices().depths()[2], 1);
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 8853984..a2702d3 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -32,6 +32,8 @@
       return FilterOp::kGt;
     case SQLITE_INDEX_CONSTRAINT_LT:
       return FilterOp::kLt;
+    case SQLITE_INDEX_CONSTRAINT_NE:
+      return FilterOp::kNeq;
     default:
       PERFETTO_FATAL("Currently unsupported constraint");
   }
@@ -100,11 +102,43 @@
   return util::OkStatus();
 }
 
-int DbSqliteTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
-  // TODO(lalitm): add proper cost estimation by looking at the columns.
+int DbSqliteTable::BestIndex(const QueryConstraints& qc, BestIndexInfo* info) {
+  // TODO(lalitm): investigate SQLITE_INDEX_SCAN_UNIQUE for id columns.
+  info->estimated_cost = static_cast<uint32_t>(EstimateCost(qc));
   return SQLITE_OK;
 }
 
+double DbSqliteTable::EstimateCost(const QueryConstraints& qc) {
+  // Currently our cost estimation algorithm is quite simplistic but is good
+  // enough for the simplest cases.
+  // TODO(lalitm): flesh out this algorithm to cover more complex cases.
+
+  // If we have no constraints, we always return the max possible value as we
+  // want to discourage the query planner from taking this road.
+  const auto& constraints = qc.constraints();
+  if (constraints.empty())
+    return std::numeric_limits<int32_t>::max();
+
+  // This means we have at least one constraint. Check if any of the constraints
+  // is an equality constraint on an id column.
+  auto id_filter = [this](const QueryConstraints::Constraint& c) {
+    uint32_t col_idx = static_cast<uint32_t>(c.iColumn);
+    const auto& col = table_->GetColumn(col_idx);
+    return sqlite_utils::IsOpEq(c.op) && col.IsId();
+  };
+
+  // If we have a eq constraint on an id column, we return 0 as it's an O(1)
+  // operation regardless of all the other constriants.
+  auto it = std::find_if(constraints.begin(), constraints.end(), id_filter);
+  if (it != constraints.end())
+    return 1;
+
+  // Otherwise, we divide the number of rows in the table by the number of
+  // constraints as a simple way of indiciating the more constraints we have
+  // the better we can do.
+  return table_->size() / constraints.size();
+}
+
 std::unique_ptr<SqliteTable::Cursor> DbSqliteTable::CreateCursor() {
   return std::unique_ptr<Cursor>(new Cursor(this));
 }
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 97f94f4..39756aa 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -63,6 +63,8 @@
   int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
 
  private:
+  double EstimateCost(const QueryConstraints& qc);
+
   const Table* table_ = nullptr;
 };
 
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index 83d1181..a30d87c 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -407,7 +407,10 @@
 
   sqlite3_stmt* raw_stmt = nullptr;
   int err = sqlite3_prepare_v2(db, sql, n, &raw_stmt, nullptr);
-
+  if (err != SQLITE_OK) {
+    PERFETTO_ELOG("Preparing database failed");
+    return {};
+  }
   ScopedStmt stmt(raw_stmt);
   PERFETTO_DCHECK(sqlite3_column_count(*stmt) == 2);
 
diff --git a/src/trace_processor/stack_profile_tracker.cc b/src/trace_processor/stack_profile_tracker.cc
index 8d0a157..be518f3 100644
--- a/src/trace_processor/stack_profile_tracker.cc
+++ b/src/trace_processor/stack_profile_tracker.cc
@@ -40,13 +40,15 @@
                                         const InternLookup* intern_lookup) {
   std::string path;
   for (SourceStringId str_id : mapping.name_ids) {
-    auto opt_str = FindString(str_id, intern_lookup);
+    auto opt_str =
+        FindString(str_id, intern_lookup, InternedStringType::kMappingPath);
     if (!opt_str)
       break;
     path += "/" + *opt_str;
   }
 
-  auto opt_build_id = FindAndInternString(mapping.build_id, intern_lookup);
+  auto opt_build_id = FindAndInternString(mapping.build_id, intern_lookup,
+                                          InternedStringType::kBuildId);
   if (!opt_build_id) {
     context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
     PERFETTO_DFATAL("Invalid string.");
@@ -86,7 +88,8 @@
 int64_t StackProfileTracker::AddFrame(SourceFrameId id,
                                       const SourceFrame& frame,
                                       const InternLookup* intern_lookup) {
-  auto opt_str_id = FindAndInternString(frame.name_id, intern_lookup);
+  auto opt_str_id = FindAndInternString(frame.name_id, intern_lookup,
+                                        InternedStringType::kFunctionName);
   if (!opt_str_id) {
     context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
     PERFETTO_DFATAL("Invalid string.");
@@ -170,11 +173,12 @@
 
 base::Optional<StringId> StackProfileTracker::FindAndInternString(
     SourceStringId id,
-    const InternLookup* intern_lookup) {
+    const InternLookup* intern_lookup,
+    StackProfileTracker::InternedStringType type) {
   if (id == 0)
     return empty_;
 
-  auto opt_str = FindString(id, intern_lookup);
+  auto opt_str = FindString(id, intern_lookup, type);
   if (!opt_str)
     return empty_;
 
@@ -183,18 +187,20 @@
 
 base::Optional<std::string> StackProfileTracker::FindString(
     SourceStringId id,
-    const InternLookup* intern_lookup) {
+    const InternLookup* intern_lookup,
+    StackProfileTracker::InternedStringType type) {
   if (id == 0)
     return "";
 
   auto it = string_map_.find(id);
   if (it == string_map_.end()) {
     if (intern_lookup) {
-      auto str = intern_lookup->GetString(id);
+      auto str = intern_lookup->GetString(id, type);
       if (!str) {
         context_->storage->IncrementStats(
             stats::stackprofile_invalid_string_id);
         PERFETTO_DFATAL("Invalid string.");
+        return base::nullopt;
       }
       return str->ToStdString();
     }
diff --git a/src/trace_processor/stack_profile_tracker.h b/src/trace_processor/stack_profile_tracker.h
index caf5b91..114865c 100644
--- a/src/trace_processor/stack_profile_tracker.h
+++ b/src/trace_processor/stack_profile_tracker.h
@@ -60,6 +60,12 @@
  public:
   using SourceStringId = uint64_t;
 
+  enum class InternedStringType {
+    kMappingPath,
+    kBuildId,
+    kFunctionName,
+  };
+
   struct SourceMapping {
     SourceStringId build_id = 0;
     uint64_t exact_offset = 0;
@@ -98,7 +104,8 @@
     virtual ~InternLookup();
 
     virtual base::Optional<base::StringView> GetString(
-        SourceStringId) const = 0;
+        SourceStringId,
+        InternedStringType) const = 0;
     virtual base::Optional<SourceMapping> GetMapping(SourceMappingId) const = 0;
     virtual base::Optional<SourceFrame> GetFrame(SourceFrameId) const = 0;
     virtual base::Optional<SourceCallstack> GetCallstack(
@@ -133,9 +140,11 @@
   // InternedData (for versions newer than Q).
   base::Optional<StringId> FindAndInternString(
       SourceStringId,
-      const InternLookup* intern_lookup);
+      const InternLookup* intern_lookup,
+      InternedStringType type);
   base::Optional<std::string> FindString(SourceStringId,
-                                         const InternLookup* intern_lookup);
+                                         const InternLookup* intern_lookup,
+                                         InternedStringType type);
   base::Optional<int64_t> FindMapping(SourceMappingId,
                                       const InternLookup* intern_lookup);
   base::Optional<int64_t> FindFrame(SourceFrameId,
diff --git a/src/trace_processor/stats.h b/src/trace_processor/stats.h
index b7bd13e..0fce227 100644
--- a/src/trace_processor/stats.h
+++ b/src/trace_processor/stats.h
@@ -102,7 +102,7 @@
   F(traced_tracing_sessions,                  kSingle,  kInfo,     kTrace),    \
   F(track_event_parser_errors,                kSingle,  kInfo,     kAnalysis), \
   F(track_event_tokenizer_errors,             kSingle,  kInfo,     kAnalysis), \
-  F(track_event_tokenizer_skipped_packets,    kSingle,  kInfo,     kAnalysis), \
+  F(tokenizer_skipped_packets,                kSingle,  kInfo,     kAnalysis), \
   F(vmstat_unknown_keys,                      kSingle,  kError,    kAnalysis), \
   F(clock_sync_failure,                       kSingle,  kError,    kAnalysis), \
   F(process_tracker_errors,                   kSingle,  kError,    kAnalysis), \
@@ -112,7 +112,8 @@
   F(heapprofd_rejected_concurrent,            kIndexed, kError,    kTrace),    \
   F(metatrace_overruns,                       kSingle,  kError,    kTrace),    \
   F(packages_list_has_parse_errors,           kSingle,  kError,    kTrace),    \
-  F(packages_list_has_read_errors,            kSingle,  kError,    kTrace)
+  F(packages_list_has_read_errors,            kSingle,  kError,    kTrace),    \
+  F(compact_sched_has_parse_errors,           kSingle,  kError,    kTrace)
 // clang-format on
 
 enum Type {
diff --git a/src/trace_processor/storage_columns.h b/src/trace_processor/storage_columns.h
index 5bde9c9..83e855b 100644
--- a/src/trace_processor/storage_columns.h
+++ b/src/trace_processor/storage_columns.h
@@ -334,7 +334,7 @@
   }
 
   NullTermStringView Get(uint32_t idx) const override {
-    const char* ptr = (*string_map_)[(*deque_)[idx]];
+    const char* ptr = (*string_map_)[static_cast<size_t>((*deque_)[idx])];
     return ptr ? NullTermStringView(ptr) : NullTermStringView();
   }
 
diff --git a/src/trace_processor/syscall_tracker.h b/src/trace_processor/syscall_tracker.h
index 76523dd..f367309 100644
--- a/src/trace_processor/syscall_tracker.h
+++ b/src/trace_processor/syscall_tracker.h
@@ -24,6 +24,7 @@
 #include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/trace_storage.h"
+#include "src/trace_processor/track_tracker.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -50,16 +51,17 @@
   void Enter(int64_t ts, UniqueTid utid, uint32_t syscall_num) {
     StringId name = SyscallNumberToStringId(syscall_num);
     if (!name.is_null()) {
-      context_->slice_tracker->Begin(ts, utid, RefType::kRefUtid, 0 /* cat */,
-                                     name);
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      context_->slice_tracker->Begin(ts, track_id, utid, RefType::kRefUtid,
+                                     0 /* cat */, name);
     }
   }
 
   void Exit(int64_t ts, UniqueTid utid, uint32_t syscall_num) {
     StringId name = SyscallNumberToStringId(syscall_num);
     if (!name.is_null()) {
-      context_->slice_tracker->End(ts, utid, RefType::kRefUtid, 0 /* cat */,
-                                   name);
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      context_->slice_tracker->End(ts, track_id, 0 /* cat */, name);
     }
   }
 
diff --git a/src/trace_processor/syscall_tracker_unittest.cc b/src/trace_processor/syscall_tracker_unittest.cc
index fa5fce5..70bf075 100644
--- a/src/trace_processor/syscall_tracker_unittest.cc
+++ b/src/trace_processor/syscall_tracker_unittest.cc
@@ -32,17 +32,17 @@
   MockSliceTracker(TraceProcessorContext* context) : SliceTracker(context) {}
   virtual ~MockSliceTracker() = default;
 
-  MOCK_METHOD6(Begin,
+  MOCK_METHOD7(Begin,
                base::Optional<uint32_t>(int64_t timestamp,
+                                        TrackId track_id,
                                         int64_t ref,
                                         RefType ref_type,
                                         StringId cat,
                                         StringId name,
                                         SetArgsCallback args_callback));
-  MOCK_METHOD6(End,
+  MOCK_METHOD5(End,
                base::Optional<uint32_t>(int64_t timestamp,
-                                        int64_t ref,
-                                        RefType ref_type,
+                                        TrackId track_id,
                                         StringId cat,
                                         StringId name,
                                         SetArgsCallback args_callback));
@@ -52,6 +52,8 @@
  public:
   SyscallTrackerTest() {
     context.storage.reset(new TraceStorage());
+    track_tracker = new TrackTracker(&context);
+    context.track_tracker.reset(track_tracker);
     slice_tracker = new MockSliceTracker(&context);
     context.slice_tracker.reset(slice_tracker);
     context.syscall_tracker.reset(new SyscallTracker(&context));
@@ -60,17 +62,18 @@
  protected:
   TraceProcessorContext context;
   MockSliceTracker* slice_tracker;
+  TrackTracker* track_tracker;
 };
 
 TEST_F(SyscallTrackerTest, ReportUnknownSyscalls) {
+  constexpr TrackId track = 0u;
   StringId begin_name = 0;
   StringId end_name = 0;
   EXPECT_CALL(*slice_tracker,
-              Begin(100, 42, RefType::kRefUtid, kNullStringId, _, _))
-      .WillOnce(DoAll(SaveArg<4>(&begin_name), Return(base::nullopt)));
-  EXPECT_CALL(*slice_tracker,
-              End(110, 42, RefType::kRefUtid, kNullStringId, _, _))
-      .WillOnce(DoAll(SaveArg<4>(&end_name), Return(base::nullopt)));
+              Begin(100, track, 42, RefType::kRefUtid, kNullStringId, _, _))
+      .WillOnce(DoAll(SaveArg<5>(&begin_name), Return(base::nullopt)));
+  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*/);
@@ -80,22 +83,22 @@
 
 TEST_F(SyscallTrackerTest, IgnoreWriteSyscalls) {
   context.syscall_tracker->SetArchitecture(kAarch64);
-  EXPECT_CALL(*slice_tracker, Begin(_, _, _, _, _, _)).Times(0);
-  EXPECT_CALL(*slice_tracker, End(_, _, _, _, _, _)).Times(0);
+  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*/);
 }
 
 TEST_F(SyscallTrackerTest, Aarch64) {
+  constexpr TrackId track = 0u;
   StringId begin_name = 0;
   StringId end_name = 0;
   EXPECT_CALL(*slice_tracker,
-              Begin(100, 42, RefType::kRefUtid, kNullStringId, _, _))
-      .WillOnce(DoAll(SaveArg<4>(&begin_name), Return(base::nullopt)));
-  EXPECT_CALL(*slice_tracker,
-              End(110, 42, RefType::kRefUtid, kNullStringId, _, _))
-      .WillOnce(DoAll(SaveArg<4>(&end_name), Return(base::nullopt)));
+              Begin(100, track, 42, RefType::kRefUtid, kNullStringId, _, _))
+      .WillOnce(DoAll(SaveArg<5>(&begin_name), Return(base::nullopt)));
+  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*/);
@@ -105,14 +108,14 @@
 }
 
 TEST_F(SyscallTrackerTest, x8664) {
+  constexpr TrackId track = 0u;
   StringId begin_name = 0;
   StringId end_name = 0;
   EXPECT_CALL(*slice_tracker,
-              Begin(100, 42, RefType::kRefUtid, kNullStringId, _, _))
-      .WillOnce(DoAll(SaveArg<4>(&begin_name), Return(base::nullopt)));
-  EXPECT_CALL(*slice_tracker,
-              End(110, 42, RefType::kRefUtid, kNullStringId, _, _))
-      .WillOnce(DoAll(SaveArg<4>(&end_name), Return(base::nullopt)));
+              Begin(100, track, 42, RefType::kRefUtid, kNullStringId, _, _))
+      .WillOnce(DoAll(SaveArg<5>(&begin_name), Return(base::nullopt)));
+  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*/);
@@ -122,8 +125,8 @@
 }
 
 TEST_F(SyscallTrackerTest, SyscallNumberTooLarge) {
-  EXPECT_CALL(*slice_tracker, Begin(_, _, _, _, _, _)).Times(0);
-  EXPECT_CALL(*slice_tracker, End(_, _, _, _, _, _)).Times(0);
+  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);
diff --git a/src/trace_processor/systrace_parser.cc b/src/trace_processor/systrace_parser.cc
index ec5d4c5..6bb6b72 100644
--- a/src/trace_processor/systrace_parser.cc
+++ b/src/trace_processor/systrace_parser.cc
@@ -20,6 +20,7 @@
 #include "src/trace_processor/event_tracker.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/slice_tracker.h"
+#include "src/trace_processor/track_tracker.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -90,13 +91,21 @@
       break;
     }
 
-    case 'S': {
-      // Currently unsupported.
-      break;
-    }
-
+    case 'S':
     case 'F': {
-      // Currently unsupported.
+      StringId name_id = context_->storage->InternString(point.name);
+      int64_t cookie = static_cast<int64_t>(point.value);
+      UniquePid upid =
+          context_->process_tracker->GetOrCreateProcess(point.tgid);
+
+      TrackId track_id = context_->track_tracker->InternAndroidAsyncTrack(
+          name_id, upid, cookie);
+      if (point.phase == 'S') {
+        context_->slice_tracker->Begin(ts, track_id, track_id,
+                                       RefType::kRefTrack, 0, name_id);
+      } else {
+        context_->slice_tracker->End(ts, track_id);
+      }
       break;
     }
 
diff --git a/src/trace_processor/systrace_parser.h b/src/trace_processor/systrace_parser.h
index f85e1dd..c8c3452 100644
--- a/src/trace_processor/systrace_parser.h
+++ b/src/trace_processor/systrace_parser.h
@@ -99,6 +99,11 @@
   size_t len = str.size();
   *out = {};
 
+  constexpr const char* kClockSyncPrefix = "trace_event_clock_sync:";
+  if (len >= strlen(kClockSyncPrefix) &&
+      strncmp(kClockSyncPrefix, s, strlen(kClockSyncPrefix)) == 0)
+    return SystraceParseResult::kUnsupported;
+
   if (len < 2)
     return SystraceParseResult::kFailure;
 
diff --git a/src/trace_processor/systrace_parser_unittest.cc b/src/trace_processor/systrace_parser_unittest.cc
index 5d6f32e..d1a1158 100644
--- a/src/trace_processor/systrace_parser_unittest.cc
+++ b/src/trace_processor/systrace_parser_unittest.cc
@@ -69,6 +69,13 @@
   ASSERT_EQ(ParseSystraceTracePoint("F|123|foo|456", &result),
             Result::kSuccess);
   EXPECT_EQ(result, SystraceTracePoint::F(123, "foo", 456));
+
+  ASSERT_EQ(ParseSystraceTracePoint("trace_event_clock_sync: parent_ts=0.123\n",
+                                    &result),
+            Result::kUnsupported);
+  ASSERT_EQ(ParseSystraceTracePoint("trace_event_clock_sync: realtime_ts=123\n",
+                                    &result),
+            Result::kUnsupported);
 }
 
 }  // namespace
diff --git a/src/trace_processor/systrace_trace_parser.cc b/src/trace_processor/systrace_trace_parser.cc
index 3d81e52..777f262 100644
--- a/src/trace_processor/systrace_trace_parser.cc
+++ b/src/trace_processor/systrace_trace_parser.cc
@@ -34,7 +34,7 @@
 namespace {
 
 std::string SubstrTrim(const std::string& input, size_t start, size_t end) {
-  auto s = input.substr(start, end - start);
+  std::string s = input.substr(start, end - start);
   s.erase(s.begin(), std::find_if(s.begin(), s.end(),
                                   [](int ch) { return !std::isspace(ch); }));
   s.erase(std::find_if(s.rbegin(), s.rend(),
@@ -44,6 +44,17 @@
   return s;
 }
 
+std::pair<size_t, size_t> FindTask(const std::string& line) {
+  size_t start;
+  for (start = 0; start < line.size() && isspace(line[start]); ++start)
+    ;
+  size_t length;
+  for (length = 0; start + length < line.size() && line[start + length] != '-';
+       ++length)
+    ;
+  return std::pair<size_t, size_t>(start, length);
+}
+
 }  // namespace
 
 SystraceTraceParser::SystraceTraceParser(TraceProcessorContext* ctx)
@@ -94,6 +105,8 @@
   return util::OkStatus();
 }
 
+// TODO(hjd): This should be more robust to being passed random input.
+// This can happen if we mess up detecting a gzip trace for example.
 util::Status SystraceTraceParser::ParseSingleSystraceEvent(
     const std::string& buffer) {
   // An example line from buffer looks something like the following:
@@ -102,8 +115,12 @@
   // However, sometimes the tgid can be missing and buffer looks like this:
   // <idle>-0     [000] ...2     0.002188: task_newtask: pid=1 ...
 
-  auto task_idx = 16u;
-  std::string task = SubstrTrim(buffer, 0, task_idx);
+  size_t task_start;
+  size_t task_length;
+  std::tie<size_t, size_t>(task_start, task_length) = FindTask(buffer);
+
+  size_t task_idx = task_start + task_length;
+  std::string task = buffer.substr(task_start, task_length);
 
   // Try and figure out whether tgid is present by searching for '(' but only
   // if it occurs before the start of cpu (indiciated by '[') - this is because
@@ -112,6 +129,10 @@
   auto cpu_idx = buffer.find('[', task_idx + 1);
   bool has_tgid = tgid_idx != std::string::npos && tgid_idx < cpu_idx;
 
+  if (cpu_idx == std::string::npos) {
+    return util::Status("Could not find [ in " + buffer);
+  }
+
   auto pid_end = has_tgid ? cpu_idx : tgid_idx;
   std::string pid_str = SubstrTrim(buffer, task_idx + 1, pid_end);
   auto pid = static_cast<uint32_t>(std::stoi(pid_str));
diff --git a/src/trace_processor/tables/macros_internal.h b/src/trace_processor/tables/macros_internal.h
index 519bae0..70068ac 100644
--- a/src/trace_processor/tables/macros_internal.h
+++ b/src/trace_processor/tables/macros_internal.h
@@ -192,11 +192,16 @@
 #define PERFETTO_TP_COLUMN_APPEND(type, name, ...) \
   name##_.Append(std::move(row.name));
 
-// Defines the accessor for a column.
-#define PERFETTO_TP_TABLE_COL_ACCESSOR(type, name, ...)      \
-  const TypedColumn<type>& name() const {                    \
-    return static_cast<const TypedColumn<type>&>(            \
-        columns_[static_cast<uint32_t>(ColumnIndex::name)]); \
+// Defines the accessors for a column.
+#define PERFETTO_TP_TABLE_COL_ACCESSOR(type, name, ...)       \
+  const TypedColumn<type>& name() const {                     \
+    return static_cast<const TypedColumn<type>&>(             \
+        columns_[static_cast<uint32_t>(ColumnIndex::name)]);  \
+  }                                                           \
+                                                              \
+  TypedColumn<type>* mutable_##name() {                       \
+    return static_cast<TypedColumn<type>*>(                   \
+        &columns_[static_cast<uint32_t>(ColumnIndex::name)]); \
   }
 
 // Definition used as the parent of root tables.
@@ -278,8 +283,10 @@
     }                                                                         \
                                                                               \
     /* Expands to                                                             \
-     * const SparseVector<col1_type>& col1() { return col1_; }                \
-     * const SparseVector<col2_type>& col2() { return col2_; }                \
+     * const TypedColumn<col1_type>& col1() { return col1_; }                 \
+     * TypedColumn<col1_type>* mutable_col1() { return &col1_; }              \
+     * const TypedColumn<col2_type>& col2() { return col2_; }                 \
+     * TypedColumn<col2_type>* mutable_col2() { return &col2_; }              \
      * ...                                                                    \
      */                                                                       \
     PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TABLE_COL_ACCESSOR)              \
diff --git a/src/trace_processor/tables/track_tables.h b/src/trace_processor/tables/track_tables.h
index 79abe06..76a4417 100644
--- a/src/trace_processor/tables/track_tables.h
+++ b/src/trace_processor/tables/track_tables.h
@@ -27,30 +27,24 @@
 #define PERFETTO_TP_TRACK_TABLE_DEF(NAME, PARENT, C) \
   NAME(TrackTable, "track")                          \
   PERFETTO_TP_ROOT_TABLE(PARENT, C)                  \
-  C(StringPool::Id, name)
+  C(StringPool::Id, name)                            \
+  C(base::Optional<uint32_t>, source_arg_set_id)
 
 PERFETTO_TP_TABLE(PERFETTO_TP_TRACK_TABLE_DEF);
 
-#define PERFETTO_TP_CHROME_SCOPED_TRACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(ChromeScopedTrackTable, "chrome_scoped_track")              \
-  PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C)                           \
-  C(base::Optional<uint32_t>, upid)
+#define PERFETTO_TP_PROCESS_TRACK_TABLE_DEF(NAME, PARENT, C) \
+  NAME(ProcessTrackTable, "process_track")                   \
+  PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C)                     \
+  C(uint32_t, upid)
 
-PERFETTO_TP_TABLE(PERFETTO_TP_CHROME_SCOPED_TRACK_TABLE_DEF);
+PERFETTO_TP_TABLE(PERFETTO_TP_PROCESS_TRACK_TABLE_DEF);
 
-#define PERFETTO_TP_CHROME_ASYNC_TRACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(ChromeAsyncTrackTable, "chrome_async_track")               \
-  PARENT(PERFETTO_TP_CHROME_SCOPED_TRACK_TABLE_DEF, C)            \
-  C(int64_t, async_id)
+#define PERFETTO_TP_THREAD_TRACK_TABLE_DEF(NAME, PARENT, C) \
+  NAME(ThreadTrackTable, "thread_track")                    \
+  PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C)                    \
+  C(uint32_t, utid)
 
-PERFETTO_TP_TABLE(PERFETTO_TP_CHROME_ASYNC_TRACK_TABLE_DEF);
-
-#define PERFETTO_TP_FUCHSIA_ASYNC_TRACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(FuchsiaAsyncTrackTable, "fuchsia_async_track")              \
-  PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C)                           \
-  C(int64_t, correlation_id)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_FUCHSIA_ASYNC_TRACK_TABLE_DEF);
+PERFETTO_TP_TABLE(PERFETTO_TP_THREAD_TRACK_TABLE_DEF);
 
 #define PERFETTO_TP_GPU_TRACK_DEF(NAME, PARENT, C) \
   NAME(GpuTrackTable, "gpu_track")                 \
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index b0318ad..3b73e0e 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -306,9 +306,10 @@
   const TraceStorage* storage = context_.storage.get();
   DbSqliteTable::RegisterTable(*db_, &storage->track_table(),
                                storage->track_table().table_name());
-  DbSqliteTable::RegisterTable(
-      *db_, &storage->chrome_async_track_table(),
-      storage->chrome_async_track_table().table_name());
+  DbSqliteTable::RegisterTable(*db_, &storage->thread_track_table(),
+                               storage->thread_track_table().table_name());
+  DbSqliteTable::RegisterTable(*db_, &storage->process_track_table(),
+                               storage->process_track_table().table_name());
   DbSqliteTable::RegisterTable(*db_, &storage->gpu_slice_table(),
                                storage->gpu_slice_table().table_name());
   DbSqliteTable::RegisterTable(*db_, &storage->gpu_track_table(),
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 342613d..d8eb471 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -771,31 +771,34 @@
 Usage: %s [OPTIONS] trace_file.pb
 
 Options:
- -h, --help                      Prints this guide.
- -v, --version                   Prints the version of trace processor.
- -d, --debug                     Enable virtual table debugging.
- -W, --wide                      Prints interactive output with double column
-                                 width.
- -p, --perf-file FILE            Writes the time taken to ingest the trace and
-                                 execute the queries to the given file. Only
-                                 valid with -q or --run-metrics and the file
-                                 will only be written if the execution
-                                 is successful.
- -q, --query-file FILE           Read and execute an SQL query from a file.
- -i, --interactive               Starts interactive mode even after a query file
-                                 is specified with -q or --run-metrics.
- -e, --export FILE               Export the trace into a SQLite database.
- --run-metrics x,y,z             Runs a comma separated list of metrics and
-                                 prints the result as a TraceMetrics proto to
-                                 stdout. The specified can either be in-built
-                                 metrics or SQL/proto files of extension
-                                 metrics.
- --metrics-output=[binary|text]  Allows the output of --run-metrics to be
-                                 specified in either proto binary or proto
-                                 text format (default: text).
- --extra-metrics PATH            Registers all SQL files at the given path to
-                                 the trace processor and extends the builtin
-                                 metrics proto with $PATH/metrics-ext.proto.)",
+ -h, --help                           Prints this guide.
+ -v, --version                        Prints the version of trace processor.
+ -d, --debug                          Enable virtual table debugging.
+ -W, --wide                           Prints interactive output with double
+                                      column width.
+ -p, --perf-file FILE                 Writes the time taken to ingest the trace
+                                      and execute the queries to the given file.
+                                      Only valid with -q or --run-metrics and
+                                      the file will only be written if the
+                                      execution is successful.
+ -q, --query-file FILE                Read and execute an SQL query from a file.
+ -i, --interactive                    Starts interactive mode even after a query
+                                      file is specified with -q or
+                                      --run-metrics.
+ -e, --export FILE                    Export the trace into a SQLite database.
+ --run-metrics x,y,z                  Runs a comma separated list of metrics and
+                                      prints the result as a TraceMetrics proto
+                                      to stdout. The specified can either be
+                                      in-built metrics or SQL/proto files of
+                                      extension metrics.
+ --metrics-output=[binary|text|json]  Allows the output of --run-metrics to be
+                                      specified in either proto binary, proto
+                                      text format or JSON format (default: proto
+                                      text).
+ --extra-metrics PATH                 Registers all SQL files at the given path
+                                      to the trace processor and extends the
+                                      builtin metrics proto with
+                                      $PATH/metrics-ext.proto.)",
                 argv[0]);
 }
 
diff --git a/src/trace_processor/trace_sorter.cc b/src/trace_processor/trace_sorter.cc
index c08ada8..6209890 100644
--- a/src/trace_processor/trace_sorter.cc
+++ b/src/trace_processor/trace_sorter.cc
@@ -40,7 +40,7 @@
   // smaller than max_ts_.
   PERFETTO_DCHECK(sort_min_ts_ < max_ts_);
 
-  // We know that all events between [0, sort_start_idx_] are sorted. Witin
+  // We know that all events between [0, sort_start_idx_] are sorted. Within
   // this range, perform a bound search and find the iterator for the min
   // timestamp that broke the monotonicity. Re-sort from there to the end.
   auto sort_end = events_.begin() + static_cast<ssize_t>(sort_start_idx_);
diff --git a/src/trace_processor/trace_sorter.h b/src/trace_processor/trace_sorter.h
index 873f965..992c981 100644
--- a/src/trace_processor/trace_sorter.h
+++ b/src/trace_processor/trace_sorter.h
@@ -68,6 +68,31 @@
 // from there to the end.
 class TraceSorter {
  public:
+  struct InlineSchedSwitch {
+    int64_t prev_state;
+    int32_t next_pid;
+    int32_t next_prio;
+    StringId next_comm;
+  };
+
+  // Discriminated union of events that are cannot be easily read from the
+  // mapped trace.
+  struct InlineEvent {
+    enum class Type { kInvalid = 0, kSchedSwitch };
+
+    static InlineEvent SchedSwitch(InlineSchedSwitch content) {
+      InlineEvent evt;
+      evt.type = Type::kSchedSwitch;
+      evt.sched_switch = content;
+      return evt;
+    }
+
+    Type type = Type::kInvalid;
+    union {
+      InlineSchedSwitch sched_switch;
+    };
+  };
+
   struct TimestampedTracePiece {
     TimestampedTracePiece(
         int64_t ts,
@@ -81,7 +106,8 @@
                                 std::move(tbv),
                                 /*value=*/nullptr,
                                 /*fpv=*/nullptr,
-                                /*sequence_state=*/sequence_state) {}
+                                /*sequence_state=*/sequence_state,
+                                InlineEvent{}) {}
 
     TimestampedTracePiece(int64_t ts, uint64_t idx, TraceBlobView tbv)
         : TimestampedTracePiece(ts,
@@ -91,7 +117,8 @@
                                 std::move(tbv),
                                 /*value=*/nullptr,
                                 /*fpv=*/nullptr,
-                                /*sequence_state=*/nullptr) {}
+                                /*sequence_state=*/nullptr,
+                                InlineEvent{}) {}
 
     TimestampedTracePiece(int64_t ts,
                           uint64_t idx,
@@ -105,7 +132,8 @@
                                 TraceBlobView(nullptr, 0, 0),
                                 std::move(value),
                                 /*fpv=*/nullptr,
-                                /*sequence_state=*/nullptr) {}
+                                /*sequence_state=*/nullptr,
+                                InlineEvent{}) {}
 
     TimestampedTracePiece(int64_t ts,
                           uint64_t idx,
@@ -118,7 +146,8 @@
                                 std::move(tbv),
                                 /*value=*/nullptr,
                                 std::move(fpv),
-                                /*sequence_state=*/nullptr) {}
+                                /*sequence_state=*/nullptr,
+                                InlineEvent{}) {}
 
     TimestampedTracePiece(
         int64_t ts,
@@ -134,7 +163,19 @@
                                 std::move(tbv),
                                 /*value=*/nullptr,
                                 /*fpv=*/nullptr,
-                                sequence_state) {}
+                                sequence_state,
+                                InlineEvent{}) {}
+
+    TimestampedTracePiece(int64_t ts, uint64_t idx, InlineEvent inline_evt)
+        : TimestampedTracePiece(ts,
+                                /*thread_ts=*/0,
+                                /*thread_instructions=*/0,
+                                idx,
+                                /*tbv=*/TraceBlobView(nullptr, 0, 0),
+                                /*value=*/nullptr,
+                                /*fpv=*/nullptr,
+                                /*sequence_state=*/nullptr,
+                                inline_evt) {}
 
     TimestampedTracePiece(
         int64_t ts,
@@ -144,7 +185,8 @@
         TraceBlobView tbv,
         std::unique_ptr<Json::Value> value,
         std::unique_ptr<FuchsiaProviderView> fpv,
-        ProtoIncrementalState::PacketSequenceState* sequence_state)
+        ProtoIncrementalState::PacketSequenceState* sequence_state,
+        InlineEvent inline_evt)
         : json_value(std::move(value)),
           fuchsia_provider_view(std::move(fpv)),
           packet_sequence_state(sequence_state),
@@ -152,7 +194,8 @@
           thread_timestamp(thread_ts),
           thread_instruction_count(thread_instructions),
           packet_idx_(idx),
-          blob_view(std::move(tbv)) {}
+          blob_view(std::move(tbv)),
+          inline_event(inline_evt) {}
 
     TimestampedTracePiece(TimestampedTracePiece&&) noexcept = default;
     TimestampedTracePiece& operator=(TimestampedTracePiece&&) = default;
@@ -177,6 +220,7 @@
     int64_t thread_instruction_count;
     uint64_t packet_idx_;
     TraceBlobView blob_view;
+    InlineEvent inline_event;
   };
 
   TraceSorter(TraceProcessorContext*, int64_t window_size_ns);
@@ -223,6 +267,21 @@
     // for a bundle are pushed.
   }
 
+  // As with |PushFtraceEvent|, doesn't immediately sort the affected queues.
+  // TODO(rsavitski): if a trace has a mix of normal & "compact" events (being
+  // pushed through this function), the ftrace batches will no longer be fully
+  // sorted by timestamp. In such situations, we will have to sort at the end of
+  // the batch. We can do better as both sub-sequences are sorted however.
+  // Consider adding extra queues, or pushing them in a merge-sort fashion
+  // instead.
+  inline void PushInlineFtraceEvent(uint32_t cpu,
+                                    int64_t timestamp,
+                                    InlineEvent inline_event) {
+    set_ftrace_batch_cpu_for_DCHECK(cpu);
+    GetQueue(cpu + 1)->Append(
+        TimestampedTracePiece(timestamp, packet_idx_++, inline_event));
+  }
+
   inline void PushTrackEventPacket(
       int64_t timestamp,
       int64_t thread_time,
diff --git a/src/trace_processor/trace_storage.cc b/src/trace_processor/trace_storage.cc
index 4fed429..662c460 100644
--- a/src/trace_processor/trace_storage.cc
+++ b/src/trace_processor/trace_storage.cc
@@ -41,15 +41,15 @@
 }
 
 std::vector<const char*> CreateRefTypeStringMap() {
-  std::vector<const char*> map(RefType::kRefMax);
-  map[RefType::kRefNoRef] = nullptr;
-  map[RefType::kRefUtid] = "utid";
-  map[RefType::kRefCpuId] = "cpu";
-  map[RefType::kRefGpuId] = "gpu";
-  map[RefType::kRefIrq] = "irq";
-  map[RefType::kRefSoftIrq] = "softirq";
-  map[RefType::kRefUpid] = "upid";
-  map[RefType::kRefTrack] = "track";
+  std::vector<const char*> map(static_cast<size_t>(RefType::kRefMax));
+  map[static_cast<size_t>(RefType::kRefNoRef)] = nullptr;
+  map[static_cast<size_t>(RefType::kRefUtid)] = "utid";
+  map[static_cast<size_t>(RefType::kRefCpuId)] = "cpu";
+  map[static_cast<size_t>(RefType::kRefGpuId)] = "gpu";
+  map[static_cast<size_t>(RefType::kRefIrq)] = "irq";
+  map[static_cast<size_t>(RefType::kRefSoftIrq)] = "softirq";
+  map[static_cast<size_t>(RefType::kRefUpid)] = "upid";
+  map[static_cast<size_t>(RefType::kRefTrack)] = "track";
   return map;
 }
 
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index e357180..6b17cae 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -66,6 +66,7 @@
   kSched = 4,
   kNestableSlices = 5,
   kMetadataTable = 6,
+  kTrack = 7,
 };
 
 // The top 8 bits are set to the TableId and the bottom 32 to the row of the
@@ -78,7 +79,7 @@
 
 using TrackId = uint32_t;
 
-enum RefType {
+enum class RefType {
   kRefNoRef = 0,
   kRefUtid = 1,
   kRefCpuId = 2,
@@ -319,6 +320,7 @@
    public:
     inline uint32_t AddSlice(int64_t start_ns,
                              int64_t duration_ns,
+                             TrackId track_id,
                              int64_t ref,
                              RefType type,
                              StringId category,
@@ -328,6 +330,7 @@
                              int64_t parent_stack_id) {
       start_ns_.emplace_back(start_ns);
       durations_.emplace_back(duration_ns);
+      track_id_.emplace_back(track_id);
       refs_.emplace_back(ref);
       types_.emplace_back(type);
       categories_.emplace_back(category);
@@ -357,6 +360,7 @@
 
     const std::deque<int64_t>& start_ns() const { return start_ns_; }
     const std::deque<int64_t>& durations() const { return durations_; }
+    const std::deque<TrackId>& track_id() const { return track_id_; }
     const std::deque<int64_t>& refs() const { return refs_; }
     const std::deque<RefType>& types() const { return types_; }
     const std::deque<StringId>& categories() const { return categories_; }
@@ -371,6 +375,7 @@
    private:
     std::deque<int64_t> start_ns_;
     std::deque<int64_t> durations_;
+    std::deque<TrackId> track_id_;
     std::deque<int64_t> refs_;
     std::deque<RefType> types_;
     std::deque<StringId> categories_;
@@ -1146,7 +1151,8 @@
     return unique_processes_[upid];
   }
 
-  const Thread& GetThread(UniqueTid utid) const {
+  // Virtual for testing.
+  virtual const Thread& GetThread(UniqueTid utid) const {
     // Allow utid == 0 for idle thread retrieval.
     PERFETTO_DCHECK(utid < unique_threads_.size());
     return unique_threads_[utid];
@@ -1166,18 +1172,18 @@
   const tables::TrackTable& track_table() const { return track_table_; }
   tables::TrackTable* mutable_track_table() { return &track_table_; }
 
-  const tables::ChromeAsyncTrackTable& chrome_async_track_table() const {
-    return chrome_async_track_table_;
+  const tables::ProcessTrackTable& process_track_table() const {
+    return process_track_table_;
   }
-  tables::ChromeAsyncTrackTable* mutable_chrome_async_track_table() {
-    return &chrome_async_track_table_;
+  tables::ProcessTrackTable* mutable_process_track_table() {
+    return &process_track_table_;
   }
 
-  const tables::FuchsiaAsyncTrackTable& fuchsia_async_track_table() const {
-    return fuchsia_async_track_table_;
+  const tables::ThreadTrackTable& thread_track_table() const {
+    return thread_track_table_;
   }
-  tables::FuchsiaAsyncTrackTable* mutable_fuchsia_async_track_table() {
-    return &fuchsia_async_track_table_;
+  tables::ThreadTrackTable* mutable_thread_track_table() {
+    return &thread_track_table_;
   }
 
   const Slices& slices() const { return slices_; }
@@ -1316,12 +1322,8 @@
   // Metadata for tracks.
   tables::TrackTable track_table_{&string_pool_, nullptr};
   tables::GpuTrackTable gpu_track_table_{&string_pool_, &track_table_};
-  tables::FuchsiaAsyncTrackTable fuchsia_async_track_table_{&string_pool_,
-                                                            &track_table_};
-  tables::ChromeScopedTrackTable chrome_scoped_track_table_{&string_pool_,
-                                                            &track_table_};
-  tables::ChromeAsyncTrackTable chrome_async_track_table_{
-      &string_pool_, &chrome_scoped_track_table_};
+  tables::ProcessTrackTable process_track_table_{&string_pool_, &track_table_};
+  tables::ThreadTrackTable thread_track_table_{&string_pool_, &track_table_};
 
   // Metadata for gpu tracks.
   GpuContexts gpu_contexts_;
diff --git a/src/trace_processor/track_tracker.cc b/src/trace_processor/track_tracker.cc
index 38e6f0e..58e1690 100644
--- a/src/trace_processor/track_tracker.cc
+++ b/src/trace_processor/track_tracker.cc
@@ -16,22 +16,53 @@
 
 #include "src/trace_processor/track_tracker.h"
 
+#include "src/trace_processor/args_tracker.h"
+
 namespace perfetto {
 namespace trace_processor {
 
+// static
+constexpr TrackId TrackTracker::kDefaultDescriptorTrackUuid;
+
 TrackTracker::TrackTracker(TraceProcessorContext* context)
-    : context_(context) {}
+    : source_key_(context->storage->InternString("source")),
+      source_id_key_(context->storage->InternString("source_id")),
+      source_scope_key_(context->storage->InternString("source_scope")),
+      fuchsia_source_(context->storage->InternString("fuchsia")),
+      chrome_source_(context->storage->InternString("chrome")),
+      android_source_(context->storage->InternString("android")),
+      descriptor_source_(context->storage->InternString("descriptor")),
+      default_descriptor_track_name_(
+          context->storage->InternString("Default Track")),
+      context_(context) {}
 
-TrackId TrackTracker::InternFuchsiaAsyncTrack(
-    const tables::FuchsiaAsyncTrackTable::Row& row) {
-  FuchsiaAsyncTrackTuple tuple{row.correlation_id};
+TrackId TrackTracker::InternThreadTrack(UniqueTid utid) {
+  auto it = thread_tracks_.find(utid);
+  if (it != thread_tracks_.end())
+    return it->second;
 
-  auto it = fuchsia_async_tracks_.find(tuple);
+  tables::ThreadTrackTable::Row row;
+  row.utid = utid;
+  auto id = context_->storage->mutable_thread_track_table()->Insert(row);
+  thread_tracks_[utid] = id;
+  return id;
+}
+
+TrackId TrackTracker::InternFuchsiaAsyncTrack(StringId name,
+                                              int64_t correlation_id) {
+  auto it = fuchsia_async_tracks_.find(correlation_id);
   if (it != fuchsia_async_tracks_.end())
     return it->second;
 
-  auto id = context_->storage->mutable_fuchsia_async_track_table()->Insert(row);
-  fuchsia_async_tracks_[tuple] = id;
+  tables::TrackTable::Row row(name);
+  auto id = context_->storage->mutable_track_table()->Insert(row);
+  fuchsia_async_tracks_[correlation_id] = id;
+
+  RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, id);
+  context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+                                 Variadic::String(fuchsia_source_));
+  context_->args_tracker->AddArg(row_id, source_id_key_, source_id_key_,
+                                 Variadic::Integer(correlation_id));
   return id;
 }
 
@@ -47,29 +78,233 @@
   return id;
 }
 
-TrackId TrackTracker::InternChromeAsyncTrack(
-    const tables::ChromeAsyncTrackTable::Row& row,
+TrackId TrackTracker::InternLegacyChromeAsyncTrack(
+    StringId name,
+    base::Optional<uint32_t> upid,
+    int64_t source_id,
     StringId source_scope) {
   ChromeTrackTuple tuple;
-
-  if (row.upid.has_value()) {
-    tuple.scope = ChromeTrackTuple::Scope::kProcess;
-    tuple.upid = row.upid;
-  } else {
-    tuple.scope = ChromeTrackTuple::Scope::kGlobal;
-  }
-
-  tuple.source_id = row.async_id;
+  tuple.upid = upid;
+  tuple.source_id = source_id;
   tuple.source_scope = source_scope;
 
   auto it = chrome_tracks_.find(tuple);
   if (it != chrome_tracks_.end())
     return it->second;
 
-  auto id = context_->storage->mutable_chrome_async_track_table()->Insert(row);
+  TrackId id;
+  if (upid.has_value()) {
+    tables::ProcessTrackTable::Row track(name);
+    track.upid = *upid;
+    id = context_->storage->mutable_process_track_table()->Insert(track);
+  } else {
+    tables::TrackTable::Row track(name);
+    id = context_->storage->mutable_track_table()->Insert(track);
+  }
   chrome_tracks_[tuple] = id;
+
+  RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, id);
+  context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+                                 Variadic::String(chrome_source_));
+  context_->args_tracker->AddArg(row_id, source_id_key_, source_id_key_,
+                                 Variadic::Integer(source_id));
+  context_->args_tracker->AddArg(row_id, source_scope_key_, source_scope_key_,
+                                 Variadic::String(source_scope));
   return id;
 }
 
+TrackId TrackTracker::InternAndroidAsyncTrack(StringId name,
+                                              UniquePid upid,
+                                              int64_t cookie) {
+  AndroidAsyncTrackTuple tuple{upid, cookie, name};
+
+  auto it = android_async_tracks_.find(tuple);
+  if (it != android_async_tracks_.end())
+    return it->second;
+
+  tables::ProcessTrackTable::Row row(name);
+  row.upid = upid;
+  auto id = context_->storage->mutable_process_track_table()->Insert(row);
+  android_async_tracks_[tuple] = id;
+
+  RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, id);
+  context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+                                 Variadic::String(android_source_));
+  context_->args_tracker->AddArg(row_id, source_id_key_, source_id_key_,
+                                 Variadic::Integer(cookie));
+  return id;
+}
+
+TrackId TrackTracker::InternLegacyChromeProcessInstantTrack(UniquePid upid) {
+  auto it = chrome_process_instant_tracks_.find(upid);
+  if (it != chrome_process_instant_tracks_.end())
+    return it->second;
+
+  tables::ProcessTrackTable::Row row;
+  row.upid = upid;
+  auto id = context_->storage->mutable_process_track_table()->Insert(row);
+  chrome_process_instant_tracks_[upid] = id;
+  return id;
+}
+
+TrackId TrackTracker::GetOrCreateLegacyChromeGlobalInstantTrack() {
+  if (!chrome_global_instant_track_id_) {
+    chrome_global_instant_track_id_ =
+        context_->storage->mutable_track_table()->Insert({});
+  }
+  return *chrome_global_instant_track_id_;
+}
+
+TrackId TrackTracker::UpdateDescriptorTrack(uint64_t uuid,
+                                            StringId name,
+                                            base::Optional<UniquePid> upid,
+                                            base::Optional<UniqueTid> utid) {
+  auto it = descriptor_tracks_.find(uuid);
+  if (it != descriptor_tracks_.end()) {
+    // Update existing track for |uuid|.
+    TrackId track_id = it->second;
+    if (name != kNullStringId) {
+      context_->storage->mutable_track_table()->mutable_name()->Set(track_id,
+                                                                    name);
+    }
+
+#if PERFETTO_DLOG_IS_ON()
+    if (upid) {
+      // Verify that upid didn't change.
+      auto process_track_row =
+          context_->storage->process_track_table().id().IndexOf(
+              SqlValue::Long(track_id));
+      if (!process_track_row) {
+        PERFETTO_DLOG("Can't update non-scoped track with uuid %" PRIu64
+                      " to a scoped track.",
+                      uuid);
+      } else {
+        auto old_upid =
+            context_->storage->process_track_table().upid()[*process_track_row];
+        if (old_upid != upid) {
+          PERFETTO_DLOG("Ignoring upid change for track with uuid %" PRIu64
+                        " from %" PRIu32 " to %" PRIu32 ".",
+                        uuid, old_upid, *upid);
+        }
+      }
+    }
+
+    if (utid) {
+      // Verify that utid didn't change.
+      auto thread_track_row =
+          context_->storage->thread_track_table().id().IndexOf(
+              SqlValue::Long(track_id));
+      if (!thread_track_row) {
+        PERFETTO_DLOG("Can't update non-thread track with uuid %" PRIu64
+                      " to a thread track.",
+                      uuid);
+      } else {
+        auto old_utid =
+            context_->storage->thread_track_table().utid()[*thread_track_row];
+        if (old_utid != utid) {
+          PERFETTO_DLOG("Ignoring utid change for track with uuid %" PRIu64
+                        " from %" PRIu32 " to %" PRIu32 ".",
+                        uuid, old_utid, *utid);
+        }
+      }
+    }
+#endif  // PERFETTO_DLOG_IS_ON()
+
+    return track_id;
+  }
+
+  TrackId track_id;
+
+  if (utid) {
+    // Update existing track for the thread if we have previously created one
+    // in GetOrCreateDescriptorTrackForThread().
+    auto utid_it = descriptor_tracks_by_utid_.find(*utid);
+    if (utid_it != descriptor_tracks_by_utid_.end()) {
+      TrackId candidate_track_id = utid_it->second;
+      // Only update this track if it hasn't been associated with a different
+      // UUID already.
+      auto descriptor_it = std::find_if(
+          descriptor_tracks_.begin(), descriptor_tracks_.end(),
+          [candidate_track_id](const std::pair<uint64_t, TrackId>& entry) {
+            return entry.second == candidate_track_id;
+          });
+      if (descriptor_it == descriptor_tracks_.end()) {
+        descriptor_tracks_[uuid] = candidate_track_id;
+
+        RowId row_id =
+            TraceStorage::CreateRowId(TableId::kTrack, candidate_track_id);
+        context_->args_tracker->AddArg(
+            row_id, source_id_key_, source_id_key_,
+            Variadic::Integer(static_cast<int64_t>(uuid)));
+
+        return candidate_track_id;
+      }
+    }
+
+    // New thread track.
+    tables::ThreadTrackTable::Row row(name);
+    row.utid = *utid;
+    track_id = context_->storage->mutable_thread_track_table()->Insert(row);
+    if (descriptor_tracks_by_utid_.find(*utid) ==
+        descriptor_tracks_by_utid_.end()) {
+      descriptor_tracks_by_utid_[*utid] = track_id;
+    }
+  } else if (upid) {
+    // New process-scoped async track.
+    tables::ProcessTrackTable::Row track(name);
+    track.upid = *upid;
+    track_id = context_->storage->mutable_process_track_table()->Insert(track);
+  } else {
+    // New global async track.
+    tables::TrackTable::Row track(name);
+    track_id = context_->storage->mutable_track_table()->Insert(track);
+  }
+
+  descriptor_tracks_[uuid] = track_id;
+
+  RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, track_id);
+  context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+                                 Variadic::String(descriptor_source_));
+  context_->args_tracker->AddArg(row_id, source_id_key_, source_id_key_,
+                                 Variadic::Integer(static_cast<int64_t>(uuid)));
+
+  return track_id;
+}
+
+base::Optional<TrackId> TrackTracker::GetDescriptorTrack(uint64_t uuid) const {
+  auto it = descriptor_tracks_.find(uuid);
+  if (it == descriptor_tracks_.end())
+    return base::nullopt;
+  return it->second;
+}
+
+TrackId TrackTracker::GetOrCreateDescriptorTrackForThread(UniqueTid utid) {
+  auto it = descriptor_tracks_by_utid_.find(utid);
+  if (it != descriptor_tracks_by_utid_.end()) {
+    return it->second;
+  }
+  // TODO(eseckler): How should this track receive its name?
+  tables::ThreadTrackTable::Row row(/*name=*/kNullStringId);
+  row.utid = utid;
+  TrackId track_id =
+      context_->storage->mutable_thread_track_table()->Insert(row);
+  descriptor_tracks_by_utid_[utid] = track_id;
+
+  RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, track_id);
+  context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+                                 Variadic::String(descriptor_source_));
+  return track_id;
+}
+
+TrackId TrackTracker::GetOrCreateDefaultDescriptorTrack() {
+  base::Optional<TrackId> opt_track_id =
+      GetDescriptorTrack(kDefaultDescriptorTrackUuid);
+  if (opt_track_id)
+    return *opt_track_id;
+
+  return UpdateDescriptorTrack(kDefaultDescriptorTrackUuid,
+                               default_descriptor_track_name_);
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/track_tracker.h b/src/trace_processor/track_tracker.h
index 6dd3872..7274edc 100644
--- a/src/trace_processor/track_tracker.h
+++ b/src/trace_processor/track_tracker.h
@@ -28,26 +28,53 @@
  public:
   explicit TrackTracker(TraceProcessorContext*);
 
-  // Interns a given Fuchsia async track into the storage.
-  TrackId InternFuchsiaAsyncTrack(
-      const tables::FuchsiaAsyncTrackTable::Row& row);
+  // Interns a thread track into the storage.
+  TrackId InternThreadTrack(UniqueTid utid);
+
+  // Interns a Fuchsia async track into the storage.
+  TrackId InternFuchsiaAsyncTrack(StringId name, int64_t correlation_id);
 
   // Interns a given GPU track into the storage.
   TrackId InternGpuTrack(const tables::GpuTrackTable::Row& row);
 
-  // Interns a given Chrome async track into the storage.
-  TrackId InternChromeAsyncTrack(const tables::ChromeAsyncTrackTable::Row& row,
-                                 StringId source_scope);
+  // Interns a legacy Chrome async event track into the storage.
+  TrackId InternLegacyChromeAsyncTrack(StringId name,
+                                       base::Optional<UniquePid> upid,
+                                       int64_t source_id,
+                                       StringId source_scope);
+
+  // Interns a Android async track into the storage.
+  TrackId InternAndroidAsyncTrack(StringId name,
+                                  UniquePid upid,
+                                  int64_t cookie);
+
+  // Interns a track for legacy Chrome process-scoped instant events into the
+  // storage.
+  TrackId InternLegacyChromeProcessInstantTrack(UniquePid upid);
+
+  // Lazily creates the track for legacy Chrome global instant events.
+  TrackId GetOrCreateLegacyChromeGlobalInstantTrack();
+
+  // Create or update the track for the TrackDescriptor with the given |uuid|.
+  // Optionally, associate the track with a process or thread.
+  TrackId UpdateDescriptorTrack(uint64_t uuid,
+                                StringId name,
+                                base::Optional<UniquePid> upid = base::nullopt,
+                                base::Optional<UniqueTid> utid = base::nullopt);
+
+  // Returns the ID of the track for the TrackDescriptor with the given |uuid|.
+  // Returns nullopt if no TrackDescriptor with this |uuid| has been parsed yet.
+  base::Optional<TrackId> GetDescriptorTrack(uint64_t uuid) const;
+
+  // Returns the ID of the TrackDescriptor track associated with the given utid.
+  // If the trace contained multiple tracks associated with the utid, the first
+  // created track is returned. Creates a new track if no such track exists.
+  TrackId GetOrCreateDescriptorTrackForThread(UniqueTid utid);
+
+  // Returns the ID of the implicit trace-global default TrackDescriptor track.
+  TrackId GetOrCreateDefaultDescriptorTrack();
 
  private:
-  struct FuchsiaAsyncTrackTuple {
-    int64_t correlation_id;
-
-    friend bool operator<(const FuchsiaAsyncTrackTuple& l,
-                          const FuchsiaAsyncTrackTuple& r) {
-      return std::tie(l.correlation_id) < std::tie(r.correlation_id);
-    }
-  };
   struct GpuTrackTuple {
     StringId track_name;
     StringId scope;
@@ -59,26 +86,51 @@
     }
   };
   struct ChromeTrackTuple {
-    enum class Scope : uint32_t {
-      kGlobal = 0,
-      kProcess,
-    };
-
-    Scope scope;
     base::Optional<int64_t> upid;
     int64_t source_id = 0;
     StringId source_scope = 0;
 
     friend bool operator<(const ChromeTrackTuple& l,
                           const ChromeTrackTuple& r) {
-      return std::tie(l.source_id, l.scope, l.upid, l.source_scope) <
-             std::tie(r.source_id, r.scope, r.upid, r.source_scope);
+      return std::tie(l.source_id, l.upid, l.source_scope) <
+             std::tie(r.source_id, r.upid, r.source_scope);
+    }
+  };
+  struct AndroidAsyncTrackTuple {
+    UniquePid upid;
+    int64_t cookie;
+    StringId name;
+
+    friend bool operator<(const AndroidAsyncTrackTuple& l,
+                          const AndroidAsyncTrackTuple& r) {
+      return std::tie(l.upid, l.cookie, l.name) <
+             std::tie(r.upid, r.cookie, r.name);
     }
   };
 
-  std::map<FuchsiaAsyncTrackTuple, TrackId> fuchsia_async_tracks_;
+  static constexpr TrackId kDefaultDescriptorTrackUuid = 0u;
+
+  std::map<UniqueTid /* utid */, TrackId> thread_tracks_;
+  std::map<int64_t /* correlation_id */, TrackId> fuchsia_async_tracks_;
   std::map<GpuTrackTuple, TrackId> gpu_tracks_;
   std::map<ChromeTrackTuple, TrackId> chrome_tracks_;
+  std::map<AndroidAsyncTrackTuple, TrackId> android_async_tracks_;
+  std::map<UniquePid, TrackId> chrome_process_instant_tracks_;
+  base::Optional<TrackId> chrome_global_instant_track_id_;
+  std::map<uint64_t /* uuid */, TrackId> descriptor_tracks_;
+  std::map<UniqueTid, TrackId> descriptor_tracks_by_utid_;
+
+  const StringId source_key_ = 0;
+  const StringId source_id_key_ = 0;
+  const StringId source_scope_key_ = 0;
+
+  const StringId fuchsia_source_ = 0;
+  const StringId chrome_source_ = 0;
+  const StringId android_source_ = 0;
+  const StringId descriptor_source_ = 0;
+
+  const StringId default_descriptor_track_name_ = 0;
+
   TraceProcessorContext* const context_;
 };
 
diff --git a/src/traced/service/builtin_producer.cc b/src/traced/service/builtin_producer.cc
index b11da94..16df2e1 100644
--- a/src/traced/service/builtin_producer.cc
+++ b/src/traced/service/builtin_producer.cc
@@ -66,13 +66,17 @@
   metatrace_dsd.set_will_notify_on_stop(true);
   endpoint_->RegisterDataSource(metatrace_dsd);
 
-  DataSourceDescriptor lazy_heapprofd_dsd;
-  lazy_heapprofd_dsd.set_name(kHeapprofdDataSourceName);
-  endpoint_->RegisterDataSource(lazy_heapprofd_dsd);
+  {
+    DataSourceDescriptor lazy_heapprofd_dsd;
+    lazy_heapprofd_dsd.set_name(kHeapprofdDataSourceName);
+    endpoint_->RegisterDataSource(lazy_heapprofd_dsd);
+  }
 
-  DataSourceDescriptor lazy_java_hprof_dsd;
-  lazy_heapprofd_dsd.set_name(kJavaHprofDataSourceName);
-  endpoint_->RegisterDataSource(lazy_java_hprof_dsd);
+  {
+    DataSourceDescriptor lazy_java_hprof_dsd;
+    lazy_java_hprof_dsd.set_name(kJavaHprofDataSourceName);
+    endpoint_->RegisterDataSource(lazy_java_hprof_dsd);
+  }
 }
 
 void BuiltinProducer::SetupDataSource(DataSourceInstanceID ds_id,
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index 5a93bbc..fcb67cb 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -252,6 +252,7 @@
     "internal/in_process_tracing_backend.cc",
     "internal/in_process_tracing_backend.h",
     "internal/tracing_muxer_impl.cc",
+    "internal/tracing_muxer_impl.h",
     "platform.cc",
     "tracing.cc",
     "track_event.cc",
diff --git a/test/configs/java_hprof.cfg b/test/configs/java_hprof.cfg
index a989d2c..652412c 100644
--- a/test/configs/java_hprof.cfg
+++ b/test/configs/java_hprof.cfg
@@ -7,7 +7,7 @@
   config {
     name: "android.java_hprof"
     java_hprof_config {
-      process_cmdline: "system_server"
+      process_cmdline: "com.google.android.inputmethod.latin"
     }
   }
 }
diff --git a/test/trace_processor/android_async_slice.textproto b/test/trace_processor/android_async_slice.textproto
new file mode 100644
index 0000000..787d929
--- /dev/null
+++ b/test/trace_processor/android_async_slice.textproto
@@ -0,0 +1,26 @@
+packet {
+  ftrace_events {
+    cpu: 3
+    event {
+      timestamp: 74289018336
+      pid: 4064
+      print {
+        ip: 18446743562018522420
+        buf: "S|1204|launching: com.android.chrome|0\n"
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 2
+    event {
+      timestamp: 74662603008
+      pid: 1257
+      print {
+        ip: 18446743562018522420
+        buf: "F|1204|launching: com.android.chrome|0\n"
+      }
+    }
+  }
+}
diff --git a/test/trace_processor/index b/test/trace_processor/index
index 41ef5c2..7bb14f4 100644
--- a/test/trace_processor/index
+++ b/test/trace_processor/index
@@ -69,6 +69,9 @@
 # Power rails
 ../data/power_rails.pb power_rails.sql power_rails_power_rails.out
 
+# Android userspace async slices
+android_async_slice.textproto process_track_slices.sql process_track_slices_android_async_slice.out
+
 
 # The below tests check the autogenerated tables.
 # Span join
@@ -120,3 +123,11 @@
 
 # Graphics frame event trace tests.
 graphics_frame_events.py graphics_frame_events.sql graphics_frame_events.out
+
+# Scheduling slices from sched_switch events. There are two tests, one for the
+# typical encoding of sched_switch events, and one for the same trace
+# re-encoded in the compact format. The output should be identical apart from
+# the latter having one slice fewer for each cpu (the first compact
+# sched_switch event doesn't start a slice). Six slices in this case.
+../data/sched_switch_original.pb sched_slices.sql sched_slices_sched_switch_original.out
+../data/sched_switch_compact.pb sched_slices.sql sched_slices_sched_switch_compact.out
diff --git a/test/trace_processor/process_track_slices.sql b/test/trace_processor/process_track_slices.sql
new file mode 100644
index 0000000..b84557f
--- /dev/null
+++ b/test/trace_processor/process_track_slices.sql
@@ -0,0 +1,9 @@
+SELECT
+  ts,
+  dur,
+  pid,
+  slice.name as slice_name,
+  process_track.name as track_name
+FROM slice
+INNER JOIN process_track ON slice.ref = process_track.id
+INNER JOIN process USING (upid)
diff --git a/test/trace_processor/process_track_slices_android_async_slice.out b/test/trace_processor/process_track_slices_android_async_slice.out
new file mode 100644
index 0000000..a30a8f0
--- /dev/null
+++ b/test/trace_processor/process_track_slices_android_async_slice.out
@@ -0,0 +1,2 @@
+"ts","dur","pid","slice_name","track_name"
+74289018336,373584672,1204,"launching: com.android.chrome","launching: com.android.chrome"
diff --git a/test/trace_processor/sched_slices.sql b/test/trace_processor/sched_slices.sql
new file mode 100644
index 0000000..1e808d5
--- /dev/null
+++ b/test/trace_processor/sched_slices.sql
@@ -0,0 +1,3 @@
+SELECT ts, cpu, dur, ts_end, end_state, priority, tid, name
+FROM sched JOIN thread ON sched.utid == thread.utid
+ORDER BY cpu, sched.ts ASC;
diff --git a/test/trace_processor/sched_slices_sched_switch_compact.out b/test/trace_processor/sched_slices_sched_switch_compact.out
new file mode 100644
index 0000000..c570299
--- /dev/null
+++ b/test/trace_processor/sched_slices_sched_switch_compact.out
@@ -0,0 +1,77 @@
+"ts","cpu","dur","ts_end","end_state","priority","tid","name"
+807082865097350,0,6629324,807082871726674,"R",120,0,"swapper/1"
+807082871726674,0,38229,807082871764903,"S",120,7,"rcu_preempt"
+807082871764903,0,40521,807082871805424,"S",120,45,"rcuop/4"
+807082871805424,0,29896,807082871835320,"S",120,7,"rcu_preempt"
+807082871835320,0,6778282,807082878613602,"R",120,0,"swapper/1"
+807082878613602,0,35781,807082878649383,"S",120,7,"rcu_preempt"
+807082878649383,0,216562,807082878865945,"S",120,45,"rcuop/4"
+807082878865945,0,135625,807082879001570,"D",120,22367,"kworker/u16:9"
+807082879001570,0,57917,807082879059487,"R",120,0,"swapper/1"
+807082879059487,0,120052,807082879179539,"S",120,22367,"kworker/u16:9"
+807082879179539,0,68398757,807082947578296,"R",120,0,"swapper/1"
+807082947578296,0,49271,807082947627567,"S",120,28673,"kworker/0:0"
+807082947627567,0,11515782,807082959143349,"R",120,0,"swapper/1"
+807082959143349,0,119167,807082959262516,"S",120,6,"ksoftirqd/0"
+807082959262516,0,126094,807082959388610,"S",120,22367,"kworker/u16:9"
+807082959388610,0,71435840,807083030824450,"R",120,0,"swapper/1"
+807083030824450,0,1662865,807083032487315,"S",120,29206,"traced"
+807083032487315,0,593281,807083033080596,"R",120,29207,"traced_probes"
+807082863108704,1,42240,807082863150944,"R",120,0,"swapper/1"
+807082863150944,1,42760,807082863193704,"S",49,4454,"irq/80-1436400."
+807082863193704,1,1931823,807082865125527,"R",120,0,"swapper/1"
+807082865125527,1,159532,807082865285059,"D",49,4458,"irq/81-114a000."
+807082865285059,1,34375,807082865319434,"R",120,0,"swapper/1"
+807082865319434,1,38333,807082865357767,"S",49,4458,"irq/81-114a000."
+807082865357767,1,82176415,807082947534182,"R",120,0,"swapper/1"
+807082947534182,1,56875,807082947591057,"S",120,28610,"kworker/1:0"
+807082947591057,1,15142918,807082962733975,"R",120,0,"swapper/1"
+807082962733975,1,77448,807082962811423,"S",120,18,"ksoftirqd/1"
+807082962811423,1,5196563,807082968007986,"R",120,0,"swapper/1"
+807082968007986,1,268437,807082968276423,"S",98,4009,"bluetooth@1.0-s"
+807082968276423,1,134688,807082968411111,"R",120,0,"swapper/1"
+807082968411111,1,116718,807082968527829,"S",120,22367,"kworker/u16:9"
+807082968527829,1,62510684,807083031038513,"R",120,0,"swapper/1"
+807083031038513,1,840416,807083031878929,"R+",120,22367,"kworker/u16:9"
+807083031878929,1,147032,807083032025961,"S",120,10,"rcuop/0"
+807083032025961,1,54218,807083032080179,"S",120,7,"rcu_preempt"
+807083032080179,1,455834,807083032536013,"D",120,22367,"kworker/u16:9"
+807083032536013,1,162604,807083032698617,"R",120,0,"swapper/1"
+807083032698617,1,276146,807083032974763,"D",120,22367,"kworker/u16:9"
+807083032974763,1,62396,807083033037159,"R",120,0,"swapper/1"
+807083033037159,1,43437,807083033080596,"S",120,22367,"kworker/u16:9"
+807083033080596,1,0,807083033080596,"R",120,0,"swapper/1"
+807082865061361,2,399583,807082865460944,"D",120,22367,"kworker/u16:9"
+807082865460944,2,46771,807082865507715,"R",120,0,"swapper/1"
+807082865507715,2,30312,807082865538027,"S",120,22367,"kworker/u16:9"
+807082865538027,2,2811459,807082868349486,"R",120,0,"swapper/1"
+807082868349486,2,60417,807082868409903,"S",120,8,"rcu_sched"
+807082868409903,2,31302,807082868441205,"S",120,46,"rcuos/4"
+807082868441205,2,78727456,807082947168661,"R",120,0,"swapper/1"
+807082947168661,2,296771,807082947465432,"S",49,627,"sugov:0"
+807082947465432,2,30989,807082947496421,"S",120,28887,"kworker/2:2"
+807082947496421,2,96927,807082947593348,"S",120,45,"rcuop/4"
+807082947593348,2,692344,807082948285692,"D",120,22367,"kworker/u16:9"
+807082948285692,2,363438,807082948649130,"R",120,0,"swapper/1"
+807082948649130,2,185052,807082948834182,"D",120,22367,"kworker/u16:9"
+807082948834182,2,357239,807082949191421,"R",120,0,"swapper/1"
+807082949191421,2,55677,807082949247098,"S",120,22367,"kworker/u16:9"
+807082949247098,2,83833498,807083033080596,"R",120,0,"swapper/1"
+807082947555119,3,37083,807082947592202,"R",120,0,"swapper/1"
+807082947592202,3,38177,807082947630379,"S",120,7,"rcu_preempt"
+807082947630379,3,4172813,807082951803192,"R",120,0,"swapper/1"
+807082951803192,3,83698,807082951886890,"S",120,7,"rcu_preempt"
+807082951886890,3,40000,807082951926890,"S",120,45,"rcuop/4"
+807082951926890,3,81153706,807083033080596,"R",120,0,"swapper/1"
+807082862976152,4,14792,807082862990944,"S",120,29207,"traced_probes"
+807082862990944,4,83901102,807082946892046,"R",120,0,"swapper/1"
+807082946892046,4,327500,807082947219546,"S",120,29207,"traced_probes"
+807082947219546,4,15052,807082947234598,"R",120,0,"swapper/1"
+807082947234598,4,68698,807082947303296,"S",120,42,"ksoftirqd/4"
+807082947303296,4,28958,807082947332254,"S",120,21092,"kworker/4:1"
+807082947332254,4,83363707,807083030695961,"R",120,0,"swapper/1"
+807083030695961,4,104895,807083030800856,"S",120,42,"ksoftirqd/4"
+807083030800856,4,2279740,807083033080596,"R",120,0,"swapper/1"
+807082952627307,7,1534375,807082954161682,"R",120,0,"swapper/1"
+807082954161682,7,43490,807082954205172,"S",49,628,"sugov:4"
+807082954205172,7,78875424,807083033080596,"R",120,0,"swapper/1"
diff --git a/test/trace_processor/sched_slices_sched_switch_original.out b/test/trace_processor/sched_slices_sched_switch_original.out
new file mode 100644
index 0000000..fe6f0c5
--- /dev/null
+++ b/test/trace_processor/sched_slices_sched_switch_original.out
@@ -0,0 +1,83 @@
+"ts","cpu","dur","ts_end","end_state","priority","tid","name"
+807082865062402,0,34948,807082865097350,"S",120,7,"rcu_preempt"
+807082865097350,0,6629324,807082871726674,"R",120,0,"swapper/1"
+807082871726674,0,38229,807082871764903,"S",120,7,"rcu_preempt"
+807082871764903,0,40521,807082871805424,"S",120,45,"rcuop/4"
+807082871805424,0,29896,807082871835320,"S",120,7,"rcu_preempt"
+807082871835320,0,6778282,807082878613602,"R",120,0,"swapper/1"
+807082878613602,0,35781,807082878649383,"S",120,7,"rcu_preempt"
+807082878649383,0,216562,807082878865945,"S",120,45,"rcuop/4"
+807082878865945,0,135625,807082879001570,"D",120,22367,"kworker/u16:9"
+807082879001570,0,57917,807082879059487,"R",120,0,"swapper/1"
+807082879059487,0,120052,807082879179539,"S",120,22367,"kworker/u16:9"
+807082879179539,0,68398757,807082947578296,"R",120,0,"swapper/1"
+807082947578296,0,49271,807082947627567,"S",120,28673,"kworker/0:0"
+807082947627567,0,11515782,807082959143349,"R",120,0,"swapper/1"
+807082959143349,0,119167,807082959262516,"S",120,6,"ksoftirqd/0"
+807082959262516,0,126094,807082959388610,"S",120,22367,"kworker/u16:9"
+807082959388610,0,71435840,807083030824450,"R",120,0,"swapper/1"
+807083030824450,0,1662865,807083032487315,"S",120,29206,"traced"
+807083032487315,0,593281,807083033080596,"R",120,29207,"traced_probes"
+807082862950996,1,157708,807082863108704,"D",49,4454,"irq/80-1436400."
+807082863108704,1,42240,807082863150944,"R",120,0,"swapper/1"
+807082863150944,1,42760,807082863193704,"S",49,4454,"irq/80-1436400."
+807082863193704,1,1931823,807082865125527,"R",120,0,"swapper/1"
+807082865125527,1,159532,807082865285059,"D",49,4458,"irq/81-114a000."
+807082865285059,1,34375,807082865319434,"R",120,0,"swapper/1"
+807082865319434,1,38333,807082865357767,"S",49,4458,"irq/81-114a000."
+807082865357767,1,82176415,807082947534182,"R",120,0,"swapper/1"
+807082947534182,1,56875,807082947591057,"S",120,28610,"kworker/1:0"
+807082947591057,1,15142918,807082962733975,"R",120,0,"swapper/1"
+807082962733975,1,77448,807082962811423,"S",120,18,"ksoftirqd/1"
+807082962811423,1,5196563,807082968007986,"R",120,0,"swapper/1"
+807082968007986,1,268437,807082968276423,"S",98,4009,"bluetooth@1.0-s"
+807082968276423,1,134688,807082968411111,"R",120,0,"swapper/1"
+807082968411111,1,116718,807082968527829,"S",120,22367,"kworker/u16:9"
+807082968527829,1,62510684,807083031038513,"R",120,0,"swapper/1"
+807083031038513,1,840416,807083031878929,"R+",120,22367,"kworker/u16:9"
+807083031878929,1,147032,807083032025961,"S",120,10,"rcuop/0"
+807083032025961,1,54218,807083032080179,"S",120,7,"rcu_preempt"
+807083032080179,1,455834,807083032536013,"D",120,22367,"kworker/u16:9"
+807083032536013,1,162604,807083032698617,"R",120,0,"swapper/1"
+807083032698617,1,276146,807083032974763,"D",120,22367,"kworker/u16:9"
+807083032974763,1,62396,807083033037159,"R",120,0,"swapper/1"
+807083033037159,1,43437,807083033080596,"S",120,22367,"kworker/u16:9"
+807083033080596,1,0,807083033080596,"R",120,0,"swapper/1"
+807082862645371,2,2415990,807082865061361,"R",120,0,"swapper/1"
+807082865061361,2,399583,807082865460944,"D",120,22367,"kworker/u16:9"
+807082865460944,2,46771,807082865507715,"R",120,0,"swapper/1"
+807082865507715,2,30312,807082865538027,"S",120,22367,"kworker/u16:9"
+807082865538027,2,2811459,807082868349486,"R",120,0,"swapper/1"
+807082868349486,2,60417,807082868409903,"S",120,8,"rcu_sched"
+807082868409903,2,31302,807082868441205,"S",120,46,"rcuos/4"
+807082868441205,2,78727456,807082947168661,"R",120,0,"swapper/1"
+807082947168661,2,296771,807082947465432,"S",49,627,"sugov:0"
+807082947465432,2,30989,807082947496421,"S",120,28887,"kworker/2:2"
+807082947496421,2,96927,807082947593348,"S",120,45,"rcuop/4"
+807082947593348,2,692344,807082948285692,"D",120,22367,"kworker/u16:9"
+807082948285692,2,363438,807082948649130,"R",120,0,"swapper/1"
+807082948649130,2,185052,807082948834182,"D",120,22367,"kworker/u16:9"
+807082948834182,2,357239,807082949191421,"R",120,0,"swapper/1"
+807082949191421,2,55677,807082949247098,"S",120,22367,"kworker/u16:9"
+807082949247098,2,83833498,807083033080596,"R",120,0,"swapper/1"
+807082947493504,3,61615,807082947555119,"S",120,28905,"kworker/3:0"
+807082947555119,3,37083,807082947592202,"R",120,0,"swapper/1"
+807082947592202,3,38177,807082947630379,"S",120,7,"rcu_preempt"
+807082947630379,3,4172813,807082951803192,"R",120,0,"swapper/1"
+807082951803192,3,83698,807082951886890,"S",120,7,"rcu_preempt"
+807082951886890,3,40000,807082951926890,"S",120,45,"rcuop/4"
+807082951926890,3,81153706,807083033080596,"R",120,0,"swapper/1"
+807082862918652,4,57500,807082862976152,"S",120,29206,"traced"
+807082862976152,4,14792,807082862990944,"S",120,29207,"traced_probes"
+807082862990944,4,83901102,807082946892046,"R",120,0,"swapper/1"
+807082946892046,4,327500,807082947219546,"S",120,29207,"traced_probes"
+807082947219546,4,15052,807082947234598,"R",120,0,"swapper/1"
+807082947234598,4,68698,807082947303296,"S",120,42,"ksoftirqd/4"
+807082947303296,4,28958,807082947332254,"S",120,21092,"kworker/4:1"
+807082947332254,4,83363707,807083030695961,"R",120,0,"swapper/1"
+807083030695961,4,104895,807083030800856,"S",120,42,"ksoftirqd/4"
+807083030800856,4,2279740,807083033080596,"R",120,0,"swapper/1"
+807082952486057,7,141250,807082952627307,"D",49,628,"sugov:4"
+807082952627307,7,1534375,807082954161682,"R",120,0,"swapper/1"
+807082954161682,7,43490,807082954205172,"S",49,628,"sugov:4"
+807082954205172,7,78875424,807083033080596,"R",120,0,"swapper/1"
diff --git a/tools/BUILD.gn b/tools/BUILD.gn
index e6c1f89..19313b5 100644
--- a/tools/BUILD.gn
+++ b/tools/BUILD.gn
@@ -23,6 +23,7 @@
   deps = [
     ":copy_protoc",
     ":idle_alloc",
+    "compact_reencode",
     "ftrace_proto_gen",
     "proto_to_cpp",
     "protoprofile",
diff --git a/tools/compact_reencode/BUILD.gn b/tools/compact_reencode/BUILD.gn
new file mode 100644
index 0000000..6556b9d
--- /dev/null
+++ b/tools/compact_reencode/BUILD.gn
@@ -0,0 +1,37 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../gn/perfetto_host_executable.gni")
+
+perfetto_host_executable("compact_reencode") {
+  testonly = true
+  deps = [
+    ":common",
+    "../../gn:default_deps",
+  ]
+}
+
+source_set("common") {
+  testonly = true
+  public_deps = [
+    "../../gn:default_deps",
+    "../../include/perfetto/protozero",
+    "../../protos/perfetto/trace:zero",
+    "../../protos/perfetto/trace/ftrace:zero",
+    "../../src/base",
+  ]
+  sources = [
+    "main.cc",
+  ]
+}
diff --git a/tools/compact_reencode/main.cc b/tools/compact_reencode/main.cc
new file mode 100644
index 0000000..139896d
--- /dev/null
+++ b/tools/compact_reencode/main.cc
@@ -0,0 +1,183 @@
+/*
+ * 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 <stdint.h>
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/protozero/proto_utils.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+// Re-encodes the given trace, converting sched events to their compact
+// representation.
+// Note: doesn't do bundle splitting/merging, the original trace must already
+// have multi-page bundles for the re-encoding to be realistic.
+
+namespace perfetto {
+namespace compact_reencode {
+namespace {
+
+void WriteToFile(const std::string& out, const char* path) {
+  PERFETTO_CHECK(!unlink(path) || errno == ENOENT);
+  auto out_fd = base::OpenFile(path, O_RDWR | O_CREAT, 0666);
+  if (!out_fd || base::WriteAll(out_fd.get(), out.data(), out.size()) !=
+                     static_cast<ssize_t>(out.size())) {
+    PERFETTO_FATAL("WriteToFile");
+  }
+}
+
+static void CopyField(protozero::Message* out, const protozero::Field& field) {
+  using protozero::proto_utils::ProtoWireType;
+  if (field.type() == ProtoWireType::kVarInt) {
+    out->AppendVarInt(field.id(), field.as_uint64());
+  } else if (field.type() == ProtoWireType::kLengthDelimited) {
+    out->AppendBytes(field.id(), field.as_bytes().data, field.as_bytes().size);
+  } else if (field.type() == ProtoWireType::kFixed32) {
+    out->AppendFixed(field.id(), field.as_uint32());
+  } else if (field.type() == ProtoWireType::kFixed64) {
+    out->AppendFixed(field.id(), field.as_uint64());
+  } else {
+    PERFETTO_FATAL("unexpected wire type");
+  }
+}
+
+void ReEncodeBundle(protos::pbzero::TracePacket* packet_out,
+                    const uint8_t* data,
+                    size_t size) {
+  protos::pbzero::FtraceEventBundle::Decoder bundle(data, size);
+  auto* bundle_out = packet_out->set_ftrace_events();
+
+  if (bundle.has_lost_events())
+    bundle_out->set_lost_events(bundle.lost_events());
+  if (bundle.has_cpu())
+    bundle_out->set_cpu(bundle.cpu());
+
+  static constexpr size_t kMaxElements = 2560;
+  protozero::StackAllocated<protozero::PackedVarIntBuffer, kMaxElements>
+      switch_timestamp;
+  protozero::StackAllocated<protozero::PackedVarIntBuffer, kMaxElements>
+      switch_prev_state;
+  protozero::StackAllocated<protozero::PackedVarIntBuffer, kMaxElements>
+      switch_next_pid;
+  protozero::StackAllocated<protozero::PackedVarIntBuffer, kMaxElements>
+      switch_next_prio;
+  protozero::StackAllocated<protozero::PackedVarIntBuffer, kMaxElements>
+      switch_next_comm_index;
+
+  uint64_t last_switch_timestamp = 0;
+
+  std::vector<std::string> string_table;
+  auto intern = [&string_table](std::string str) {
+    for (size_t i = 0; i < string_table.size(); i++) {
+      if (str == string_table[i])
+        return static_cast<uint32_t>(i);
+    }
+    size_t new_idx = string_table.size();
+    string_table.push_back(str);
+    return static_cast<uint32_t>(new_idx);
+  };
+
+  for (auto event_it = bundle.event(); event_it; ++event_it) {
+    protos::pbzero::FtraceEvent::Decoder event(event_it->data(),
+                                               event_it->size());
+    if (!event.has_sched_switch()) {
+      CopyField(bundle_out, *event_it);
+    } else {
+      switch_timestamp.Append(event.timestamp() - last_switch_timestamp);
+      last_switch_timestamp = event.timestamp();
+
+      protos::pbzero::SchedSwitchFtraceEvent::Decoder sswitch(
+          event.sched_switch());
+
+      auto iid = intern(sswitch.next_comm().ToStdString());
+      switch_next_comm_index.Append(iid);
+
+      switch_next_pid.Append(sswitch.next_pid());
+      switch_next_prio.Append(sswitch.next_prio());
+      switch_prev_state.Append(sswitch.prev_state());
+    }
+  }
+  auto* compact_sched = bundle_out->set_compact_sched();
+
+  for (const auto& s : string_table)
+    compact_sched->add_switch_next_comm_table(s.data(), s.size());
+
+  compact_sched->set_switch_timestamp(switch_timestamp);
+  compact_sched->set_switch_next_comm_index(switch_next_comm_index);
+  compact_sched->set_switch_next_pid(switch_next_pid);
+  compact_sched->set_switch_next_prio(switch_next_prio);
+  compact_sched->set_switch_prev_state(switch_prev_state);
+}
+
+std::string ReEncode(const std::string& raw) {
+  protos::pbzero::Trace::Decoder trace(raw);
+  protozero::HeapBuffered<protos::pbzero::Trace> output;
+
+  for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+    const auto* data = packet_it->data();
+    size_t size = packet_it->size();
+    protozero::ProtoDecoder packet(data, size);
+    protos::pbzero::TracePacket* packet_out = output->add_packet();
+
+    for (auto field = packet.ReadField(); field.valid();
+         field = packet.ReadField()) {
+      if (field.id() == protos::pbzero::TracePacket::kFtraceEventsFieldNumber) {
+        ReEncodeBundle(packet_out, field.data(), field.size());
+      } else {
+        CopyField(packet_out, field);
+      }
+    }
+  }
+  // Minor technicality: we will be a tiny bit off the real encoding since
+  // we've encoded the top-level Trace & TracePacket sizes redundantly, while
+  // the tracing service writes them as a minimal varint (so only a few bytes
+  // off per trace packet).
+  return output.SerializeAsString();
+}
+
+int Main(int argc, const char** argv) {
+  if (argc < 3) {
+    PERFETTO_LOG("Usage: %s input output", argv[0]);
+    return 1;
+  }
+  const char* in_path = argv[1];
+  const char* out_path = argv[2];
+
+  std::string raw;
+  if (!base::ReadFile(in_path, &raw)) {
+    PERFETTO_PLOG("ReadFile");
+    return 1;
+  }
+
+  std::string raw_out = ReEncode(raw);
+  WriteToFile(raw_out, out_path);
+  return 0;
+}
+
+}  // namespace
+}  // namespace compact_reencode
+}  // namespace perfetto
+
+int main(int argc, const char** argv) {
+  return perfetto::compact_reencode::Main(argc, argv);
+}
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 89d7aa7..a633bbb 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -82,6 +82,7 @@
 
 target_host_supported = [
     '//protos/perfetto/trace:perfetto_trace_protos',
+    '//:libperfetto',
 ]
 
 target_host_only = [
diff --git a/tools/gen_bazel b/tools/gen_bazel
index a8643ec..09fd086 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -47,6 +47,7 @@
 
 # These targets will be exported with public visibility in the generated BUILD.
 public_targets = [
+    '//:libperfetto_client_experimental',
     '//src/perfetto_cmd:perfetto',
     '//src/traced/probes:traced_probes',
     '//src/traced/service:traced',
@@ -66,8 +67,10 @@
 ] + public_targets
 
 # Root proto targets (to force discovery of intermediate proto targets).
+# These targets are marked public.
 proto_targets = [
     '//protos/perfetto/trace:merged_trace',
+    '//protos/perfetto/config:merged_config',
     '//protos/perfetto/metrics:lite',
     '//protos/perfetto/trace:lite',
     '//protos/perfetto/config:lite',
@@ -230,6 +233,9 @@
   deps = [':' + get_sources_label(x) for x in target.proto_deps]
   sources_label.deps = sorted(deps)
 
+  if target.name in proto_targets:
+    sources_label.visibility = ['//visibility:public']
+
   return [plugin_label, sources_label]
 
 
diff --git a/tools/heap_profile b/tools/heap_profile
index 3938b64..5680a68 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -22,6 +22,7 @@
 import atexit
 import hashlib
 import os
+import shutil
 import signal
 import subprocess
 import sys
@@ -342,6 +343,7 @@
   if os.path.lexists(symlink_path):
     os.unlink(symlink_path)
   os.symlink(profile_path, symlink_path)
+  shutil.copyfile('/tmp/profile', os.path.join(profile_path, 'raw-trace'))
 
   print("Wrote profiles to {} (symlink {})".format(profile_path, symlink_path))
   print("These can be viewed using pprof. Googlers: head to pprof/ and "
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 166fe0b..f96a19b 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -192,8 +192,8 @@
 
   # Example traces for regression tests.
   ('buildtools/test_data.zip',
-   'https://storage.googleapis.com/perfetto/test-data-20190830-110949.zip',
-   '875db1f3da7574745d8abc593bc696c2abf6ee2d',
+   'https://storage.googleapis.com/perfetto/test-data-20190920-011709.zip',
+   '75fc970bf2778d506c9d77f94a6381b04f10b692',
    'all',
   ),
 
diff --git a/tools/trace_to_text/BUILD.gn b/tools/trace_to_text/BUILD.gn
index 485954d..44126e7 100644
--- a/tools/trace_to_text/BUILD.gn
+++ b/tools/trace_to_text/BUILD.gn
@@ -37,6 +37,9 @@
 }
 
 source_set("utils") {
+  deps = [
+    ":symbolizer",
+  ]
   public_deps = [
     "../../gn:default_deps",
     "../../include/perfetto/base",
@@ -49,8 +52,6 @@
     "../../src/trace_processor:lib",
   ]
   sources = [
-    "profile_visitor.cc",
-    "profile_visitor.h",
     "utils.cc",
     "utils.h",
   ]
@@ -77,7 +78,6 @@
 
 source_set("symbolizer") {
   public_deps = [
-    ":utils",
     "../../gn:default_deps",
     "../../include/perfetto/base",
     "../../include/perfetto/ext/base",
@@ -90,8 +90,6 @@
   ]
   sources = [
     "symbolizer.cc",
-    "trace_symbol_table.cc",
-    "trace_symbol_table.h",
   ]
 }
 
@@ -128,6 +126,7 @@
     ":pprofbuilder",
     ":symbolizer",
     ":utils",
+    "../../gn:zlib",
   ]
   public_deps = [
     "../../gn:default_deps",
diff --git a/tools/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index 225bf70..52ff38e 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -20,10 +20,10 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "tools/trace_to_text/symbolize_profile.h"
 #include "tools/trace_to_text/trace_to_profile.h"
 #include "tools/trace_to_text/trace_to_systrace.h"
 #include "tools/trace_to_text/trace_to_text.h"
-#include "tools/trace_to_text/symbolize_profile.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
 #include "perfetto_version.gen.h"
@@ -31,22 +31,22 @@
 #define PERFETTO_GET_GIT_REVISION() "unknown"
 #endif
 
+namespace perfetto {
+namespace trace_to_text {
 namespace {
 
 int Usage(const char* argv0) {
   printf(
-      "Usage: %s systrace|json|text|profile [--truncate start|end] [trace.pb] "
+      "Usage: %s systrace|json|ctrace|text|profile [--truncate start|end] "
+      "[trace.pb] "
       "[trace.txt]\n",
       argv0);
   return 1;
 }
 
-}  // namespace
-
-int main(int argc, char** argv) {
+int Main(int argc, char** argv) {
   std::vector<const char*> positional_args;
-  perfetto::trace_to_text::Keep truncate_keep =
-      perfetto::trace_to_text::Keep::kAll;
+  Keep truncate_keep = Keep::kAll;
   for (int i = 1; i < argc; i++) {
     if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
       printf("%s\n", PERFETTO_GET_GIT_REVISION());
@@ -55,9 +55,9 @@
                strcmp(argv[i], "--truncate") == 0) {
       i++;
       if (i <= argc && strcmp(argv[i], "start") == 0) {
-        truncate_keep = perfetto::trace_to_text::Keep::kStart;
+        truncate_keep = Keep::kStart;
       } else if (i <= argc && strcmp(argv[i], "end") == 0) {
-        truncate_keep = perfetto::trace_to_text::Keep::kEnd;
+        truncate_keep = Keep::kEnd;
       } else {
         PERFETTO_ELOG(
             "--truncate must specify whether to keep the end or the "
@@ -105,28 +105,39 @@
   std::string format(positional_args[0]);
 
   if (format == "json")
-    return perfetto::trace_to_text::TraceToSystrace(input_stream, output_stream,
-                                                    truncate_keep,
-                                                    /*wrap_in_json=*/true);
+    return TraceToSystrace(input_stream, output_stream, kSystraceJson,
+                           truncate_keep);
+
   if (format == "systrace")
-    return perfetto::trace_to_text::TraceToSystrace(input_stream, output_stream,
-                                                    truncate_keep,
-                                                    /*wrap_in_json=*/false);
-  if (truncate_keep != perfetto::trace_to_text::Keep::kAll) {
+    return TraceToSystrace(input_stream, output_stream, kSystraceNormal,
+                           truncate_keep);
+
+  if (format == "ctrace")
+    return TraceToSystrace(input_stream, output_stream, kSystraceCompressed,
+                           truncate_keep);
+
+  if (truncate_keep != Keep::kAll) {
     PERFETTO_ELOG(
         "--truncate is unsupported for text|profile|symbolize format.");
     return 1;
   }
 
   if (format == "text")
-    return perfetto::trace_to_text::TraceToText(input_stream, output_stream);
+    return TraceToText(input_stream, output_stream);
 
   if (format == "profile")
-    return perfetto::trace_to_text::TraceToProfile(input_stream, output_stream);
+    return TraceToProfile(input_stream, output_stream);
 
   if (format == "symbolize")
-    return perfetto::trace_to_text::SymbolizeProfile(input_stream,
-                                                     output_stream);
+    return SymbolizeProfile(input_stream, output_stream);
 
   return Usage(argv[0]);
 }
+
+}  // namespace
+}  // namespace trace_to_text
+}  // namespace perfetto
+
+int main(int argc, char** argv) {
+  return perfetto::trace_to_text::Main(argc, argv);
+}
diff --git a/tools/trace_to_text/pprof_builder.cc b/tools/trace_to_text/pprof_builder.cc
index c46b4a6..aef6eae 100644
--- a/tools/trace_to_text/pprof_builder.cc
+++ b/tools/trace_to_text/pprof_builder.cc
@@ -25,18 +25,19 @@
 #include <utility>
 #include <vector>
 
-#include "tools/trace_to_text/profile_visitor.h"
-#include "tools/trace_to_text/trace_symbol_table.h"
 #include "tools/trace_to_text/utils.h"
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/time.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/utils.h"
 #include "perfetto/profiling/symbolizer.h"
 
 #include "protos/perfetto/trace/profiling/profile_common.pb.h"
 #include "protos/perfetto/trace/profiling/profile_packet.pb.h"
 #include "protos/perfetto/trace/trace.pb.h"
-#include "protos/perfetto/trace/trace_packet.pb.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "protos/third_party/pprof/profile.pb.h"
 
 namespace perfetto {
@@ -44,6 +45,25 @@
 
 namespace {
 
+using ::protozero::proto_utils::kMessageLengthFieldSize;
+using ::protozero::proto_utils::MakeTagLengthDelimited;
+using ::protozero::proto_utils::WriteVarInt;
+
+using GLine = ::perfetto::third_party::perftools::profiles::Line;
+using GMapping = ::perfetto::third_party::perftools::profiles::Mapping;
+using GLocation = ::perfetto::third_party::perftools::profiles::Location;
+using GProfile = ::perfetto::third_party::perftools::profiles::Profile;
+using GValueType = ::perfetto::third_party::perftools::profiles::ValueType;
+using GFunction = ::perfetto::third_party::perftools::profiles::Function;
+using GSample = ::perfetto::third_party::perftools::profiles::Sample;
+
+struct View {
+  const char* type;
+  const char* unit;
+  const char* aggregator;
+  const char* filter;
+};
+
 void MaybeDemangle(std::string* name) {
   int ignored;
   char* data = abi::__cxa_demangle(name->c_str(), nullptr, nullptr, &ignored);
@@ -53,296 +73,454 @@
   }
 }
 
-using ::perfetto::protos::Callstack;
-using ::perfetto::protos::Frame;
-using ::perfetto::protos::InternedString;
-using ::perfetto::protos::Mapping;
-using ::perfetto::protos::ProfilePacket;
-using ::perfetto::protos::InternedData;
+const View kSpaceView{"space", "bytes", "SUM(size)", nullptr};
+const View kAllocSpaceView{"alloc_space", "bytes", "SUM(size)", "size > 0"};
+const View kAllocObjectsView{"alloc_objects", "count", "sum(count)",
+                             "size > 0"};
+const View kObjectsView{"objects", "count", "SUM(count)", nullptr};
 
-using GLine = ::perfetto::third_party::perftools::profiles::Line;
-using GMapping = ::perfetto::third_party::perftools::profiles::Mapping;
-using GLocation = ::perfetto::third_party::perftools::profiles::Location;
-using GProfile = ::perfetto::third_party::perftools::profiles::Profile;
-using GValueType = ::perfetto::third_party::perftools::profiles::ValueType;
-using GFunction = ::perfetto::third_party::perftools::profiles::Function;
-using GSample = ::perfetto::third_party::perftools::profiles::Sample;
+const View kViews[] = {kAllocObjectsView, kObjectsView, kAllocSpaceView,
+                       kSpaceView};
 
-enum Strings : int64_t {
-  kEmpty = 0,
-  kObjects,
-  kAllocObjects,
-  kCount,
-  kSpace,
-  kAllocSpace,
-  kBytes,
-  kIdleSpace,
-  kMaxSpace,
+using Iterator = trace_processor::TraceProcessor::Iterator;
+
+constexpr const char* kQueryProfiles =
+    "select distinct hpa.upid, hpa.ts, p.pid from heap_profile_allocation hpa, "
+    "process p where p.upid = hpa.upid;";
+
+struct Callsite {
+  int64_t id;
+  int64_t frame_id;
 };
 
-class GProfileWriter : public ProfileVisitor {
+// Return map from callsite_id to list of frame_ids that make up the callstack.
+std::vector<std::vector<int64_t>> GetCallsiteToFrames(
+    trace_processor::TraceProcessor* tp) {
+  Iterator count_it =
+      tp->ExecuteQuery("select count(*) from stack_profile_callsite;");
+  if (!count_it.Next()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to get number of callsites: %s",
+                            count_it.Status().message().c_str());
+    return {};
+  }
+  int64_t count = count_it.Get(0).long_value;
+
+  Iterator it = tp->ExecuteQuery(
+      "select id, parent_id, frame_id from stack_profile_callsite order by "
+      "depth;");
+  std::vector<std::vector<int64_t>> result(static_cast<size_t>(count));
+  while (it.Next()) {
+    int64_t id = it.Get(0).long_value;
+    int64_t parent_id = it.Get(1).long_value;
+    int64_t frame_id = it.Get(2).long_value;
+    std::vector<int64_t>& path = result[static_cast<size_t>(id)];
+    path.push_back(frame_id);
+    if (parent_id != -1) {
+      const std::vector<int64_t>& parent_path =
+          result[static_cast<size_t>(parent_id)];
+      path.insert(path.end(), parent_path.begin(), parent_path.end());
+    }
+  }
+
+  if (!it.Status().ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                            it.Status().message().c_str());
+    return {};
+  }
+  return result;
+}
+
+struct Line {
+  int64_t symbol_id;
+  uint32_t line_number;
+};
+
+std::map<int64_t, std::vector<Line>> GetSymbolSetIdToLines(
+    trace_processor::TraceProcessor* tp) {
+  std::map<int64_t, std::vector<Line>> result;
+  Iterator it = tp->ExecuteQuery(
+      "SELECT symbol_set_id, id, line_number FROM stack_profile_symbol;");
+  while (it.Next()) {
+    int64_t symbol_set_id = it.Get(0).long_value;
+    int64_t id = it.Get(1).long_value;
+    int64_t line_number = it.Get(2).long_value;
+    result[symbol_set_id].emplace_back(
+        Line{id, static_cast<uint32_t>(line_number)});
+  }
+
+  if (!it.Status().ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                            it.Status().message().c_str());
+    return {};
+  }
+  return result;
+}
+
+class GProfileBuilder {
  public:
-  GProfileWriter(TraceSymbolTable* symbol_table) : symbol_table_(symbol_table) {
-    GValueType* value_type = profile_.add_sample_type();
-    value_type->set_type(kMaxSpace);
-    value_type->set_unit(kBytes);
-
-    value_type = profile_.add_sample_type();
-    value_type->set_type(kObjects);
-    value_type->set_unit(kCount);
-
-    value_type = profile_.add_sample_type();
-    value_type->set_type(kAllocObjects);
-    value_type->set_unit(kCount);
-
-    value_type = profile_.add_sample_type();
-    value_type->set_type(kIdleSpace);
-    value_type->set_unit(kBytes);
-
-    value_type = profile_.add_sample_type();
-    value_type->set_type(kAllocSpace);
-    value_type->set_unit(kBytes);
-
-    // The last value is the default one selected.
-    value_type = profile_.add_sample_type();
-    value_type->set_type(kSpace);
-    value_type->set_unit(kBytes);
+  GProfileBuilder(
+      const std::vector<std::vector<int64_t>>& callsite_to_frames,
+      const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines,
+      int64_t max_symbol_id)
+      : callsite_to_frames_(callsite_to_frames),
+        symbol_set_id_to_lines_(symbol_set_id_to_lines),
+        max_symbol_id_(max_symbol_id) {
+    // The pprof format expects the first entry in the string table to be the
+    // empty string.
+    Intern("");
   }
 
-  int64_t InternInGProfile(const std::string& str) {
-    decltype(string_table_)::iterator it;
-    std::tie(it, std::ignore) =
-        string_table_.emplace(str, string_table_.size());
-    return static_cast<int64_t>(it->second);
-  }
-
-  bool AddInternedString(const InternedString& interned_string) override {
-    string_lookup_.emplace(interned_string.iid(), interned_string.str());
-    return true;
-  }
-
-  bool AddCallstack(const Callstack& callstack) override {
-    std::vector<uint64_t> frame_ids(
-        static_cast<size_t>(callstack.frame_ids().size()));
-    std::reverse_copy(callstack.frame_ids().cbegin(),
-                      callstack.frame_ids().cend(), frame_ids.begin());
-    callstack_lookup_.emplace(callstack.iid(), std::move(frame_ids));
-    return true;
-  }
-
-  bool AddMapping(const Mapping& mapping) override {
-    mapping_base_.emplace(mapping.iid(), mapping.start() - mapping.load_bias());
-    GMapping* gmapping = profile_.add_mapping();
-    gmapping->set_id(mapping.iid());
-    gmapping->set_memory_start(mapping.start());
-    gmapping->set_memory_limit(mapping.end());
-    gmapping->set_file_offset(mapping.exact_offset());
-    std::string filename;
-    for (uint64_t str_id : mapping.path_string_ids()) {
-      auto it = string_lookup_.find(str_id);
-      if (it == string_lookup_.end()) {
-        PERFETTO_ELOG("Mapping %" PRIu64
-                      " referring to invalid string_id %" PRIu64 ".",
-                      static_cast<uint64_t>(mapping.iid()), str_id);
-        return false;
-      }
-
-      filename += "/" + it->second;
+  std::vector<Iterator> BuildViewIterators(trace_processor::TraceProcessor* tp,
+                                           uint64_t upid,
+                                           uint64_t ts) {
+    std::vector<Iterator> view_its;
+    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+      const View& v = kViews[i];
+      std::string query = "SELECT hpa.callsite_id ";
+      query += ", " + std::string(v.aggregator) +
+               " FROM heap_profile_allocation hpa ";
+      query += "WHERE hpa.upid = " + std::to_string(upid) + " ";
+      query += "AND hpa.ts <= " + std::to_string(ts) + " ";
+      if (v.filter)
+        query += "AND " + std::string(v.filter) + " ";
+      query += "GROUP BY hpa.callsite_id;";
+      view_its.emplace_back(tp->ExecuteQuery(query));
     }
-
-    gmapping->set_filename(InternInGProfile(filename));
-
-    auto str_it = string_lookup_.find(mapping.build_id());
-    if (str_it != string_lookup_.end()) {
-      const std::string& build_id = str_it->second;
-      gmapping->set_build_id(InternInGProfile(base::ToHex(build_id)));
-    }
-    return true;
+    return view_its;
   }
 
-  bool AddFrame(const Frame& frame) override {
-    auto it = mapping_base_.find(frame.mapping_id());
-    if (it == mapping_base_.end()) {
-      PERFETTO_ELOG("Frame referring to invalid mapping ID %" PRIu64,
-                    static_cast<uint64_t>(frame.mapping_id()));
-      return false;
-    }
-    uint64_t mapping_base = it->second;
-
-    GLocation* glocation = profile_.add_location();
-    glocation->set_id(frame.iid());
-    glocation->set_mapping_id(frame.mapping_id());
-    glocation->set_address(frame.rel_pc() + mapping_base);
-
-    const std::vector<SymbolizedFrame>* symbolized_frames =
-        symbol_table_->Get(frame.iid());
-    std::vector<SymbolizedFrame> frames;
-    if (symbolized_frames == nullptr) {
-      // Write out whatever was in the profile initially.
-      auto str_it = string_lookup_.find(frame.function_name_id());
-      if (str_it == string_lookup_.end()) {
-        PERFETTO_ELOG("Function referring to invalid string id %" PRIu64,
-                      static_cast<uint64_t>(frame.function_name_id()));
-        return false;
-      }
-      frames.emplace_back(SymbolizedFrame{str_it->second, "", 0});
-    } else {
-      frames = *symbolized_frames;
-    }
-
-    for (const SymbolizedFrame& sym_frame : frames) {
-      GLine* gline = glocation->add_line();
-      uint64_t function_id = ++max_function_id_;
-      gline->set_function_id(function_id);
-      gline->set_line(sym_frame.line);
-      std::string function_name = sym_frame.function_name;
-      // This assumes both the device that captured the trace and the host
-      // machine use the same mangling scheme. This is a reasonable
-      // assumption as the Itanium ABI is the de-facto standard for mangling.
-      MaybeDemangle(&function_name);
-      GFunction* gfunction = profile_.add_function();
-      gfunction->set_id(function_id);
-      gfunction->set_name(InternInGProfile(function_name));
-      gfunction->set_filename(InternInGProfile(sym_frame.file_name));
-    }
-    return true;
-  }
-
-  bool AddProfiledFrameSymbols(const protos::ProfiledFrameSymbols&) override {
-    return true;
-  }
-
-  bool Finalize() {
-    // We keep the interning table as string -> uint64_t for fast and easy
-    // lookup. When dumping, we need to turn it into a uint64_t -> string
-    // table so we get it sorted by key order.
-    std::map<uint64_t, std::string> inverted_string_table;
-    for (const auto& p : string_table_)
-      inverted_string_table[p.second] = p.first;
-    for (const auto& p : inverted_string_table)
-      profile_.add_string_table(p.second);
-    return true;
-  }
-
-  bool WriteProfileForProcess(
-      uint64_t pid,
-      const std::vector<const ProfilePacket::ProcessHeapSamples*>& proc_samples,
-      std::string* serialized) {
-    GProfile cur_profile = profile_;
-    for (const ProfilePacket::ProcessHeapSamples* samples : proc_samples) {
-      if (samples->rejected_concurrent()) {
-        PERFETTO_ELOG("WARNING: The profile for %" PRIu64
-                      " was rejected due to a concurrent profile.",
-                      pid);
-      }
-      if (samples->buffer_overran()) {
-        PERFETTO_ELOG("WARNING: The profile for %" PRIu64
-                      " ended early due to a buffer overrun.",
-                      pid);
-      }
-      if (samples->buffer_corrupted()) {
-        PERFETTO_ELOG("WARNING: The profile for %" PRIu64
-                      " ended early due to a buffer corruption."
-                      " THIS IS ALWAYS A BUG IN HEAPPROFD OR"
-                      " CLIENT MEMORY CORRUPTION.",
-                      pid);
-      }
-
-      for (const ProfilePacket::HeapSample& sample : samples->samples()) {
-        GSample* gsample = cur_profile.add_sample();
-        auto it = callstack_lookup_.find(sample.callstack_id());
-        if (it == callstack_lookup_.end()) {
-          PERFETTO_ELOG("Callstack referring to invalid callstack id %" PRIu64,
-                        static_cast<uint64_t>(sample.callstack_id()));
+  bool WriteAllocations(std::vector<Iterator>* view_its,
+                        std::set<int64_t>* seen_frames) {
+    for (;;) {
+      bool all_next = true;
+      bool any_next = false;
+      for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+        Iterator& it = (*view_its)[i];
+        bool next = it.Next();
+        if (!it.Status().ok()) {
+          PERFETTO_DFATAL_OR_ELOG("Invalid view iterator: %s",
+                                  it.Status().message().c_str());
           return false;
         }
-        for (uint64_t frame_id : it->second)
-          gsample->add_location_id(frame_id);
-        gsample->add_value(static_cast<int64_t>(sample.self_max()));
-        gsample->add_value(
-            static_cast<int64_t>(sample.alloc_count() - sample.free_count()));
-        gsample->add_value(static_cast<int64_t>(sample.alloc_count()));
-        gsample->add_value(static_cast<int64_t>(sample.self_idle()));
-        gsample->add_value(static_cast<int64_t>(sample.self_allocated()));
-        gsample->add_value(static_cast<int64_t>(sample.self_allocated() -
-                                                sample.self_freed()));
+        all_next = all_next && next;
+        any_next = any_next || next;
+      }
+
+      if (!all_next) {
+        PERFETTO_DCHECK(!any_next);
+        break;
+      }
+
+      GSample* gsample = result_.add_sample();
+      for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+        int64_t callstack_id = (*view_its)[i].Get(0).long_value;
+        if (i == 0) {
+          auto frames = FramesForCallstack(callstack_id);
+          if (frames.empty())
+            return false;
+          for (int64_t frame : frames)
+            gsample->add_location_id(ToPprofId(frame));
+          seen_frames->insert(frames.cbegin(), frames.cend());
+        } else {
+          if (callstack_id != (*view_its)[i].Get(0).long_value) {
+            PERFETTO_DFATAL_OR_ELOG("Wrong callstack.");
+            return false;
+          }
+        }
+        gsample->add_value((*view_its)[i].Get(1).long_value);
       }
     }
-    *serialized = cur_profile.SerializeAsString();
     return true;
   }
 
+  bool WriteMappings(trace_processor::TraceProcessor* tp,
+                     const std::set<int64_t> seen_mappings) {
+    Iterator mapping_it = tp->ExecuteQuery(
+        "SELECT id, exact_offset, start, end, name "
+        "FROM stack_profile_mapping;");
+    size_t mappings_no = 0;
+    while (mapping_it.Next()) {
+      int64_t id = mapping_it.Get(0).long_value;
+      if (seen_mappings.find(id) == seen_mappings.end())
+        continue;
+      ++mappings_no;
+      GMapping* gmapping = result_.add_mapping();
+      gmapping->set_id(ToPprofId(id));
+      // Do not set the build_id here to avoid downstream services
+      // trying to symbolize (e.g. b/141735056)
+      gmapping->set_file_offset(
+          static_cast<uint64_t>(mapping_it.Get(1).long_value));
+      gmapping->set_memory_start(
+          static_cast<uint64_t>(mapping_it.Get(2).long_value));
+      gmapping->set_memory_limit(
+          static_cast<uint64_t>(mapping_it.Get(3).long_value));
+      gmapping->set_filename(Intern(mapping_it.Get(4).string_value));
+    }
+    if (!mapping_it.Status().ok()) {
+      PERFETTO_DFATAL_OR_ELOG("Invalid mapping iterator: %s",
+                              mapping_it.Status().message().c_str());
+      return false;
+    }
+    if (mappings_no != seen_mappings.size()) {
+      PERFETTO_DFATAL_OR_ELOG("Missing mappings.");
+      return false;
+    }
+    return true;
+  }
+
+  bool WriteSymbols(trace_processor::TraceProcessor* tp,
+                    const std::set<int64_t>& seen_symbol_ids) {
+    Iterator symbol_it = tp->ExecuteQuery(
+        "SELECT id, name, source_file FROM stack_profile_symbol");
+    size_t symbols_no = 0;
+    while (symbol_it.Next()) {
+      int64_t id = symbol_it.Get(0).long_value;
+      if (seen_symbol_ids.find(id) == seen_symbol_ids.end())
+        continue;
+      ++symbols_no;
+      GFunction* gfunction = result_.add_function();
+      gfunction->set_id(ToPprofId(id));
+      gfunction->set_name(Intern(symbol_it.Get(1).string_value));
+      gfunction->set_filename(Intern(symbol_it.Get(2).string_value));
+    }
+
+    if (!symbol_it.Status().ok()) {
+      PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                              symbol_it.Status().message().c_str());
+      return false;
+    }
+
+    if (symbols_no != seen_symbol_ids.size()) {
+      PERFETTO_DFATAL_OR_ELOG("Missing symbols.");
+      return false;
+    }
+    return true;
+  }
+
+  bool WriteFrames(trace_processor::TraceProcessor* tp,
+                   const std::set<int64_t>& seen_frames,
+                   std::set<int64_t>* seen_mappings,
+                   std::set<int64_t>* seen_symbol_ids) {
+    Iterator frame_it = tp->ExecuteQuery(
+        "SELECT spf.id, spf.name, spf.mapping, spf.rel_pc, spf.symbol_set_id "
+        "FROM stack_profile_frame spf;");
+    size_t frames_no = 0;
+    while (frame_it.Next()) {
+      int64_t frame_id = frame_it.Get(0).long_value;
+      if (seen_frames.find(frame_id) == seen_frames.end())
+        continue;
+      frames_no++;
+      std::string frame_name = frame_it.Get(1).string_value;
+      int64_t mapping_id = frame_it.Get(2).long_value;
+      int64_t rel_pc = frame_it.Get(3).long_value;
+      int64_t symbol_set_id = frame_it.Get(4).long_value;
+
+      seen_mappings->emplace(mapping_id);
+      GLocation* glocation = result_.add_location();
+      glocation->set_id(ToPprofId(frame_id));
+      glocation->set_mapping_id(ToPprofId(mapping_id));
+      // TODO(fmayer): Convert to abspc.
+      // relpc + (mapping.start - (mapping.exact_offset -
+      //                           mapping.start_offset)).
+      glocation->set_address(static_cast<uint64_t>(rel_pc));
+      if (symbol_set_id) {
+        for (const Line& line : LineForSymbolSetId(symbol_set_id)) {
+          seen_symbol_ids->emplace(line.symbol_id);
+          GLine* gline = glocation->add_line();
+          gline->set_line(line.line_number);
+          gline->set_function_id(ToPprofId(line.symbol_id));
+        }
+      } else {
+        int64_t synthesized_symbol_id = ++max_symbol_id_;
+        std::string demangled_name = frame_name;
+        MaybeDemangle(&demangled_name);
+
+        GFunction* gfunction = result_.add_function();
+        gfunction->set_id(ToPprofId(synthesized_symbol_id));
+        gfunction->set_name(Intern(demangled_name));
+        gfunction->set_system_name(Intern(frame_name));
+
+        GLine* gline = glocation->add_line();
+        gline->set_line(0);
+        gline->set_function_id(ToPprofId(synthesized_symbol_id));
+      }
+    }
+
+    if (!frame_it.Status().ok()) {
+      PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                              frame_it.Status().message().c_str());
+      return false;
+    }
+    if (frames_no != seen_frames.size()) {
+      PERFETTO_DFATAL_OR_ELOG("Missing frames.");
+      return false;
+    }
+    return true;
+  }
+
+  uint64_t ToPprofId(int64_t id) {
+    PERFETTO_DCHECK(id >= 0);
+    return static_cast<uint64_t>(id) + 1;
+  }
+
+  void WriteSampleTypes() {
+    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+      auto* sample_type = result_.add_sample_type();
+      sample_type->set_type(Intern(kViews[i].type));
+      sample_type->set_unit(Intern(kViews[i].unit));
+    }
+  }
+
+  GProfile GenerateGProfile(trace_processor::TraceProcessor* tp,
+                            uint64_t upid,
+                            uint64_t ts) {
+    std::set<int64_t> seen_frames;
+    std::set<int64_t> seen_mappings;
+    std::set<int64_t> seen_symbol_ids;
+
+    std::vector<Iterator> view_its = BuildViewIterators(tp, upid, ts);
+
+    WriteSampleTypes();
+    if (!WriteAllocations(&view_its, &seen_frames))
+      return {};
+    if (!WriteFrames(tp, seen_frames, &seen_mappings, &seen_symbol_ids))
+      return {};
+    if (!WriteMappings(tp, seen_mappings))
+      return {};
+    if (!WriteSymbols(tp, seen_symbol_ids))
+      return {};
+    return std::move(result_);
+  }
+
+  const std::vector<int64_t>& FramesForCallstack(int64_t callstack_id) {
+    return callsite_to_frames_[static_cast<size_t>(callstack_id)];
+  }
+
+  const std::vector<Line>& LineForSymbolSetId(int64_t symbol_set_id) {
+    auto it = symbol_set_id_to_lines_.find(symbol_set_id);
+    if (it == symbol_set_id_to_lines_.end())
+      return empty_line_vector_;
+    return it->second;
+  }
+
+  int64_t Intern(const std::string& s) {
+    auto it = string_table_.find(s);
+    if (it == string_table_.end()) {
+      std::tie(it, std::ignore) =
+          string_table_.emplace(s, string_table_.size());
+      result_.add_string_table(s);
+    }
+    return it->second;
+  }
+
  private:
-  TraceSymbolTable* symbol_table_;
-  GProfile profile_;
-
-  uint64_t max_function_id_ = 0;
-
-  std::map<uint64_t, uint64_t> mapping_base_;
-  std::set<uint64_t> functions_to_dump_;
-  std::map<uint64_t, const std::vector<uint64_t>> callstack_lookup_;
-  std::map<uint64_t, std::string> string_lookup_;
-  std::map<std::string, uint64_t> string_table_{
-      {"", kEmpty},
-      {"objects", kObjects},
-      {"alloc_objects", kAllocObjects},
-      {"count", kCount},
-      {"space", kSpace},
-      {"alloc_space", kAllocSpace},
-      {"bytes", kBytes},
-      {"idle_space", kIdleSpace},
-      {"max_space", kMaxSpace}};
+  GProfile result_;
+  std::map<std::string, int64_t> string_table_;
+  const std::vector<std::vector<int64_t>>& callsite_to_frames_;
+  const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines_;
+  const std::vector<Line> empty_line_vector_;
+  int64_t max_symbol_id_;
 };
 
-bool DumpProfilePacket(const std::vector<ProfilePacket>& packet_fragments,
-                       const SequencedBundle& bundle,
-                       std::vector<SerializedProfile>* output,
-                       Symbolizer* symbolizer) {
-  TraceSymbolTable symbol_table(symbolizer);
-  if (!symbol_table.Visit(packet_fragments, bundle))
-    return false;
-  if (!symbol_table.Finalize())
-    return false;
-
-  GProfileWriter writer(&symbol_table);
-  if (!writer.Visit(packet_fragments, bundle))
-    return false;
-
-  if (!writer.Finalize())
-    return false;
-
-  std::map<uint64_t, std::vector<const ProfilePacket::ProcessHeapSamples*>>
-      heap_samples;
-  for (const ProfilePacket& packet : packet_fragments) {
-    for (const ProfilePacket::ProcessHeapSamples& samples :
-         packet.process_dumps()) {
-      heap_samples[samples.pid()].emplace_back(&samples);
-    }
-  }
-  for (const auto& p : heap_samples) {
-    std::string serialized;
-    if (!writer.WriteProfileForProcess(p.first, p.second, &serialized))
-      return false;
-    output->emplace_back(SerializedProfile{p.first, std::move(serialized)});
-  }
-  return true;
-}
-
 }  // namespace
 
 bool TraceToPprof(std::istream* input,
                   std::vector<SerializedProfile>* output,
-                  Symbolizer* symbolizer) {
-  return VisitCompletePacket(
-      input, [output, symbolizer](
-                 uint32_t, const std::vector<ProfilePacket>& packet_fragments,
-                 const SequencedBundle& bundle) {
-        return DumpProfilePacket(packet_fragments, bundle, output, symbolizer);
-      });
+                  Symbolizer* symbolizer,
+                  uint64_t pid,
+                  const std::vector<uint64_t>& timestamps) {
+  trace_processor::Config config;
+  std::unique_ptr<trace_processor::TraceProcessor> tp =
+      trace_processor::TraceProcessor::CreateInstance(config);
+
+  if (!ReadTrace(tp.get(), input))
+    return false;
+
+  tp->NotifyEndOfFile();
+  return TraceToPprof(tp.get(), output, symbolizer, pid, timestamps);
 }
 
-bool TraceToPprof(std::istream* input, std::vector<SerializedProfile>* output) {
-  return TraceToPprof(input, output, nullptr);
+bool TraceToPprof(trace_processor::TraceProcessor* tp,
+                  std::vector<SerializedProfile>* output,
+                  Symbolizer* symbolizer,
+                  uint64_t pid,
+                  const std::vector<uint64_t>& timestamps) {
+  if (symbolizer) {
+    SymbolizeDatabase(
+        tp, symbolizer, [&tp](perfetto::protos::TracePacket packet) {
+          size_t size = static_cast<size_t>(packet.ByteSize());
+          std::unique_ptr<uint8_t[]> buf(new uint8_t[size]);
+          packet.SerializeToArray(buf.get(), packet.ByteSize());
+
+          std::unique_ptr<uint8_t[]> preamble(new uint8_t[11]);
+          preamble[0] =
+              MakeTagLengthDelimited(protos::pbzero::Trace::kPacketFieldNumber);
+          uint8_t* end = WriteVarInt(size, &preamble[1]);
+          size_t preamble_size = static_cast<size_t>(end - &preamble[0]);
+          auto status = tp->Parse(std::move(preamble), preamble_size);
+          if (!status.ok()) {
+            PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
+                                    status.message().c_str());
+            return;
+          }
+          status = tp->Parse(std::move(buf), size);
+          if (!status.ok()) {
+            PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
+                                    status.message().c_str());
+            return;
+          }
+        });
+  }
+
+  tp->NotifyEndOfFile();
+  auto max_symbol_id_it =
+      tp->ExecuteQuery("SELECT MAX(id) from stack_profile_symbol");
+  if (!max_symbol_id_it.Next()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to get max symbol set id: %s",
+                            max_symbol_id_it.Status().message().c_str());
+    return false;
+  }
+
+  int64_t max_symbol_id = max_symbol_id_it.Get(0).long_value;
+  auto callsite_to_frames = GetCallsiteToFrames(tp);
+  auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp);
+
+  Iterator it = tp->ExecuteQuery(kQueryProfiles);
+  while (it.Next()) {
+    GProfileBuilder builder(callsite_to_frames, symbol_set_id_to_lines,
+                            max_symbol_id);
+    uint64_t upid = static_cast<uint64_t>(it.Get(0).long_value);
+    uint64_t ts = static_cast<uint64_t>(it.Get(1).long_value);
+    uint64_t profile_pid = static_cast<uint64_t>(it.Get(2).long_value);
+    if ((pid > 0 && profile_pid != pid) ||
+        (!timestamps.empty() && std::find(timestamps.begin(), timestamps.end(),
+                                          ts) == timestamps.end())) {
+      continue;
+    }
+
+    std::string pid_query = "select pid from process where upid = ";
+    pid_query += std::to_string(upid) + ";";
+    Iterator pid_it = tp->ExecuteQuery(pid_query);
+    PERFETTO_CHECK(pid_it.Next());
+
+    GProfile profile = builder.GenerateGProfile(tp, upid, ts);
+    output->emplace_back(
+        SerializedProfile{static_cast<uint64_t>(pid_it.Get(0).long_value),
+                          profile.SerializeAsString()});
+  }
+  if (!it.Status().ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                            it.Status().message().c_str());
+    return false;
+  }
+  return true;
+}
+
+bool TraceToPprof(std::istream* input,
+                  std::vector<SerializedProfile>* output,
+                  uint64_t pid,
+                  const std::vector<uint64_t>& timestamps) {
+  return TraceToPprof(input, output, nullptr, pid, timestamps);
 }
 
 }  // namespace trace_to_text
diff --git a/tools/trace_to_text/profile_visitor.cc b/tools/trace_to_text/profile_visitor.cc
deleted file mode 100644
index fb41fba..0000000
--- a/tools/trace_to_text/profile_visitor.cc
+++ /dev/null
@@ -1,169 +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 "tools/trace_to_text/profile_visitor.h"
-
-#include <unordered_map>
-#include "protos/perfetto/trace/trace.pb.h"
-#include "protos/perfetto/trace/trace_packet.pb.h"
-
-#include "perfetto/ext/base/string_splitter.h"
-
-namespace perfetto {
-namespace trace_to_text {
-
-namespace {
-using ::perfetto::protos::Callstack;
-using ::perfetto::protos::Frame;
-using ::perfetto::protos::InternedData;
-using ::perfetto::protos::InternedString;
-using ::perfetto::protos::Mapping;
-using ::perfetto::protos::ProfiledFrameSymbols;
-using ::perfetto::protos::ProfilePacket;
-
-struct ProfilePackets {
-  uint32_t seq_id;
-  std::vector<protos::ProfilePacket> packets;
-};
-
-bool IsPacketIndexContiguous(
-    const std::vector<perfetto::protos::ProfilePacket>& packets) {
-  for (size_t i = 1; i < packets.size(); ++i) {
-    // Ensure we are not missing a chunk.
-    if (packets[i - 1].index() + 1 != packets[i].index()) {
-      return false;
-    }
-  }
-  return true;
-}
-}  // namespace
-
-bool ProfileVisitor::Visit(
-    const std::vector<protos::ProfilePacket>& packet_fragments,
-    const SequencedBundle& bundle) {
-  for (const ProfilePacket& packet : packet_fragments) {
-    for (const InternedString& interned_string : packet.strings())
-      if (!AddInternedString(interned_string))
-        return false;
-  }
-  for (const InternedData& data : bundle.interned_data) {
-    for (const InternedString& interned_string : data.build_ids())
-      if (!AddInternedString(interned_string))
-        return false;
-    for (const InternedString& interned_string : data.mapping_paths())
-      if (!AddInternedString(interned_string))
-        return false;
-    for (const InternedString& interned_string : data.function_names())
-      if (!AddInternedString(interned_string))
-        return false;
-    for (const InternedString& interned_string : data.source_paths())
-      if (!AddInternedString(interned_string))
-        return false;
-    // TODO (140860736): This should be outside the interned section.
-    for (const ProfiledFrameSymbols& pfs : data.profiled_frame_symbols())
-      if (!AddProfiledFrameSymbols(pfs))
-        return false;
-  }
-  for (const ProfiledFrameSymbols& pfs : bundle.symbols)
-    if (!AddProfiledFrameSymbols(pfs))
-      return false;
-
-  for (const ProfilePacket& packet : packet_fragments) {
-    for (const Callstack& callstack : packet.callstacks())
-      if (!AddCallstack(callstack))
-        return false;
-  }
-  for (const InternedData& data : bundle.interned_data) {
-    for (const Callstack& callstack : data.callstacks())
-      if (!AddCallstack(callstack))
-        return false;
-  }
-
-  for (const ProfilePacket& packet : packet_fragments) {
-    for (const Mapping& mapping : packet.mappings())
-      if (!AddMapping(mapping))
-        return false;
-  }
-  for (const InternedData& data : bundle.interned_data) {
-    for (const Mapping& callstack : data.mappings()) {
-      if (!AddMapping(callstack))
-        return false;
-    }
-  }
-
-  for (const ProfilePacket& packet : packet_fragments) {
-    for (const Frame& frame : packet.frames()) {
-      if (!AddFrame(frame))
-        return false;
-    }
-  }
-  for (const InternedData& data : bundle.interned_data) {
-    for (const Frame& frame : data.frames()) {
-      if (!AddFrame(frame))
-        return false;
-    }
-  }
-  return true;
-}
-
-ProfileVisitor::~ProfileVisitor() = default;
-
-bool VisitCompletePacket(
-    std::istream* input,
-    const std::function<bool(uint32_t,
-                             const std::vector<protos::ProfilePacket>&,
-                             const SequencedBundle&)>& fn) {
-  // Rolling profile packets per seq id. Cleared on finalization.
-  std::unordered_map<uint32_t, std::vector<protos::ProfilePacket>>
-      rolling_profile_packets_by_seq;
-  std::vector<ProfilePackets> complete_profile_packets;
-  // Append-only interned data and symbols by seq id
-  std::unordered_map<uint32_t, SequencedBundle> bundle_by_seq;
-  ForEachPacketInTrace(input, [&rolling_profile_packets_by_seq,
-                               &complete_profile_packets, &bundle_by_seq](
-                                  const protos::TracePacket& packet) {
-    uint32_t seq_id = packet.trusted_packet_sequence_id();
-    if (packet.has_interned_data()) {
-      bundle_by_seq[seq_id].interned_data.emplace_back(packet.interned_data());
-    }
-
-    if (packet.has_profile_packet()) {
-      std::vector<protos::ProfilePacket>& rolling_profile_packets =
-          rolling_profile_packets_by_seq[seq_id];
-      rolling_profile_packets.emplace_back(packet.profile_packet());
-
-      if (!packet.profile_packet().continued()) {
-        if (IsPacketIndexContiguous(rolling_profile_packets)) {
-          complete_profile_packets.push_back({seq_id, rolling_profile_packets});
-          rolling_profile_packets_by_seq.erase(seq_id);
-        }
-      }
-    }
-  });
-
-  bool success = true;
-  for (const auto& packets : complete_profile_packets) {
-    success &=
-        fn(packets.seq_id, packets.packets, bundle_by_seq[packets.seq_id]);
-  }
-  if (!rolling_profile_packets_by_seq.empty()) {
-    PERFETTO_ELOG("WARNING: Truncated heap dump.");
-    return false;
-  }
-  return success;
-}
-}  // namespace trace_to_text
-}  // namespace perfetto
diff --git a/tools/trace_to_text/profile_visitor.h b/tools/trace_to_text/profile_visitor.h
deleted file mode 100644
index 4723f14..0000000
--- a/tools/trace_to_text/profile_visitor.h
+++ /dev/null
@@ -1,61 +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 TOOLS_TRACE_TO_TEXT_PROFILE_VISITOR_H_
-#define TOOLS_TRACE_TO_TEXT_PROFILE_VISITOR_H_
-
-#include <vector>
-
-#include "perfetto/base/logging.h"
-
-#include "tools/trace_to_text/utils.h"
-
-#include "protos/perfetto/trace/interned_data/interned_data.pb.h"
-#include "protos/perfetto/trace/profiling/profile_common.pb.h"
-#include "protos/perfetto/trace/profiling/profile_packet.pb.h"
-#include "protos/perfetto/trace/trace_packet.pb.h"
-
-namespace perfetto {
-namespace trace_to_text {
-
-struct SequencedBundle {
-  std::vector<protos::InternedData> interned_data;
-  std::vector<protos::ProfiledFrameSymbols> symbols;
-};
-
-class ProfileVisitor {
- public:
-  bool Visit(const std::vector<protos::ProfilePacket>&, const SequencedBundle&);
-  virtual bool AddInternedString(
-      const protos::InternedString& interned_string) = 0;
-  virtual bool AddCallstack(const protos::Callstack& callstack) = 0;
-  virtual bool AddMapping(const protos::Mapping& mapping) = 0;
-  virtual bool AddFrame(const protos::Frame& frame) = 0;
-  virtual bool AddProfiledFrameSymbols(
-      const protos::ProfiledFrameSymbols& symbol) = 0;
-  virtual ~ProfileVisitor();
-};
-
-bool VisitCompletePacket(
-    std::istream* input,
-    const std::function<bool(uint32_t,
-                             const std::vector<protos::ProfilePacket>&,
-                             const SequencedBundle&)>& fn);
-
-}  // namespace trace_to_text
-}  // namespace perfetto
-
-#endif  // TOOLS_TRACE_TO_TEXT_PROFILE_VISITOR_H_
diff --git a/tools/trace_to_text/symbolize_profile.cc b/tools/trace_to_text/symbolize_profile.cc
index 519021c..aa7c2ef 100644
--- a/tools/trace_to_text/symbolize_profile.cc
+++ b/tools/trace_to_text/symbolize_profile.cc
@@ -46,86 +46,6 @@
   *output << std::string(length_field, end);
   *output << str;
 }
-
-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";
-
-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<std::pair<std::string, std::string>, std::vector<uint64_t>>
-GetUnsymbolizedFrames(trace_processor::TraceProcessor* tp) {
-  std::map<std::pair<std::string, std::string>, std::vector<uint64_t>> res;
-  for (Iterator it = tp->ExecuteQuery(kQueryUnsymbolized); it.Next();)
-    res[std::make_pair(it.Get(0).string_value, FromHex(it.Get(1).string_value))]
-        .emplace_back(it.Get(2).long_value);
-
-  return res;
-}
-
-}  // namespace
-
-void SymbolizeDatabase(
-    trace_processor::TraceProcessor* tp,
-    Symbolizer* symbolizer,
-    std::function<void(const perfetto::protos::TracePacket&)> 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;
-
-    perfetto::protos::TracePacket packet;
-    perfetto::protos::ModuleSymbols* module_symbols =
-        packet.mutable_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[0]);
-      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);
-  }
 }
 
 // Ingest profile, and emit a symbolization table for each sequence. This can
@@ -150,6 +70,8 @@
   if (!ReadTrace(tp.get(), input))
     PERFETTO_FATAL("Failed to read trace.");
 
+  tp->NotifyEndOfFile();
+
   SymbolizeDatabase(tp.get(), symbolizer.get(),
                     [output](const perfetto::protos::TracePacket& packet) {
                       WriteTracePacket(packet.SerializeAsString(), output);
diff --git a/tools/trace_to_text/symbolize_profile.h b/tools/trace_to_text/symbolize_profile.h
index e0867ae..3c787fd 100644
--- a/tools/trace_to_text/symbolize_profile.h
+++ b/tools/trace_to_text/symbolize_profile.h
@@ -29,11 +29,6 @@
 
 int SymbolizeProfile(std::istream* input, std::ostream* output);
 
-void SymbolizeDatabase(
-    trace_processor::TraceProcessor* tp,
-    Symbolizer* symbolizer,
-    std::function<void(const perfetto::protos::TracePacket&)> callback);
-
 }  // namespace trace_to_text
 }  // namespace perfetto
 
diff --git a/tools/trace_to_text/trace_symbol_table.cc b/tools/trace_to_text/trace_symbol_table.cc
deleted file mode 100644
index 4b31301..0000000
--- a/tools/trace_to_text/trace_symbol_table.cc
+++ /dev/null
@@ -1,166 +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 "tools/trace_to_text/trace_symbol_table.h"
-
-#include <algorithm>
-#include <map>
-#include <set>
-#include <string>
-#include <vector>
-
-#include <inttypes.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "perfetto/profiling/symbolizer.h"
-#include "perfetto/protozero/proto_utils.h"
-
-#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/optional.h"
-#include "perfetto/ext/base/pipe.h"
-#include "perfetto/ext/base/utils.h"
-
-#include "tools/trace_to_text/utils.h"
-
-#include "protos/perfetto/trace/profiling/profile_common.pb.h"
-#include "protos/perfetto/trace/profiling/profile_packet.pb.h"
-#include "protos/perfetto/trace/trace.pbzero.h"
-#include "protos/perfetto/trace/trace_packet.pb.h"
-
-#include "protos/perfetto/trace/interned_data/interned_data.pb.h"
-
-namespace perfetto {
-namespace trace_to_text {
-namespace {
-
-using ::protozero::proto_utils::kMessageLengthFieldSize;
-using ::protozero::proto_utils::MakeTagLengthDelimited;
-using ::protozero::proto_utils::WriteVarInt;
-
-using ::perfetto::protos::Callstack;
-using ::perfetto::protos::Frame;
-using ::perfetto::protos::InternedData;
-using ::perfetto::protos::InternedString;
-using ::perfetto::protos::Mapping;
-using ::perfetto::protos::ProfiledFrameSymbols;
-using ::perfetto::protos::ProfilePacket;
-
-}  // namespace
-
-bool TraceSymbolTable::AddInternedString(const InternedString& string) {
-  interned_strings_.emplace(string.iid(), string.str());
-  max_string_intern_id_ =
-      std::max<uint64_t>(string.iid(), max_string_intern_id_);
-  return true;
-}
-
-bool TraceSymbolTable::AddMapping(const Mapping& mapping) {
-  mappings_.emplace(mapping.iid(), ResolveMapping(mapping));
-  return true;
-}
-
-bool TraceSymbolTable::AddFrame(const Frame& frame) {
-  if (symbols_for_frame_.find(frame.iid()) == symbols_for_frame_.end()) {
-    to_symbolize_[frame.mapping_id()].emplace_back(frame.iid());
-    rel_pc_for_frame_[frame.iid()] = frame.rel_pc();
-  }
-  return true;
-}
-
-bool TraceSymbolTable::AddProfiledFrameSymbols(
-    const protos::ProfiledFrameSymbols& symbol) {
-  std::vector<SymbolizedFrame> frames;
-  const auto& name_ids = symbol.function_name_id();
-  const auto& file_ids = symbol.file_name_id();
-  const auto& lines = symbol.line_number();
-
-  if (name_ids.size() != file_ids.size() || file_ids.size() != lines.size()) {
-    PERFETTO_DFATAL_OR_ELOG("Invalid ProfiledFrameSymbols");
-    return false;
-  }
-
-  for (int i = 0; i < name_ids.size(); ++i) {
-    SymbolizedFrame frame{ResolveString(name_ids.Get(i)),
-                          ResolveString(file_ids.Get(i)), lines.Get(i)};
-    frames.emplace_back(std::move(frame));
-  }
-
-  symbols_for_frame_[symbol.frame_iid()] = std::move(frames);
-  return true;
-}
-
-bool TraceSymbolTable::Finalize() {
-  if (symbolizer_ == nullptr)
-    return true;
-
-  for (const auto& mapping_and_frame_iids : to_symbolize_) {
-    auto it = mappings_.find(mapping_and_frame_iids.first);
-    if (it == mappings_.end()) {
-      PERFETTO_DFATAL_OR_ELOG("Invalid mapping.");
-      return false;
-    }
-    const ResolvedMapping& mapping = it->second;
-    const std::vector<uint64_t>& frame_iids = mapping_and_frame_iids.second;
-    std::vector<uint64_t> rel_pcs;
-    for (uint64_t frame_iid : frame_iids)
-      rel_pcs.emplace_back(rel_pc_for_frame_[frame_iid]);
-    auto result =
-        symbolizer_->Symbolize(mapping.mapping_name, mapping.build_id, rel_pcs);
-    if (result.empty())
-      continue;
-    if (result.size() != frame_iids.size()) {
-      PERFETTO_DFATAL_OR_ELOG("Invalid response from symbolizer.");
-      return false;
-    }
-    for (size_t i = 0; i < frame_iids.size(); ++i) {
-      if (!result.empty())
-        symbols_for_frame_[frame_iids[i]] = std::move(result[i]);
-    }
-  }
-  return true;
-}
-
-const std::vector<SymbolizedFrame>* TraceSymbolTable::Get(
-    uint64_t frame_iid) const {
-  auto it = symbols_for_frame_.find(frame_iid);
-  if (it == symbols_for_frame_.end())
-    return nullptr;
-  return &it->second;
-}
-
-const std::string& TraceSymbolTable::ResolveString(uint64_t iid) {
-  auto it = interned_strings_.find(iid);
-  if (it == interned_strings_.end())
-    return kEmptyString;
-  return it->second;
-}
-
-TraceSymbolTable::ResolvedMapping TraceSymbolTable::ResolveMapping(
-    const Mapping& mapping) {
-  std::string path;
-  for (uint64_t iid : mapping.path_string_ids()) {
-    path += "/";
-    path += ResolveString(iid);
-  }
-  return {std::move(path), ResolveString(mapping.build_id())};
-}
-
-}  // namespace trace_to_text
-}  // namespace perfetto
diff --git a/tools/trace_to_text/trace_symbol_table.h b/tools/trace_to_text/trace_symbol_table.h
deleted file mode 100644
index 067d613..0000000
--- a/tools/trace_to_text/trace_symbol_table.h
+++ /dev/null
@@ -1,76 +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 TOOLS_TRACE_TO_TEXT_TRACE_SYMBOL_TABLE_H_
-#define TOOLS_TRACE_TO_TEXT_TRACE_SYMBOL_TABLE_H_
-
-#include <iostream>
-
-#include "perfetto/profiling/symbolizer.h"
-#include "tools/trace_to_text/profile_visitor.h"
-
-namespace perfetto {
-namespace trace_to_text {
-
-class TraceSymbolTable : public ProfileVisitor {
- public:
-  TraceSymbolTable(Symbolizer* symbolizer) : symbolizer_(symbolizer) {}
-  bool AddCallstack(const protos::Callstack&) override { return true; }
-  bool AddInternedString(const protos::InternedString& string) override;
-  bool AddFrame(const protos::Frame& frame) override;
-  bool AddMapping(const protos::Mapping& mapping) override;
-  bool AddProfiledFrameSymbols(
-      const protos::ProfiledFrameSymbols& symbol) override;
-
-  const std::vector<SymbolizedFrame>* Get(uint64_t frame_iid) const;
-  // Call Finalize before using Get or WriteResult.
-  bool Finalize();
-
- private:
-  // This is so we can return a const std::string& in ResolveString.
-  const std::string kEmptyString = "";
-  struct ResolvedMapping {
-    std::string mapping_name;
-    std::string build_id;
-  };
-
-  const std::string& ResolveString(uint64_t iid);
-  ResolvedMapping ResolveMapping(const protos::Mapping& mapping);
-
-  // Can be nullptr to disable symbolization. Then TraceSymbolTable only reads
-  // the symbol table from the trace.
-  Symbolizer* symbolizer_;
-
-  std::map<uint64_t, std::string> interned_strings_;
-  std::map<uint64_t, ResolvedMapping> mappings_;
-
-  std::map<std::string, uint64_t> intern_table_;
-  uint64_t max_string_intern_id_ = 0;
-
-  std::map<uint64_t /* frame id */, uint64_t /* rel_pc */> rel_pc_for_frame_;
-
-  std::map<uint64_t /* mapping_id */, std::vector<uint64_t> /* frame id */>
-      to_symbolize_;
-
-  std::map<uint64_t /* frame_id */, std::vector<SymbolizedFrame>>
-      symbols_for_frame_;
-};
-int SymbolizeProfile(std::istream* input, std::ostream* output);
-
-}  // namespace trace_to_text
-}  // namespace perfetto
-
-#endif  // TOOLS_TRACE_TO_TEXT_TRACE_SYMBOL_TABLE_H_
diff --git a/tools/trace_to_text/trace_to_profile.cc b/tools/trace_to_text/trace_to_profile.cc
index e70931d..cc00194 100644
--- a/tools/trace_to_text/trace_to_profile.cc
+++ b/tools/trace_to_text/trace_to_profile.cc
@@ -47,7 +47,10 @@
 namespace perfetto {
 namespace trace_to_text {
 
-int TraceToProfile(std::istream* input, std::ostream* output) {
+int TraceToProfile(std::istream* input,
+                   std::ostream* output,
+                   uint64_t pid,
+                   std::vector<uint64_t> timestamps) {
   std::unique_ptr<Symbolizer> symbolizer;
   auto binary_path = GetPerfettoBinaryPath();
   if (!binary_path.empty()) {
@@ -61,7 +64,7 @@
   }
 
   std::vector<SerializedProfile> profiles;
-  TraceToPprof(input, &profiles, symbolizer.get());
+  TraceToPprof(input, &profiles, symbolizer.get(), pid, timestamps);
   if (profiles.empty()) {
     return 0;
   }
diff --git a/tools/trace_to_text/trace_to_profile.h b/tools/trace_to_text/trace_to_profile.h
index 4e84f8e..629d3ef 100644
--- a/tools/trace_to_text/trace_to_profile.h
+++ b/tools/trace_to_text/trace_to_profile.h
@@ -18,11 +18,15 @@
 #define TOOLS_TRACE_TO_TEXT_TRACE_TO_PROFILE_H_
 
 #include <iostream>
+#include <vector>
 
 namespace perfetto {
 namespace trace_to_text {
 
-int TraceToProfile(std::istream* input, std::ostream* output);
+int TraceToProfile(std::istream* input,
+                   std::ostream* output,
+                   uint64_t pid = 0,
+                   std::vector<uint64_t> timestamps = {});
 
 }  // namespace trace_to_text
 }  // namespace perfetto
diff --git a/tools/trace_to_text/trace_to_systrace.cc b/tools/trace_to_text/trace_to_systrace.cc
index c987165..a7afb5d 100644
--- a/tools/trace_to_text/trace_to_systrace.cc
+++ b/tools/trace_to_text/trace_to_systrace.cc
@@ -25,6 +25,8 @@
 #include <memory>
 #include <utility>
 
+#include <zlib.h>
+
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/paged_memory.h"
@@ -46,6 +48,8 @@
 
 namespace {
 
+const size_t kCompressionBufferSize = 500 * 1024;
+
 // Having an empty traceEvents object is necessary for trace viewer to
 // load the json properly.
 const char kTraceHeader[] = R"({
@@ -123,13 +127,79 @@
   }
 }
 
+class TraceWriter {
+ public:
+  TraceWriter(std::ostream* output) : output_(output) {}
+  virtual ~TraceWriter() = default;
+
+  void Write(std::string s) { Write(s.data(), s.size()); }
+
+  virtual void Write(const char* data, size_t sz) {
+    output_->write(data, static_cast<std::streamsize>(sz));
+  }
+
+ private:
+  std::ostream* output_;
+};
+
+class DeflateTraceWriter : public TraceWriter {
+ public:
+  DeflateTraceWriter(std::ostream* output)
+      : TraceWriter(output),
+        buf_(base::PagedMemory::Allocate(kCompressionBufferSize)),
+        start_(static_cast<uint8_t*>(buf_.Get())),
+        end_(start_ + buf_.size()) {
+    CheckEq(deflateInit(&stream_, 9), Z_OK);
+    stream_.next_out = start_;
+    stream_.avail_out = static_cast<unsigned int>(end_ - start_);
+  }
+
+  ~DeflateTraceWriter() override {
+    while (deflate(&stream_, Z_FINISH) != Z_STREAM_END) {
+      Flush();
+    }
+    CheckEq(deflateEnd(&stream_), Z_OK);
+  }
+
+  void Write(const char* data, size_t sz) override {
+    stream_.next_in = reinterpret_cast<uint8_t*>(const_cast<char*>(data));
+    stream_.avail_in = static_cast<unsigned int>(sz);
+    while (stream_.avail_in > 0) {
+      CheckEq(deflate(&stream_, Z_NO_FLUSH), Z_OK);
+      if (stream_.avail_out == 0) {
+        Flush();
+      }
+    }
+  }
+
+ private:
+  void Flush() {
+    TraceWriter::Write(reinterpret_cast<char*>(start_),
+                       static_cast<size_t>(stream_.next_out - start_));
+    stream_.next_out = start_;
+    stream_.avail_out = static_cast<unsigned int>(end_ - start_);
+  }
+
+  void CheckEq(int actual_code, int expected_code) {
+    if (actual_code == expected_code)
+      return;
+    PERFETTO_FATAL("Expected %d got %d: %s", actual_code, expected_code,
+                   stream_.msg);
+  }
+
+  z_stream stream_{};
+  base::PagedMemory buf_;
+  uint8_t* const start_;
+  uint8_t* const end_;
+};
+
 class QueryWriter {
  public:
-  QueryWriter(trace_processor::TraceProcessor* tp, std::ostream* output)
+  QueryWriter(trace_processor::TraceProcessor* tp, TraceWriter* trace_writer)
       : tp_(tp),
         buffer_(base::PagedMemory::Allocate(kBufferSize)),
         global_writer_(static_cast<char*>(buffer_.Get()), kBufferSize),
-        output_(output) {}
+        trace_writer_(trace_writer) {}
 
   template <typename Callback>
   bool RunQuery(const std::string& sql, Callback callback) {
@@ -142,7 +212,7 @@
       if (global_writer_.pos() + line_writer.pos() >= global_writer_.size()) {
         fprintf(stderr, "Writing row %" PRIu32 PROGRESS_CHAR, rows);
         auto str = global_writer_.GetStringView();
-        output_->write(str.data(), static_cast<std::streamsize>(str.size()));
+        trace_writer_->Write(str.data(), str.size());
         global_writer_.reset();
       }
       global_writer_.AppendStringView(line_writer.GetStringView());
@@ -157,7 +227,7 @@
 
     // Flush any dangling pieces in the global writer.
     auto str = global_writer_.GetStringView();
-    output_->write(str.data(), static_cast<std::streamsize>(str.size()));
+    trace_writer_->Write(str.data(), str.size());
     global_writer_.reset();
     return true;
   }
@@ -168,24 +238,31 @@
   trace_processor::TraceProcessor* tp_ = nullptr;
   base::PagedMemory buffer_;
   base::StringWriter global_writer_;
-  std::ostream* output_ = nullptr;
+  TraceWriter* trace_writer_;
 };
 
 }  // namespace
 
 int TraceToSystrace(std::istream* input,
                     std::ostream* output,
-                    Keep truncate_keep,
-                    bool wrap_in_json) {
+                    SystraceKind kind,
+                    Keep truncate_keep) {
+  bool wrap_in_json = kind == kSystraceJson;
+  bool compress = kind == kSystraceCompressed;
+
+  std::unique_ptr<TraceWriter> trace_writer(
+      compress ? new DeflateTraceWriter(output) : new TraceWriter(output));
+
   trace_processor::Config config;
   std::unique_ptr<trace_processor::TraceProcessor> tp =
       trace_processor::TraceProcessor::CreateInstance(config);
 
   if (!ReadTrace(tp.get(), input))
     return 1;
+  tp->NotifyEndOfFile();
   using Iterator = trace_processor::TraceProcessor::Iterator;
 
-  QueryWriter q_writer(tp.get(), output);
+  QueryWriter q_writer(tp.get(), trace_writer.get());
   if (wrap_in_json) {
     *output << kTraceHeader;
 
@@ -230,18 +307,18 @@
     *output << kFtraceJsonHeader;
   } else {
     *output << "TRACE:\n";
-    *output << kFtraceHeader;
+    trace_writer->Write(kFtraceHeader);
   }
 
   fprintf(stderr, "Converting trace events" PROGRESS_CHAR);
   fflush(stderr);
 
-  static const char kEstimatSql[] = "select count(1) from raw";
+  static const char kEstimateSql[] = "select count(1) from raw";
   uint32_t raw_events = 0;
   auto e_callback = [&raw_events](Iterator* it, base::StringWriter*) {
     raw_events = static_cast<uint32_t>(it->Get(0).long_value);
   };
-  if (!q_writer.RunQuery(kEstimatSql, e_callback))
+  if (!q_writer.RunQuery(kEstimateSql, e_callback))
     return 1;
 
   auto raw_callback = [wrap_in_json](Iterator* it, base::StringWriter* writer) {
diff --git a/tools/trace_to_text/trace_to_systrace.h b/tools/trace_to_text/trace_to_systrace.h
index c1c4659..3862578 100644
--- a/tools/trace_to_text/trace_to_systrace.h
+++ b/tools/trace_to_text/trace_to_systrace.h
@@ -23,11 +23,12 @@
 namespace trace_to_text {
 
 enum class Keep { kStart = 0, kEnd, kAll };
+enum SystraceKind { kSystraceNormal = 0, kSystraceCompressed, kSystraceJson };
 
 int TraceToSystrace(std::istream* input,
                     std::ostream* output,
-                    Keep truncate_keep,
-                    bool wrap_in_json);
+                    SystraceKind kind,
+                    Keep truncate_keep);
 
 }  // namespace trace_to_text
 }  // namespace perfetto
diff --git a/tools/trace_to_text/utils.cc b/tools/trace_to_text/utils.cc
index 6813df1..4f5169f 100644
--- a/tools/trace_to_text/utils.cc
+++ b/tools/trace_to_text/utils.cc
@@ -33,6 +33,64 @@
 
 namespace perfetto {
 namespace trace_to_text {
+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";
+
+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;
+}
+
+}  // namespace
 
 void ForEachPacketBlobInTrace(
     std::istream* input,
@@ -138,12 +196,45 @@
     file_size += static_cast<uint64_t>(rsize);
     tp->Parse(std::move(buf), static_cast<size_t>(rsize));
   }
-  tp->NotifyEndOfFile();
 
   fprintf(stderr, "Loaded trace%c", kProgressChar);
   fflush(stderr);
   return true;
 }
 
+void SymbolizeDatabase(
+    trace_processor::TraceProcessor* tp,
+    Symbolizer* symbolizer,
+    std::function<void(perfetto::protos::TracePacket)> 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;
+
+    perfetto::protos::TracePacket packet;
+    perfetto::protos::ModuleSymbols* module_symbols =
+        packet.mutable_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(std::move(packet));
+  }
+}
+
 }  // namespace trace_to_text
 }  // namespace perfetto
diff --git a/tools/trace_to_text/utils.h b/tools/trace_to_text/utils.h
index 729d902..4bbcb98 100644
--- a/tools/trace_to_text/utils.h
+++ b/tools/trace_to_text/utils.h
@@ -27,6 +27,7 @@
 #include <vector>
 
 #include "perfetto/base/build_config.h"
+#include "perfetto/profiling/symbolizer.h"
 #include "perfetto/trace_processor/trace_processor.h"
 
 namespace perfetto {
@@ -57,6 +58,11 @@
 
 bool ReadTrace(trace_processor::TraceProcessor* tp, std::istream* input);
 
+void SymbolizeDatabase(
+    trace_processor::TraceProcessor* tp,
+    Symbolizer* symbolizer,
+    std::function<void(perfetto::protos::TracePacket)> callback);
+
 }  // namespace trace_to_text
 }  // namespace perfetto
 
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 1acf957..b5ac993 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -459,6 +459,28 @@
     }
   }
 
+  button {
+    background-color: #262f3c;
+    color: #fff;
+    font-size: 0.875rem;
+    padding-left: 1rem;
+    padding-right: 1rem;
+    padding-top: .5rem;
+    padding-bottom: .5rem;
+    border-radius: .25rem;
+    margin-top: 12px;
+  }
+
+  .explanation {
+    font-size: 14px;
+    width: 35%;
+    margin-top: 10px;
+  }
+
+  .material-icons {
+    vertical-align: middle;
+    margin-right: 10px;
+  }
 }
 
 .tickbar {
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index abd54a1..13f9f6b 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -15,7 +15,7 @@
 import {Draft} from 'immer';
 
 import {assertExists} from '../base/logging';
-import {ConvertTrace} from '../controller/trace_converter';
+import {ConvertTrace, ConvertTraceToPprof} from '../controller/trace_converter';
 
 import {
   AdbRecordingTarget,
@@ -95,11 +95,21 @@
     state.videoEnabled = true;
   },
 
+  // TODO(b/141359485): Actions should only modify state.
   convertTraceToJson(
-      _: StateDraft, args: {file: File, truncate?: 'start'|'end'}): void {
+      _: StateDraft, args: {file: Blob, truncate?: 'start'|'end'}): void {
     ConvertTrace(args.file, args.truncate);
   },
 
+  convertTraceToPprof(_: StateDraft, args: {
+    pid: number,
+    src: string|File|ArrayBuffer,
+    ts1: number,
+    ts2?: number
+  }): void {
+    ConvertTraceToPprof(args.pid, args.src, args.ts1, args.ts2);
+  },
+
   openTraceFromUrl(state: StateDraft, args: {url: string}): void {
     clearTraceState(state);
     const id = `${state.nextId++}`;
@@ -394,13 +404,16 @@
     state.currentSelection = {kind: 'CHROME_SLICE', id: args.id};
   },
 
-  selectTimeSpan(
-      state: StateDraft, args: {startTs: number, endTs: number}): void {
-    state.currentSelection = {
-      kind: 'TIMESPAN',
-      startTs: args.startTs,
-      endTs: args.endTs,
-    };
+  selectTimeSpan(state: StateDraft, args: {startTs: number, endTs: number}):
+      void {
+        state.timeSpan = {
+          startTs: args.startTs,
+          endTs: args.endTs,
+        };
+      },
+
+  removeTimeSpan(state: StateDraft, _: {}): void {
+    state.timeSpan = null;
   },
 
   selectThreadState(
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index dffbaaf..130859c 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -121,8 +121,7 @@
   id: number;
 }
 
-export interface TimeSpanSelection {
-  kind: 'TIMESPAN';
+export interface TimeSpanRange {
   startTs: number;
   endTs: number;
 }
@@ -136,9 +135,8 @@
   cpu: number;
 }
 
-type Selection =
-    NoteSelection|SliceSelection|CounterSelection|HeapDumpSelection|
-    ChromeSliceSelection|TimeSpanSelection|ThreadStateSelection;
+type Selection = NoteSelection|SliceSelection|CounterSelection|
+    HeapDumpSelection|ChromeSliceSelection|ThreadStateSelection;
 
 export interface LogsPagination {
   offset: number;
@@ -177,6 +175,7 @@
   permalink: PermalinkConfig;
   notes: ObjectById<Note>;
   status: Status;
+  timeSpan: TimeSpanRange|null;
   currentSelection: Selection|null;
 
   logsPagination: LogsPagination;
@@ -379,6 +378,7 @@
 
     status: {msg: '', timestamp: 0},
     currentSelection: null,
+    timeSpan: null,
 
     video: null,
     videoEnabled: false,
diff --git a/ui/src/controller/adb.ts b/ui/src/controller/adb.ts
index 54c6733..12ed7e5 100644
--- a/ui/src/controller/adb.ts
+++ b/ui/src/controller/adb.ts
@@ -263,8 +263,12 @@
 
     //  The stream will resolve this promise once it receives the
     //  acknowledgement message from the device.
-    return new Promise<AdbStream>((resolve, _) => {
-      stream.onConnect = () => resolve(stream);
+    return new Promise<AdbStream>((resolve, reject) => {
+      stream.onConnect = () => {
+        stream.onClose = () => {};
+        resolve(stream);
+      };
+      stream.onClose = () => reject();
     });
   }
 
diff --git a/ui/src/controller/adb_record_controller.ts b/ui/src/controller/adb_record_controller.ts
index f52714d..a1643b6 100644
--- a/ui/src/controller/adb_record_controller.ts
+++ b/ui/src/controller/adb_record_controller.ts
@@ -29,11 +29,10 @@
   RECORDING,
   FETCHING
 }
-const DEFAULT_DESTINATION_FILE = '/data/misc/perfetto-traces/trace';
+const DEFAULT_DESTINATION_FILE = '/data/misc/perfetto-traces/trace-by-ui';
 const textDecoder = new _TextDecoder();
 
 export class AdbConsumerPort extends RpcConsumerPort {
-  // public for testing
   traceDestFile = DEFAULT_DESTINATION_FILE;
   private state = AdbState.READY;
   private adb: Adb;
@@ -171,7 +170,12 @@
   }
 
   generateReadTraceCommand(): string {
-    return `gzip -c ${this.traceDestFile}`;
+    // We attempt to delete the trace file after tracing. On a non-root shell,
+    // this will fail (due to selinux denial), but perfetto cmd will be able to
+    // override the file later. However, on a root shell, we need to clean up
+    // the file since perfetto cmd might otherwise fail to override it in a
+    // future session.
+    return `gzip -c ${this.traceDestFile} && rm -f ${this.traceDestFile}`;
   }
 
   generateStartTracingCommand(tracingConfig: Uint8Array) {
diff --git a/ui/src/controller/adb_socket_controller.ts b/ui/src/controller/adb_socket_controller.ts
index 2a03154..bd88643 100644
--- a/ui/src/controller/adb_socket_controller.ts
+++ b/ui/src/controller/adb_socket_controller.ts
@@ -46,6 +46,8 @@
   params: Uint8Array;
 }
 
+const TRACED_SOCKET = '/dev/socket/traced_consumer';
+
 export class AdbSocketConsumerPort extends RpcConsumerPort {
   private state = State.DISCONNECTED;
   private adb: Adb;
@@ -86,7 +88,7 @@
     if (this.state === State.BINDING_IN_PROGRESS) return;
     if (this.state === State.DISCONNECTED) {
       this.state = State.BINDING_IN_PROGRESS;
-      this.device = await this.findDevice();
+      this.device = await AdbSocketConsumerPort.findDevice();
       if (!this.device) {
         this.sendErrorMessage(`Device with serial ${
             globals.state.serialAndroidDeviceConnected} not found.`);
@@ -109,7 +111,8 @@
     const requestId = this.requestId++;
     const methodId = this.findMethodId(method);
     if (methodId === undefined) {
-      this.sendErrorMessage('Calling unsupported method on target.');
+      // This can happen with 'GetTraceStats': it seems that not all the Andorid
+      // <= 9 devices support it.
       console.error(`Method ${method} not supported by the target`);
       return;
     }
@@ -122,9 +125,7 @@
     this.sendFrame(frame);
   }
 
-  async sendFrame(frame: Frame) {
-    console.assert(this.socket !== undefined);
-    if (!this.socket) return;
+  static generateFrameBufferToSend(frame: Frame): Uint8Array {
     const frameProto: Uint8Array = perfetto.ipc.Frame.encode(frame).finish();
     const frameLen = frameProto.length;
     const buf = new Uint8Array(WIRE_PROTOCOL_HEADER_SIZE + frameLen);
@@ -133,13 +134,19 @@
     for (let i = 0; i < frameLen; i++) {
       dv.setUint8(WIRE_PROTOCOL_HEADER_SIZE + i, frameProto[i]);
     }
+    return buf;
+  }
+
+  async sendFrame(frame: Frame) {
+    console.assert(this.socket !== undefined);
+    if (!this.socket) return;
+    const buf = AdbSocketConsumerPort.generateFrameBufferToSend(frame);
     await this.socket.write(buf);
   }
 
   async listenForMessages() {
-    this.socket = await this.adb.socket('/dev/socket/traced_consumer');
-
-    this.socket.onData = newData => this.handleReceivedData(newData);
+    this.socket = await this.adb.socket(TRACED_SOCKET);
+    this.socket.onData = (raw) => this.handleReceivedData(raw);
     this.socket.onClose = () => {
       this.state = State.DISCONNECTED;
       this.commandQueue = [];
@@ -304,13 +311,24 @@
     return undefined;
   }
 
-  async findDevice() {
+  static async findDevice() {
     if (!globals.state.androidDeviceConnected) return undefined;
     const targetSerial = globals.state.androidDeviceConnected.serial;
     const devices = await navigator.usb.getDevices();
     return devices.find(d => d.serialNumber === targetSerial);
   }
 
+  static async hasSocketAccess(device: USBDevice, adb: Adb) {
+    await adb.connect(device);
+    try {
+      const socket = await adb.socket(TRACED_SOCKET);
+      socket.close();
+      return true;
+    } catch (e) {
+      return false;
+    }
+  }
+
   handleIncomingFrame(frame: perfetto.ipc.Frame) {
     const requestId = frame.requestId as number;
     switch (frame.msg) {
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index 062770b..179c0ee 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -27,8 +27,9 @@
 
 import {ControllerAny} from './controller';
 
-type PublishKinds = 'OverviewData'|'TrackData'|'Threads'|'QueryResult'|
-    'LegacyTrace'|'SliceDetails'|'CounterDetails'|'HeapDumpDetails'|'Loading'|
+type PublishKinds =
+    'OverviewData'|'TrackData'|'Threads'|'QueryResult'|'LegacyTrace'|
+    'SliceDetails'|'CounterDetails'|'HeapDumpDetails'|'FileDownload'|'Loading'|
     'Search'|'BufferUsage'|'RecordingLog'|'SearchResult';
 
 export interface App {
diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts
index ddf4276..7bafa23 100644
--- a/ui/src/controller/permalink_controller.ts
+++ b/ui/src/controller/permalink_controller.ts
@@ -24,6 +24,10 @@
 
 export const BUCKET_NAME = 'perfetto-ui-data';
 
+function needsToBeUploaded(obj: {}): obj is ArrayBuffer|File {
+  return obj instanceof ArrayBuffer || obj instanceof File;
+}
+
 export class PermalinkController extends Controller<'main'> {
   private lastRequestId?: string;
   constructor() {
@@ -57,10 +61,12 @@
     const state = globals.state;
 
     // Upload each loaded trace.
-    const fileToUrl = new Map<File, string>();
+    const fileToUrl = new Map<File|ArrayBuffer, string>();
     for (const engine of Object.values<EngineConfig>(state.engines)) {
-      if (!(engine.source instanceof File)) continue;
-      PermalinkController.updateStatus(`Uploading ${engine.source.name}`);
+      if (!needsToBeUploaded(engine.source)) continue;
+      const name = engine.source instanceof File ? engine.source.name :
+                                                   `trace ${engine.id}`;
+      PermalinkController.updateStatus(`Uploading ${name}`);
       const url = await this.saveTrace(engine.source);
       fileToUrl.set(engine.source, url);
     }
@@ -69,8 +75,9 @@
     const uploadState = produce(state, draft => {
       for (const engine of Object.values<Draft<EngineConfig>>(
                draft.engines)) {
-        if (!(engine.source instanceof File)) continue;
-        engine.source = fileToUrl.get(engine.source)!;
+        if (!needsToBeUploaded(engine.source)) continue;
+        const newSource = fileToUrl.get(engine.source);
+        if (newSource) engine.source = newSource;
       }
       draft.permalink = {};
     });
@@ -100,7 +107,7 @@
     return hash;
   }
 
-  private static async saveTrace(trace: File): Promise<string> {
+  private static async saveTrace(trace: File|ArrayBuffer): Promise<string> {
     // TODO(hjd): This should probably also be a hash but that requires
     // trace processor support.
     const name = uuidv4();
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 419c801..d14d695 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -33,6 +33,7 @@
 } from '../common/protos';
 import {MeminfoCounters, VmstatCounters} from '../common/protos';
 import {
+  AdbRecordingTarget,
   isAndroidTarget,
   isChromeTarget,
   MAX_TIME,
@@ -446,10 +447,14 @@
   private consumerPort: ConsumerPort;
   private traceBuffer: Uint8Array[] = [];
   private bufferUpdateInterval: ReturnType<typeof setTimeout>|undefined;
+  private adb = new AdbOverWebUsb();
 
   // We have a different controller for each targetOS. The correct one will be
-  // created when needed, and stored here.
-  private controllers = new Map<TargetOs, RpcConsumerPort>();
+  // created when needed, and stored here. When the key is a string, it is the
+  // serial of the target (used for android devices). When the key is a single
+  // char, it is the 'targetOS'
+  private controllerPromises = new Map<string, Promise<RpcConsumerPort>>();
+
   constructor(args: {app: App, extensionPort: MessagePort}) {
     super('main');
     this.app = args.app;
@@ -566,6 +571,7 @@
   }
 
   onError(message: string) {
+    console.error('Error in record controller: ', message);
     globals.dispatch(
         Actions.setLastRecordingError({error: message.substr(0, 150)}));
     globals.dispatch(Actions.stopRecording({}));
@@ -584,38 +590,59 @@
   // - Android device target: WebUSB is used to communicate using the adb
   // protocol. Actually, there is no full consumer_port implementation, but
   // only the support to start tracing and fetch the file.
-  async getTargetController(target: TargetOs): Promise<RpcConsumerPort> {
-    let controller = this.controllers.get(target);
-    if (controller) return controller;
+  async getTargetController(target: TargetOs, device?: AdbRecordingTarget):
+      Promise<RpcConsumerPort> {
+    const identifier = this.getTargetIdentifier(target, device);
 
-    if (isChromeTarget(target)) {
-      controller = new ChromeExtensionConsumerPort(this.extensionPort, this);
-    } else if (isAndroidTarget(target)) {
-      // TODO(nicomazz): create the correct controller also based on the
-      // selected android device. TargetOS may be changed from a single char to
-      // something else.
-      const socketAccess = await this.hasSocketAccess();
-      controller = socketAccess ?
-          new AdbSocketConsumerPort(new AdbOverWebUsb(), this) :
-          new AdbConsumerPort(new AdbOverWebUsb(), this);
-    }
+    // The reason why caching the target 'record controller' Promise is that
+    // multiple rcp calls can happen while we are trying to understand if an
+    // android device has a socket connection available or not.
+    const precedentPromise = this.controllerPromises.get(identifier);
+    if (precedentPromise) return precedentPromise;
 
-    if (!controller) throw Error(`Unknown target: ${target}`);
+    const controllerPromise =
+        new Promise<RpcConsumerPort>(async (resolve, _) => {
+          let controller: RpcConsumerPort|undefined = undefined;
+          if (isChromeTarget(target)) {
+            controller =
+                new ChromeExtensionConsumerPort(this.extensionPort, this);
+          } else if (isAndroidTarget(target)) {
+            if (!device) throw Error(`No android device connected`);
+            const socketAccess = await this.hasSocketAccess(device);
 
-    this.controllers.set(target, controller);
-    return controller;
+            controller = socketAccess ?
+                new AdbSocketConsumerPort(this.adb, this) :
+                new AdbConsumerPort(this.adb, this);
+          }
+
+          if (!controller) throw Error(`Unknown target: ${target}`);
+          resolve(controller);
+        });
+
+    this.controllerPromises.set(identifier, controllerPromise);
+    return controllerPromise;
   }
 
-  private hasSocketAccess() {
-    // TODO(nicomazz): implement proper logic
-    return Promise.resolve(false);
+  private getTargetIdentifier(target: TargetOs, device?: AdbRecordingTarget):
+      string {
+    return device ? device.serial : target;
+  }
+
+  private async hasSocketAccess(target: AdbRecordingTarget) {
+    const devices = await navigator.usb.getDevices();
+    const device = devices.find(d => d.serialNumber === target.serial);
+    console.assert(device);
+    if (!device) return Promise.resolve(false);
+    return AdbSocketConsumerPort.hasSocketAccess(device, this.adb);
   }
 
   private async rpcImpl(
       method: RPCImplMethod, requestData: Uint8Array,
       _callback: RPCImplCallback) {
     try {
-      (await this.getTargetController(this.app.state.recordConfig.targetOS))
+      const state = this.app.state;
+      (await this.getTargetController(
+           state.recordConfig.targetOS, state.androidDeviceConnected))
           .handleCommand(method.name, requestData);
     } catch (e) {
       console.error(`error invoking ${method}: ${e.message}`);
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 9e881b7..99cbfcf 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -124,6 +124,9 @@
   }
 
   async heapDumpDetails(ts: number, upid: number) {
+    const pidValue = await this.args.engine.query(
+        `select pid from process where upid = ${upid}`);
+    const pid = pidValue.columns[0].longValues![0];
     const allocatedMemory = await this.args.engine.query(
         `select sum(size) from heap_profile_allocation where ts <= ${
             ts} and size > 0 and upid = ${upid}`);
@@ -133,7 +136,7 @@
             ts} and upid = ${upid}`);
     const allocatedNotFreed = allocatedNotFreedMemory.columns[0].longValues![0];
     const startTime = fromNs(ts) - globals.state.traceTime.startSec;
-    return {ts: startTime, allocated, allocatedNotFreed};
+    return {ts: startTime, allocated, allocatedNotFreed, tsNs: ts, pid};
   }
 
   async counterDetails(ts: number, rightTs: number, id: number) {
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 34a79a4..cfa03f2 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -307,6 +307,40 @@
       }
     }
 
+
+    const upidToProcessTracks = new Map();
+    const rawProcessTracks = await engine.query(`
+      select id, upid, name, maxDepth
+      from process_track
+      join (
+        select ref as id, max(depth) as maxDepth
+        from slice
+        where ref_type = 'track' group by ref
+      ) using(id)
+    `);
+    for (let i = 0; i < rawProcessTracks.numRecords; i++) {
+      const trackId = rawProcessTracks.columns[0].longValues![i];
+      const upid = rawProcessTracks.columns[1].longValues![i];
+      const name = rawProcessTracks.columns[2].stringValues![i];
+      const maxDepth = rawProcessTracks.columns[3].longValues![i];
+      const track = {
+        engineId: this.engineId,
+        kind: 'AsyncSliceTrack',
+        name,
+        config: {
+          trackId,
+          maxDepth,
+        },
+      };
+
+      const tracks = upidToProcessTracks.get(upid);
+      if (tracks) {
+        tracks.push(track);
+      } else {
+        upidToProcessTracks.set(upid, [track]);
+      }
+    }
+
     const heapProfiles = await engine.query(`
       select distinct(upid) from heap_profile_allocation`);
 
@@ -513,6 +547,12 @@
               config: {upid}
             });
           }
+
+          if (upidToProcessTracks.has(upid)) {
+            for (const track of upidToProcessTracks.get(upid)) {
+              tracksToAdd.push(Object.assign(track, {trackGroup: pUuid}));
+            }
+          }
         }
       }
       const counterThreadNames = counterUtids[utid];
diff --git a/ui/src/controller/trace_converter.ts b/ui/src/controller/trace_converter.ts
index 5941cb4..e4648a0 100644
--- a/ui/src/controller/trace_converter.ts
+++ b/ui/src/controller/trace_converter.ts
@@ -53,6 +53,74 @@
   (self as {} as {mod: {}}).mod = mod;
 }
 
+export async function ConvertTraceToPprof(
+    pid: number, src: string|File|ArrayBuffer, ts1: number, ts2?: number) {
+  generateBlob(src).then(result => {
+    const mod = trace_to_text({
+      noInitialRun: true,
+      locateFile: (s: string) => s,
+      print: updateStatus,
+      printErr: updateStatus,
+      onRuntimeInitialized: () => {
+        updateStatus('Converting trace');
+        const timestamps = `${ts1}${ts2 === undefined ? '' : `,${ts2}`}`;
+        mod.callMain([
+          'profile',
+          `--pid`,
+          `${pid}`,
+          `--timestamps`,
+          timestamps,
+          '/fs/trace.proto'
+        ]);
+        updateStatus('Trace conversion completed');
+        const heapDirName =
+            Object.keys(mod.FS.lookupPath('/tmp/').node.contents)[0];
+        const heapDirContents =
+            mod.FS.lookupPath(`/tmp/${heapDirName}`).node.contents;
+        const heapDumpFiles = Object.keys(heapDirContents);
+        let fileNum = 0;
+        heapDumpFiles.forEach(heapDump => {
+          const fileContents =
+              mod.FS.lookupPath(`/tmp/${heapDirName}/${heapDump}`)
+                  .node.contents;
+          fileNum++;
+          const fileName = `/heap_dump.${fileNum}.${pid}.pb`;
+          downloadFile(new Blob([fileContents]), fileName);
+        });
+        updateStatus('Profile(s) downloaded');
+      },
+      onAbort: () => {
+        console.log('ABORT');
+      },
+    });
+    mod.FS.mkdir('/fs');
+    mod.FS.mount(
+        mod.FS.filesystems.WORKERFS,
+        {blobs: [{name: 'trace.proto', data: result}]},
+        '/fs');
+  });
+}
+
+async function generateBlob(src: string|ArrayBuffer|File) {
+  let blob: Blob = new Blob();
+  if (typeof src === 'string') {
+    const resp = await fetch(src);
+    if (resp.status !== 200) {
+      throw new Error(`fetch() failed with HTTP error ${resp.status}`);
+    }
+    blob = await resp.blob();
+  } else if (src instanceof ArrayBuffer) {
+    blob = new Blob([new Uint8Array(src, 0, src.byteLength)]);
+  } else {
+    blob = src;
+  }
+  return blob;
+}
+
+function downloadFile(file: Blob, name: string) {
+  globals.publish('FileDownload', {file, name});
+}
+
 function updateStatus(msg: {}) {
   console.log(msg);
   globals.dispatch(Actions.updateStatus({
diff --git a/ui/src/frontend/chrome_slice_panel.ts b/ui/src/frontend/chrome_slice_panel.ts
index 9a6f142..97ce65c 100644
--- a/ui/src/frontend/chrome_slice_panel.ts
+++ b/ui/src/frontend/chrome_slice_panel.ts
@@ -31,9 +31,11 @@
               [m('table',
                  [
                    m('tr', m('th', `Name`), m('td', `${sliceInfo.name}`)),
-                   m('tr',
-                     m('th', `Category`),
-                     m('td', `${sliceInfo.category}`)),
+                   (sliceInfo.category === '[NULL]') ?
+                       null :
+                       m('tr',
+                         m('th', `Category`),
+                         m('td', `${sliceInfo.category}`)),
                    m('tr',
                      m('th', `Start time`),
                      m('td', `${timeToCode(sliceInfo.ts)}`)),
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index f9aca97..e62a889 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -44,8 +44,10 @@
 
 export interface HeapDumpDetails {
   ts?: number;
+  tsNs?: number;
   allocated?: number;
   allocatedNotFreed?: number;
+  pid?: number;
 }
 
 export interface QuantizedLoad {
diff --git a/ui/src/frontend/heap_dump_panel.ts b/ui/src/frontend/heap_dump_panel.ts
index d6c14e1..7c31636 100644
--- a/ui/src/frontend/heap_dump_panel.ts
+++ b/ui/src/frontend/heap_dump_panel.ts
@@ -14,6 +14,7 @@
 
 import * as m from 'mithril';
 
+import {Actions} from '../common/actions';
 import {timeToCode} from '../common/time';
 
 import {globals} from './globals';
@@ -22,13 +23,19 @@
 interface HeapDumpDetailsPanelAttrs {}
 
 export class HeapDumpDetailsPanel extends Panel<HeapDumpDetailsPanelAttrs> {
+  private ts = 0;
+  private pid = 0;
+
   view() {
     const heapDumpInfo = globals.heapDumpDetails;
     if (heapDumpInfo && heapDumpInfo.ts && heapDumpInfo.allocated &&
-        heapDumpInfo.allocatedNotFreed) {
+        heapDumpInfo.allocatedNotFreed && heapDumpInfo.tsNs &&
+        heapDumpInfo.pid) {
+      this.ts = heapDumpInfo.tsNs;
+      this.pid = heapDumpInfo.pid;
       return m(
           '.details-panel',
-          m('.details-panel-heading', `Heap Snapshot Details:`),
+          m('.details-panel-heading', `Heap Profile Details:`),
           m(
               '.details-table',
               [m('table',
@@ -47,7 +54,23 @@
                            heapDumpInfo.allocatedNotFreed
                                .toLocaleString()} bytes`)),
                  ])],
-              ));
+              ),
+          m('.explanation',
+            'Heap profile support is in beta. To explore a heap profile,',
+            ' download and open it in ',
+            m(`a[href='https://pprof.corp.google.com']`, 'pprof'),
+            ' (Googlers only) or ',
+            m(`a[href='https://www.speedscope.app']`, 'Speedscope'),
+            '.'),
+          m('button',
+            {
+              onclick: () => {
+                this.downloadPprof();
+              }
+            },
+            m('i.material-icons', 'file_download'),
+            'Download profile'),
+      );
     } else {
       return m(
           '.details-panel',
@@ -55,5 +78,14 @@
     }
   }
 
+  downloadPprof() {
+    const engine = Object.values(globals.state.engines)[0];
+    if (!engine) return;
+    const src = engine.source;
+    // TODO(tneda): add second timestamp
+    globals.dispatch(
+        Actions.convertTraceToPprof({pid: this.pid, ts1: this.ts, src}));
+  }
+
   renderCanvas() {}
 }
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 3790f61..2b1effe 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -128,6 +128,17 @@
     this.redraw();
   }
 
+  publishFileDownload(args: {file: File, name?: string}) {
+    const url = URL.createObjectURL(args.file);
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = args.name !== undefined ? args.name : args.file.name;
+    document.body.appendChild(a);
+    a.click();
+    document.body.removeChild(a);
+    URL.revokeObjectURL(url);
+  }
+
   publishLoading(loading: boolean) {
     globals.loading = loading;
     globals.rafScheduler.scheduleRedraw();
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index e1ac1dc..3ad39f4 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -16,6 +16,7 @@
 
 import {globals} from './globals';
 import {toggleHelp} from './help_modal';
+import {horizontalScrollAndZoomToRange} from './scroll_helper';
 import {executeSearch} from './search_handler';
 
 // Handles all key events than are not handled by the
@@ -64,6 +65,8 @@
   }
 
   if (startTs !== -1 && endTs !== -1) {
-    globals.makeSelection(Actions.selectTimeSpan({startTs, endTs}));
+    globals.dispatch(Actions.selectTimeSpan({startTs, endTs}));
+    // Zoom into the highlighted time region.
+    horizontalScrollAndZoomToRange(startTs, endTs);
   }
 }
diff --git a/ui/src/frontend/scroll_helper.ts b/ui/src/frontend/scroll_helper.ts
new file mode 100644
index 0000000..97f8de6
--- /dev/null
+++ b/ui/src/frontend/scroll_helper.ts
@@ -0,0 +1,83 @@
+// 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 {getContainingTrackId} from '../common/state';
+import {fromNs, TimeSpan, toNs} from '../common/time';
+
+import {globals} from './globals';
+
+/**
+ * Given a timestamp, if |ts| is not currently in view move the view to
+ * center |ts|, keeping the same zoom level.
+ */
+export function horizontalScrollToTs(ts: number) {
+  const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
+  const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
+  const currentViewNs = endNs - startNs;
+  if (ts < startNs || ts > endNs) {
+    // TODO(taylori): This is an ugly jump, we should do a smooth pan instead.
+    globals.frontendLocalState.updateVisibleTime(new TimeSpan(
+        fromNs(ts - currentViewNs / 2), fromNs(ts + currentViewNs / 2)));
+  }
+}
+
+/**
+ * Given a start and end timestamp (in ns), move the view to center this range
+ * and zoom to a level where the range is 1/5 of the viewport.
+ */
+export function horizontalScrollAndZoomToRange(startTs: number, endTs: number) {
+  const visibleDur = globals.frontendLocalState.visibleWindowTime.end -
+      globals.frontendLocalState.visibleWindowTime.start;
+  const selectDur = endTs - startTs;
+  const viewStartNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
+  const viewEndNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
+  if (selectDur / visibleDur < 0.05 || startTs < viewStartNs ||
+      endTs > viewEndNs) {
+    globals.frontendLocalState.updateVisibleTime(
+        new TimeSpan(startTs - (selectDur * 2), endTs + (selectDur * 2)));
+  }
+}
+
+/**
+ * Given a track id, find a track with that id and scroll it into view. If the
+ * track is nested inside a track group, scroll to that track group instead.
+ */
+export function verticalScrollToTrack(trackId: string|number) {
+  const trackIdString = trackId.toString();
+  let track = document.querySelector('#track_' + trackIdString);
+
+  if (!track) {
+    const parentTrackId = getContainingTrackId(globals.state, trackIdString);
+    if (parentTrackId) {
+      track = document.querySelector('#track_' + parentTrackId);
+    }
+  }
+
+  if (!track) {
+    console.error(`Can't scroll, track (${trackIdString}) not found.`);
+    return;
+  }
+
+  // block: 'nearest' means that it will only scroll if the track is not
+  // currently in view.
+  track.scrollIntoView({behavior: 'smooth', block: 'nearest'});
+}
+
+/**
+ * Scroll vertically and horizontally to reach track (|trackId|) at |ts|.
+ */
+export function scrollToTrackAndTs(trackId: string|number, ts: number) {
+  verticalScrollToTrack(trackId);
+  horizontalScrollToTs(ts);
+}
\ No newline at end of file
diff --git a/ui/src/frontend/search_handler.ts b/ui/src/frontend/search_handler.ts
index 4c64440..351fd27 100644
--- a/ui/src/frontend/search_handler.ts
+++ b/ui/src/frontend/search_handler.ts
@@ -14,10 +14,10 @@
 
 import {searchSegment} from '../base/binary_search';
 import {Actions} from '../common/actions';
-import {getContainingTrackId} from '../common/state';
-import {fromNs, TimeSpan, toNs} from '../common/time';
+import {toNs} from '../common/time';
 
 import {globals} from './globals';
+import {scrollToTrackAndTs} from './scroll_helper';
 
 export function executeSearch(reverse = false) {
   const state = globals.frontendLocalState;
@@ -58,39 +58,11 @@
 }
 
 function moveViewportToCurrentSearch() {
-  // Move viewport if our selection moves outside.
-  const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
-  const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
   const currentTs = globals.currentSearchResults
                         .tsStarts[globals.frontendLocalState.searchIndex];
-  const currentViewNs = endNs - startNs;
-  if (currentTs < startNs || currentTs > endNs) {
-    // TODO(taylori): This is an ugly jump, we should do a smooth pan instead.
-    globals.frontendLocalState.updateVisibleTime(new TimeSpan(
-        fromNs(currentTs - currentViewNs / 2),
-        fromNs(currentTs + currentViewNs / 2)));
-  }
-
-  // Update vertical (up/down) scroll position
   const trackId = globals.currentSearchResults
                       .trackIds[globals.frontendLocalState.searchIndex];
-  let track = document.querySelector('#track_' + trackId);
-
-  if (!track) {
-    const parentTrackId = getContainingTrackId(globals.state, trackId);
-    if (parentTrackId) {
-      track = document.querySelector('#track_' + parentTrackId);
-    }
-  }
-
-  if (!track) {
-    console.error(`Can't scroll search result track not found (${trackId})`);
-    return;
-  }
-
-  // block: 'nearest' means that it will only scroll if the track is not
-  // currently in view.
-  track.scrollIntoView({behavior: 'smooth', block: 'nearest'});
+  scrollToTrackAndTs(trackId, currentTs);
 }
 
 function selectCurrentSearchResult() {
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 1cee13e..4f453ea 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -111,19 +111,28 @@
         i: 'filter_none'
       },
       {t: 'Record new trace', a: navigateRecord, i: 'fiber_smart_record'},
+    ],
+  },
+  {
+    title: 'Current Trace',
+    summary: 'Actions on the current trace',
+    expanded: true,
+    hideIfNoTraceLoaded: true,
+    items: [
       {t: 'Show timeline', a: navigateViewer, i: 'line_style'},
       {
-        t: 'Share current trace',
+        t: 'Share',
         a: dispatchCreatePermalink,
         i: 'share',
         disableInLocalOnlyMode: true,
       },
       {
-        t: 'Download current trace',
+        t: 'Download',
         a: downloadTrace,
         i: 'file_download',
         disableInLocalOnlyMode: true,
       },
+      {t: 'Legacy UI', a: openCurrentTraceWithOldUI, i: 'filter_none'},
     ],
   },
   {
@@ -230,6 +239,31 @@
   getFileElement().click();
 }
 
+function openCurrentTraceWithOldUI() {
+  console.assert(isTraceLoaded());
+  if (!isTraceLoaded) return;
+  const engine = Object.values(globals.state.engines)[0];
+  const src = engine.source;
+  if (src instanceof ArrayBuffer) {
+    openInOldUIWithSizeCheck(new Blob([src]));
+  } else if (src instanceof File) {
+    openInOldUIWithSizeCheck(src);
+  } else {
+    console.assert(typeof src === 'string');
+    console.error('Loading from an URL to catapult is not yet supported');
+    // TODO(nicomazz): Find how to get the data of the current trace if it is
+    // from an URL. It seems that the trace downloaded is given to the trace
+    // processor, but not kept somewhere accessible. Maybe the only way is to
+    // download the trace (again), and then open it. An alternative can be to
+    // save a copy.
+  }
+}
+
+function isTraceLoaded(): boolean {
+  const engine = Object.values(globals.state.engines)[0];
+  return engine !== undefined;
+}
+
 function popupVideoSelectionDialog(e: Event) {
   e.preventDefault();
   delete getFileElement().dataset['useCatapultLegacyUi'];
@@ -262,61 +296,7 @@
       openFileWithLegacyTraceViewer(file);
       return;
     }
-
-    // Perfetto traces smaller than 50mb can be safely opened in the legacy UI.
-    if (file.size < 1024 * 1024 * 50) {
-      globals.dispatch(Actions.convertTraceToJson({file}));
-      return;
-    }
-
-    // Give the user the option to truncate larger perfetto traces.
-    const size = Math.round(file.size / (1024 * 1024));
-    showModal({
-      title: 'Legacy UI may fail to open this trace',
-      content:
-          m('div',
-            m('p',
-              `This trace is ${size}mb, opening it in the legacy UI ` +
-                  `may fail.`),
-            m('p',
-              'More options can be found at ',
-              m('a',
-                {
-                  href: 'https://goto.google.com/opening-large-traces',
-                  target: '_blank'
-                },
-                'go/opening-large-traces'),
-              '.')),
-      buttons: [
-        {
-          text: 'Open full trace (not recommended)',
-          primary: false,
-          id: 'open',
-          action: () => {
-            globals.dispatch(Actions.convertTraceToJson({file}));
-          }
-        },
-        {
-          text: 'Open beginning of trace',
-          primary: true,
-          id: 'truncate-start',
-          action: () => {
-            globals.dispatch(
-                Actions.convertTraceToJson({file, truncate: 'start'}));
-          }
-        },
-        {
-          text: 'Open end of trace',
-          primary: true,
-          id: 'truncate-end',
-          action: () => {
-            globals.dispatch(
-                Actions.convertTraceToJson({file, truncate: 'end'}));
-          }
-        }
-
-      ]
-    });
+    openInOldUIWithSizeCheck(file);
     return;
   }
 
@@ -346,6 +326,64 @@
 
 }
 
+function openInOldUIWithSizeCheck(trace: Blob) {
+  // Perfetto traces smaller than 50mb can be safely opened in the legacy UI.
+  if (trace.size < 1024 * 1024 * 50) {
+    globals.dispatch(Actions.convertTraceToJson({file: trace}));
+    return;
+  }
+
+  // Give the user the option to truncate larger perfetto traces.
+  const size = Math.round(trace.size / (1024 * 1024));
+  showModal({
+    title: 'Legacy UI may fail to open this trace',
+    content:
+        m('div',
+          m('p',
+            `This trace is ${size}mb, opening it in the legacy UI ` +
+                `may fail.`),
+          m('p',
+            'More options can be found at ',
+            m('a',
+              {
+                href: 'https://goto.google.com/opening-large-traces',
+                target: '_blank'
+              },
+              'go/opening-large-traces'),
+            '.')),
+    buttons: [
+      {
+        text: 'Open full trace (not recommended)',
+        primary: false,
+        id: 'open',
+        action: () => {
+          globals.dispatch(Actions.convertTraceToJson({file: trace}));
+        }
+      },
+      {
+        text: 'Open beginning of trace',
+        primary: true,
+        id: 'truncate-start',
+        action: () => {
+          globals.dispatch(
+              Actions.convertTraceToJson({file: trace, truncate: 'start'}));
+        }
+      },
+      {
+        text: 'Open end of trace',
+        primary: true,
+        id: 'truncate-end',
+        action: () => {
+          globals.dispatch(
+              Actions.convertTraceToJson({file: trace, truncate: 'end'}));
+        }
+      }
+
+    ]
+  });
+  return;
+}
+
 function navigateRecord(e: Event) {
   e.preventDefault();
   globals.dispatch(Actions.navigate({route: '/record'}));
@@ -357,16 +395,12 @@
 }
 
 function localOnlyMode(): boolean {
-  if (globals.frontendLocalState.localOnlyMode) return true;
-  const engine = Object.values(globals.state.engines)[0];
-  if (!engine) return true;
-  const src = engine.source;
-  return (src instanceof ArrayBuffer);
+  return globals.frontendLocalState.localOnlyMode;
 }
 
 function dispatchCreatePermalink(e: Event) {
   e.preventDefault();
-  if (localOnlyMode()) return;
+  if (localOnlyMode() || !isTraceLoaded()) return;
 
   const result = confirm(
       `Upload the trace and generate a permalink. ` +
@@ -376,26 +410,31 @@
 
 function downloadTrace(e: Event) {
   e.preventDefault();
-  if (localOnlyMode()) return;
+  if (!isTraceLoaded() || localOnlyMode()) return;
 
   const engine = Object.values(globals.state.engines)[0];
   if (!engine) return;
   const src = engine.source;
   if (typeof src === 'string') {
     window.open(src);
-  } else if (src instanceof ArrayBuffer) {
-    console.error('Can not download external trace.');
     return;
-  } else {
-    const url = URL.createObjectURL(src);
-    const a = document.createElement('a');
-    a.href = url;
-    a.download = src.name;
-    document.body.appendChild(a);
-    a.click();
-    document.body.removeChild(a);
-    URL.revokeObjectURL(url);
   }
+
+  let url = '';
+  if (src instanceof ArrayBuffer) {
+    const blob = new Blob([src], {type: 'application/octet-stream'});
+    url = URL.createObjectURL(blob);
+  } else {
+    console.assert(src instanceof File);
+    url = URL.createObjectURL(src);
+  }
+
+  const a = document.createElement('a');
+  a.href = url;
+  document.body.appendChild(a);
+  a.click();
+  document.body.removeChild(a);
+  URL.revokeObjectURL(url);
 }
 
 
@@ -403,6 +442,7 @@
   view() {
     const vdomSections = [];
     for (const section of SECTIONS) {
+      if (section.hideIfNoTraceLoaded && !isTraceLoaded()) continue;
       const vdomItems = [];
       for (const item of section.items) {
         let attrs = {
@@ -430,8 +470,8 @@
                   globals.rafScheduler.scheduleFullRedraw();
                 }
               },
-              m('h1', section.title),
-              m('h2', section.summary), ),
+              m('h1', {title: section.summary}, section.title),
+              m('h2', section.summary)),
             m('.section-content', m('ul', vdomItems))));
     }
     if (globals.state.videoEnabled) {
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index cc899db..7c4df77 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -124,10 +124,10 @@
       ctx.fillRect(xAndTime[0], 0, 1, size.height);
     }
 
-    const selection = globals.state.currentSelection;
-    if (selection !== null && selection.kind === `TIMESPAN`) {
-      const start = Math.min(selection.startTs, selection.endTs);
-      const end = Math.max(selection.startTs, selection.endTs);
+    const timeSpan = globals.state.timeSpan;
+    if (timeSpan !== null) {
+      const start = Math.min(timeSpan.startTs, timeSpan.endTs);
+      const end = Math.max(timeSpan.startTs, timeSpan.endTs);
       this.renderSpan(ctx, size, new TimeSpan(start, end));
     } else if (globals.frontendLocalState.showTimeSelectPreview) {
       this.renderHover(ctx, size, globals.frontendLocalState.hoveredTimestamp);
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 9f2bd85..6b5cd99 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -135,6 +135,15 @@
                             size.height,
                             `rgb(52,69,150)`);
     }
+    if (globals.state.timeSpan !== null) {
+      drawVerticalSelection(
+          ctx,
+          localState.timeScale,
+          globals.state.timeSpan.startTs,
+          globals.state.timeSpan.endTs,
+          size.height,
+          `rgba(0,0,0,0.5)`);
+    }
     if (globals.state.currentSelection !== null) {
       if (globals.state.currentSelection.kind === 'NOTE') {
         const note = globals.state.notes[globals.state.currentSelection.id];
@@ -144,15 +153,6 @@
                                size.height,
                                note.color);
       }
-      if (globals.state.currentSelection.kind === 'TIMESPAN') {
-        drawVerticalSelection(
-            ctx,
-            localState.timeScale,
-            globals.state.currentSelection.startTs,
-            globals.state.currentSelection.endTs,
-            size.height,
-            `rgba(0,0,0,0.5)`);
-      }
       if (globals.state.currentSelection.kind === 'SLICE' &&
           globals.sliceDetails.wakeupTs !== undefined) {
         drawVerticalLineAtTime(
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 18ad066..5912c96 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -146,11 +146,18 @@
         attrs.track.onMouseOut();
         globals.rafScheduler.scheduleRedraw();
       },
-      onclick: (e:MouseEvent) => {
+      onclick: (e: MouseEvent) => {
         // If we are selecting a timespan - do not pass the click to the track.
-        const selection = globals.state.currentSelection;
-        if (selection && selection.kind === 'TIMESPAN') return;
-        if (attrs.track.onMouseClick({x: e.layerX, y: e.layerY})) {
+        if (e.shiftKey) return;
+        // If the click is outside of the current timespan, clear it.
+        const clickTime =
+            globals.frontendLocalState.timeScale.pxToTime(e.layerX);
+        if (globals.state.timeSpan !== null &&
+            (clickTime < globals.state.timeSpan.startTs ||
+             clickTime > globals.state.timeSpan.endTs)) {
+          globals.dispatch(Actions.removeTimeSpan({}));
+          e.stopPropagation();
+        } else if (attrs.track.onMouseClick({x: e.layerX, y: e.layerY})) {
           e.stopPropagation();
         }
         globals.rafScheduler.scheduleRedraw();
@@ -249,7 +256,15 @@
                              size.height,
                              `rgb(52,69,150)`);
     }
-
+    if (globals.state.timeSpan !== null) {
+      drawVerticalSelection(
+          ctx,
+          localState.timeScale,
+          globals.state.timeSpan.startTs,
+          globals.state.timeSpan.endTs,
+          size.height,
+          `rgba(0,0,0,0.5)`);
+    }
     if (globals.state.currentSelection !== null) {
       if (globals.state.currentSelection.kind === 'NOTE') {
         const note = globals.state.notes[globals.state.currentSelection.id];
@@ -259,15 +274,6 @@
                                size.height,
                                note.color);
       }
-      if (globals.state.currentSelection.kind === 'TIMESPAN') {
-        drawVerticalSelection(
-            ctx,
-            localState.timeScale,
-            globals.state.currentSelection.startTs,
-            globals.state.currentSelection.endTs,
-            size.height,
-            `rgba(0,0,0,0.5)`);
-      }
       if (globals.state.currentSelection.kind === 'SLICE' &&
           globals.sliceDetails.wakeupTs !== undefined) {
         drawVerticalLineAtTime(
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index a6c415f..63d7a48 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -257,7 +257,7 @@
                                scale.pxToTime(startPx - TRACK_SHELL_WIDTH));
         const endTs = Math.min(traceTime.endSec,
                                scale.pxToTime(endPx - TRACK_SHELL_WIDTH));
-        globals.makeSelection(Actions.selectTimeSpan({startTs, endTs}));
+        globals.dispatch(Actions.selectTimeSpan({startTs, endTs}));
         globals.rafScheduler.scheduleRedraw();
       }
     });
@@ -309,9 +309,7 @@
           }));
           break;
         case 'HEAP_DUMP':
-          detailsPanels.push(m(HeapDumpDetailsPanel, {
-            key: 'heap_dump',
-          }));
+          detailsPanels.push(m(HeapDumpDetailsPanel, {key: 'heap_dump'}));
           break;
         case 'CHROME_SLICE':
           detailsPanels.push(m(ChromeSliceDetailsPanel));
diff --git a/ui/src/tracks/all_controller.ts b/ui/src/tracks/all_controller.ts
index 79d3070..ca4f7c8 100644
--- a/ui/src/tracks/all_controller.ts
+++ b/ui/src/tracks/all_controller.ts
@@ -25,3 +25,4 @@
 import './process_summary/controller';
 import './thread_state/controller';
 import './vsync/controller';
+import './async_slices/controller';
diff --git a/ui/src/tracks/all_frontend.ts b/ui/src/tracks/all_frontend.ts
index dfa57fc..3ce3de0 100644
--- a/ui/src/tracks/all_frontend.ts
+++ b/ui/src/tracks/all_frontend.ts
@@ -25,3 +25,4 @@
 import './process_summary/frontend';
 import './thread_state/frontend';
 import './vsync/frontend';
+import './async_slices/frontend';
diff --git a/ui/src/tracks/async_slices/common.ts b/ui/src/tracks/async_slices/common.ts
new file mode 100644
index 0000000..3b7c885
--- /dev/null
+++ b/ui/src/tracks/async_slices/common.ts
@@ -0,0 +1,22 @@
+// 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.
+
+export {Data} from '../chrome_slices/common';
+
+export const SLICE_TRACK_KIND = 'AsyncSliceTrack';
+
+export interface Config {
+  maxDepth: number;
+  trackId: number;
+}
diff --git a/ui/src/tracks/async_slices/controller.ts b/ui/src/tracks/async_slices/controller.ts
new file mode 100644
index 0000000..e56999b
--- /dev/null
+++ b/ui/src/tracks/async_slices/controller.ts
@@ -0,0 +1,143 @@
+// 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 {fromNs, toNs} from '../../common/time';
+import {LIMIT} from '../../common/track_data';
+import {
+  TrackController,
+  trackControllerRegistry,
+} from '../../controller/track_controller';
+
+import {Config, Data, SLICE_TRACK_KIND} from './common';
+
+class AsyncSliceTrackController extends TrackController<Config, Data> {
+  static readonly kind = SLICE_TRACK_KIND;
+  private setup = false;
+
+  async onBoundsChange(start: number, end: number, resolution: number):
+      Promise<Data> {
+    const startNs = toNs(start);
+    const endNs = toNs(end);
+    // Ns in 1px width. We want all slices smaller than 1px to be grouped.
+    const minNs = toNs(resolution);
+
+    if (!this.setup) {
+      await this.query(
+          `create virtual table ${this.tableName('window')} using window;`);
+
+      await this.query(
+          `create view ${this.tableName('small')} as ` +
+          `select ts,dur,depth,name,slice_id from slice ` +
+          `where ref_type = 'track' ` +
+          `and ref = ${this.config.trackId} ` +
+          `and dur < ${minNs} ` +
+          `order by ts;`);
+
+      await this.query(`create virtual table ${this.tableName('span')} using
+      span_join(${this.tableName('small')} PARTITIONED depth,
+      ${this.tableName('window')});`);
+
+      this.setup = true;
+    }
+
+    const windowDurNs = Math.max(1, endNs - startNs);
+
+    this.query(`update ${this.tableName('window')} set
+    window_start=${startNs},
+    window_dur=${windowDurNs},
+    quantum=${minNs}`);
+
+    await this.query(`drop view if exists ${this.tableName('small')}`);
+    await this.query(`drop view if exists ${this.tableName('big')}`);
+    await this.query(`drop view if exists ${this.tableName('summary')}`);
+
+    await this.query(
+        `create view ${this.tableName('small')} as ` +
+        `select ts,dur,depth,name, slice_id from slice ` +
+        `where ref_type = 'track' ` +
+        `and ref = ${this.config.trackId} ` +
+        `and dur < ${minNs} ` +
+        `order by ts `);
+
+    await this.query(
+        `create view ${this.tableName('big')} as ` +
+        `select ts,dur,depth,name, slice_id from slice ` +
+        `where ref_type = 'track' ` +
+        `and ref = ${this.config.trackId} ` +
+        `and ts >= ${startNs} - dur ` +
+        `and ts <= ${endNs} ` +
+        `and dur >= ${minNs} ` +
+        `order by ts `);
+
+    // So that busy slices never overlap, we use the start of the bucket
+    // as the ts, even though min(ts) would technically be more accurate.
+    await this.query(`create view ${this.tableName('summary')} as select
+      (quantum_ts * ${minNs} + ${startNs}) as ts,
+      ${minNs} as dur,
+      depth,
+      'Busy' as name,
+      -1 as slice_id
+      from ${this.tableName('span')}
+      group by depth, quantum_ts
+      order by ts;`);
+
+    const query = `select * from ${this.tableName('summary')} UNION ` +
+        `select * from ${this.tableName('big')} order by ts limit ${LIMIT}`;
+
+    const rawResult = await this.query(query);
+
+    if (rawResult.error) {
+      throw new Error(`Query error "${query}": ${rawResult.error}`);
+    }
+
+    const numRows = +rawResult.numRecords;
+
+    const slices: Data = {
+      start,
+      end,
+      resolution,
+      length: numRows,
+      strings: [],
+      sliceIds: new Float64Array(numRows),
+      starts: new Float64Array(numRows),
+      ends: new Float64Array(numRows),
+      depths: new Uint16Array(numRows),
+      titles: new Uint16Array(numRows),
+    };
+
+    const stringIndexes = new Map<string, number>();
+    function internString(str: string) {
+      let idx = stringIndexes.get(str);
+      if (idx !== undefined) return idx;
+      idx = slices.strings.length;
+      slices.strings.push(str);
+      stringIndexes.set(str, idx);
+      return idx;
+    }
+
+    for (let row = 0; row < numRows; row++) {
+      const cols = rawResult.columns;
+      const startSec = fromNs(+cols[0].longValues![row]);
+      slices.starts[row] = startSec;
+      slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
+      slices.depths[row] = +cols[2].longValues![row];
+      slices.titles[row] = internString(cols[3].stringValues![row]);
+      slices.sliceIds[row] = +cols[4].longValues![row];
+    }
+    return slices;
+  }
+}
+
+
+trackControllerRegistry.register(AsyncSliceTrackController);
diff --git a/ui/src/tracks/async_slices/frontend.ts b/ui/src/tracks/async_slices/frontend.ts
new file mode 100644
index 0000000..918f831
--- /dev/null
+++ b/ui/src/tracks/async_slices/frontend.ts
@@ -0,0 +1,29 @@
+// 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 {TrackState} from '../../common/state';
+import {Track} from '../../frontend/track';
+import {trackRegistry} from '../../frontend/track_registry';
+import {ChromeSliceTrack} from '../chrome_slices/frontend';
+
+import {SLICE_TRACK_KIND} from './common';
+
+export class AsyncSliceTrack extends ChromeSliceTrack {
+  static readonly kind = SLICE_TRACK_KIND;
+  static create(trackState: TrackState): Track {
+    return new AsyncSliceTrack(trackState);
+  }
+}
+
+trackRegistry.register(AsyncSliceTrack);
diff --git a/ui/src/tracks/chrome_slices/common.ts b/ui/src/tracks/chrome_slices/common.ts
index ad240c3..68653da 100644
--- a/ui/src/tracks/chrome_slices/common.ts
+++ b/ui/src/tracks/chrome_slices/common.ts
@@ -30,5 +30,4 @@
   ends: Float64Array;
   depths: Uint16Array;
   titles: Uint16Array;      // Index in |strings|.
-  categories: Uint16Array;  // Index in |strings|.
 }
diff --git a/ui/src/tracks/chrome_slices/controller.ts b/ui/src/tracks/chrome_slices/controller.ts
index 1d44a7c..cbe6486 100644
--- a/ui/src/tracks/chrome_slices/controller.ts
+++ b/ui/src/tracks/chrome_slices/controller.ts
@@ -38,10 +38,8 @@
 
       await this.query(
           `create view ${this.tableName('small')} as ` +
-          `select ts,dur,depth,cat,name,slice_id from slices ` +
+          `select ts,dur,depth,name,slice_id from slice ` +
           `where utid = ${this.config.utid} ` +
-          `and ts >= ${startNs} - dur ` +
-          `and ts <= ${endNs} ` +
           `and dur < ${minNs} ` +
           `order by ts;`);
 
@@ -65,16 +63,14 @@
 
     await this.query(
         `create view ${this.tableName('small')} as ` +
-        `select ts,dur,depth,cat,name, slice_id from slices ` +
+        `select ts,dur,depth,name,slice_id from slice ` +
         `where utid = ${this.config.utid} ` +
-        `and ts >= ${startNs} - dur ` +
-        `and ts <= ${endNs} ` +
         `and dur < ${minNs} ` +
         `order by ts `);
 
     await this.query(
         `create view ${this.tableName('big')} as ` +
-        `select ts,dur,depth,cat,name, slice_id from slices ` +
+        `select ts,dur,depth,name,slice_id from slice ` +
         `where utid = ${this.config.utid} ` +
         `and ts >= ${startNs} - dur ` +
         `and ts <= ${endNs} ` +
@@ -87,11 +83,10 @@
       (quantum_ts * ${minNs} + ${startNs}) as ts,
       ${minNs} as dur,
       depth,
-      cat,
       'Busy' as name,
       -1 as slice_id
       from ${this.tableName('span')}
-      group by cat, depth, quantum_ts
+      group by depth, quantum_ts
       order by ts;`);
 
     const query = `select * from ${this.tableName('summary')} UNION ` +
@@ -116,7 +111,6 @@
       ends: new Float64Array(numRows),
       depths: new Uint16Array(numRows),
       titles: new Uint16Array(numRows),
-      categories: new Uint16Array(numRows),
     };
 
     const stringIndexes = new Map<string, number>();
@@ -135,9 +129,8 @@
       slices.starts[row] = startSec;
       slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
       slices.depths[row] = +cols[2].longValues![row];
-      slices.categories[row] = internString(cols[3].stringValues![row]);
-      slices.titles[row] = internString(cols[4].stringValues![row]);
-      slices.sliceIds[row] = +cols[5].longValues![row];
+      slices.titles[row] = internString(cols[3].stringValues![row]);
+      slices.sliceIds[row] = +cols[4].longValues![row];
     }
     return slices;
   }
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index 5826f67..544d84f 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -34,9 +34,9 @@
   return hash & 0xff;
 }
 
-class ChromeSliceTrack extends Track<Config, Data> {
-  static readonly kind = SLICE_TRACK_KIND;
-  static create(trackState: TrackState): ChromeSliceTrack {
+export class ChromeSliceTrack extends Track<Config, Data> {
+  static readonly kind: string = SLICE_TRACK_KIND;
+  static create(trackState: TrackState): Track {
     return new ChromeSliceTrack(trackState);
   }