Merge "perfetto-ui: Tidy up scrolling code"
diff --git a/Android.bp b/Android.bp
index 89742c8..2cc1d07 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",
diff --git a/BUILD b/BUILD
index a349220..8bf03e6 100644
--- a/BUILD
+++ b/BUILD
@@ -1269,6 +1269,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",
@@ -1277,6 +1285,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",
@@ -1686,6 +1705,9 @@
     srcs = [
         "protos/perfetto/trace/perfetto_trace.proto",
     ],
+    visibility = [
+        "//visibility:public",
+    ],
 )
 
 # GN target: //protos/perfetto/config/profiling:zero
@@ -1842,6 +1864,9 @@
     srcs = [
         "protos/perfetto/metrics/metrics.proto",
     ],
+    visibility = [
+        "//visibility:public",
+    ],
     deps = [
         ":protos_perfetto_metrics_android_protos",
     ],
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/rules.bzl b/bazel/rules.bzl
index 87c48c7..2b45862 100644
--- a/bazel/rules.bzl
+++ b/bazel/rules.bzl
@@ -21,7 +21,7 @@
 
 def default_cc_args():
     return {
-        "deps": [PERFETTO_CONFIG.root + ":build_config_hdr"],
+        "deps": PERFETTO_CONFIG.deps.build_config,
         "copts": [],
         "includes": ["include"],
         "linkopts": select({
diff --git a/bazel/standalone/perfetto_cfg.bzl b/bazel/standalone/perfetto_cfg.bzl
index e89c7a0..cd4df1a 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"],
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/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/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/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/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/row_map.h b/src/trace_processor/db/row_map.h
index e6e1f81..7b2b10e 100644
--- a/src/trace_processor/db/row_map.h
+++ b/src/trace_processor/db/row_map.h
@@ -82,7 +82,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);
     }
@@ -109,8 +109,13 @@
   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 7e375ac..719b533 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)));
 }
 
@@ -70,10 +70,10 @@
   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));
 
   auto res = row_map.SelectRows(picker);
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/export_json.cc b/src/trace_processor/export_json.cc
index fd5ea07..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>
@@ -848,3 +851,5 @@
 }  // namespace json
 }  // namespace trace_processor
 }  // namespace perfetto
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/fuchsia_trace_parser.cc b/src/trace_processor/fuchsia_trace_parser.cc
index 5313dd3..9afcfe8 100644
--- a/src/trace_processor/fuchsia_trace_parser.cc
+++ b/src/trace_processor/fuchsia_trace_parser.cc
@@ -197,6 +197,9 @@
           UniqueTid utid =
               procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
                                   static_cast<uint32_t>(tinfo.pid));
+          // TODO(lalitm): make use of this track id.
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+          perfetto::base::ignore_result(track_id);
           RowId row = context_->event_tracker->PushInstant(ts, name, 0, utid,
                                                            RefType::kRefUtid);
           for (const Arg& arg : args) {
@@ -266,6 +269,9 @@
           UniqueTid utid =
               procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
                                   static_cast<uint32_t>(tinfo.pid));
+          // TODO(lalitm): make use of this track id.
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+          perfetto::base::ignore_result(track_id);
           slices->Begin(ts, utid, RefType::kRefUtid, cat, name);
           break;
         }
@@ -273,6 +279,9 @@
           UniqueTid utid =
               procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
                                   static_cast<uint32_t>(tinfo.pid));
+          // TODO(lalitm): make use of this track id.
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+          perfetto::base::ignore_result(track_id);
           // 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.
@@ -285,6 +294,9 @@
           UniqueTid utid =
               procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
                                   static_cast<uint32_t>(tinfo.pid));
+          // TODO(lalitm): make use of this track id.
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+          perfetto::base::ignore_result(track_id);
           slices->Scoped(ts, utid, RefType::kRefUtid, cat, name, end_ts - ts);
           break;
         }
