Merge "Update to use new UUID format"
diff --git a/Android.bp b/Android.bp
index 514776e..44db57c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -4682,6 +4682,7 @@
   name: "perfetto_src_trace_processor_unittests",
   srcs: [
     "src/trace_processor/args_table_unittest.cc",
+    "src/trace_processor/basic_types_unittest.cc",
     "src/trace_processor/clock_tracker_unittest.cc",
     "src/trace_processor/event_tracker_unittest.cc",
     "src/trace_processor/filtered_row_index_unittest.cc",
diff --git a/include/perfetto/base/compiler.h b/include/perfetto/base/compiler.h
index 0b2efbf..0f08b42 100644
--- a/include/perfetto/base/compiler.h
+++ b/include/perfetto/base/compiler.h
@@ -38,25 +38,6 @@
 #define PERFETTO_NO_INLINE
 #endif
 
-// TODO(lalitm): is_trivially_constructible is currently not available
-// in some environments we build in. Reenable when that environment supports
-// this.
-#if defined(__GLIBCXX__)
-#define PERFETTO_IS_TRIVIALLY_CONSTRUCTIBLE(T) true
-#else
-#define PERFETTO_IS_TRIVIALLY_CONSTRUCTIBLE(T) \
-  std::is_trivially_constructible<T>::value
-#endif
-
-// TODO(lalitm): is_trivially_copyable is currently not available
-// in some environments we build in. Reenable when that environment supports
-// this.
-#if defined(__GLIBCXX__)
-#define PERFETTO_IS_TRIVIALLY_COPYABLE(T) true
-#else
-#define PERFETTO_IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable<T>::value
-#endif
-
 #if defined(__GNUC__) || defined(__clang__)
 #define PERFETTO_DEBUG_FUNCTION_IDENTIFIER() __PRETTY_FUNCTION__
 #elif defined(_MSC_VER)
diff --git a/include/perfetto/protozero/proto_decoder.h b/include/perfetto/protozero/proto_decoder.h
index 5794cc9..b58e64e 100644
--- a/include/perfetto/protozero/proto_decoder.h
+++ b/include/perfetto/protozero/proto_decoder.h
@@ -332,7 +332,7 @@
     // implicit initializers on all the ~1000 entries. We need it to initialize
     // only on the first |max_field_id| fields, the remaining capacity doesn't
     // require initialization.
-    static_assert(PERFETTO_IS_TRIVIALLY_CONSTRUCTIBLE(Field) &&
+    static_assert(std::is_trivially_constructible<Field>::value &&
                       std::is_trivially_destructible<Field>::value &&
                       std::is_trivial<Field>::value,
                   "Field must be a trivial aggregate type");
diff --git a/include/perfetto/trace_processor/basic_types.h b/include/perfetto/trace_processor/basic_types.h
index efac31c..ca540d2 100644
--- a/include/perfetto/trace_processor/basic_types.h
+++ b/include/perfetto/trace_processor/basic_types.h
@@ -57,6 +57,13 @@
     return value;
   }
 
+  static SqlValue Double(double v) {
+    SqlValue value;
+    value.double_value = v;
+    value.type = Type::kDouble;
+    return value;
+  }
+
   static SqlValue String(const char* v) {
     SqlValue value;
     value.string_value = v;
@@ -69,7 +76,7 @@
     return double_value;
   }
 
-  int Compare(const SqlValue& value) const {
+  int64_t Compare(const SqlValue& value) const {
     // TODO(lalitm): this is almost the same as what SQLite does with the
     // exception of comparisions between long and double - we choose (for
     // performance reasons) to omit comparisions between them.
@@ -80,10 +87,11 @@
       case Type::kNull:
         return 0;
       case Type::kLong:
-        return static_cast<int>(long_value - value.long_value);
+        return long_value - value.long_value;
       case Type::kDouble: {
-        double diff = double_value - value.double_value;
-        return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
+        return double_value < value.double_value
+                   ? -1
+                   : (double_value > value.double_value ? 1 : 0);
       }
       case Type::kString:
         return strcmp(string_value, value.string_value);
@@ -92,7 +100,7 @@
         int ret = memcmp(bytes_value, value.bytes_value, bytes);
         if (ret != 0)
           return ret;
-        return static_cast<int>(bytes_count - value.bytes_count);
+        return static_cast<int64_t>(bytes_count - value.bytes_count);
       }
     }
     PERFETTO_FATAL("For GCC");
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index d126980..d8352b4 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -3049,7 +3049,7 @@
     ROOT_JNI_MONITOR = 14;
   };
   // Objects retained by this root.
-  repeated uint64 object_ids = 1;
+  repeated uint64 object_ids = 1 [packed = true];
 
   optional Type root_type = 2;
 }
@@ -3065,10 +3065,10 @@
 
   // Indices for InternedData.field_names for the name of the field referring
   // to the object.
-  repeated uint64 reference_field_id = 4;
+  repeated uint64 reference_field_id = 4 [packed = true];
 
   // Ids of the Object that is referred to.
-  repeated uint64 reference_object_id = 5;
+  repeated uint64 reference_object_id = 5 [packed = true];
 }
 
 message HeapGraph {
diff --git a/protos/perfetto/trace/profiling/heap_graph.proto b/protos/perfetto/trace/profiling/heap_graph.proto
index d29c292..7e2d5d4 100644
--- a/protos/perfetto/trace/profiling/heap_graph.proto
+++ b/protos/perfetto/trace/profiling/heap_graph.proto
@@ -43,7 +43,7 @@
     ROOT_JNI_MONITOR = 14;
   };
   // Objects retained by this root.
-  repeated uint64 object_ids = 1;
+  repeated uint64 object_ids = 1 [packed = true];
 
   optional Type root_type = 2;
 }
@@ -59,10 +59,10 @@
 
   // Indices for InternedData.field_names for the name of the field referring
   // to the object.
-  repeated uint64 reference_field_id = 4;
+  repeated uint64 reference_field_id = 4 [packed = true];
 
   // Ids of the Object that is referred to.