diff --git a/src/trace_processor/json_trace_parser.cc b/src/trace_processor/json_trace_parser.cc
index 5dc2816..1f0dbb4 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,10 +87,16 @@
 
   switch (phase) {
     case 'B': {  // TRACE_EVENT_BEGIN.
+      // TODO(lalitm): make use of this track id.
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      perfetto::base::ignore_result(track_id);
       slice_tracker->Begin(timestamp, utid, RefType::kRefUtid, cat_id, name_id);
       break;
     }
     case 'E': {  // TRACE_EVENT_END.
+      // TODO(lalitm): make use of this track id.
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      perfetto::base::ignore_result(track_id);
       slice_tracker->End(timestamp, utid, RefType::kRefUtid, cat_id, name_id);
       break;
     }
@@ -100,6 +105,9 @@
           json_trace_utils::CoerceToNs(value["dur"]);
       if (!opt_dur.has_value())
         return;
+      // TODO(lalitm): make use of this track id.
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      perfetto::base::ignore_result(track_id);
       slice_tracker->Scoped(timestamp, utid, RefType::kRefUtid, cat_id, name_id,
                             opt_dur.value());
       break;
@@ -124,3 +132,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/proto_incremental_state.h b/src/trace_processor/proto_incremental_state.h
index 408b2fd..837325c 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 {
@@ -161,7 +166,9 @@
     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:
@@ -191,7 +198,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 +262,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 d8c972a..c2410d5 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -36,6 +36,8 @@
 #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"
@@ -132,9 +134,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,
@@ -1771,6 +1788,9 @@
 
   switch (static_cast<char>(phase)) {
     case 'B': {  // TRACE_EVENT_PHASE_BEGIN.
+      // TODO(lalitm): make use of this track id.
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      perfetto::base::ignore_result(track_id);
       auto opt_slice_id = slice_tracker->Begin(
           ts, utid, RefType::kRefUtid, category_id, name_id, args_callback);
       if (opt_slice_id.has_value()) {
@@ -1785,6 +1805,9 @@
       break;
     }
     case 'E': {  // TRACE_EVENT_PHASE_END.
+      // TODO(lalitm): make use of this track id.
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      perfetto::base::ignore_result(track_id);
       auto opt_slice_id = slice_tracker->End(
           ts, utid, RefType::kRefUtid, category_id, name_id, args_callback);
       if (opt_slice_id.has_value()) {
@@ -1798,6 +1821,9 @@
       auto duration_ns = legacy_event.duration_us() * 1000;
       if (duration_ns < 0)
         return;
+      // TODO(lalitm): make use of this track id.
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      perfetto::base::ignore_result(track_id);
       auto opt_slice_id =
           slice_tracker->Scoped(ts, utid, RefType::kRefUtid, category_id,
                                 name_id, duration_ns, args_callback);
@@ -1823,6 +1849,9 @@
       switch (legacy_event.instant_event_scope()) {
         case LegacyEvent::SCOPE_UNSPECIFIED:
         case LegacyEvent::SCOPE_THREAD: {
+          // TODO(lalitm): make use of this track id.
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+          perfetto::base::ignore_result(track_id);
           auto opt_slice_id =
               slice_tracker->Scoped(ts, utid, RefType::kRefUtid, category_id,
                                     name_id, duration_ns, args_callback);
@@ -2436,6 +2465,9 @@
       sprintf(fallback, "Event %d", eid);
       name_id = context_->storage->InternString(fallback);
     }
+    // TODO(lalitm): make use of this track id.
+    TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+    perfetto::base::ignore_result(track_id);
     context_->slice_tracker->Scoped(ts, utid, RefType::kRefUtid, cat_id,
                                     name_id, event.event_duration_ns());
   } else if (event.has_counter_id()) {
diff --git a/src/trace_processor/proto_trace_tokenizer.cc b/src/trace_processor/proto_trace_tokenizer.cc
index 2714db9..e093ae8 100644
--- a/src/trace_processor/proto_trace_tokenizer.cc
+++ b/src/trace_processor/proto_trace_tokenizer.cc
@@ -28,6 +28,7 @@
 #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"
@@ -58,7 +59,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 +78,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
@@ -424,17 +425,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()));
   }
 
diff --git a/src/trace_processor/slice_tracker.cc b/src/trace_processor/slice_tracker.cc
index 70083da..500d23c 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,6 +46,9 @@
   UniqueTid utid =
       context_->process_tracker->UpdateThread(ftrace_tid, atrace_tgid);
   ftrace_to_atrace_tgid_[ftrace_tid] = atrace_tgid;
+  // TODO(lalitm): make use of this track id.
+  TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+  perfetto::base::ignore_result(track_id);
   return Begin(timestamp, utid, RefType::kRefUtid, category, name);
 }
 
@@ -137,6 +141,9 @@
   }
   UniqueTid utid =
       context_->process_tracker->UpdateThread(ftrace_tid, actual_tgid);
+  // TODO(lalitm): make use of this track id.
+  TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+  perfetto::base::ignore_result(track_id);
   return End(timestamp, utid, RefType::kRefUtid);
 }
 
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/syscall_tracker.h b/src/trace_processor/syscall_tracker.h
index 76523dd..4ad84a1 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,6 +51,9 @@
   void Enter(int64_t ts, UniqueTid utid, uint32_t syscall_num) {
     StringId name = SyscallNumberToStringId(syscall_num);
     if (!name.is_null()) {
+      // TODO(lalitm): make use of this track id.
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      perfetto::base::ignore_result(track_id);
       context_->slice_tracker->Begin(ts, utid, RefType::kRefUtid, 0 /* cat */,
                                      name);
     }
@@ -58,6 +62,9 @@
   void Exit(int64_t ts, UniqueTid utid, uint32_t syscall_num) {
     StringId name = SyscallNumberToStringId(syscall_num);
     if (!name.is_null()) {
+      // TODO(lalitm): make use of this track id.
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      perfetto::base::ignore_result(track_id);
       context_->slice_tracker->End(ts, utid, RefType::kRefUtid, 0 /* cat */,
                                    name);
     }
diff --git a/src/trace_processor/syscall_tracker_unittest.cc b/src/trace_processor/syscall_tracker_unittest.cc
index fa5fce5..21861ec 100644
--- a/src/trace_processor/syscall_tracker_unittest.cc
+++ b/src/trace_processor/syscall_tracker_unittest.cc
@@ -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,6 +62,7 @@
  protected:
   TraceProcessorContext context;
   MockSliceTracker* slice_tracker;
+  TrackTracker* track_tracker;
 };
 
 TEST_F(SyscallTrackerTest, ReportUnknownSyscalls) {
diff --git a/src/trace_processor/systrace_parser.cc b/src/trace_processor/systrace_parser.cc
index ec5d4c5..082dec5 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, RefType::kRefTrack, 0,
+                                       name_id);
+      } else {
+        context_->slice_tracker->End(ts, track_id, RefType::kRefTrack);
+      }
       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/tables/track_tables.h b/src/trace_processor/tables/track_tables.h
index fa142ae..76a4417 100644
--- a/src/trace_processor/tables/track_tables.h
+++ b/src/trace_processor/tables/track_tables.h
@@ -39,6 +39,13 @@
 
 PERFETTO_TP_TABLE(PERFETTO_TP_PROCESS_TRACK_TABLE_DEF);
 
+#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_THREAD_TRACK_TABLE_DEF);
+
 #define PERFETTO_TP_GPU_TRACK_DEF(NAME, PARENT, C) \
   NAME(GpuTrackTable, "gpu_track")                 \
   PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C)           \
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_storage.h b/src/trace_processor/trace_storage.h
index df9092b..0799eae 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -1174,6 +1174,13 @@
     return &process_track_table_;
   }
 
+  const tables::ThreadTrackTable& thread_track_table() const {
+    return thread_track_table_;
+  }
+  tables::ThreadTrackTable* mutable_thread_track_table() {
+    return &thread_track_table_;
+  }
+
   const Slices& slices() const { return slices_; }
   Slices* mutable_slices() { return &slices_; }
 
@@ -1311,6 +1318,7 @@
   tables::TrackTable track_table_{&string_pool_, nullptr};
   tables::GpuTrackTable gpu_track_table_{&string_pool_, &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 0a22369..cfbbf76 100644
--- a/src/trace_processor/track_tracker.cc
+++ b/src/trace_processor/track_tracker.cc
@@ -27,8 +27,22 @@
       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")),
       context_(context) {}
 
+TrackId TrackTracker::InternThreadTrack(UniqueTid utid) {
+  ThreadTrackTuple tuple{utid};
+  auto it = thread_tracks_.find(tuple);
+  if (it != thread_tracks_.end())
+    return it->second;
+
+  tables::ThreadTrackTable::Row row;
+  row.utid = utid;
+  auto id = context_->storage->mutable_thread_track_table()->Insert(row);
+  thread_tracks_[tuple] = id;
+  return id;
+}
+
 TrackId TrackTracker::InternFuchsiaAsyncTrack(StringId name,
                                               int64_t correlation_id) {
   FuchsiaAsyncTrackTuple tuple{correlation_id};
@@ -95,5 +109,27 @@
   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;
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/track_tracker.h b/src/trace_processor/track_tracker.h
index fc5f3de..8aa7ee8 100644
--- a/src/trace_processor/track_tracker.h
+++ b/src/trace_processor/track_tracker.h
@@ -28,6 +28,9 @@
  public:
   explicit TrackTracker(TraceProcessorContext*);
 
+  // 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);
 
@@ -40,7 +43,18 @@
                             int64_t source_id,
                             StringId source_scope);
 
+  // Interns a Android async track into the storage.
+  TrackId InternAndroidAsyncTrack(StringId name, uint32_t upid, int64_t cookie);
+
  private:
+  struct ThreadTrackTuple {
+    UniqueTid utid;
+
+    friend bool operator<(const ThreadTrackTuple& l,
+                          const ThreadTrackTuple& r) {
+      return l.utid < r.utid;
+    }
+  };
   struct FuchsiaAsyncTrackTuple {
     int64_t correlation_id;
 
@@ -70,10 +84,23 @@
              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<ThreadTrackTuple, TrackId> thread_tracks_;
   std::map<FuchsiaAsyncTrackTuple, TrackId> fuchsia_async_tracks_;
   std::map<GpuTrackTuple, TrackId> gpu_tracks_;
   std::map<ChromeTrackTuple, TrackId> chrome_tracks_;
+  std::map<AndroidAsyncTrackTuple, TrackId> android_async_tracks_;
 
   StringId source_key_ = 0;
   StringId source_id_key_ = 0;
@@ -81,6 +108,7 @@
 
   StringId fuchsia_source_ = 0;
   StringId chrome_source_ = 0;
+  StringId android_source_ = 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/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 83d0fe0..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
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/tools/gen_android_bp b/tools/gen_android_bp
index 8bfe686..28360fa 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..47fc90e 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -66,8 +66,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 +232,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/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index 225bf70..9377bb6 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -20,10 +20,11 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.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"
@@ -35,18 +36,29 @@
 
 int Usage(const char* argv0) {
   printf(
-      "Usage: %s systrace|json|text|profile [--truncate start|end] [trace.pb] "
+      "Usage: %s systrace|json|text|profile [--pid PID] "
+      "[--timestamps TIMESTAMP1,TIMESTAMP2,...] "
+      "[--truncate start|end] [trace.pb] "
       "[trace.txt]\n",
       argv0);
   return 1;
 }
 
+uint64_t StringToUint64OrDie(const char* str) {
+  char* end;
+  uint64_t number = static_cast<uint64_t>(strtoll(str, &end, 10));
+  if (*end != '\0')
+    PERFETTO_FATAL("Invalid %s. Expected decimal integer.", str);
+  return number;
+}
 }  // namespace
 
 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;
+  uint64_t pid = 0;
+  std::vector<uint64_t> timestamps;
   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());