-  repeated uint64 reference_object_id = 5;
+  repeated uint64 reference_object_id = 5 [packed = true];
 }
 
 message HeapGraph {
diff --git a/src/protozero/proto_decoder.cc b/src/protozero/proto_decoder.cc
index bd563fb..a5f1b95 100644
--- a/src/protozero/proto_decoder.cc
+++ b/src/protozero/proto_decoder.cc
@@ -208,7 +208,7 @@
   PERFETTO_CHECK(new_capacity > size_);
   std::unique_ptr<Field[]> new_storage(new Field[new_capacity]);
 
-  static_assert(PERFETTO_IS_TRIVIALLY_COPYABLE(Field),
+  static_assert(std::is_trivially_copyable<Field>::value,
                 "Field must be trivially copyable");
   memcpy(&new_storage[0], fields_, sizeof(Field) * size_);
 
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 80d394d..a096cd2 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -357,6 +357,7 @@
 perfetto_unittest_source_set("unittests") {
   testonly = true
   sources = [
+    "basic_types_unittest.cc",
     "clock_tracker_unittest.cc",
     "event_tracker_unittest.cc",
     "forwarding_trace_parser_unittest.cc",
diff --git a/src/trace_processor/basic_types_unittest.cc b/src/trace_processor/basic_types_unittest.cc
new file mode 100644
index 0000000..a2a0fd2
--- /dev/null
+++ b/src/trace_processor/basic_types_unittest.cc
@@ -0,0 +1,77 @@
+/*
+ * 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 "perfetto/trace_processor/basic_types.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+TEST(SqlValueTest, DifferentTypes) {
+  ASSERT_LT(SqlValue(), SqlValue::Long(10));
+  ASSERT_LT(SqlValue::Long(10), SqlValue::Double(10.0));
+  ASSERT_LT(SqlValue::Double(10.0), SqlValue::String("10"));
+}
+
+TEST(SqlValueTest, CompareLong) {
+  SqlValue int32_min = SqlValue::Long(std::numeric_limits<int32_t>::min());
+  SqlValue minus_1 = SqlValue::Long(-1);
+  SqlValue zero = SqlValue::Long(0);
+  SqlValue uint32_max = SqlValue::Long(std::numeric_limits<uint32_t>::max());
+
+  ASSERT_LT(int32_min, minus_1);
+  ASSERT_LT(int32_min, uint32_max);
+  ASSERT_LT(minus_1, uint32_max);
+
+  ASSERT_GT(uint32_max, zero);
+
+  ASSERT_EQ(zero, zero);
+}
+
+TEST(SqlValueTest, CompareDouble) {
+  SqlValue int32_min = SqlValue::Double(std::numeric_limits<int32_t>::min());
+  SqlValue minus_1 = SqlValue::Double(-1.0);
+  SqlValue zero = SqlValue::Double(0);
+  SqlValue uint32_max = SqlValue::Double(std::numeric_limits<uint32_t>::max());
+
+  ASSERT_LT(int32_min, minus_1);
+  ASSERT_LT(int32_min, uint32_max);
+  ASSERT_LT(minus_1, uint32_max);
+
+  ASSERT_GT(uint32_max, zero);
+
+  ASSERT_EQ(zero, zero);
+}
+
+TEST(SqlValueTest, CompareString) {
+  SqlValue a = SqlValue::String("a");
+  SqlValue aa = SqlValue::String("aa");
+  SqlValue b = SqlValue::String("b");
+
+  ASSERT_LT(a, aa);
+  ASSERT_LT(aa, b);
+  ASSERT_LT(a, b);
+
+  ASSERT_GT(aa, a);
+
+  ASSERT_EQ(a, a);
+  ASSERT_EQ(aa, SqlValue::String("aa"));
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/bit_vector_benchmark.cc b/src/trace_processor/db/bit_vector_benchmark.cc
index 6c896d1..54c00f9 100644
--- a/src/trace_processor/db/bit_vector_benchmark.cc
+++ b/src/trace_processor/db/bit_vector_benchmark.cc
@@ -22,6 +22,20 @@
 
 using perfetto::trace_processor::BitVector;
 
+bool IsBenchmarkFunctionalOnly() {
+  return getenv("BENCHMARK_FUNCTIONAL_TEST_ONLY") != nullptr;
+}
+
+void BitVectorArgs(benchmark::internal::Benchmark* b) {
+  b->Arg(64);
+
+  if (!IsBenchmarkFunctionalOnly()) {
+    b->Arg(512);
+    b->Arg(8192);
+    b->Arg(123456);
+    b->Arg(1234567);
+  }
+}
 }
 
 static void BM_BitVectorAppendTrue(benchmark::State& state) {
@@ -72,12 +86,7 @@
     benchmark::ClobberMemory();
   }
 }
-BENCHMARK(BM_BitVectorSet)
-    ->Arg(64)
-    ->Arg(512)
-    ->Arg(8192)
-    ->Arg(123456)
-    ->Arg(1234567);
+BENCHMARK(BM_BitVectorSet)->Apply(BitVectorArgs);
 
 static void BM_BitVectorClear(benchmark::State& state) {
   static constexpr uint32_t kRandomSeed = 42;
@@ -107,12 +116,7 @@
     benchmark::ClobberMemory();
   }
 }
-BENCHMARK(BM_BitVectorClear)
-    ->Arg(64)
-    ->Arg(512)
-    ->Arg(8192)
-    ->Arg(123456)
-    ->Arg(1234567);
+BENCHMARK(BM_BitVectorClear)->Apply(BitVectorArgs);
 
 static void BM_BitVectorIndexOfNthSet(benchmark::State& state) {
   static constexpr uint32_t kRandomSeed = 42;
@@ -142,12 +146,7 @@
     pool_idx = (pool_idx + 1) % kPoolSize;
   }
 }
-BENCHMARK(BM_BitVectorIndexOfNthSet)
-    ->Arg(64)
-    ->Arg(512)
-    ->Arg(8192)
-    ->Arg(123456)
-    ->Arg(1234567);
+BENCHMARK(BM_BitVectorIndexOfNthSet)->Apply(BitVectorArgs);
 
 static void BM_BitVectorGetNumBitsSet(benchmark::State& state) {
   static constexpr uint32_t kRandomSeed = 42;
@@ -175,12 +174,7 @@
   }
   PERFETTO_CHECK(res == count);
 }
-BENCHMARK(BM_BitVectorGetNumBitsSet)
-    ->Arg(64)
-    ->Arg(512)
-    ->Arg(8192)
-    ->Arg(123456)
-    ->Arg(1234567);
+BENCHMARK(BM_BitVectorGetNumBitsSet)->Apply(BitVectorArgs);
 
 static void BM_BitVectorResize(benchmark::State& state) {
   static constexpr uint32_t kRandomSeed = 42;
@@ -239,9 +233,4 @@
     benchmark::ClobberMemory();
   }
 }
-BENCHMARK(BM_BitVectorUpdateSetBits)
-    ->Arg(64)
-    ->Arg(512)
-    ->Arg(8192)
-    ->Arg(123456)
-    ->Arg(1234567);
+BENCHMARK(BM_BitVectorUpdateSetBits)->Apply(BitVectorArgs);
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index b2af643..518ac6a 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -142,6 +142,16 @@
     PERFETTO_FATAL("For GCC");
   }
 
+  // Sorts |idx| in ascending or descending order (determined by |desc|) based
+  // on the contents of this column.
+  void StableSort(bool desc, std::vector<uint32_t>* idx) const {
+    if (desc) {
+      StableSort<true /* desc */>(idx);
+    } else {
+      StableSort<false /* desc */>(idx);
+    }
+  }
+
   // Updates the given RowMap by only keeping rows where this column meets the
   // given filter constraint.
   void FilterInto(FilterOp op, SqlValue value, RowMap* rm) const {
@@ -580,6 +590,63 @@
     }
   }
 
+  template <bool desc>
+  void StableSort(std::vector<uint32_t>* out) const {
+    switch (type_) {
+      case ColumnType::kInt32: {
+        if (IsNullable()) {
+          StableSort<desc, int32_t, true /* is_nullable */>(out);
+        } else {
+          StableSort<desc, int32_t, false /* is_nullable */>(out);
+        }
+        break;
+      }
+      case ColumnType::kUint32: {
+        if (IsNullable()) {
+          StableSort<desc, uint32_t, true /* is_nullable */>(out);
+        } else {
+          StableSort<desc, uint32_t, false /* is_nullable */>(out);
+        }
+        break;
+      }
+      case ColumnType::kInt64: {
+        if (IsNullable()) {
+          StableSort<desc, int64_t, true /* is_nullable */>(out);
+        } else {
+          StableSort<desc, int64_t, false /* is_nullable */>(out);
+        }
+        break;
+      }
+      case ColumnType::kString: {
+        row_map().StableSort(out, [this](uint32_t a_idx, uint32_t b_idx) {
+          auto a_str = GetStringPoolStringAtIdx(a_idx);
+          auto b_str = GetStringPoolStringAtIdx(b_idx);
+          return desc ? b_str < a_str : a_str < b_str;
+        });
+        break;
+      }
+      case ColumnType::kId:
+        row_map().StableSort(out, [](uint32_t a_idx, uint32_t b_idx) {
+          return desc ? b_idx < a_idx : a_idx < b_idx;
+        });
+    }
+  }
+
+  template <bool desc, typename T, bool is_nullable>
+  void StableSort(std::vector<uint32_t>* out) const {
+    const auto& sv = sparse_vector<T>();
+    row_map().StableSort(out, [&sv](uint32_t a_idx, uint32_t b_idx) {
+      if (is_nullable) {
+        auto a_val = sv.Get(a_idx);
+        auto b_val = sv.Get(b_idx);
+        return desc ? b_val < a_val : a_val < b_val;
+      }
+      auto a_val = sv.GetNonNull(a_idx);
+      auto b_val = sv.GetNonNull(b_idx);
+      return desc ? b_val < a_val : a_val < b_val;
+    });
+  }
+
   template <typename T>
   static ColumnType ToColumnType() {
     if (std::is_same<T, uint32_t>::value) {
diff --git a/src/trace_processor/db/row_map.h b/src/trace_processor/db/row_map.h
index 53264da..543d2c2 100644
--- a/src/trace_processor/db/row_map.h
+++ b/src/trace_processor/db/row_map.h
@@ -127,6 +127,9 @@
       }
     }
 
+    Iterator(Iterator&&) noexcept = default;
+    Iterator& operator=(Iterator&&) = default;
+
     // Forwards the iterator to the next row of the RowMap.
     void Next() {
       switch (rm_->mode_) {
@@ -190,6 +193,9 @@
     }
 
    private:
+    Iterator(const Iterator&) = delete;
+    Iterator& operator=(const Iterator&) = delete;
+
     // Only one of the below will be non-null depending on the mode of the
     // RowMap.
     std::unique_ptr<RangeIterator> range_it_;
@@ -434,6 +440,26 @@
     }
   }
 
+  template <typename Comparator>
+  void StableSort(std::vector<uint32_t>* out, Comparator c) const {
+    switch (mode_) {
+      case Mode::kRange: {
+        StableSort(out, c, [this](uint32_t off) { return start_idx_ + off; });
+        break;
+      }
+      case Mode::kBitVector: {
+        StableSort(out, c, [this](uint32_t off) {
+          return bit_vector_.IndexOfNthSet(off);
+        });
+        break;
+      }
+      case Mode::kIndexVector: {
+        StableSort(out, c, [this](uint32_t off) { return index_vector_[off]; });
+        break;
+      }
+    }
+  }
+
   // Returns the iterator over the rows in this RowMap.
   Iterator IterateRows() const { return Iterator(this); }
 
@@ -530,6 +556,13 @@
     }
   }
 
+  template <typename Comparator, typename Indexer>
+  void StableSort(std::vector<uint32_t>* out, Comparator c, Indexer i) const {
+    std::stable_sort(
+        out->begin(), out->end(),
+        [&c, &i](uint32_t a, uint32_t b) { return c(i(a), i(b)); });
+  }
+
   RowMap SelectRowsSlow(const RowMap& selector) const;
 
   Mode mode_ = Mode::kRange;
diff --git a/src/trace_processor/db/table.cc b/src/trace_processor/db/table.cc
index 3b22356..d23ba87 100644
--- a/src/trace_processor/db/table.cc
+++ b/src/trace_processor/db/table.cc
@@ -96,17 +96,36 @@
   std::vector<uint32_t> idx(size_);
   std::iota(idx.begin(), idx.end(), 0);
 