@@ -64,6 +76,16 @@
             "start of the trace.");
         return Usage(argv[0]);
       }
+    } else if (i <= argc && strcmp(argv[i], "--pid") == 0) {
+      i++;
+      pid = StringToUint64OrDie(argv[i]);
+    } else if (i <= argc && strcmp(argv[i], "--timestamps") == 0) {
+      i++;
+      std::vector<std::string> ts_strings =
+          perfetto::base::SplitString(argv[i], ",");
+      for (const auto& ts : ts_strings) {
+        timestamps.emplace_back(StringToUint64OrDie(ts.c_str()));
+      }
     } else {
       positional_args.push_back(argv[i]);
     }
@@ -121,8 +143,10 @@
   if (format == "text")
     return perfetto::trace_to_text::TraceToText(input_stream, output_stream);
 
-  if (format == "profile")
-    return perfetto::trace_to_text::TraceToProfile(input_stream, output_stream);
+  if (format == "profile") {
+    return perfetto::trace_to_text::TraceToProfile(input_stream, output_stream,
+                                                   pid, timestamps);
+  }
 
   if (format == "symbolize")
     return perfetto::trace_to_text::SymbolizeProfile(input_stream,
diff --git a/tools/trace_to_text/pprof_builder.cc b/tools/trace_to_text/pprof_builder.cc
index 5d56cf9..1cea63b 100644
--- a/tools/trace_to_text/pprof_builder.cc
+++ b/tools/trace_to_text/pprof_builder.cc
@@ -85,30 +85,14 @@
 using Iterator = trace_processor::TraceProcessor::Iterator;
 
 constexpr const char* kQueryProfiles =
-    "select distinct hpa.upid, hpa.ts from heap_profile_allocation hpa;";
+    "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;
 };
 