-  // Sort the row indices according to the given order by constraints.
-  std::sort(idx.begin(), idx.end(), [this, &od](uint32_t a, uint32_t b) {
-    for (const Order& o : od) {
-      const Column& col = columns_[o.col_idx];
-      int cmp =
-          col.Get(a) < col.Get(b) ? -1 : (col.Get(b) < col.Get(a) ? 1 : 0);
-      if (cmp != 0)
-        return o.desc ? cmp > 0 : cmp < 0;
-    }
-    return false;
-  });
+  // As our data is columnar, it's always more efficient to sort one column
+  // at a time rather than try and sort lexiographically all at once.
+  // To preserve correctness, we need to stably sort the index vector once
+  // for each order by in *reverse* order. Reverse order is important as it
+  // preserves the lexiographical property.
+  //
+  // For example, suppose we have the following:
+  // Table {
+  //   Column x;
+  //   Column y
+  //   Column z;
+  // }
+  //
+  // Then, to sort "y asc, x desc", we could do one of two things:
+  //  1) sort the index vector all at once and on each index, we compare
+  //     y then z. This is slow as the data is columnar and we need to
+  //     repeatedly branch inside each column.
+  //  2) we can stably sort first on x desc and then sort on y asc. This will
+  //     first put all the x in the correct order such that when we sort on
+  //     y asc, we will have the correct order of x where y is the same (since
+  //     the sort is stable).
+  //
+  // TODO(lalitm): it is possible that we could sort the last constraint (i.e.
+  // the first constraint in the below loop) in a non-stable way. However, this
+  // is more subtle than it appears as we would then need special handling where
+  // there are order bys on a column which is already sorted (e.g. ts, id).
+  // Investigate whether the performance gains from this are worthwhile.
+  for (auto it = od.rbegin(); it != od.rend(); ++it) {
+    columns_[it->col_idx].StableSort(it->desc, &idx);
+  }
 
   // Return a copy of this table with the RowMaps using the computed ordered
   // RowMap.
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index e9410b4..a5e71b9 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -43,6 +43,9 @@
       }
     }
 
+    Iterator(Iterator&&) noexcept = default;
+    Iterator& operator=(Iterator&&) = default;
+
     // Advances the iterator to the next row of the table.
     void Next() {
       for (auto& it : its_) {
@@ -60,6 +63,9 @@
     }
 
    private:
+    Iterator(const Iterator&) = delete;
+    Iterator& operator=(const Iterator&) = delete;
+
     const Table* table_ = nullptr;
     std::vector<RowMap::Iterator> its_;
   };
diff --git a/src/trace_processor/importers/proto/heap_graph_module.cc b/src/trace_processor/importers/proto/heap_graph_module.cc
index 50bba45..7216d90 100644
--- a/src/trace_processor/importers/proto/heap_graph_module.cc
+++ b/src/trace_processor/importers/proto/heap_graph_module.cc
@@ -65,6 +65,28 @@
   }
 }
 
+// Iterate over a repeated field of varints, independent of whether it is
+// packed or not.
+template <int32_t field_no, typename T, typename F>
+bool ForEachVarInt(const T& decoder, F fn) {
+  auto field = decoder.template at<field_no>();
+  bool parse_error = false;
+  if (field.type() == protozero::proto_utils::ProtoWireType::kLengthDelimited) {
+    // packed repeated
+    auto it = decoder.template GetPackedRepeated<
+        ::protozero::proto_utils::ProtoWireType::kVarInt, uint64_t>(
+        field_no, &parse_error);
+    for (; it; ++it)
+      fn(*it);
+  } else {
+    // non-packed repeated
+    auto it = decoder.template GetRepeated<uint64_t>(field_no);
+    for (; it; ++it)
+      fn(*it);
+  }
+  return parse_error;
+}
+
 }  // namespace
 
 void HeapGraphModule::ParseHeapGraph(int64_t ts, protozero::ConstBytes blob) {
@@ -78,21 +100,37 @@
     obj.object_id = object.id();
     obj.self_size = object.self_size();
     obj.type_id = object.type_id();
-    auto ref_field_ids_it = object.reference_field_id();
-    auto ref_object_ids_it = object.reference_object_id();
-    for (; ref_field_ids_it && ref_object_ids_it;
-         ++ref_field_ids_it, ++ref_object_ids_it) {
-      HeapGraphTracker::SourceObject::Reference ref;
-      ref.field_name_id = *ref_field_ids_it;
-      ref.owned_object_id = *ref_object_ids_it;
-      obj.references.emplace_back(std::move(ref));
+
+    std::vector<uint64_t> field_ids;
+    std::vector<uint64_t> object_ids;
+
+    bool parse_error = ForEachVarInt<
+        protos::pbzero::HeapGraphObject::kReferenceFieldIdFieldNumber>(
+        object, [&field_ids](uint64_t value) { field_ids.push_back(value); });
+
+    if (!parse_error) {
+      parse_error = ForEachVarInt<
+          protos::pbzero::HeapGraphObject::kReferenceObjectIdFieldNumber>(
+          object,
+          [&object_ids](uint64_t value) { object_ids.push_back(value); });
     }
 
-    if (ref_field_ids_it || ref_object_ids_it) {
-      context_->storage->IncrementIndexedStats(stats::heap_graph_missing_packet,
-                                               static_cast<int>(upid));
+    if (parse_error) {
+      context_->storage->IncrementIndexedStats(
+          stats::heap_graph_malformed_packet, static_cast<int>(upid));
+      break;
+    }
+    if (field_ids.size() != object_ids.size()) {
+      context_->storage->IncrementIndexedStats(
+          stats::heap_graph_malformed_packet, static_cast<int>(upid));
       continue;
     }
+    for (size_t i = 0; i < field_ids.size(); ++i) {
+      HeapGraphTracker::SourceObject::Reference ref;
+      ref.field_name_id = field_ids[i];
+      ref.owned_object_id = object_ids[i];
+      obj.references.emplace_back(std::move(ref));
+    }
     context_->heap_graph_tracker->AddObject(upid, ts, std::move(obj));
   }
   for (auto it = heap_graph.type_names(); it; ++it) {
@@ -118,8 +156,16 @@
 
     HeapGraphTracker::SourceRoot src_root;
     src_root.root_type = context_->storage->InternString(str_view);
-    for (auto obj_it = entry.object_ids(); obj_it; ++obj_it)
-      src_root.object_ids.emplace_back(*obj_it);
+    bool parse_error =
+        ForEachVarInt<protos::pbzero::HeapGraphRoot::kObjectIdsFieldNumber>(
+            entry, [&src_root](uint64_t value) {
+              src_root.object_ids.emplace_back(value);
+            });
+    if (parse_error) {
+      context_->storage->IncrementIndexedStats(
+          stats::heap_graph_malformed_packet, static_cast<int>(upid));
+      break;
+    }
     context_->heap_graph_tracker->AddRoot(upid, ts, std::move(src_root));
   }
   if (!heap_graph.continued()) {
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index e2d6aef..55af73d 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -95,6 +95,7 @@
 
     int64_t reference_set_id =
         context_->storage->heap_graph_reference_table().size();
+    std::set<int64_t> seen_owned;
     for (const SourceObject::Reference& ref : obj.references) {
       // This is true for unset reference fields.
       if (ref.owned_object_id == 0)
@@ -107,7 +108,10 @@
         continue;
 
       int64_t owned_row = it->second;
-      walker_.AddEdge(owner_row, owned_row);
+      bool inserted;
+      std::tie(std::ignore, inserted) = seen_owned.emplace(owned_row);
+      if (inserted)
+        walker_.AddEdge(owner_row, owned_row);
 
       auto field_name_it = interned_field_names_.find(ref.field_name_id);
       if (field_name_it == interned_field_names_.end()) {
diff --git a/src/trace_processor/importers/proto/heap_graph_walker.cc b/src/trace_processor/importers/proto/heap_graph_walker.cc
index 8655d85..b512571 100644
--- a/src/trace_processor/importers/proto/heap_graph_walker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_walker.cc
@@ -66,8 +66,11 @@
 }
 
 void HeapGraphWalker::AddEdge(int64_t owner_row, int64_t owned_row) {
-  GetNode(owner_row).children.emplace(&GetNode(owned_row));
-  GetNode(owned_row).parents.emplace(&GetNode(owner_row));
+  Node& owner_node = GetNode(owner_row);
+  Node& owned_node = GetNode(owned_row);
+
+  owner_node.children.emplace_back(&owned_node);
+  owned_node.parents.emplace_back(&owner_node);
 }
 
 void HeapGraphWalker::MarkRoot(int64_t row) {
@@ -268,23 +271,43 @@
 }
 
 void HeapGraphWalker::FindSCC(Node* node) {
-  node->node_index = node->lowlink = next_node_index_++;
-  node_stack_.push_back(node);
-  node->on_stack = true;
-  for (Node* child : node->children) {
-    PERFETTO_CHECK(child->reachable);
-    if (child->node_index == 0) {
-      FindSCC(child);
-      if (child->lowlink < node->lowlink)
-        node->lowlink = child->lowlink;
-    } else if (child->on_stack) {
-      if (child->node_index < node->lowlink)
+  std::vector<Node*> walk_stack;
+  std::vector<size_t> walk_child;
+
+  walk_stack.emplace_back(node);
+  walk_child.emplace_back(0);
+
+  while (!walk_stack.empty()) {
+    node = walk_stack.back();
+    size_t& child_idx = walk_child.back();
+
+    if (child_idx == 0) {
+      node->node_index = node->lowlink = next_node_index_++;
+      node_stack_.push_back(node);
+      node->on_stack = true;
+    } else {
+      Node* prev_child = node->children[child_idx - 1];
+      if (prev_child->node_index > node->node_index &&
+          prev_child->lowlink < node->lowlink)
+        node->lowlink = prev_child->lowlink;
+    }
+
+    if (child_idx == node->children.size()) {
+      if (node->lowlink == node->node_index)
+        FoundSCC(node);
+      walk_stack.pop_back();
+      walk_child.pop_back();
+    } else {
+      Node* child = node->children[child_idx++];
+      PERFETTO_CHECK(child->reachable);
+      if (child->node_index == 0) {
+        walk_stack.emplace_back(child);
+        walk_child.emplace_back(0);
+      } else if (child->on_stack && child->node_index < node->lowlink) {
         node->lowlink = child->node_index;
+      }
     }
   }
-
-  if (node->lowlink == node->node_index)
-    FoundSCC(node);
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/heap_graph_walker.h b/src/trace_processor/importers/proto/heap_graph_walker.h
index b23f2a3..c05c6df 100644
--- a/src/trace_processor/importers/proto/heap_graph_walker.h
+++ b/src/trace_processor/importers/proto/heap_graph_walker.h
@@ -120,11 +120,8 @@
 
  private:
   struct Node {
-    // These are sets to conveniently get rid of double edges between nodes.
-    // We do not care if an object owns another object via multiple references
-    // or only one.
-    std::set<Node*> children;
-    std::set<Node*> parents;
+    std::vector<Node*> children;
+    std::vector<Node*> parents;
     uint64_t self_size = 0;
     uint64_t retained_size = 0;
 
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 14afab1..e93578c 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -212,7 +212,12 @@
     } else {
       auto args = proc.cmdline();
       base::StringView argv0 = args ? *args : base::StringView();
-      context_->process_tracker->SetProcessMetadata(pid, ppid, argv0);
+      UniquePid upid =
+          context_->process_tracker->SetProcessMetadata(pid, ppid, argv0);
+      if (proc.has_uid()) {
+        context_->process_tracker->SetProcessUid(
+            upid, static_cast<uint32_t>(proc.uid()));
+      }
     }
   }
 
diff --git a/src/trace_processor/process_table.cc b/src/trace_processor/process_table.cc
index adc9d1e..65d29f9 100644
--- a/src/trace_processor/process_table.cc
+++ b/src/trace_processor/process_table.cc
@@ -47,6 +47,7 @@
           SqliteTable::Column(Column::kEndTs, "end_ts", SqlValue::Type::kLong),
           SqliteTable::Column(Column::kParentUpid, "parent_upid",
                               SqlValue::Type::kLong),
+          SqliteTable::Column(Column::kUid, "uid", SqlValue::Type::kLong),
       },
       {Column::kUpid});
   return util::OkStatus();
@@ -147,6 +148,14 @@
       }
       break;
     }
+    case Column::kUid: {
+      if (process.uid.has_value()) {
+        sqlite3_result_int64(context, process.uid.value());
+      } else {
+        sqlite3_result_null(context);
+      }
+      break;
+    }
     default:
       PERFETTO_FATAL("Unknown column %d", N);
       break;
diff --git a/src/trace_processor/process_table.h b/src/trace_processor/process_table.h
index c4c1ed5..74b0386 100644
--- a/src/trace_processor/process_table.h
+++ b/src/trace_processor/process_table.h
@@ -36,7 +36,8 @@
     kPid = 2,
     kStartTs = 3,
     kEndTs = 4,
-    kParentUpid = 5
+    kParentUpid = 5,
+    kUid = 6
   };
   class Cursor : public SqliteTable::Cursor {
    public:
diff --git a/src/trace_processor/process_tracker.cc b/src/trace_processor/process_tracker.cc
index e9c0335..1ccc4b7 100644
--- a/src/trace_processor/process_tracker.cc
+++ b/src/trace_processor/process_tracker.cc
@@ -194,6 +194,10 @@
   return upid;
 }
 
+void ProcessTracker::SetProcessUid(UniquePid upid, uint32_t uid) {
+  context_->storage->GetMutableProcess(upid)->uid = uid;
+}
+
 void ProcessTracker::SetProcessNameIfUnset(UniquePid upid,
                                            StringId process_name_id) {
   TraceStorage::Process* process = context_->storage->GetMutableProcess(upid);
diff --git a/src/trace_processor/process_tracker.h b/src/trace_processor/process_tracker.h
index 7c7ea55..985212b 100644
--- a/src/trace_processor/process_tracker.h
+++ b/src/trace_processor/process_tracker.h
@@ -91,6 +91,9 @@
                                        base::Optional<uint32_t> ppid,
                                        base::StringView name);
 
+  // Sets the process user id.
+  void SetProcessUid(UniquePid upid, uint32_t uid);
+
   // Assigns the given name to the process identified by |upid| if it does not
   // have a name yet.
   void SetProcessNameIfUnset(UniquePid upid, StringId process_name_id);
diff --git a/src/trace_processor/span_join_operator_table.cc b/src/trace_processor/span_join_operator_table.cc
index 931289c..24077d7 100644
--- a/src/trace_processor/span_join_operator_table.cc
+++ b/src/trace_processor/span_join_operator_table.cc
@@ -169,8 +169,14 @@
   return std::unique_ptr<SpanJoinOperatorTable::Cursor>(new Cursor(this, db_));
 }
 
-int SpanJoinOperatorTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
+int SpanJoinOperatorTable::BestIndex(const QueryConstraints& qc,
+                                     BestIndexInfo* info) {
   // TODO(lalitm): figure out cost estimation.
+  const auto& ob = qc.order_by();
+  if (ob.size() == 1 && ob.front().iColumn == Column::kTimestamp &&
+      !ob.front().desc) {
+    info->sqlite_omit_order_by = true;
+  }
   return SQLITE_OK;
 }
 
@@ -187,8 +193,7 @@
       continue;
 
     if (col_name == kTsColumnName || col_name == kDurColumnName) {
-      // We don't support constraints on ts or duration in the child tables.
-      PERFETTO_DFATAL("ts or duration constraints on child tables");
+      // Allow SQLite handle any constraints on ts or duration.
       continue;
     }
     auto op = sqlite_utils::OpToString(cs.op);
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 6afcbfe..d8da768 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -30,6 +30,9 @@
    public:
     explicit Cursor(DbSqliteTable* table);
 
+    Cursor(Cursor&&) noexcept = default;
+    Cursor& operator=(Cursor&&) = default;
+
     // Implementation of SqliteTable::Cursor.
     int Filter(const QueryConstraints& qc, sqlite3_value** argv) override;
     int Next() override;
@@ -37,6 +40,9 @@
     int Column(sqlite3_context*, int N) override;
 
    private:
+    Cursor(const Cursor&) = delete;
+    Cursor& operator=(const Cursor&) = delete;
+
     const Table* initial_db_table_ = nullptr;
 
     base::Optional<Table> db_table_;
diff --git a/src/trace_processor/tables/macros_benchmark.cc b/src/trace_processor/tables/macros_benchmark.cc
index 779acb2..8150371 100644
--- a/src/trace_processor/tables/macros_benchmark.cc
+++ b/src/trace_processor/tables/macros_benchmark.cc
@@ -44,6 +44,32 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
+namespace {
+
+bool IsBenchmarkFunctionalOnly() {
+  return getenv("BENCHMARK_FUNCTIONAL_TEST_ONLY") != nullptr;
+}
+
+void TableFilterArgs(benchmark::internal::Benchmark* b) {
+  if (IsBenchmarkFunctionalOnly()) {
+    b->Arg(1024);
+  } else {
+    b->RangeMultiplier(8);
+    b->Range(1024, 2 * 1024 * 1024);
+  }
+}
+
+void TableSortArgs(benchmark::internal::Benchmark* b) {
+  if (IsBenchmarkFunctionalOnly()) {
+    b->Arg(64);
+  } else {
+    b->RangeMultiplier(8);
+    b->Range(1024, 256 * 1024);
+  }
+}
+
+}  // namespace
+
 using perfetto::trace_processor::ChildTestTable;
 using perfetto::trace_processor::RootTestTable;
 using perfetto::trace_processor::SqlValue;
@@ -80,9 +106,7 @@
       it = child.IterateRows();
   }
 }
-BENCHMARK(BM_TableIteratorChild)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+BENCHMARK(BM_TableIteratorChild)->Apply(TableFilterArgs);
 
 static void BM_TableFilterIdColumn(benchmark::State& state) {
   StringPool pool;
@@ -96,9 +120,7 @@
     benchmark::DoNotOptimize(root.Filter({root.id().eq(SqlValue::Long(30))}));
   }
 }