-// Walk tree bottom up and assign the inverse of the frame_ids of the path
-// that was used to reach each node into result.
-void Walk(const std::vector<std::vector<Callsite>> children_map,
-          std::vector<std::vector<int64_t>>* result,
-          std::vector<int64_t> parents,
-          const Callsite& root) {
-  PERFETTO_DCHECK((*result)[static_cast<size_t>(root.id)].empty());
-  parents.push_back(root.frame_id);
-  // pprof stores the frames the other way round that we do, reverse here.
-  (*result)[static_cast<size_t>(root.id)].assign(parents.rbegin(),
-                                                 parents.rend());
-  const std::vector<Callsite>& children =
-      children_map[static_cast<size_t>(root.id)];
-  for (const Callsite& child : children)
-    Walk(children_map, result, parents, child);
-}
-
 // 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) {
@@ -120,20 +104,22 @@
     return {};
   }
   int64_t count = count_it.Get(0).long_value;
-  std::vector<std::vector<Callsite>> children(static_cast<size_t>(count));
 
   Iterator it = tp->ExecuteQuery(
-      "select id, parent_id, frame_id from stack_profile_callsite;");
-  std::vector<Callsite> roots;
+      "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;
-    Callsite callsite{id, frame_id};
-    if (parent_id == -1)
-      roots.emplace_back(callsite);
-    else
-      children[static_cast<size_t>(parent_id)].emplace_back(callsite);
+    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()) {
@@ -141,13 +127,6 @@
                             it.Status().message().c_str());
     return {};
   }
-
-  std::vector<std::vector<int64_t>> result(static_cast<size_t>(count));
-  auto start = base::GetWallTimeMs();
-  for (const Callsite& root : roots)
-    Walk(children, &result, {}, root);
-  PERFETTO_DLOG("Walked %zu in %llu", children.size(),
-                (base::GetWallTimeMs() - start).count());
   return result;
 }
 
@@ -443,18 +422,28 @@
 
 bool TraceToPprof(std::istream* input,
                   std::vector<SerializedProfile>* output,
-                  Symbolizer* 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 1;
+    return false;
 
   tp->NotifyEndOfFile();
+  return TraceToPprof(tp.get(), output, symbolizer, pid, timestamps);
+}
+
+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.get(), symbolizer, [&tp](perfetto::protos::TracePacket packet) {
+        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());
@@ -489,8 +478,8 @@
   }
 
   int64_t max_symbol_id = max_symbol_id_it.Get(0).long_value;
-  auto callsite_to_frames = GetCallsiteToFrames(tp.get());
-  auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp.get());
+  auto callsite_to_frames = GetCallsiteToFrames(tp);
+  auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp);
 
   Iterator it = tp->ExecuteQuery(kQueryProfiles);
   while (it.Next()) {
@@ -498,12 +487,19 @@
                             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.get(), upid, ts);
+    GProfile profile = builder.GenerateGProfile(tp, upid, ts);
     output->emplace_back(
         SerializedProfile{static_cast<uint64_t>(pid_it.Get(0).long_value),
                           profile.SerializeAsString()});
@@ -516,8 +512,11 @@
   return true;
 }
 
-bool TraceToPprof(std::istream* input, std::vector<SerializedProfile>* output) {
-  return TraceToPprof(input, output, nullptr);
+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/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/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 b3c37c6..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: 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++}`;
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/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/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 620981f..63d7a48 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -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);
   }