-BENCHMARK(BM_TableFilterIdColumn)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+BENCHMARK(BM_TableFilterIdColumn)->Apply(TableFilterArgs);
 
 static void BM_TableFilterRootNonNullEqMatchMany(benchmark::State& state) {
   StringPool pool;
@@ -118,9 +140,7 @@
         root.Filter({root.root_non_null().eq(SqlValue::Long(0))}));
   }
 }
-BENCHMARK(BM_TableFilterRootNonNullEqMatchMany)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+BENCHMARK(BM_TableFilterRootNonNullEqMatchMany)->Apply(TableFilterArgs);
 
 static void BM_TableFilterRootNullableEqMatchMany(benchmark::State& state) {
   StringPool pool;
@@ -144,9 +164,7 @@
         root.Filter({root.root_nullable().eq(SqlValue::Long(1))}));
   }
 }
-BENCHMARK(BM_TableFilterRootNullableEqMatchMany)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+BENCHMARK(BM_TableFilterRootNullableEqMatchMany)->Apply(TableFilterArgs);
 
 static void BM_TableFilterChildNonNullEqMatchMany(benchmark::State& state) {
   StringPool pool;
@@ -169,9 +187,7 @@
         child.Filter({child.child_non_null().eq(SqlValue::Long(0))}));
   }
 }
-BENCHMARK(BM_TableFilterChildNonNullEqMatchMany)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+BENCHMARK(BM_TableFilterChildNonNullEqMatchMany)->Apply(TableFilterArgs);
 
 static void BM_TableFilterChildNullableEqMatchMany(benchmark::State& state) {
   StringPool pool;
@@ -197,9 +213,7 @@
         child.Filter({child.child_nullable().eq(SqlValue::Long(1))}));
   }
 }
-BENCHMARK(BM_TableFilterChildNullableEqMatchMany)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+BENCHMARK(BM_TableFilterChildNullableEqMatchMany)->Apply(TableFilterArgs);
 
 static void BM_TableFilterChildNonNullEqMatchManyInParent(
     benchmark::State& state) {
@@ -224,8 +238,7 @@
   }
 }
 BENCHMARK(BM_TableFilterChildNonNullEqMatchManyInParent)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+    ->Apply(TableFilterArgs);
 
 static void BM_TableFilterChildNullableEqMatchManyInParent(
     benchmark::State& state) {
@@ -250,8 +263,7 @@
   }
 }
 BENCHMARK(BM_TableFilterChildNullableEqMatchManyInParent)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+    ->Apply(TableFilterArgs);
 
 static void BM_TableFilterParentSortedEq(benchmark::State& state) {
   StringPool pool;
@@ -270,9 +282,7 @@
         root.Filter({root.root_sorted().eq(SqlValue::Long(22))}));
   }
 }
-BENCHMARK(BM_TableFilterParentSortedEq)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+BENCHMARK(BM_TableFilterParentSortedEq)->Apply(TableFilterArgs);
 
 static void BM_TableFilterChildSortedEq(benchmark::State& state) {
   StringPool pool;
@@ -293,9 +303,7 @@
         child.Filter({child.child_sorted().eq(SqlValue::Long(22))}));
   }
 }
-BENCHMARK(BM_TableFilterChildSortedEq)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+BENCHMARK(BM_TableFilterChildSortedEq)->Apply(TableFilterArgs);
 
 static void BM_TableFilterChildSortedEqInParent(benchmark::State& state) {
   StringPool pool;
@@ -319,9 +327,7 @@
         child.Filter({child.root_sorted().eq(SqlValue::Long(22))}));
   }
 }
-BENCHMARK(BM_TableFilterChildSortedEqInParent)
-    ->RangeMultiplier(8)
-    ->Range(1024, 2 * 1024 * 1024);
+BENCHMARK(BM_TableFilterChildSortedEqInParent)->Apply(TableFilterArgs);
 
 static void BM_TableSortRootNonNull(benchmark::State& state) {
   StringPool pool;
@@ -342,7 +348,7 @@
     benchmark::DoNotOptimize(root.Sort({root.root_non_null().ascending()}));
   }
 }
-BENCHMARK(BM_TableSortRootNonNull)->RangeMultiplier(8)->Range(1024, 256 * 1024);
+BENCHMARK(BM_TableSortRootNonNull)->Apply(TableSortArgs);
 
 static void BM_TableSortRootNullable(benchmark::State& state) {
   StringPool pool;
@@ -365,9 +371,7 @@
     benchmark::DoNotOptimize(root.Sort({root.root_nullable().ascending()}));
   }
 }
-BENCHMARK(BM_TableSortRootNullable)
-    ->RangeMultiplier(8)
-    ->Range(1024, 256 * 1024);
+BENCHMARK(BM_TableSortRootNullable)->Apply(TableSortArgs);
 
 static void BM_TableSortChildNonNullInParent(benchmark::State& state) {
   StringPool pool;
@@ -395,9 +399,7 @@
     benchmark::DoNotOptimize(child.Sort({child.root_non_null().ascending()}));
   }
 }
-BENCHMARK(BM_TableSortChildNonNullInParent)
-    ->RangeMultiplier(8)
-    ->Range(1024, 256 * 1024);
+BENCHMARK(BM_TableSortChildNonNullInParent)->Apply(TableSortArgs);
 
 static void BM_TableSortChildNullableInParent(benchmark::State& state) {
   StringPool pool;
@@ -429,6 +431,4 @@
     benchmark::DoNotOptimize(child.Sort({child.root_nullable().ascending()}));
   }
 }
-BENCHMARK(BM_TableSortChildNullableInParent)
-    ->RangeMultiplier(8)
-    ->Range(1024, 256 * 1024);
+BENCHMARK(BM_TableSortChildNullableInParent)->Apply(TableSortArgs);
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index 7243b42..2d40985 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -115,6 +115,7 @@
     StringId name_id = 0;
     uint32_t pid = 0;
     base::Optional<UniquePid> parent_upid;
+    base::Optional<uint32_t> uid;
   };
 
   // Information about a unique thread seen in a trace.
diff --git a/src/traced/probes/ps/process_stats_data_source.cc b/src/traced/probes/ps/process_stats_data_source.cc
index 6c9d2da..de68e0d 100644
--- a/src/traced/probes/ps/process_stats_data_source.cc
+++ b/src/traced/probes/ps/process_stats_data_source.cc
@@ -171,9 +171,14 @@
 }
 
 void ProcessStatsDataSource::OnPids(const std::vector<int32_t>& pids) {
-  PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_ON_PIDS);
   if (!enable_on_demand_dumps_)
     return;
+  WriteProcessTree(pids);
+}
+
+void ProcessStatsDataSource::WriteProcessTree(
+    const std::vector<int32_t>& pids) {
+  PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_ON_PIDS);
   PERFETTO_DCHECK(!cur_ps_tree_);
   int pids_scanned = 0;
   for (int32_t pid : pids) {
@@ -414,7 +419,7 @@
 
   // Ensure that we write once long-term process info (e.g., name) for new pids
   // that we haven't seen before.
-  OnPids(pids);
+  WriteProcessTree(pids);
 }
 
 // Returns true if the stats for the given |pid| have been written, false it
diff --git a/src/traced/probes/ps/process_stats_data_source.h b/src/traced/probes/ps/process_stats_data_source.h
index b2fa51c..f301e5d 100644
--- a/src/traced/probes/ps/process_stats_data_source.h
+++ b/src/traced/probes/ps/process_stats_data_source.h
@@ -105,6 +105,9 @@
   void WriteAllProcessStats();
   bool WriteMemCounters(int32_t pid, const std::string& proc_status);
 
+  // Scans /proc/pid/status and writes the ProcessTree packet for input pids.
+  void WriteProcessTree(const std::vector<int32_t>& pids);
+
   // Read and "latch" the current procfs scan-start timestamp, which
   // we reset only in FinalizeCurPacket.
   uint64_t CacheProcFsScanStartTimestamp();
diff --git a/test/synth_common.py b/test/synth_common.py
index 3383b81..142e76e 100644
--- a/test/synth_common.py
+++ b/test/synth_common.py
@@ -173,11 +173,13 @@
     if ts is not None:
       self.packet.timestamp = ts
 
-  def add_process(self, pid, ppid, cmdline):
+  def add_process(self, pid, ppid, cmdline, uid=None):
     process = self.packet.process_tree.processes.add()
     process.pid = pid
     process.ppid = ppid
     process.cmdline.append(cmdline)
+    if uid is not None:
+      process.uid = uid
     self.proc_map[pid] = cmdline
 
   def add_thread(self, tid, tgid, cmdline):
diff --git a/test/trace_processor/counters_ref_type_null.sql b/test/trace_processor/counters_ref_type_null.sql
index 557b933..36a0b90 100644
--- a/test/trace_processor/counters_ref_type_null.sql
+++ b/test/trace_processor/counters_ref_type_null.sql
@@ -1,3 +1,3 @@
-select id, counter_id, ts, value, arg_set_id, name, ref, ref_type from counters
+select ts, value, name, ref, ref_type from counters
 where name = 'MemAvailable' and ref_type is null
 limit 10
diff --git a/test/trace_processor/counters_ref_type_null_memory_counters.out b/test/trace_processor/counters_ref_type_null_memory_counters.out
index 0457994..d416b76 100644
--- a/test/trace_processor/counters_ref_type_null_memory_counters.out
+++ b/test/trace_processor/counters_ref_type_null_memory_counters.out
@@ -1,11 +1,11 @@
-"id","counter_id","ts","value","arg_set_id","name","ref","ref_type"
-4294968280,984,22240334823167,2696392704.000000,0,"MemAvailable",0,"[NULL]"
-4294969268,984,22240356169836,2696392704.000000,0,"MemAvailable",0,"[NULL]"
-4294970256,984,22240468594483,2696392704.000000,0,"MemAvailable",0,"[NULL]"
-4294971244,984,22240566948190,2696392704.000000,0,"MemAvailable",0,"[NULL]"
-4294972232,984,22240667383304,2696392704.000000,0,"MemAvailable",0,"[NULL]"
-4294973220,984,22240766505085,2696392704.000000,0,"MemAvailable",0,"[NULL]"
-4294974208,984,22240866794106,2696392704.000000,0,"MemAvailable",0,"[NULL]"
-4294975196,984,22240968271928,2696392704.000000,0,"MemAvailable",0,"[NULL]"
-4294976184,984,22241065777407,2696392704.000000,0,"MemAvailable",0,"[NULL]"
-4294977172,984,22241165839708,2696392704.000000,0,"MemAvailable",0,"[NULL]"
+"ts","value","name","ref","ref_type"
+22240334823167,2696392704.000000,"MemAvailable",0,"[NULL]"
+22240356169836,2696392704.000000,"MemAvailable",0,"[NULL]"
+22240468594483,2696392704.000000,"MemAvailable",0,"[NULL]"
+22240566948190,2696392704.000000,"MemAvailable",0,"[NULL]"
+22240667383304,2696392704.000000,"MemAvailable",0,"[NULL]"
+22240766505085,2696392704.000000,"MemAvailable",0,"[NULL]"
+22240866794106,2696392704.000000,"MemAvailable",0,"[NULL]"
+22240968271928,2696392704.000000,"MemAvailable",0,"[NULL]"
+22241065777407,2696392704.000000,"MemAvailable",0,"[NULL]"
+22241165839708,2696392704.000000,"MemAvailable",0,"[NULL]"
diff --git a/test/trace_processor/index b/test/trace_processor/index
index 958b11e..808b2cf 100644
--- a/test/trace_processor/index
+++ b/test/trace_processor/index
@@ -12,6 +12,7 @@
 
 # Test for the process<>thread tracking logic.
 synth_process_tracking.py process_tracking.sql process_tracking.out
+synth_process_tracking.py process_tracking_uid.sql process_tracking_uid.out
 process_tracking_short_lived_1.py process_tracking.sql process_tracking_process_tracking_short_lived_1.out
 process_tracking_short_lived_2.py process_tracking.sql process_tracking_process_tracking_short_lived_2.out
 process_tracking_exec.py process_tracking.sql process_tracking_process_tracking_exec.out
diff --git a/test/trace_processor/mm_event.out b/test/trace_processor/mm_event.out
index a26a895..aee7eb1 100644
--- a/test/trace_processor/mm_event.out
+++ b/test/trace_processor/mm_event.out
@@ -1,41 +1,41 @@
-"id","ts","name","value","ref","ref_type","arg_set_id"
-4294967296,1409847208580693,"mem.mm.min_flt.count",1.000000,0,"upid",0
-4294967297,1409847208580693,"mem.mm.min_flt.max_lat",9.000000,0,"upid",0
-4294967298,1409847208580693,"mem.mm.min_flt.avg_lat",9.000000,0,"upid",0
-4294967299,1409847209537724,"mem.mm.min_flt.count",1.000000,0,"upid",0
-4294967300,1409847209537724,"mem.mm.min_flt.max_lat",759.000000,0,"upid",0
-4294967301,1409847209537724,"mem.mm.min_flt.avg_lat",759.000000,0,"upid",0
-4294967302,1409847213761006,"mem.mm.min_flt.count",1.000000,0,"upid",0
-4294967303,1409847213761006,"mem.mm.min_flt.max_lat",10.000000,0,"upid",0
-4294967304,1409847213761006,"mem.mm.min_flt.avg_lat",10.000000,0,"upid",0
-4294967305,1409847214052256,"mem.mm.min_flt.count",1.000000,0,"upid",0
-4294967306,1409847214052256,"mem.mm.min_flt.max_lat",90.000000,0,"upid",0
-4294967307,1409847214052256,"mem.mm.min_flt.avg_lat",90.000000,0,"upid",0
-4294967308,1409847216341006,"mem.mm.min_flt.count",1.000000,0,"upid",0
-4294967309,1409847216341006,"mem.mm.min_flt.max_lat",961.000000,0,"upid",0
-4294967310,1409847216341006,"mem.mm.min_flt.avg_lat",961.000000,0,"upid",0
-4294967311,1409847216341944,"mem.mm.min_flt.count",1.000000,0,"upid",0
-4294967312,1409847216341944,"mem.mm.min_flt.max_lat",889.000000,0,"upid",0
-4294967313,1409847216341944,"mem.mm.min_flt.avg_lat",889.000000,0,"upid",0
-4294967314,1409847218889548,"mem.mm.min_flt.count",1.000000,0,"upid",0
-4294967315,1409847218889548,"mem.mm.min_flt.max_lat",10.000000,0,"upid",0
-4294967316,1409847218889548,"mem.mm.min_flt.avg_lat",10.000000,0,"upid",0
-4294967317,1409847219001371,"mem.mm.min_flt.count",1.000000,0,"upid",0
-4294967318,1409847219001371,"mem.mm.min_flt.max_lat",66.000000,0,"upid",0
-4294967319,1409847219001371,"mem.mm.min_flt.avg_lat",66.000000,0,"upid",0
-4294967320,1409847352399457,"mem.mm.min_flt.count",12.000000,0,"upid",0
-4294967321,1409847352399457,"mem.mm.min_flt.max_lat",148.000000,0,"upid",0
-4294967322,1409847352399457,"mem.mm.min_flt.avg_lat",54.000000,0,"upid",0
-4294967323,1409847383618992,"mem.mm.min_flt.count",4.000000,0,"upid",0
-4294967324,1409847383618992,"mem.mm.min_flt.max_lat",34.000000,0,"upid",0
-4294967325,1409847383618992,"mem.mm.min_flt.avg_lat",21.000000,0,"upid",0
-4294967326,1409847431535611,"mem.mm.reclaim.count",1.000000,0,"upid",0
-4294967327,1409847431535611,"mem.mm.reclaim.max_lat",21.000000,0,"upid",0
-4294967328,1409847431535611,"mem.mm.reclaim.avg_lat",21.000000,0,"upid",0
-4294967329,1409847438281133,"mem.mm.compaction.count",1.000000,0,"upid",0
-4294967330,1409847438281133,"mem.mm.compaction.max_lat",6564.000000,0,"upid",0
-4294967331,1409847438281133,"mem.mm.compaction.avg_lat",6564.000000,0,"upid",0
-4294967332,1409847446501654,"mem.mm.min_flt.count",1.000000,0,"upid",0
-4294967333,1409847446501654,"mem.mm.min_flt.max_lat",12.000000,0,"upid",0
-4294967334,1409847446501654,"mem.mm.min_flt.avg_lat",12.000000,0,"upid",0
-4294967335,1409847447831498,"mem.mm.min_flt.count",1.000000,0,"upid",0
+"ts","name","value","ref","ref_type"
+1409847208580693,"mem.mm.min_flt.count",1.000000,0,"upid"
+1409847208580693,"mem.mm.min_flt.max_lat",9.000000,0,"upid"
+1409847208580693,"mem.mm.min_flt.avg_lat",9.000000,0,"upid"
+1409847209537724,"mem.mm.min_flt.count",1.000000,0,"upid"
+1409847209537724,"mem.mm.min_flt.max_lat",759.000000,0,"upid"
+1409847209537724,"mem.mm.min_flt.avg_lat",759.000000,0,"upid"
+1409847213761006,"mem.mm.min_flt.count",1.000000,0,"upid"
+1409847213761006,"mem.mm.min_flt.max_lat",10.000000,0,"upid"
+1409847213761006,"mem.mm.min_flt.avg_lat",10.000000,0,"upid"
+1409847214052256,"mem.mm.min_flt.count",1.000000,0,"upid"
+1409847214052256,"mem.mm.min_flt.max_lat",90.000000,0,"upid"
+1409847214052256,"mem.mm.min_flt.avg_lat",90.000000,0,"upid"
+1409847216341006,"mem.mm.min_flt.count",1.000000,0,"upid"
+1409847216341006,"mem.mm.min_flt.max_lat",961.000000,0,"upid"
+1409847216341006,"mem.mm.min_flt.avg_lat",961.000000,0,"upid"
+1409847216341944,"mem.mm.min_flt.count",1.000000,0,"upid"
+1409847216341944,"mem.mm.min_flt.max_lat",889.000000,0,"upid"
+1409847216341944,"mem.mm.min_flt.avg_lat",889.000000,0,"upid"
+1409847218889548,"mem.mm.min_flt.count",1.000000,0,"upid"
+1409847218889548,"mem.mm.min_flt.max_lat",10.000000,0,"upid"
+1409847218889548,"mem.mm.min_flt.avg_lat",10.000000,0,"upid"
+1409847219001371,"mem.mm.min_flt.count",1.000000,0,"upid"
+1409847219001371,"mem.mm.min_flt.max_lat",66.000000,0,"upid"
+1409847219001371,"mem.mm.min_flt.avg_lat",66.000000,0,"upid"
+1409847352399457,"mem.mm.min_flt.count",12.000000,0,"upid"
+1409847352399457,"mem.mm.min_flt.max_lat",148.000000,0,"upid"
+1409847352399457,"mem.mm.min_flt.avg_lat",54.000000,0,"upid"
+1409847383618992,"mem.mm.min_flt.count",4.000000,0,"upid"
+1409847383618992,"mem.mm.min_flt.max_lat",34.000000,0,"upid"
+1409847383618992,"mem.mm.min_flt.avg_lat",21.000000,0,"upid"
+1409847431535611,"mem.mm.reclaim.count",1.000000,0,"upid"
+1409847431535611,"mem.mm.reclaim.max_lat",21.000000,0,"upid"
+1409847431535611,"mem.mm.reclaim.avg_lat",21.000000,0,"upid"
+1409847438281133,"mem.mm.compaction.count",1.000000,0,"upid"
+1409847438281133,"mem.mm.compaction.max_lat",6564.000000,0,"upid"
+1409847438281133,"mem.mm.compaction.avg_lat",6564.000000,0,"upid"
+1409847446501654,"mem.mm.min_flt.count",1.000000,0,"upid"
+1409847446501654,"mem.mm.min_flt.max_lat",12.000000,0,"upid"
+1409847446501654,"mem.mm.min_flt.avg_lat",12.000000,0,"upid"
+1409847447831498,"mem.mm.min_flt.count",1.000000,0,"upid"
diff --git a/test/trace_processor/mm_event.sql b/test/trace_processor/mm_event.sql
index 187d0ff..e286c68 100644
--- a/test/trace_processor/mm_event.sql
+++ b/test/trace_processor/mm_event.sql
@@ -1,4 +1,4 @@
-select id, ts, name, value, ref, ref_type, arg_set_id
+select ts, name, value, ref, ref_type
 from counters
 where name like 'mem.mm.%'
 order by ts
diff --git a/test/trace_processor/process_tracking_uid.out b/test/trace_processor/process_tracking_uid.out
new file mode 100644
index 0000000..ae90e8d
--- /dev/null
+++ b/test/trace_processor/process_tracking_uid.out
@@ -0,0 +1,6 @@
+"pid","uid"
+0,"[NULL]"
+10,1001
+20,1002
+30,"[NULL]"
+40,"[NULL]"
diff --git a/test/trace_processor/process_tracking_uid.sql b/test/trace_processor/process_tracking_uid.sql
new file mode 100644
index 0000000..de06399
--- /dev/null
+++ b/test/trace_processor/process_tracking_uid.sql
@@ -0,0 +1,3 @@
+select pid, uid
+from process
+order by pid;
diff --git a/test/trace_processor/synth_process_tracking.py b/test/trace_processor/synth_process_tracking.py
index 6686d89..a7a4d2d 100644
--- a/test/trace_processor/synth_process_tracking.py
+++ b/test/trace_processor/synth_process_tracking.py
@@ -40,7 +40,7 @@
 # SQL level we should be able to tell that p1-t0 and p1-t2 belong to 'process1'
 # but p1-t1 should be left unjoinable.
 trace.add_process_tree_packet(ts=5)
-trace.add_process(10, 0, "process1")
+trace.add_process(10, 0, "process1", 1001)
 trace.add_thread(12, 10, "p1-t2")
 
 # Now create another process (pid=20) with three threads(tids=20,21,22).
@@ -58,7 +58,7 @@
 
 # From the process tracker viewpoint we pretend we only scraped tids=20,21.
 trace.add_process_tree_packet(ts=15)
-trace.add_process(20, 0, "process_2")
+trace.add_process(20, 0, "process_2", 1002)
 trace.add_thread(21, 20, "p2-t1")
 
 # Finally the very complex case: a third process (pid=30) which spawns threads
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 7edcbba..8c5fb67 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -302,7 +302,8 @@
     //}
     const maxCpuFreq = await engine.query(`
      select max(value)
-     from counters
+     from counter c
+     inner join cpu_counter_track t on c.track_id = t.id
      where name = 'cpufreq';
     `);
 
@@ -325,13 +326,28 @@
       // cpu freq data.
       // TODO(taylori): Find a way to display cpu idle
       // events even if there are no cpu freq events.
-      const freqExists = await engine.query(`
-        select value
-        from counters
-        where name = 'cpufreq' and ref = ${cpu}
+      const cpuFreqIdle = await engine.query(`
+        select
+          id as cpu_freq_id,
+          (
+            select id
+            from cpu_counter_track
+            where name = 'cpuidle'
+            and cpu = ${cpu}
+            limit 1
+          ) as cpu_idle_id
+        from cpu_counter_track
+        where name = 'cpufreq' and cpu = ${cpu}
         limit 1;
       `);
-      if (freqExists.numRecords > 0) {
+      if (cpuFreqIdle.numRecords > 0) {
+        const freqTrackId = +cpuFreqIdle.columns[0].longValues![0];
+
+        const idleTrackExists: boolean = !cpuFreqIdle.columns[1].isNulls![0];
+        const idleTrackId = idleTrackExists ?
+            +cpuFreqIdle.columns[1].longValues![0] :
+            undefined;
+
         tracksToAdd.push({
           engineId: this.engineId,
           kind: CPU_FREQ_TRACK_KIND,
@@ -340,6 +356,8 @@
           config: {
             cpu,
             maximumValue: +maxCpuFreq.columns[0].doubleValues![0],
+            freqTrackId,
+            idleTrackId,
           }
         });
       }
@@ -414,46 +432,69 @@
       }
     }
 
-
-    const counters = await engine.query(`
-      select name, ref, ref_type
-      from counter_definitions
-      where ref is not null
-      group by name, ref, ref_type
-      order by ref_type desc
+    // Add global or GPU counter tracks that are not bound to any pid/tid.
+    const globalCounters = await engine.query(`
+      select name, id
+      from counter_track
+      where type = 'counter_track'
+      union
+      select name, id
+      from gpu_counter_track
+      where name != 'gpufreq'
     `);
-
-    interface CounterMap {
-      [index: number]: string[];
+    for (let i = 0; i < globalCounters.numRecords; i++) {
+      const name = globalCounters.columns[0].stringValues![i];
+      const trackId = +globalCounters.columns[1].longValues![i];
+      tracksToAdd.push({
+        engineId: this.engineId,
+        kind: 'CounterTrack',
+        name,
+        trackGroup: SCROLLING_TRACK_GROUP,
+        config: {
+          name,
+          trackId,
+        }
+      });
     }
 
-    const counterUpids: CounterMap = new Array();
-    const counterUtids: CounterMap = new Array();
-    for (let i = 0; i < counters.numRecords; i++) {
-      const name = counters.columns[0].stringValues![i];
-      const ref = +counters.columns[1].longValues![i];
-      const refType = counters.columns[2].stringValues![i];
-      if (refType === 'upid') {
-        const el = counterUpids[ref];
-        el === undefined ? counterUpids[ref] = [name] :
-                           counterUpids[ref].push(name);
-      } else if (refType === 'utid') {
-        const el = counterUtids[ref];
-        el === undefined ? counterUtids[ref] = [name] :
-                           counterUtids[ref].push(name);
-      } else if (
-          refType === '[NULL]' || (refType === 'gpu' && name !== 'gpufreq')) {
-        // Add global or GPU counter tracks that are not bound to any pid/tid.
-        tracksToAdd.push({
-          engineId: this.engineId,
-          kind: 'CounterTrack',
-          name,
-          trackGroup: SCROLLING_TRACK_GROUP,
-          config: {
-            name,
-            ref: 0,
-          }
-        });
+    interface CounterTrack {
+      name: string;
+      trackId: number;
+    }
+
+    const counterUtids = new Map<number, CounterTrack[]>();
+    const threadCounters = await engine.query(`
+      select name, utid, id
+      from thread_counter_track
+    `);
+    for (let i = 0; i < threadCounters.numRecords; i++) {
+      const name = threadCounters.columns[0].stringValues![i];
+      const utid = +threadCounters.columns[1].longValues![i];
+      const trackId = +threadCounters.columns[2].longValues![i];
+
+      const el = counterUtids.get(utid);
+      if (el === undefined) {
+        counterUtids.set(utid, [{name, trackId}]);
+      } else {
+        el.push({name, trackId});
+      }
+    }
+
+    const counterUpids = new Map<number, CounterTrack[]>();
+    const processCounters = await engine.query(`
+      select name, upid, id
+      from process_counter_track
+    `);
+    for (let i = 0; i < processCounters.numRecords; i++) {
+      const name = processCounters.columns[0].stringValues![i];
+      const upid = +processCounters.columns[1].longValues![i];
+      const trackId = +processCounters.columns[2].longValues![i];
+
+      const el = counterUpids.get(upid);
+      if (el === undefined) {
+        counterUpids.set(upid, [{name, trackId}]);
+      } else {
+        el.push({name, trackId});
       }
     }
 
@@ -526,8 +567,8 @@
       const threadTrack =
           utid === null ? undefined : utidToThreadTrack.get(utid);
       if (threadTrack === undefined &&
-          (upid === null || counterUpids[upid] === undefined) &&
-          counterUtids[utid] === undefined && !threadHasSched &&
+          (upid === null || counterUpids.get(upid) === undefined) &&
+          counterUtids.get(utid) === undefined && !threadHasSched &&
           (upid === null || upid !== null && !heapUpids.has(upid))) {
         continue;
       }
@@ -571,18 +612,15 @@
         }));
 
         if (upid !== null) {
-          const counterNames = counterUpids[upid];
+          const counterNames = counterUpids.get(upid);
           if (counterNames !== undefined) {
             counterNames.forEach(element => {
               tracksToAdd.push({
                 engineId: this.engineId,
                 kind: 'CounterTrack',
-                name: element,
+                name: element.name,
                 trackGroup: pUuid,
-                config: {
-                  name: element,
-                  ref: upid,
-                }
+                config: {name: element.name, trackId: element.trackId}
               });
             });
           }
@@ -612,17 +650,17 @@
           }
         }
       }
-      const counterThreadNames = counterUtids[utid];
+      const counterThreadNames = counterUtids.get(utid);
       if (counterThreadNames !== undefined) {
         counterThreadNames.forEach(element => {
           tracksToAdd.push({
             engineId: this.engineId,
             kind: 'CounterTrack',
-            name: element,
+            name: element.name,
             trackGroup: pUuid,
             config: {
-              name: element,
-              ref: utid,
+              name: element.name,
+              trackId: element.trackId,
             }
           });
         });
diff --git a/ui/src/tracks/counter/common.ts b/ui/src/tracks/counter/common.ts
index 0eb6dfb..53a603d 100644
--- a/ui/src/tracks/counter/common.ts
+++ b/ui/src/tracks/counter/common.ts
@@ -30,6 +30,6 @@
   name: string;
   maximumValue?: number;
   minimumValue?: number;
-  ref: number;
+  trackId: number;
   scale?: 'DEFAULT'|'RELATIVE';
 }
diff --git a/ui/src/tracks/counter/controller.ts b/ui/src/tracks/counter/controller.ts
index 4085845..0be0371 100644
--- a/ui/src/tracks/counter/controller.ts
+++ b/ui/src/tracks/counter/controller.ts
@@ -39,20 +39,22 @@
 
     if (!this.setup) {
       const result = await this.query(`
-      select max(value), min(value) from
-        counters where name = '${this.config.name}'
-        and ref = ${this.config.ref}`);
+        select max(value), min(value)
+        from counter
+        where track_id = ${this.config.trackId}`);
       this.maximumValueSeen = +result.columns[0].doubleValues![0];
       this.minimumValueSeen = +result.columns[1].doubleValues![0];
       await this.query(
         `create virtual table ${this.tableName('window')} using window;`);
 
-      await this.query(`create view ${this.tableName('counter_view')} as
-        select ts,
-        lead(ts, 1, ts) over (partition by ref_type order by ts) - ts as dur,
-        value, name, ref
-        from counters
-        where name = '${this.config.name}' and ref = ${this.config.ref};`);
+      await this.query(`
+        create view ${this.tableName('counter_view')} as
+        select
+          ts,
+          lead(ts, 1, ts) over (order by ts) - ts as dur,
+          value
+        from counter
+        where track_id = ${this.config.trackId};`);
 
       await this.query(`create virtual table ${this.tableName('span')} using
         span_join(${this.tableName('counter_view')},
@@ -60,13 +62,16 @@
       this.setup = true;
     }
 
-    const result = await this.engine.queryOneRow(`select count(*)
-    from (select
-      ts,
-      lead(ts, 1, ts) over (partition by ref_type order by ts) as ts_end,
-      from counters
-      where name = '${this.config.name}' and ref = ${this.config.ref})
-    where ts <= ${endNs} and ${startNs} <= ts_end`);
+    const result = await this.engine.queryOneRow(`
+      select count(*)
+      from (
+        select
+          ts,
+          lead(ts, 1, ts) over (order by ts) as ts_end,
+        from counter
+        where track_id = ${this.config.trackId}
+      )
+      where ts <= ${endNs} and ${startNs} <= ts_end`);
 
     // Only quantize if we have too much data to draw.
     const isQuantized = result[0] > LIMIT;
@@ -94,18 +99,32 @@
       // Union that with the query that finds all the counters within
       // the current query range.
       query = `
-      select * from (select ts, value, counter_id from counters
-      where name = '${this.config.name}' and ref = ${this.config.ref} and
-      ts <= ${startNs} order by ts desc limit 1)
-      UNION
-      select * from (select ts, value, counter_id
-        from (select
-          ts,
-          lead(ts, 1, ts) over (partition by ref_type order by ts) as ts_end,
-          value, counter_id
-          from counters
-          where name = '${this.config.name}' and ref = ${this.config.ref})
-      where ts <= ${endNs} and ${startNs} <= ts_end limit ${LIMIT});`;
+      select *
+      from (
+        select ts, value, track_id
+        from counter
+        where
+          track_id = ${this.config.trackId} and
+          ts <= ${startNs}
+        order by ts desc
+        limit 1
+      )
+      union
+      select *
+      from (
+        select ts, value, track_id
+        from (
+          select
+            ts,
+            lead(ts, 1, ts) over (order by ts) as ts_end,
+            value,
+            track_id
+          from counter
+          where track_id = ${this.config.trackId}
+        )
+        where ts <= ${endNs} and ${startNs} <= ts_end
+        limit ${LIMIT}
+      );`;
     }
 
     const rawResult = await this.query(query);
diff --git a/ui/src/tracks/cpu_freq/common.ts b/ui/src/tracks/cpu_freq/common.ts
index 8968907..fff0c8f 100644
--- a/ui/src/tracks/cpu_freq/common.ts
+++ b/ui/src/tracks/cpu_freq/common.ts
@@ -28,5 +28,8 @@
 
 export interface Config {
   cpu: number;
+  freqTrackId: number;
+  idleTrackId?: number;
   maximumValue?: number;
-  minimumValue?: number;}
+  minimumValue?: number;
+}
diff --git a/ui/src/tracks/cpu_freq/controller.ts b/ui/src/tracks/cpu_freq/controller.ts
index 190a3d9..bdfbdae 100644
--- a/ui/src/tracks/cpu_freq/controller.ts
+++ b/ui/src/tracks/cpu_freq/controller.ts
@@ -38,9 +38,9 @@
 
     if (!this.setup) {
       const result = await this.query(`
-      select max(value) from
-        counters where name = 'cpufreq'
-        and ref = ${this.config.cpu}`);
+        select max(value)
+        from counter
+        where track_id = ${this.config.freqTrackId}`);
       this.maximumValueSeen = +result.columns[0].doubleValues![0];
 
       await this.query(
@@ -49,36 +49,39 @@
       await this.query(`create view ${this.tableName('freq')}
           as select
             ts,
-            lead(ts) over (order by ts) - ts as dur,
-            ref as cpu,
-            name as freq_name,
+            lead(ts) over () - ts as dur,
             value as freq_value
-          from counters
-          where name = 'cpufreq'
-            and ref = ${this.config.cpu}
-            and ref_type = 'cpu';
+          from counter c
+          where track_id = ${this.config.freqTrackId};
       `);
 
-      await this.query(`create view ${this.tableName('idle')}
-        as select
-          ts,
-          lead(ts) over (order by ts) - ts as dur,
-          ref as cpu,
-          name as idle_name,
-          value as idle_value
-        from counters
-        where name = 'cpuidle'
-          and ref = ${this.config.cpu}
-          and ref_type = 'cpu';
-      `);
+      // If there is no idle track, just make the idle track a single row
+      // which spans the entire time range.
+      if (this.config.idleTrackId === undefined) {
+        await this.query(`create view ${this.tableName('idle')} as
+           select
+             0 as ts,
+             ${Number.MAX_SAFE_INTEGER} as dur,
+             -1 as idle_value;
+          `);
+      } else {
+        await this.query(`create view ${this.tableName('idle')}
+          as select
+            ts,
+            lead(ts) over () - ts as dur,
+            value as idle_value
+          from counter c
+          where track_id = ${this.config.idleTrackId};
+        `);
+      }
 
       await this.query(`create virtual table ${this.tableName('freq_idle')}
-              using span_join(${this.tableName('freq')} PARTITIONED cpu,
-                              ${this.tableName('idle')} PARTITIONED cpu);`);
+              using span_join(${this.tableName('freq')},
+                              ${this.tableName('idle')});`);
 
       await this.query(`create virtual table ${this.tableName('span_activity')}
-      using span_join(${this.tableName('freq_idle')} PARTITIONED cpu,
-                      ${this.tableName('window')});`);
+              using span_join(${this.tableName('freq_idle')},
+                              ${this.tableName('window')});`);
 
       // TODO(taylori): Move the idle value processing to the TP.
       await this.query(`create view ${this.tableName('activity')}
@@ -86,7 +89,6 @@
         ts,
         dur,
         quantum_ts,
-        cpu,
         case idle_value
           when 4294967295 then -1
           else idle_value