Merge "trace_processor: add all bits iterator"
diff --git a/BUILD b/BUILD
index 04c6763..74e1390 100644
--- a/BUILD
+++ b/BUILD
@@ -728,6 +728,7 @@
         "src/trace_processor/systrace_trace_parser.h",
         "src/trace_processor/thread_table.cc",
         "src/trace_processor/thread_table.h",
+        "src/trace_processor/timestamped_trace_piece.h",
         "src/trace_processor/trace_blob_view.h",
         "src/trace_processor/trace_parser.h",
         "src/trace_processor/trace_processor.cc",
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 0d73d56..9302662 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -3005,11 +3005,27 @@
 // Begin of protos/perfetto/trace/profiling/heap_graph.proto
 
 message HeapGraphRoot {
+  enum Type {
+    ROOT_UNKNOWN = 0;
+    ROOT_JNI_GLOBAL = 1;
+    ROOT_JNI_LOCAL = 2;
+    ROOT_JAVA_FRAME = 3;
+    ROOT_NATIVE_STACK = 4;
+    ROOT_STICKY_CLASS = 5;
+    ROOT_THREAD_BLOCK = 6;
+    ROOT_MONITOR_USED = 7;
+    ROOT_THREAD_OBJECT = 8;
+    ROOT_INTERNED_STRING = 9;
+    ROOT_FINALIZING = 10;
+    ROOT_DEBUGGER = 11;
+    ROOT_REFERENCE_CLEANUP = 12;
+    ROOT_VM_INTERNAL = 13;
+    ROOT_JNI_MONITOR = 14;
+  };
   // Objects retained by this root.
   repeated uint64 object_ids = 1;
 
-  // From art:RootType, e.g. "kRootThreadObject".
-  optional string root_type = 2;
+  optional Type root_type = 2;
 }
 
 message HeapGraphObject {
diff --git a/protos/perfetto/trace/profiling/heap_graph.proto b/protos/perfetto/trace/profiling/heap_graph.proto
index f037545..d29c292 100644
--- a/protos/perfetto/trace/profiling/heap_graph.proto
+++ b/protos/perfetto/trace/profiling/heap_graph.proto
@@ -25,11 +25,27 @@
 package perfetto.protos;
 
 message HeapGraphRoot {
+  enum Type {
+    ROOT_UNKNOWN = 0;
+    ROOT_JNI_GLOBAL = 1;
+    ROOT_JNI_LOCAL = 2;
+    ROOT_JAVA_FRAME = 3;
+    ROOT_NATIVE_STACK = 4;
+    ROOT_STICKY_CLASS = 5;
+    ROOT_THREAD_BLOCK = 6;
+    ROOT_MONITOR_USED = 7;
+    ROOT_THREAD_OBJECT = 8;
+    ROOT_INTERNED_STRING = 9;
+    ROOT_FINALIZING = 10;
+    ROOT_DEBUGGER = 11;
+    ROOT_REFERENCE_CLEANUP = 12;
+    ROOT_VM_INTERNAL = 13;
+    ROOT_JNI_MONITOR = 14;
+  };
   // Objects retained by this root.
   repeated uint64 object_ids = 1;
 
-  // From art:RootType, e.g. "kRootThreadObject".
-  optional string root_type = 2;
+  optional Type root_type = 2;
 }
 
 message HeapGraphObject {
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index fe3ce2a..c99e665 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -151,6 +151,7 @@
     "systrace_trace_parser.h",
     "thread_table.cc",
     "thread_table.h",
+    "timestamped_trace_piece.h",
     "trace_blob_view.h",
     "trace_parser.h",
     "trace_processor.cc",
diff --git a/src/trace_processor/event_tracker.cc b/src/trace_processor/event_tracker.cc
index 79757a9..2886c3a 100644
--- a/src/trace_processor/event_tracker.cc
+++ b/src/trace_processor/event_tracker.cc
@@ -240,7 +240,7 @@
     defn_id = definitions->AddCounterDefinition(name_id, ref, ref_type);
   }
   RowId row_id = PushCounter(timestamp, value, defn_id);
-  if (resolve_utid_to_upid) {
+  if (resolve_utid_to_upid && row_id != kInvalidRowId) {
     auto table_and_row = TraceStorage::ParseRowId(row_id);
     PendingUpidResolutionCounter pending;
     pending.row = table_and_row.second;
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index 5f6a99f..765b0a5 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -24,6 +24,7 @@
 #include "src/trace_processor/proto_trace_parser.h"
 #include "src/trace_processor/proto_trace_tokenizer.h"
 #include "src/trace_processor/systrace_trace_parser.h"
+#include "src/trace_processor/trace_sorter.h"
 
 // JSON parsing and exporting is only supported in the standalone and
 // Chromium builds.
diff --git a/src/trace_processor/fuchsia_trace_parser.cc b/src/trace_processor/fuchsia_trace_parser.cc
index 8e9a50a..3423db9 100644
--- a/src/trace_processor/fuchsia_trace_parser.cc
+++ b/src/trace_processor/fuchsia_trace_parser.cc
@@ -63,24 +63,25 @@
 
 void FuchsiaTraceParser::ParseFtracePacket(uint32_t,
                                            int64_t,
-                                           TraceSorter::TimestampedTracePiece) {
+                                           TimestampedTracePiece) {
   PERFETTO_FATAL("Fuchsia Trace Parser cannot handle ftrace packets.");
 }
 
-void FuchsiaTraceParser::ParseTracePacket(
-    int64_t,
-    TraceSorter::TimestampedTracePiece ttp) {
+void FuchsiaTraceParser::ParseTracePacket(int64_t, TimestampedTracePiece ttp) {
   PERFETTO_DCHECK(ttp.fuchsia_provider_view != nullptr);
 
   // The timestamp is also present in the record, so we'll ignore the one passed
   // as an argument.
-  const uint64_t* current =
-      reinterpret_cast<const uint64_t*>(ttp.blob_view.data());
+  fuchsia_trace_utils::RecordCursor cursor(&ttp.blob_view);
   FuchsiaProviderView* provider_view = ttp.fuchsia_provider_view.get();
   ProcessTracker* procs = context_->process_tracker.get();
   SliceTracker* slices = context_->slice_tracker.get();
 
-  uint64_t header = *current++;
+  uint64_t header;
+  if (!cursor.ReadUint64(&header)) {
+    context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+    return;
+  }
   uint32_t record_type = fuchsia_trace_utils::ReadField<uint32_t>(header, 0, 3);
   switch (record_type) {
     case kEvent: {
@@ -95,25 +96,39 @@
       uint32_t name_ref =
           fuchsia_trace_utils::ReadField<uint32_t>(header, 48, 63);
 
-      int64_t ts = fuchsia_trace_utils::ReadTimestamp(
-          &current, provider_view->get_ticks_per_second());
+      int64_t ts;
+      if (!cursor.ReadTimestamp(provider_view->get_ticks_per_second(), &ts)) {
+        context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+        return;
+      }
       fuchsia_trace_utils::ThreadInfo tinfo;
       if (fuchsia_trace_utils::IsInlineThread(thread_ref)) {
-        tinfo = fuchsia_trace_utils::ReadInlineThread(&current);
+        if (!cursor.ReadInlineThread(&tinfo)) {
+          context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+          return;
+        }
       } else {
         tinfo = provider_view->GetThread(thread_ref);
       }
       StringId cat;
       if (fuchsia_trace_utils::IsInlineString(cat_ref)) {
-        cat = context_->storage->InternString(
-            fuchsia_trace_utils::ReadInlineString(&current, cat_ref));
+        base::StringView cat_string_view;
+        if (!cursor.ReadInlineString(cat_ref, &cat_string_view)) {
+          context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+          return;
+        }
+        cat = context_->storage->InternString(cat_string_view);
       } else {
         cat = provider_view->GetString(cat_ref);
       }
       StringId name;
       if (fuchsia_trace_utils::IsInlineString(name_ref)) {
-        name = context_->storage->InternString(
-            fuchsia_trace_utils::ReadInlineString(&current, name_ref));
+        base::StringView name_string_view;
+        if (!cursor.ReadInlineString(name_ref, &name_string_view)) {
+          context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+          return;
+        }
+        name = context_->storage->InternString(name_string_view);
       } else {
         name = provider_view->GetString(name_ref);
       }
@@ -121,8 +136,12 @@
       // Read arguments
       std::vector<Arg> args;
       for (uint32_t i = 0; i < n_args; i++) {
-        const uint64_t* arg_base = current;
-        uint64_t arg_header = *current++;
+        size_t arg_base = cursor.WordIndex();
+        uint64_t arg_header;
+        if (!cursor.ReadUint64(&arg_header)) {
+          context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+          return;
+        }
         uint32_t arg_type =
             fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 0, 3);
         uint32_t arg_size_words =
@@ -131,8 +150,12 @@
             fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 16, 31);
         Arg arg;
         if (fuchsia_trace_utils::IsInlineString(arg_name_ref)) {
-          arg.name = context_->storage->InternString(
-              fuchsia_trace_utils::ReadInlineString(&current, arg_name_ref));
+          base::StringView arg_name_view;
+          if (!cursor.ReadInlineString(arg_name_ref, &arg_name_view)) {
+            context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+            return;
+          }
+          arg.name = context_->storage->InternString(arg_name_view);
         } else {
           arg.name = provider_view->GetString(arg_name_ref);
         }
@@ -149,17 +172,30 @@
             arg.value = fuchsia_trace_utils::ArgValue::Uint32(
                 fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 32, 63));
             break;
-          case kInt64:
-            arg.value = fuchsia_trace_utils::ArgValue::Int64(
-                static_cast<int64_t>(*current++));
+          case kInt64: {
+            int64_t value;
+            if (!cursor.ReadInt64(&value)) {
+              context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+              return;
+            }
+            arg.value = fuchsia_trace_utils::ArgValue::Int64(value);
             break;
-          case kUint64:
-            arg.value = fuchsia_trace_utils::ArgValue::Uint64(*current++);
+          }
+          case kUint64: {
+            uint64_t value;
+            if (!cursor.ReadUint64(&value)) {
+              context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+              return;
+            }
+            arg.value = fuchsia_trace_utils::ArgValue::Uint64(value);
             break;
+          }
           case kDouble: {
             double value;
-            memcpy(&value, current, sizeof(double));
-            current++;
+            if (!cursor.ReadDouble(&value)) {
+              context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+              return;
+            }
             arg.value = fuchsia_trace_utils::ArgValue::Double(value);
             break;
           }
@@ -168,28 +204,43 @@
                 fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 32, 47);
             StringId value;
             if (fuchsia_trace_utils::IsInlineString(arg_value_ref)) {
-              value = context_->storage->InternString(
-                  fuchsia_trace_utils::ReadInlineString(&current,
-                                                        arg_value_ref));
+              base::StringView arg_value_view;
+              if (!cursor.ReadInlineString(arg_value_ref, &arg_value_view)) {
+                context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+                return;
+              }
+              value = context_->storage->InternString(arg_value_view);
             } else {
               value = provider_view->GetString(arg_value_ref);
             }
             arg.value = fuchsia_trace_utils::ArgValue::String(value);
             break;
           }
-          case kPointer:
-            arg.value = fuchsia_trace_utils::ArgValue::Pointer(*current++);
+          case kPointer: {
+            uint64_t value;
+            if (!cursor.ReadUint64(&value)) {
+              context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+              return;
+            }
+            arg.value = fuchsia_trace_utils::ArgValue::Pointer(value);
             break;
-          case kKoid:
-            arg.value = fuchsia_trace_utils::ArgValue::Koid(*current++);
+          }
+          case kKoid: {
+            uint64_t value;
+            if (!cursor.ReadUint64(&value)) {
+              context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+              return;
+            }
+            arg.value = fuchsia_trace_utils::ArgValue::Koid(value);
             break;
+          }
           default:
             arg.value = fuchsia_trace_utils::ArgValue::Unknown();
             break;
         }
 
         args.push_back(arg);
-        current = arg_base + arg_size_words;
+        cursor.SetWordIndex(arg_base + arg_size_words);
       }
 
       switch (event_type) {
@@ -282,12 +333,16 @@
           break;
         }
         case kDurationComplete: {
-          int64_t end_ts = fuchsia_trace_utils::ReadTimestamp(
-              &current, provider_view->get_ticks_per_second());
+          int64_t end_ts;
+          if (!cursor.ReadTimestamp(provider_view->get_ticks_per_second(),
+                                    &end_ts)) {
+            context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+            return;
+          }
           int64_t duration = end_ts - ts;
           if (duration < 0) {
             context_->storage->IncrementStats(stats::fuchsia_invalid_event);
-            break;
+            return;
           }
           UniqueTid utid =
               procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
@@ -298,7 +353,11 @@
           break;
         }
         case kAsyncBegin: {
-          int64_t correlation_id = static_cast<int64_t>(*current++);
+          int64_t correlation_id;
+          if (!cursor.ReadInt64(&correlation_id)) {
+            context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+            return;
+          }
           TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
               name, correlation_id);
           slices->Begin(ts, track_id, track_id, RefType::kRefTrack, cat, name);
@@ -308,7 +367,11 @@
           // TODO(eseckler): Consider storing these instants as 0-duration
           // slices instead, so that they get nested underneath begin/end
           // slices.
-          int64_t correlation_id = static_cast<int64_t>(*current++);
+          int64_t correlation_id;
+          if (!cursor.ReadInt64(&correlation_id)) {
+            context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+            return;
+          }
           TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
               name, correlation_id);
           RowId row = context_->event_tracker->PushInstant(
@@ -322,7 +385,11 @@
           break;
         }
         case kAsyncEnd: {
-          int64_t correlation_id = static_cast<int64_t>(*current++);
+          int64_t correlation_id;
+          if (!cursor.ReadInt64(&correlation_id)) {
+            context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+            return;
+          }
           TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
               name, correlation_id);
           slices->End(ts, track_id, cat, name);
diff --git a/src/trace_processor/fuchsia_trace_parser.h b/src/trace_processor/fuchsia_trace_parser.h
index 67cc95b..8992ce6 100644
--- a/src/trace_processor/fuchsia_trace_parser.h
+++ b/src/trace_processor/fuchsia_trace_parser.h
@@ -18,6 +18,7 @@
 #define SRC_TRACE_PROCESSOR_FUCHSIA_TRACE_PARSER_H_
 
 #include "src/trace_processor/fuchsia_provider_view.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/trace_parser.h"
 
 namespace perfetto {
@@ -31,11 +32,8 @@
   ~FuchsiaTraceParser() override;
 
   // TraceParser implementation
-  void ParseTracePacket(int64_t timestamp,
-                        TraceSorter::TimestampedTracePiece) override;
-  void ParseFtracePacket(uint32_t,
-                         int64_t,
-                         TraceSorter::TimestampedTracePiece) override;
+  void ParseTracePacket(int64_t timestamp, TimestampedTracePiece) override;
+  void ParseFtracePacket(uint32_t, int64_t, TimestampedTracePiece) override;
 
  private:
   TraceProcessorContext* const context_;
diff --git a/src/trace_processor/fuchsia_trace_tokenizer.cc b/src/trace_processor/fuchsia_trace_tokenizer.cc
index c056d1e..5f9a4b8 100644
--- a/src/trace_processor/fuchsia_trace_tokenizer.cc
+++ b/src/trace_processor/fuchsia_trace_tokenizer.cc
@@ -187,8 +187,12 @@
   ProcessTracker* procs = context_->process_tracker.get();
   TraceSorter* sorter = context_->sorter.get();
 
-  const uint64_t* record = reinterpret_cast<const uint64_t*>(tbv.data());
-  uint64_t header = *record;
+  fuchsia_trace_utils::RecordCursor cursor(&tbv);
+  uint64_t header;
+  if (!cursor.ReadUint64(&header)) {
+    context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+    return;
+  }
 
   uint32_t record_type = fuchsia_trace_utils::ReadField<uint32_t>(header, 0, 3);
 
@@ -208,8 +212,12 @@
               fuchsia_trace_utils::ReadField<uint32_t>(header, 20, 51);
           uint32_t name_len =
               fuchsia_trace_utils::ReadField<uint32_t>(header, 52, 59);
-          std::string name(reinterpret_cast<const char*>(&record[1]), name_len);
-          RegisterProvider(provider_id, name);
+          base::StringView name_view;
+          if (!cursor.ReadInlineString(name_len, &name_view)) {
+            context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+            return;
+          }
+          RegisterProvider(provider_id, name_view.ToStdString());
           break;
         }
         case kProviderSection: {
@@ -228,14 +236,21 @@
       break;
     }
     case kInitialization: {
-      current_provider_->ticks_per_second = record[1];
+      if (!cursor.ReadUint64(&current_provider_->ticks_per_second)) {
+        context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+        return;
+      }
       break;
     }
     case kString: {
       uint32_t index = fuchsia_trace_utils::ReadField<uint32_t>(header, 16, 30);
       if (index != 0) {
         uint32_t len = fuchsia_trace_utils::ReadField<uint32_t>(header, 32, 46);
-        base::StringView s(reinterpret_cast<const char*>(&record[1]), len);
+        base::StringView s;
+        if (!cursor.ReadInlineString(len, &s)) {
+          context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+          return;
+        }
         StringId id = storage->InternString(s);
 
         current_provider_->string_table[index] = id;
@@ -246,8 +261,10 @@
       uint32_t index = fuchsia_trace_utils::ReadField<uint32_t>(header, 16, 23);
       if (index != 0) {
         fuchsia_trace_utils::ThreadInfo tinfo;
-        tinfo.pid = record[1];
-        tinfo.tid = record[2];
+        if (!cursor.ReadInlineThread(&tinfo)) {
+          context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+          return;
+        }
 
         current_provider_->thread_table[index] = tinfo;
       }
@@ -265,22 +282,25 @@
       // the record. This means the thread information if not inline, and any
       // non-inline strings (name, category for now, arg names and string values
       // in the future.
-      const uint64_t* current = &record[1];
       auto provider_view =
           std::unique_ptr<FuchsiaProviderView>(new FuchsiaProviderView());
       provider_view->set_ticks_per_second(current_provider_->ticks_per_second);
 
-      uint64_t ticks = *current++;
+      uint64_t ticks;
+      if (!cursor.ReadUint64(&ticks)) {
+        context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+        return;
+      }
       int64_t ts = fuchsia_trace_utils::TicksToNs(
           ticks, current_provider_->ticks_per_second);
       if (ts < 0) {
         storage->IncrementStats(stats::fuchsia_timestamp_overflow);
-        break;
+        return;
       }
 
       if (fuchsia_trace_utils::IsInlineThread(thread_ref)) {
         // Skip over inline thread
-        fuchsia_trace_utils::ReadInlineThread(&current);
+        cursor.ReadInlineThread(nullptr);
       } else {
         provider_view->InsertThread(
             thread_ref, current_provider_->thread_table[thread_ref]);
@@ -288,7 +308,7 @@
 
       if (fuchsia_trace_utils::IsInlineString(cat_ref)) {
         // Skip over inline string
-        fuchsia_trace_utils::ReadInlineString(&current, cat_ref);
+        cursor.ReadInlineString(cat_ref, nullptr);
       } else {
         provider_view->InsertString(cat_ref,
                                     current_provider_->string_table[cat_ref]);
@@ -296,7 +316,7 @@
 
       if (fuchsia_trace_utils::IsInlineString(name_ref)) {
         // Skip over inline string
-        fuchsia_trace_utils::ReadInlineString(&current, name_ref);
+        cursor.ReadInlineString(name_ref, nullptr);
       } else {
         provider_view->InsertString(name_ref,
                                     current_provider_->string_table[name_ref]);
@@ -305,8 +325,12 @@
       uint32_t n_args =
           fuchsia_trace_utils::ReadField<uint32_t>(header, 20, 23);
       for (uint32_t i = 0; i < n_args; i++) {
-        const uint64_t* arg_base = current;
-        uint64_t arg_header = *current++;
+        const size_t arg_base = cursor.WordIndex();
+        uint64_t arg_header;
+        if (!cursor.ReadUint64(&arg_header)) {
+          storage->IncrementStats(stats::fuchsia_invalid_event);
+          return;
+        }
         uint32_t arg_type =
             fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 0, 3);
         uint32_t arg_size_words =
@@ -316,7 +340,7 @@
 
         if (fuchsia_trace_utils::IsInlineString(arg_name_ref)) {
           // Skip over inline string
-          fuchsia_trace_utils::ReadInlineString(&current, arg_name_ref);
+          cursor.ReadInlineString(arg_name_ref, nullptr);
         } else {
           provider_view->InsertString(
               arg_name_ref, current_provider_->string_table[arg_name_ref]);
@@ -327,14 +351,14 @@
               fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 32, 47);
           if (fuchsia_trace_utils::IsInlineString(arg_value_ref)) {
             // Skip over inline string
-            fuchsia_trace_utils::ReadInlineString(&current, arg_value_ref);
+            cursor.ReadInlineString(arg_value_ref, nullptr);
           } else {
             provider_view->InsertString(
                 arg_value_ref, current_provider_->string_table[arg_value_ref]);
           }
         }
 
-        current = arg_base + arg_size_words;
+        cursor.SetWordIndex(arg_base + arg_size_words);
       }
 
       sorter->PushFuchsiaRecord(ts, std::move(tbv), std::move(provider_view));
@@ -347,13 +371,20 @@
       uint32_t name_ref =
           fuchsia_trace_utils::ReadField<uint32_t>(header, 24, 39);
 
-      const uint64_t* current = &record[1];
-      uint64_t obj_id = *current++;
+      uint64_t obj_id;
+      if (!cursor.ReadUint64(&obj_id)) {
+        storage->IncrementStats(stats::fuchsia_invalid_event);
+        return;
+      }
 
       StringId name = StringId();
       if (fuchsia_trace_utils::IsInlineString(name_ref)) {
-        name = storage->InternString(
-            fuchsia_trace_utils::ReadInlineString(&current, name_ref));
+        base::StringView name_view;
+        if (!cursor.ReadInlineString(name_ref, &name_view)) {
+          storage->IncrementStats(stats::fuchsia_invalid_event);
+          return;
+        }
+        name = storage->InternString(name_view);
       } else {
         name = current_provider_->string_table[name_ref];
       }
@@ -376,8 +407,12 @@
 
           // Scan for a Kernel Object argument named "process"
           for (uint32_t i = 0; i < n_args; i++) {
-            const uint64_t* arg_base = current;
-            uint64_t arg_header = *current++;
+            const size_t arg_base = cursor.WordIndex();
+            uint64_t arg_header;
+            if (!cursor.ReadUint64(&arg_header)) {
+              storage->IncrementStats(stats::fuchsia_invalid_event);
+              return;
+            }
             uint32_t arg_type =
                 fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 0, 3);
             uint32_t arg_size =
@@ -387,19 +422,24 @@
                   fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 16, 31);
               base::StringView arg_name;
               if (fuchsia_trace_utils::IsInlineString(arg_name_ref)) {
-                arg_name = fuchsia_trace_utils::ReadInlineString(&current,
-                                                                 arg_name_ref);
+                if (!cursor.ReadInlineString(arg_name_ref, &arg_name)) {
+                  storage->IncrementStats(stats::fuchsia_invalid_event);
+                  return;
+                }
               } else {
                 arg_name = storage->GetString(
                     current_provider_->string_table[arg_name_ref]);
               }
 
               if (arg_name == "process") {
-                pid = *current++;
+                if (!cursor.ReadUint64(&pid)) {
+                  storage->IncrementStats(stats::fuchsia_invalid_event);
+                  return;
+                }
               }
             }
 
-            current = arg_base + arg_size;
+            cursor.SetWordIndex(arg_base + arg_size);
           }
 
           pid_table_[obj_id] = pid;
@@ -429,26 +469,32 @@
       int32_t outgoing_priority =
           fuchsia_trace_utils::ReadField<int32_t>(header, 44, 51);
 
-      uint64_t ticks = record[1];
-      int64_t ts = fuchsia_trace_utils::TicksToNs(
-          ticks, current_provider_->ticks_per_second);
+      int64_t ts;
+      if (!cursor.ReadTimestamp(current_provider_->ticks_per_second, &ts)) {
+        context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+        return;
+      }
       if (ts == -1) {
         context_->storage->IncrementStats(stats::fuchsia_invalid_event);
-        break;
+        return;
       }
 
-      const uint64_t* current = &record[2];
-
       fuchsia_trace_utils::ThreadInfo outgoing_thread;
       if (fuchsia_trace_utils::IsInlineThread(outgoing_thread_ref)) {
-        outgoing_thread = fuchsia_trace_utils::ReadInlineThread(&current);
+        if (!cursor.ReadInlineThread(&outgoing_thread)) {
+          context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+          return;
+        }
       } else {
         outgoing_thread = current_provider_->thread_table[outgoing_thread_ref];
       }
 
       fuchsia_trace_utils::ThreadInfo incoming_thread;
       if (fuchsia_trace_utils::IsInlineThread(incoming_thread_ref)) {
-        incoming_thread = fuchsia_trace_utils::ReadInlineThread(&current);
+        if (!cursor.ReadInlineThread(&incoming_thread)) {
+          context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+          return;
+        }
       } else {
         incoming_thread = current_provider_->thread_table[incoming_thread_ref];
       }
diff --git a/src/trace_processor/fuchsia_trace_utils.cc b/src/trace_processor/fuchsia_trace_utils.cc
index 6d87731..cca1997 100644
--- a/src/trace_processor/fuchsia_trace_utils.cc
+++ b/src/trace_processor/fuchsia_trace_utils.cc
@@ -31,35 +31,10 @@
   return (string_ref & kInlineStringMarker) || (string_ref == 0);
 }
 
-base::StringView ReadInlineString(const uint64_t** current_ptr,
-                                  uint32_t string_ref) {
-  // Note that this works correctly for the empty string, where string_ref is 0.
-  size_t len = string_ref & kInlineStringLengthMask;
-  size_t len_words = (len + 7) / 8;
-  base::StringView s(reinterpret_cast<const char*>(*current_ptr), len);
-  *current_ptr += len_words;
-  return s;
-}
-
 bool IsInlineThread(uint32_t thread_ref) {
   return thread_ref == 0;
 }
 
-ThreadInfo ReadInlineThread(const uint64_t** current_ptr) {
-  ThreadInfo ret;
-  ret.pid = **current_ptr;
-  (*current_ptr)++;
-  ret.tid = **current_ptr;
-  (*current_ptr)++;
-  return ret;
-}
-
-int64_t ReadTimestamp(const uint64_t** current_ptr, uint64_t ticks_per_second) {
-  uint64_t ticks = **current_ptr;
-  (*current_ptr)++;
-  return TicksToNs(ticks, ticks_per_second);
-}
-
 // Converts a tick count to nanoseconds. Returns -1 if the result would not
 // fit in a nonnegative int64_t. Negative timestamps are not allowed by the
 // Fuchsia trace format. Also returns -1 if ticks_per_second is zero.
@@ -114,6 +89,107 @@
   PERFETTO_FATAL("Not reached");  // Make GCC happy.
 }
 
+size_t RecordCursor::WordIndex() {
+  return word_index_;
+}
+
+void RecordCursor::SetWordIndex(size_t index) {
+  word_index_ = index;
+}
+
+bool RecordCursor::ReadTimestamp(uint64_t ticks_per_second, int64_t* ts_out) {
+  const uint64_t* ts_data;
+  if (!ReadWords(1, &ts_data)) {
+    return false;
+  }
+  if (ts_out != nullptr) {
+    *ts_out = TicksToNs(*ts_data, ticks_per_second);
+  }
+  return true;
+}
+
+bool RecordCursor::ReadInlineString(uint32_t string_ref_or_len,
+                                    base::StringView* string_out) {
+  // Note that this works correctly for the empty string, where string_ref is 0.
+  size_t len = string_ref_or_len & kInlineStringLengthMask;
+  size_t len_words = (len + 7) / 8;
+  const uint64_t* string_data;
+  if (!ReadWords(len_words, &string_data)) {
+    return false;
+  }
+  if (string_out != nullptr) {
+    *string_out =
+        base::StringView(reinterpret_cast<const char*>(string_data), len);
+  }
+  return true;
+}
+
+bool RecordCursor::ReadInlineThread(ThreadInfo* thread_out) {
+  const uint64_t* thread_data;
+  if (!ReadWords(2, &thread_data)) {
+    return false;
+  }
+  if (thread_out != nullptr) {
+    thread_out->pid = thread_data[0];
+    thread_out->tid = thread_data[1];
+  }
+  return true;
+}
+
+bool RecordCursor::ReadInt64(int64_t* out) {
+  const uint64_t* out_data;
+  if (!ReadWords(1, &out_data)) {
+    return false;
+  }
+  if (out != nullptr) {
+    *out = static_cast<int64_t>(*out_data);
+  }
+  return true;
+}
+
+bool RecordCursor::ReadUint64(uint64_t* out) {
+  const uint64_t* out_data;
+  if (!ReadWords(1, &out_data)) {
+    return false;
+  }
+  if (out != nullptr) {
+    *out = *out_data;
+  }
+  return true;
+}
+
+bool RecordCursor::ReadDouble(double* out) {
+  static_assert(sizeof(double) == sizeof(uint64_t), "double must be 64 bits");
+
+  const uint64_t* out_data;
+  if (!ReadWords(1, &out_data)) {
+    return false;
+  }
+  if (out != nullptr) {
+    memcpy(out, out_data, sizeof(double));
+  }
+  return true;
+}
+
+bool RecordCursor::ReadWords(size_t num_words, const uint64_t** data_out) {
+  const uint64_t* end =
+      reinterpret_cast<const uint64_t*>(tbv_.data() + tbv_.length());
+  const uint64_t* data =
+      reinterpret_cast<const uint64_t*>(tbv_.data()) + word_index_;
+  // This addition is unconditional so that callers with data_out == nullptr do
+  // not necessarily have to check the return value, as future calls will fail
+  // due to attempting to read out of bounds.
+  word_index_ += num_words;
+  if (data + num_words <= end) {
+    if (data_out != nullptr) {
+      *data_out = data;
+    }
+    return true;
+  } else {
+    return false;
+  }
+}
+
 }  // namespace fuchsia_trace_utils
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/fuchsia_trace_utils.h b/src/trace_processor/fuchsia_trace_utils.h
index c101dcf..3a70c86 100644
--- a/src/trace_processor/fuchsia_trace_utils.h
+++ b/src/trace_processor/fuchsia_trace_utils.h
@@ -22,6 +22,7 @@
 #include <functional>
 
 #include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_storage.h"
 
 namespace perfetto {
@@ -40,12 +41,7 @@
 }
 
 bool IsInlineString(uint32_t);
-base::StringView ReadInlineString(const uint64_t**, uint32_t);
-
 bool IsInlineThread(uint32_t);
-ThreadInfo ReadInlineThread(const uint64_t**);
-
-int64_t ReadTimestamp(const uint64_t**, uint64_t);
 int64_t TicksToNs(uint64_t ticks, uint64_t ticks_per_second);
 
 class ArgValue {
@@ -191,6 +187,35 @@
   };
 };
 
+// This class maintains a location into the record, with helper functions to
+// read various trace data from the current location in a safe manner.
+//
+// In the context of Fuchsia trace records, a "word" is defined as 64 bits
+// regardless of platform. For more information, see
+// https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/docs/development/tracing/trace-format/
+class RecordCursor {
+ public:
+  RecordCursor(const TraceBlobView* tbv) : tbv_(*tbv), word_index_(0) {}
+
+  size_t WordIndex();
+  void SetWordIndex(size_t index);
+
+  bool ReadTimestamp(uint64_t ticks_per_second, int64_t* ts_out);
+  bool ReadInlineString(uint32_t string_ref_or_len,
+                        base::StringView* string_out);
+  bool ReadInlineThread(ThreadInfo* thread_out);
+
+  bool ReadInt64(int64_t* out);
+  bool ReadUint64(uint64_t* out);
+  bool ReadDouble(double* out);
+
+ private:
+  bool ReadWords(size_t num_words, const uint64_t** data_out);
+
+  const TraceBlobView& tbv_;
+  size_t word_index_;
+};
+
 }  // namespace fuchsia_trace_utils
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/graphics_event_parser.cc b/src/trace_processor/graphics_event_parser.cc
index 7265028..3276db8 100644
--- a/src/trace_processor/graphics_event_parser.cc
+++ b/src/trace_processor/graphics_event_parser.cc
@@ -456,6 +456,8 @@
           vulkan_memory_event_row);
 
   if (vulkan_memory_event.has_annotations()) {
+    auto global_row_id =
+        TraceStorage::CreateRowId(TableId::kVulkanMemoryAllocation, row_id);
     for (auto itt = vulkan_memory_event.annotations(); itt; ++itt) {
       protos::pbzero::VulkanMemoryEventAnnotation::Decoder annotation(
           itt->data(), itt->size());
@@ -463,17 +465,17 @@
           *(context_->vulkan_memory_tracker->FindString(annotation.key_iid()));
       if (annotation.has_int_value()) {
         context_->args_tracker->AddArg(
-            row_id, annotation_id, annotation_id,
+            global_row_id, annotation_id, annotation_id,
             Variadic::Integer(annotation.int_value()));
 
       } else if (annotation.has_double_value()) {
         context_->args_tracker->AddArg(
-            row_id, annotation_id, annotation_id,
+            global_row_id, annotation_id, annotation_id,
             Variadic::Real(annotation.double_value()));
 
       } else if (annotation.has_string_iid()) {
         context_->args_tracker->AddArg(
-            row_id, annotation_id, annotation_id,
+            global_row_id, annotation_id, annotation_id,
             Variadic::String(*(context_->vulkan_memory_tracker->FindString(
                 annotation.string_iid()))));
       }
diff --git a/src/trace_processor/heap_graph_tracker.cc b/src/trace_processor/heap_graph_tracker.cc
index 57502e3..bd60ffa 100644
--- a/src/trace_processor/heap_graph_tracker.cc
+++ b/src/trace_processor/heap_graph_tracker.cc
@@ -22,20 +22,34 @@
 HeapGraphTracker::HeapGraphTracker(TraceProcessorContext* context)
     : context_(context) {}
 
-void HeapGraphTracker::AddObject(UniquePid upid, int64_t ts, SourceObject obj) {
+bool HeapGraphTracker::SetPidAndTimestamp(UniquePid upid, int64_t ts) {
   if (current_upid_ != 0 && current_upid_ != upid) {
     context_->storage->IncrementStats(stats::heap_graph_non_finalized_graph);
-    return;
+    return false;
   }
   if (current_ts_ != 0 && current_ts_ != ts) {
     context_->storage->IncrementStats(stats::heap_graph_non_finalized_graph);
-    return;
+    return false;
   }
   current_upid_ = upid;
   current_ts_ = ts;
+  return true;
+}
+
+void HeapGraphTracker::AddObject(UniquePid upid, int64_t ts, SourceObject obj) {
+  if (!SetPidAndTimestamp(upid, ts))
+    return;
+
   current_objects_.emplace_back(std::move(obj));
 }
 
+void HeapGraphTracker::AddRoot(UniquePid upid, int64_t ts, SourceRoot root) {
+  if (!SetPidAndTimestamp(upid, ts))
+    return;
+
+  current_roots_.emplace_back(std::move(root));
+}
+
 void HeapGraphTracker::AddInternedTypeName(uint64_t intern_id,
                                            StringPool::Id strid) {
   interned_type_names_.emplace(intern_id, strid);
@@ -66,7 +80,7 @@
     }
     context_->storage->mutable_heap_graph_object_table()->Insert(
         {current_upid_, current_ts_, static_cast<int64_t>(obj.object_id),
-         static_cast<int64_t>(obj.self_size), -1, it->second});
+         static_cast<int64_t>(obj.self_size), -1, it->second, base::nullopt});
     object_id_to_row_.emplace(
         obj.object_id, context_->storage->heap_graph_object_table().size() - 1);
   }
@@ -106,10 +120,26 @@
         ->mutable_reference_set_id()
         ->Set(static_cast<uint32_t>(owner_row), reference_set_id);
   }
+
+  for (const SourceRoot& root : current_roots_) {
+    for (uint64_t obj_id : root.object_ids) {
+      auto it = object_id_to_row_.find(obj_id);
+      // This can only happen for an invalid type string id, which is already
+      // reported as an error. Silently continue here.
+      if (it == object_id_to_row_.end())
+        continue;
+
+      int64_t obj_row = it->second;
+      context_->storage->mutable_heap_graph_object_table()
+          ->mutable_root_type()
+          ->Set(static_cast<uint32_t>(obj_row), root.root_type);
+    }
+  }
   interned_field_names_.clear();
   object_id_to_row_.clear();
   interned_type_names_.clear();
   current_objects_.clear();
+  current_roots_.clear();
   current_upid_ = 0;
   current_ts_ = 0;
 }
diff --git a/src/trace_processor/heap_graph_tracker.h b/src/trace_processor/heap_graph_tracker.h
index c65a7b1..7d0ec6a 100644
--- a/src/trace_processor/heap_graph_tracker.h
+++ b/src/trace_processor/heap_graph_tracker.h
@@ -46,8 +46,14 @@
     std::vector<Reference> references;
   };
 
+  struct SourceRoot {
+    StringPool::Id root_type;
+    std::vector<uint64_t> object_ids;
+  };
+
   explicit HeapGraphTracker(TraceProcessorContext* context);
 
+  void AddRoot(UniquePid upid, int64_t ts, SourceRoot root);
   void AddObject(UniquePid upid, int64_t ts, SourceObject obj);
   void AddInternedTypeName(uint64_t intern_id, StringPool::Id strid);
   void AddInternedFieldName(uint64_t intern_id, StringPool::Id strid);
@@ -55,11 +61,13 @@
   void SetPacketIndex(uint64_t index);
 
  private:
+  bool SetPidAndTimestamp(UniquePid upid, int64_t ts);
   TraceProcessorContext* const context_;
 
   UniquePid current_upid_ = 0;
   int64_t current_ts_ = 0;
   std::vector<SourceObject> current_objects_;
+  std::vector<SourceRoot> current_roots_;
   std::map<uint64_t, StringPool::Id> interned_type_names_;
   std::map<uint64_t, StringPool::Id> interned_field_names_;
   std::map<uint64_t, int64_t> object_id_to_row_;
diff --git a/src/trace_processor/json_trace_parser.cc b/src/trace_processor/json_trace_parser.cc
index 54a548f..2216eab 100644
--- a/src/trace_processor/json_trace_parser.cc
+++ b/src/trace_processor/json_trace_parser.cc
@@ -45,12 +45,12 @@
 
 void JsonTraceParser::ParseFtracePacket(uint32_t,
                                         int64_t,
-                                        TraceSorter::TimestampedTracePiece) {
+                                        TimestampedTracePiece) {
   PERFETTO_FATAL("Json Trace Parser cannot handle ftrace packets.");
 }
 
 void JsonTraceParser::ParseTracePacket(int64_t timestamp,
-                                       TraceSorter::TimestampedTracePiece ttp) {
+                                       TimestampedTracePiece ttp) {
   PERFETTO_DCHECK(ttp.json_value != nullptr);
   const Json::Value& value = *(ttp.json_value);
 
diff --git a/src/trace_processor/json_trace_parser.h b/src/trace_processor/json_trace_parser.h
index 9850924..bf0d5a5 100644
--- a/src/trace_processor/json_trace_parser.h
+++ b/src/trace_processor/json_trace_parser.h
@@ -23,8 +23,8 @@
 #include <tuple>
 #include <unordered_map>
 
+#include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/trace_parser.h"
-#include "src/trace_processor/trace_sorter.h"
 #include "src/trace_processor/trace_storage.h"
 
 namespace Json {
@@ -47,11 +47,8 @@
   ~JsonTraceParser() override;
 
   // TraceParser implementation.
-  void ParseTracePacket(int64_t timestamp,
-                        TraceSorter::TimestampedTracePiece) override;
-  void ParseFtracePacket(uint32_t,
-                         int64_t,
-                         TraceSorter::TimestampedTracePiece) override;
+  void ParseTracePacket(int64_t timestamp, TimestampedTracePiece) override;
+  void ParseFtracePacket(uint32_t, int64_t, TimestampedTracePiece) override;
 
  private:
   TraceProcessorContext* const context_;
diff --git a/src/trace_processor/json_trace_tokenizer.h b/src/trace_processor/json_trace_tokenizer.h
index 499342a..e5a1254 100644
--- a/src/trace_processor/json_trace_tokenizer.h
+++ b/src/trace_processor/json_trace_tokenizer.h
@@ -20,7 +20,6 @@
 #include <stdint.h>
 
 #include "src/trace_processor/chunked_trace_reader.h"
-#include "src/trace_processor/trace_sorter.h"
 #include "src/trace_processor/trace_storage.h"
 
 namespace Json {
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index 007f425..5a766d2 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -41,6 +41,7 @@
 #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/timestamped_trace_piece.h"
 #include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/track_tracker.h"
 #include "src/trace_processor/variadic.h"
@@ -205,6 +206,43 @@
 constexpr int64_t kPendingThreadInstructionDelta = -1;
 }  // namespace
 
+const char* HeapGraphRootTypeToString(int32_t type) {
+  switch (type) {
+    case protos::pbzero::HeapGraphRoot::ROOT_UNKNOWN:
+      return "ROOT_UNKNOWN";
+    case protos::pbzero::HeapGraphRoot::ROOT_JNI_GLOBAL:
+      return "ROOT_JNI_GLOBAL";
+    case protos::pbzero::HeapGraphRoot::ROOT_JNI_LOCAL:
+      return "ROOT_JNI_LOCAL";
+    case protos::pbzero::HeapGraphRoot::ROOT_JAVA_FRAME:
+      return "ROOT_JAVA_FRAME";
+    case protos::pbzero::HeapGraphRoot::ROOT_NATIVE_STACK:
+      return "ROOT_NATIVE_STACK";
+    case protos::pbzero::HeapGraphRoot::ROOT_STICKY_CLASS:
+      return "ROOT_STICKY_CLASS";
+    case protos::pbzero::HeapGraphRoot::ROOT_THREAD_BLOCK:
+      return "ROOT_THREAD_BLOCK";
+    case protos::pbzero::HeapGraphRoot::ROOT_MONITOR_USED:
+      return "ROOT_MONITOR_USED";
+    case protos::pbzero::HeapGraphRoot::ROOT_THREAD_OBJECT:
+      return "ROOT_THREAD_OBJECT";
+    case protos::pbzero::HeapGraphRoot::ROOT_INTERNED_STRING:
+      return "ROOT_INTERNED_STRING";
+    case protos::pbzero::HeapGraphRoot::ROOT_FINALIZING:
+      return "ROOT_FINALIZING";
+    case protos::pbzero::HeapGraphRoot::ROOT_DEBUGGER:
+      return "ROOT_DEBUGGER";
+    case protos::pbzero::HeapGraphRoot::ROOT_REFERENCE_CLEANUP:
+      return "ROOT_REFERENCE_CLEANUP";
+    case protos::pbzero::HeapGraphRoot::ROOT_VM_INTERNAL:
+      return "ROOT_VM_INTERNAL";
+    case protos::pbzero::HeapGraphRoot::ROOT_JNI_MONITOR:
+      return "ROOT_JNI_MONITOR";
+    default:
+      return "ROOT_UNKNOWN";
+  }
+}
+
 }  // namespace
 
 ProtoTraceParser::ProtoTraceParser(TraceProcessorContext* context)
@@ -394,9 +432,7 @@
 
 ProtoTraceParser::~ProtoTraceParser() = default;
 
-void ProtoTraceParser::ParseTracePacket(
-    int64_t ts,
-    TraceSorter::TimestampedTracePiece ttp) {
+void ProtoTraceParser::ParseTracePacket(int64_t ts, TimestampedTracePiece ttp) {
   PERFETTO_DCHECK(ttp.json_value == nullptr);
   const TraceBlobView& blob = ttp.blob_view;
 
@@ -677,14 +713,13 @@
   }
 }
 
-void ProtoTraceParser::ParseFtracePacket(
-    uint32_t cpu,
-    int64_t ts,
-    TraceSorter::TimestampedTracePiece ttp) {
+void ProtoTraceParser::ParseFtracePacket(uint32_t cpu,
+                                         int64_t ts,
+                                         TimestampedTracePiece ttp) {
   PERFETTO_DCHECK(ttp.json_value == nullptr);
 
   // Handle the (optional) alternative encoding format for sched_switch.
-  if (ttp.inline_event.type == TraceSorter::InlineEvent::Type::kSchedSwitch) {
+  if (ttp.inline_event.type == InlineEvent::Type::kSchedSwitch) {
     const auto& event = ttp.inline_event.sched_switch;
     context_->event_tracker->PushSchedSwitchCompact(
         cpu, ts, event.prev_state, static_cast<uint32_t>(event.next_pid),
@@ -2686,6 +2721,17 @@
     context_->heap_graph_tracker->AddInternedFieldName(
         entry.iid(), context_->storage->InternString(str_view));
   }
+  for (auto it = heap_graph.roots(); it; ++it) {
+    protos::pbzero::HeapGraphRoot::Decoder entry(it->data(), it->size());
+    const char* str = HeapGraphRootTypeToString(entry.root_type());
+    auto str_view = base::StringView(str);
+
+    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->as_uint64());
+    context_->heap_graph_tracker->AddRoot(upid, ts, std::move(src_root));
+  }
   if (!heap_graph.continued()) {
     context_->heap_graph_tracker->FinalizeProfile();
   }
diff --git a/src/trace_processor/proto_trace_parser.h b/src/trace_processor/proto_trace_parser.h
index 15f841e..7416702 100644
--- a/src/trace_processor/proto_trace_parser.h
+++ b/src/trace_processor/proto_trace_parser.h
@@ -29,10 +29,12 @@
 #include "src/trace_processor/graphics_event_parser.h"
 #include "src/trace_processor/proto_incremental_state.h"
 #include "src/trace_processor/slice_tracker.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_parser.h"
 #include "src/trace_processor/trace_storage.h"
 
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
 namespace perfetto {
@@ -48,11 +50,10 @@
   ~ProtoTraceParser() override;
 
   // TraceParser implementation.
-  void ParseTracePacket(int64_t timestamp,
-                        TraceSorter::TimestampedTracePiece) override;
+  void ParseTracePacket(int64_t timestamp, TimestampedTracePiece) override;
   void ParseFtracePacket(uint32_t cpu,
                          int64_t timestamp,
-                         TraceSorter::TimestampedTracePiece) override;
+                         TimestampedTracePiece) override;
 
   void ParseProcessTree(ConstBytes);
   void ParseProcessStats(int64_t timestamp, ConstBytes);
diff --git a/src/trace_processor/proto_trace_tokenizer.cc b/src/trace_processor/proto_trace_tokenizer.cc
index e27ae54..70b6c0b 100644
--- a/src/trace_processor/proto_trace_tokenizer.cc
+++ b/src/trace_processor/proto_trace_tokenizer.cc
@@ -803,7 +803,7 @@
   auto comm_it = compact.switch_next_comm_index(&parse_error);
   for (; timestamp_it && pstate_it && npid_it && nprio_it && comm_it;
        ++timestamp_it, ++pstate_it, ++npid_it, ++nprio_it, ++comm_it) {
-    TraceSorter::InlineSchedSwitch event{};
+    InlineSchedSwitch event{};
 
     // delta-encoded timestamp
     timestamp_acc += static_cast<int64_t>(*timestamp_it);
@@ -817,8 +817,8 @@
     event.next_pid = *npid_it;
     event.next_prio = *nprio_it;
 
-    context_->sorter->PushInlineFtraceEvent(
-        cpu, event_timestamp, TraceSorter::InlineEvent::SchedSwitch(event));
+    context_->sorter->PushInlineFtraceEvent(cpu, event_timestamp,
+                                            InlineEvent::SchedSwitch(event));
   }
 
   // Check that all packed buffers were decoded correctly, and fully.
diff --git a/src/trace_processor/tables/profiler_tables.h b/src/trace_processor/tables/profiler_tables.h
index 16133d8..c81ec7c 100644
--- a/src/trace_processor/tables/profiler_tables.h
+++ b/src/trace_processor/tables/profiler_tables.h
@@ -41,7 +41,8 @@
   C(int64_t, object_id)                                    \
   C(int64_t, self_size)                                    \
   C(int64_t, reference_set_id)                             \
-  C(StringPool::Id, type_name)
+  C(StringPool::Id, type_name)                             \
+  C(base::Optional<StringPool::Id>, root_type)
 
 PERFETTO_TP_TABLE(PERFETTO_TP_HEAP_GRAPH_OBJECT_DEF);
 
diff --git a/src/trace_processor/timestamped_trace_piece.h b/src/trace_processor/timestamped_trace_piece.h
new file mode 100644
index 0000000..1a4f346
--- /dev/null
+++ b/src/trace_processor/timestamped_trace_piece.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_TIMESTAMPED_TRACE_PIECE_H_
+#define SRC_TRACE_PROCESSOR_TIMESTAMPED_TRACE_PIECE_H_
+
+#include "perfetto/base/build_config.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/fuchsia_provider_view.h"
+#include "src/trace_processor/proto_incremental_state.h"
+#include "src/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/trace_processor_context.h"
+#include "src/trace_processor/trace_storage.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+#include <json/value.h>
+#else
+// Json traces are only supported in standalone and Chromium builds.
+namespace Json {
+class Value {};
+}  // namespace Json
+#endif
+
+namespace perfetto {
+namespace trace_processor {
+
+struct InlineSchedSwitch {
+  int64_t prev_state;
+  int32_t next_pid;
+  int32_t next_prio;
+  StringId next_comm;
+};
+
+// Discriminated union of events that are cannot be easily read from the
+// mapped trace.
+struct InlineEvent {
+  enum class Type { kInvalid = 0, kSchedSwitch };
+
+  static InlineEvent SchedSwitch(InlineSchedSwitch content) {
+    InlineEvent evt;
+    evt.type = Type::kSchedSwitch;
+    evt.sched_switch = content;
+    return evt;
+  }
+
+  Type type = Type::kInvalid;
+  union {
+    InlineSchedSwitch sched_switch;
+  };
+};
+
+// A TimestampedTracePiece is (usually a reference to) a piece of a trace that
+// is sorted by TraceSorter.
+struct TimestampedTracePiece {
+  TimestampedTracePiece(
+      int64_t ts,
+      uint64_t idx,
+      TraceBlobView tbv,
+      ProtoIncrementalState::PacketSequenceState* sequence_state)
+      : TimestampedTracePiece(ts,
+                              /*thread_ts=*/0,
+                              /*thread_instructions=*/0,
+                              idx,
+                              std::move(tbv),
+                              /*value=*/nullptr,
+                              /*fpv=*/nullptr,
+                              /*sequence_state=*/sequence_state,
+                              InlineEvent{}) {}
+
+  TimestampedTracePiece(int64_t ts, uint64_t idx, TraceBlobView tbv)
+      : TimestampedTracePiece(ts,
+                              /*thread_ts=*/0,
+                              /*thread_instructions=*/0,
+                              idx,
+                              std::move(tbv),
+                              /*value=*/nullptr,
+                              /*fpv=*/nullptr,
+                              /*sequence_state=*/nullptr,
+                              InlineEvent{}) {}
+
+  TimestampedTracePiece(int64_t ts,
+                        uint64_t idx,
+                        std::unique_ptr<Json::Value> value)
+      : TimestampedTracePiece(ts,
+                              /*thread_ts=*/0,
+                              /*thread_instructions=*/0,
+                              idx,
+                              // TODO(dproy): Stop requiring TraceBlobView in
+                              // TimestampedTracePiece.
+                              TraceBlobView(nullptr, 0, 0),
+                              std::move(value),
+                              /*fpv=*/nullptr,
+                              /*sequence_state=*/nullptr,
+                              InlineEvent{}) {}
+
+  TimestampedTracePiece(int64_t ts,
+                        uint64_t idx,
+                        TraceBlobView tbv,
+                        std::unique_ptr<FuchsiaProviderView> fpv)
+      : TimestampedTracePiece(ts,
+                              /*thread_ts=*/0,
+                              /*thread_instructions=*/0,
+                              idx,
+                              std::move(tbv),
+                              /*value=*/nullptr,
+                              std::move(fpv),
+                              /*sequence_state=*/nullptr,
+                              InlineEvent{}) {}
+
+  TimestampedTracePiece(
+      int64_t ts,
+      int64_t thread_ts,
+      int64_t thread_instructions,
+      uint64_t idx,
+      TraceBlobView tbv,
+      ProtoIncrementalState::PacketSequenceState* sequence_state)
+      : TimestampedTracePiece(ts,
+                              thread_ts,
+                              thread_instructions,
+                              idx,
+                              std::move(tbv),
+                              /*value=*/nullptr,
+                              /*fpv=*/nullptr,
+                              sequence_state,
+                              InlineEvent{}) {}
+
+  TimestampedTracePiece(int64_t ts, uint64_t idx, InlineEvent inline_evt)
+      : TimestampedTracePiece(ts,
+                              /*thread_ts=*/0,
+                              /*thread_instructions=*/0,
+                              idx,
+                              /*tbv=*/TraceBlobView(nullptr, 0, 0),
+                              /*value=*/nullptr,
+                              /*fpv=*/nullptr,
+                              /*sequence_state=*/nullptr,
+                              inline_evt) {}
+
+  TimestampedTracePiece(
+      int64_t ts,
+      int64_t thread_ts,
+      int64_t thread_instructions,
+      uint64_t idx,
+      TraceBlobView tbv,
+      std::unique_ptr<Json::Value> value,
+      std::unique_ptr<FuchsiaProviderView> fpv,
+      ProtoIncrementalState::PacketSequenceState* sequence_state,
+      InlineEvent inline_evt)
+      : json_value(std::move(value)),
+        fuchsia_provider_view(std::move(fpv)),
+        packet_sequence_state(sequence_state),
+        packet_sequence_state_generation(
+            sequence_state ? sequence_state->current_generation() : 0),
+        timestamp(ts),
+        thread_timestamp(thread_ts),
+        thread_instruction_count(thread_instructions),
+        packet_idx_(idx),
+        blob_view(std::move(tbv)),
+        inline_event(inline_evt) {}
+
+  TimestampedTracePiece(TimestampedTracePiece&&) noexcept = default;
+  TimestampedTracePiece& operator=(TimestampedTracePiece&&) = default;
+
+  // For std::lower_bound().
+  static inline bool Compare(const TimestampedTracePiece& x, int64_t ts) {
+    return x.timestamp < ts;
+  }
+
+  // For std::sort().
+  inline bool operator<(const TimestampedTracePiece& o) const {
+    return timestamp < o.timestamp ||
+           (timestamp == o.timestamp && packet_idx_ < o.packet_idx_);
+  }
+
+  std::unique_ptr<Json::Value> json_value;
+  std::unique_ptr<FuchsiaProviderView> fuchsia_provider_view;
+  ProtoIncrementalState::PacketSequenceState* packet_sequence_state;
+  size_t packet_sequence_state_generation;
+
+  int64_t timestamp;
+  int64_t thread_timestamp;
+  int64_t thread_instruction_count;
+  uint64_t packet_idx_;
+  TraceBlobView blob_view;
+  InlineEvent inline_event;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_TIMESTAMPED_TRACE_PIECE_H_
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index d070664..58242f6 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -151,7 +151,7 @@
   ASSERT_GT(it.Get(0).long_value, 0);
 }
 
-TEST_F(TraceProcessorIntegrationTest, DISABLED_Clusterfuzz14767) {
+TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14767) {
   ASSERT_TRUE(LoadTrace("clusterfuzz_14767", 4096 * 1024).ok());
   auto it = Query("select sum(value) from stats where severity = 'error';");
   ASSERT_TRUE(it.Next());
diff --git a/src/trace_processor/trace_parser.h b/src/trace_processor/trace_parser.h
index 63c2ce1..fa3b4ef 100644
--- a/src/trace_processor/trace_parser.h
+++ b/src/trace_processor/trace_parser.h
@@ -19,7 +19,7 @@
 
 #include <stdint.h>
 
-#include "src/trace_processor/trace_sorter.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -28,11 +28,10 @@
  public:
   virtual ~TraceParser();
 
-  virtual void ParseTracePacket(int64_t timestamp,
-                                TraceSorter::TimestampedTracePiece) = 0;
+  virtual void ParseTracePacket(int64_t timestamp, TimestampedTracePiece) = 0;
   virtual void ParseFtracePacket(uint32_t cpu,
                                  int64_t timestamp,
-                                 TraceSorter::TimestampedTracePiece) = 0;
+                                 TimestampedTracePiece) = 0;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/trace_sorter.h b/src/trace_processor/trace_sorter.h
index d0df2cf..4aababf 100644
--- a/src/trace_processor/trace_sorter.h
+++ b/src/trace_processor/trace_sorter.h
@@ -23,18 +23,14 @@
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/fuchsia_provider_view.h"
 #include "src/trace_processor/proto_incremental_state.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/trace_storage.h"
 
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
-#include <json/value.h>
-#else
-// Json traces are only supported in standalone and Chromium builds.
 namespace Json {
-class Value {};
+class Value;
 }  // namespace Json
-#endif
 
 namespace perfetto {
 namespace trace_processor {
@@ -68,164 +64,6 @@
 // from there to the end.
 class TraceSorter {
  public:
-  struct InlineSchedSwitch {
-    int64_t prev_state;
-    int32_t next_pid;
-    int32_t next_prio;
-    StringId next_comm;
-  };
-
-  // Discriminated union of events that are cannot be easily read from the
-  // mapped trace.
-  struct InlineEvent {
-    enum class Type { kInvalid = 0, kSchedSwitch };
-
-    static InlineEvent SchedSwitch(InlineSchedSwitch content) {
-      InlineEvent evt;
-      evt.type = Type::kSchedSwitch;
-      evt.sched_switch = content;
-      return evt;
-    }
-
-    Type type = Type::kInvalid;
-    union {
-      InlineSchedSwitch sched_switch;
-    };
-  };
-
-  struct TimestampedTracePiece {
-    TimestampedTracePiece(
-        int64_t ts,
-        uint64_t idx,
-        TraceBlobView tbv,
-        ProtoIncrementalState::PacketSequenceState* sequence_state)
-        : TimestampedTracePiece(ts,
-                                /*thread_ts=*/0,
-                                /*thread_instructions=*/0,
-                                idx,
-                                std::move(tbv),
-                                /*value=*/nullptr,
-                                /*fpv=*/nullptr,
-                                /*sequence_state=*/sequence_state,
-                                InlineEvent{}) {}
-
-    TimestampedTracePiece(int64_t ts, uint64_t idx, TraceBlobView tbv)
-        : TimestampedTracePiece(ts,
-                                /*thread_ts=*/0,
-                                /*thread_instructions=*/0,
-                                idx,
-                                std::move(tbv),
-                                /*value=*/nullptr,
-                                /*fpv=*/nullptr,
-                                /*sequence_state=*/nullptr,
-                                InlineEvent{}) {}
-
-    TimestampedTracePiece(int64_t ts,
-                          uint64_t idx,
-                          std::unique_ptr<Json::Value> value)
-        : TimestampedTracePiece(ts,
-                                /*thread_ts=*/0,
-                                /*thread_instructions=*/0,
-                                idx,
-                                // TODO(dproy): Stop requiring TraceBlobView in
-                                // TimestampedTracePiece.
-                                TraceBlobView(nullptr, 0, 0),
-                                std::move(value),
-                                /*fpv=*/nullptr,
-                                /*sequence_state=*/nullptr,
-                                InlineEvent{}) {}
-
-    TimestampedTracePiece(int64_t ts,
-                          uint64_t idx,
-                          TraceBlobView tbv,
-                          std::unique_ptr<FuchsiaProviderView> fpv)
-        : TimestampedTracePiece(ts,
-                                /*thread_ts=*/0,
-                                /*thread_instructions=*/0,
-                                idx,
-                                std::move(tbv),
-                                /*value=*/nullptr,
-                                std::move(fpv),
-                                /*sequence_state=*/nullptr,
-                                InlineEvent{}) {}
-
-    TimestampedTracePiece(
-        int64_t ts,
-        int64_t thread_ts,
-        int64_t thread_instructions,
-        uint64_t idx,
-        TraceBlobView tbv,
-        ProtoIncrementalState::PacketSequenceState* sequence_state)
-        : TimestampedTracePiece(ts,
-                                thread_ts,
-                                thread_instructions,
-                                idx,
-                                std::move(tbv),
-                                /*value=*/nullptr,
-                                /*fpv=*/nullptr,
-                                sequence_state,
-                                InlineEvent{}) {}
-
-    TimestampedTracePiece(int64_t ts, uint64_t idx, InlineEvent inline_evt)
-        : TimestampedTracePiece(ts,
-                                /*thread_ts=*/0,
-                                /*thread_instructions=*/0,
-                                idx,
-                                /*tbv=*/TraceBlobView(nullptr, 0, 0),
-                                /*value=*/nullptr,
-                                /*fpv=*/nullptr,
-                                /*sequence_state=*/nullptr,
-                                inline_evt) {}
-
-    TimestampedTracePiece(
-        int64_t ts,
-        int64_t thread_ts,
-        int64_t thread_instructions,
-        uint64_t idx,
-        TraceBlobView tbv,
-        std::unique_ptr<Json::Value> value,
-        std::unique_ptr<FuchsiaProviderView> fpv,
-        ProtoIncrementalState::PacketSequenceState* sequence_state,
-        InlineEvent inline_evt)
-        : json_value(std::move(value)),
-          fuchsia_provider_view(std::move(fpv)),
-          packet_sequence_state(sequence_state),
-          packet_sequence_state_generation(
-              sequence_state ? sequence_state->current_generation() : 0),
-          timestamp(ts),
-          thread_timestamp(thread_ts),
-          thread_instruction_count(thread_instructions),
-          packet_idx_(idx),
-          blob_view(std::move(tbv)),
-          inline_event(inline_evt) {}
-
-    TimestampedTracePiece(TimestampedTracePiece&&) noexcept = default;
-    TimestampedTracePiece& operator=(TimestampedTracePiece&&) = default;
-
-    // For std::lower_bound().
-    static inline bool Compare(const TimestampedTracePiece& x, int64_t ts) {
-      return x.timestamp < ts;
-    }
-
-    // For std::sort().
-    inline bool operator<(const TimestampedTracePiece& o) const {
-      return timestamp < o.timestamp ||
-             (timestamp == o.timestamp && packet_idx_ < o.packet_idx_);
-    }
-
-    std::unique_ptr<Json::Value> json_value;
-    std::unique_ptr<FuchsiaProviderView> fuchsia_provider_view;
-    ProtoIncrementalState::PacketSequenceState* packet_sequence_state;
-    size_t packet_sequence_state_generation;
-
-    int64_t timestamp;
-    int64_t thread_timestamp;
-    int64_t thread_instruction_count;
-    uint64_t packet_idx_;
-    TraceBlobView blob_view;
-    InlineEvent inline_event;
-  };
-
   TraceSorter(TraceProcessorContext*, int64_t window_size_ns);
 
   inline void PushTracePacket(int64_t timestamp,
diff --git a/src/trace_processor/trace_sorter_unittest.cc b/src/trace_processor/trace_sorter_unittest.cc
index 116902e..da93c61 100644
--- a/src/trace_processor/trace_sorter_unittest.cc
+++ b/src/trace_processor/trace_sorter_unittest.cc
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/trace_sorter.h"
 #include "test/gtest_and_gmock.h"
@@ -46,7 +47,7 @@
 
   void ParseFtracePacket(uint32_t cpu,
                          int64_t timestamp,
-                         TraceSorter::TimestampedTracePiece ttp) override {
+                         TimestampedTracePiece ttp) override {
     TraceBlobView& tbv = ttp.blob_view;
     MOCK_ParseFtracePacket(cpu, timestamp, tbv.data(), tbv.length());
   }
@@ -54,8 +55,7 @@
   MOCK_METHOD3(MOCK_ParseTracePacket,
                void(int64_t ts, const uint8_t* data, size_t length));
 
-  void ParseTracePacket(int64_t ts,
-                        TraceSorter::TimestampedTracePiece ttp) override {
+  void ParseTracePacket(int64_t ts, TimestampedTracePiece ttp) override {
     TraceBlobView& tbv = ttp.blob_view;
     MOCK_ParseTracePacket(ts, tbv.data(), tbv.length());
   }
diff --git a/src/trace_processor/trace_storage.cc b/src/trace_processor/trace_storage.cc
index 662c460..1b9f256 100644
--- a/src/trace_processor/trace_storage.cc
+++ b/src/trace_processor/trace_storage.cc
@@ -131,6 +131,9 @@
   if (start_ns == std::numeric_limits<int64_t>::max()) {
     return std::make_pair(0, 0);
   }
+  if (start_ns == end_ns) {
+    end_ns += 1;
+  }
   return std::make_pair(start_ns, end_ns);
 }
 
diff --git a/test/cts/heapprofd_java_test_cts.cc b/test/cts/heapprofd_java_test_cts.cc
index 3ad1316..a2f26b7 100644
--- a/test/cts/heapprofd_java_test_cts.cc
+++ b/test/cts/heapprofd_java_test_cts.cc
@@ -73,14 +73,19 @@
   ASSERT_GT(packets.size(), 0);
 
   size_t objects = 0;
-  for (const auto& packet : packets)
+  size_t roots = 0;
+  for (const auto& packet : packets) {
     objects += packet.heap_graph().objects_size();
+    roots += packet.heap_graph().roots_size();
+  }
   ASSERT_GT(objects, 0);
+  ASSERT_GT(roots, 0);
 }
 
 void AssertNoProfileContents(std::vector<protos::TracePacket> packets) {
   // If profile packets are present, they must be empty.
   for (const auto& packet : packets) {
+    ASSERT_EQ(packet.heap_graph().roots_size(), 0);
     ASSERT_EQ(packet.heap_graph().objects_size(), 0);
     ASSERT_EQ(packet.heap_graph().type_names_size(), 0);
     ASSERT_EQ(packet.heap_graph().field_names_size(), 0);
diff --git a/test/metrics/heap_profile.textproto b/test/metrics/heap_profile.textproto
index e64cb5c..831b002 100644
--- a/test/metrics/heap_profile.textproto
+++ b/test/metrics/heap_profile.textproto
@@ -14,6 +14,8 @@
 }
 packet {
   trusted_packet_sequence_id: 999
+  previous_packet_dropped: true
+  incremental_state_cleared: true
   timestamp: 10
   profile_packet {
     strings {
diff --git a/test/trace_processor/heap_graph.textproto b/test/trace_processor/heap_graph.textproto
index 64cca48..eb32c54 100644
--- a/test/trace_processor/heap_graph.textproto
+++ b/test/trace_processor/heap_graph.textproto
@@ -16,6 +16,10 @@
   trusted_packet_sequence_id: 999
   timestamp: 10
   heap_graph {
+    roots {
+      root_type: ROOT_JAVA_FRAME
+      object_ids: 0x01
+    }
     objects {
       id: 0x01
       type_id: 1
@@ -31,6 +35,8 @@
     continued: true
     index: 1
   }
+}
+packet {
   heap_graph {
     type_names {
       iid: 1
diff --git a/test/trace_processor/heap_graph_object.out b/test/trace_processor/heap_graph_object.out
index fc3b7a4..3f43952 100644
--- a/test/trace_processor/heap_graph_object.out
+++ b/test/trace_processor/heap_graph_object.out
@@ -1,3 +1,3 @@
-"id","type","upid","graph_sample_ts","object_id","self_size","reference_set_id","type_name"
-0,"heap_graph_object",0,10,1,64,0,"FactoryProducerDelegateImplActor"
-1,"heap_graph_object",0,10,2,32,1,"Foo"
+"id","type","upid","graph_sample_ts","object_id","self_size","reference_set_id","type_name","root_type"
+0,"heap_graph_object",0,10,1,64,0,"FactoryProducerDelegateImplActor","ROOT_JAVA_FRAME"
+1,"heap_graph_object",0,10,2,32,1,"Foo","[NULL]"
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index de152ea..cb99f09 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -12,11 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {LoadingManager} from '../controller/loading_manager';
-
 import {RawQueryResult, TraceProcessor} from './protos';
 import {TimeSpan} from './time';
 
+export interface LoadingTracker {
+  beginLoading(): void;
+  endLoading(): void;
+}
+
+export class NullLoadingTracker implements LoadingTracker {
+  beginLoading(): void {}
+
+  endLoading(): void {}
+}
+
 /**
  * Abstract interface of a trace proccessor.
  * This class is wrapper for multiple proto services defined in:
@@ -32,10 +41,10 @@
   abstract readonly id: string;
   private _cpus?: number[];
   private _numGpus?: number;
-  private loadingManager: LoadingManager;
+  private loadingTracker: LoadingTracker;
 
-  constructor() {
-    this.loadingManager = LoadingManager.getInstance;
+  constructor(tracker?: LoadingTracker) {
+    this.loadingTracker = tracker ? tracker : new NullLoadingTracker();
   }
 
   /**
@@ -60,9 +69,9 @@
    */
   query(sqlQuery: string): Promise<RawQueryResult> {
     const timeQueuedNs = Math.floor(performance.now() * 1e6);
-    this.loadingManager.beginLoading();
+    this.loadingTracker.beginLoading();
     return this.rpc.rawQuery({sqlQuery, timeQueuedNs}).finally(() => {
-      this.loadingManager.endLoading();
+      this.loadingTracker.endLoading();
     });
   }
 
diff --git a/ui/src/common/wasm_engine_proxy.ts b/ui/src/common/wasm_engine_proxy.ts
index 449a83b..8194894 100644
--- a/ui/src/common/wasm_engine_proxy.ts
+++ b/ui/src/common/wasm_engine_proxy.ts
@@ -13,13 +13,13 @@
 // limitations under the License.
 
 import * as protobufjs from 'protobufjs/light';
+import {Message, Method, rpc} from 'protobufjs/light';
 
 import {defer} from '../base/deferred';
 import {WasmBridgeRequest, WasmBridgeResponse} from '../engine/wasm_bridge';
 
-import {Engine} from './engine';
+import {Engine, LoadingTracker} from './engine';
 import {TraceProcessor} from './protos';
-import {Method, rpc, Message} from 'protobufjs/light';
 
 const activeWorkers = new Map<string, Worker>();
 let warmWorker: null|Worker = null;
@@ -77,8 +77,9 @@
   private nextRequestId: number;
   readonly id: string;
 
-  constructor(args: {id: string, worker: Worker}) {
-    super();
+  constructor(
+      args: {id: string, loadingTracker?: LoadingTracker, worker: Worker}) {
+    super(args.loadingTracker);
     this.nextRequestId = 0;
     this.pendingCallbacks = new Map();
     this.id = args.id;
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index 179c0ee..244c6cc 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -26,6 +26,7 @@
 } from '../common/wasm_engine_proxy';
 
 import {ControllerAny} from './controller';
+import {LoadingManager} from './loading_manager';
 
 type PublishKinds =
     'OverviewData'|'TrackData'|'Threads'|'QueryResult'|'LegacyTrace'|
@@ -94,8 +95,11 @@
 
   createEngine(): Engine {
     const id = new Date().toUTCString();
-    const portAndId = {id, worker: createWasmEngine(id)};
-    return new WasmEngineProxy(portAndId);
+    return new WasmEngineProxy({
+      id,
+      worker: createWasmEngine(id),
+      loadingTracker: LoadingManager.getInstance,
+    });
   }
 
   destroyEngine(id: string): void {
diff --git a/ui/src/controller/loading_manager.ts b/ui/src/controller/loading_manager.ts
index afaec74..64c7123 100644
--- a/ui/src/controller/loading_manager.ts
+++ b/ui/src/controller/loading_manager.ts
@@ -12,14 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {LoadingTracker} from '../common/engine';
+
 import {globals} from './globals';
 
 // Used to keep track of whether the engine is currently querying.
-export class LoadingManager {
+export class LoadingManager implements LoadingTracker {
   private static _instance: LoadingManager;
   private currentlyLoading = 0;
 
-  static get getInstance() {
+  static get getInstance(): LoadingManager {
     return this._instance || (this._instance = new this());
   }
 
@@ -43,4 +45,4 @@
   private isLoading() {
     return this.currentlyLoading > 0;
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 4e8afe9..2257307 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -116,7 +116,7 @@
   }
 
   async sliceDetails(id: number) {
-    const sqlQuery = `SELECT ts, dur, priority, end_state, utid FROM sched
+    const sqlQuery = `SELECT ts, dur, priority, end_state, utid, cpu FROM sched
     WHERE row_id = ${id}`;
     this.args.engine.query(sqlQuery).then(result => {
       // Check selection is still the same on completion of query.
@@ -128,8 +128,9 @@
         const priority = result.columns[2].longValues![0] as number;
         const endState = result.columns[3].stringValues![0];
         const utid = result.columns[4].longValues![0] as number;
+        const cpu = result.columns[5].longValues![0] as number;
         const selected: SliceDetails =
-            {ts: timeFromStart, dur, priority, endState, id, utid};
+            {ts: timeFromStart, dur, priority, endState, cpu, id, utid};
         this.schedulingDetails(ts, utid).then(wakeResult => {
           Object.assign(selected, wakeResult);
           globals.publish('SliceDetails', selected);
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index d6c83cb..5cf35ab 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -495,7 +495,8 @@
           utid === null ? undefined : utidToThreadTrack.get(utid);
       if (threadTrack === undefined &&
           (upid === null || counterUpids[upid] === undefined) &&
-          counterUtids[utid] === undefined && !threadHasSched) {
+          counterUtids[utid] === undefined && !threadHasSched &&
+          (upid === null || upid !== null && !heapUpids.has(upid))) {
         continue;
       }
 
@@ -523,11 +524,16 @@
           config: {pidForColor, upid, utid},
         });
 
+        const name = upid === null ?
+            `${threadName} ${tid}` :
+            `${
+                processName === null && heapUpids.has(upid) ?
+                    'Heap Profile for' :
+                    processName} ${pid}`;
         addTrackGroupActions.push(Actions.addTrackGroup({
           engineId: this.engineId,
           summaryTrackId,
-          name: upid === null ? `${threadName} ${tid}` :
-                                `${processName} ${pid}`,
+          name,
           id: pUuid,
           collapsed: true,
         }));
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
new file mode 100644
index 0000000..1bed6ea
--- /dev/null
+++ b/ui/src/frontend/details_panel.ts
@@ -0,0 +1,181 @@
+// 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 * as m from 'mithril';
+
+import {LogExists, LogExistsKey} from '../common/logs';
+
+import {ChromeSliceDetailsPanel} from './chrome_slice_panel';
+import {CounterDetailsPanel} from './counter_panel';
+import {DragGestureHandler} from './drag_gesture_handler';
+import {globals} from './globals';
+import {HeapProfileDetailsPanel} from './heap_profile_panel';
+import {LogPanel} from './logs_panel';
+import {NotesEditorPanel} from './notes_panel';
+import {AnyAttrsVnode, PanelContainer} from './panel_container';
+import {SliceDetailsPanel} from './slice_panel';
+import {ThreadStatePanel} from './thread_state_panel';
+
+const UP_ICON = 'keyboard_arrow_up';
+const DOWN_ICON = 'keyboard_arrow_down';
+const DRAG_HANDLE_HEIGHT_PX = 28;
+const DEFAULT_DETAILS_HEIGHT_PX = 230 + DRAG_HANDLE_HEIGHT_PX;
+
+function hasLogs(): boolean {
+  const data = globals.trackDataStore.get(LogExistsKey) as LogExists;
+  return data && data.exists;
+}
+
+interface DragHandleAttrs {
+  height: number;
+  resize: (height: number) => void;
+}
+
+class DragHandle implements m.ClassComponent<DragHandleAttrs> {
+  private dragStartHeight = 0;
+  private height = 0;
+  private resize: (height: number) => void = () => {};
+  private isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
+
+  oncreate({dom, attrs}: m.CVnodeDOM<DragHandleAttrs>) {
+    this.resize = attrs.resize;
+    this.height = attrs.height;
+    this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
+    const elem = dom as HTMLElement;
+    new DragGestureHandler(
+        elem,
+        this.onDrag.bind(this),
+        this.onDragStart.bind(this),
+        this.onDragEnd.bind(this));
+  }
+
+  onupdate({attrs}: m.CVnodeDOM<DragHandleAttrs>) {
+    this.resize = attrs.resize;
+    this.height = attrs.height;
+    this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
+  }
+
+  onDrag(_x: number, y: number) {
+    const newHeight = this.dragStartHeight + (DRAG_HANDLE_HEIGHT_PX / 2) - y;
+    this.isClosed = Math.floor(newHeight) <= DRAG_HANDLE_HEIGHT_PX;
+    this.resize(Math.floor(newHeight));
+    globals.rafScheduler.scheduleFullRedraw();
+  }
+
+  onDragStart(_x: number, _y: number) {
+    this.dragStartHeight = this.height;
+  }
+
+  onDragEnd() {}
+
+  view() {
+    const icon = this.isClosed ? UP_ICON : DOWN_ICON;
+    const title = this.isClosed ? 'Show panel' : 'Hide panel';
+    return m(
+        '.handle',
+        m('.handle-title', 'Current Selection'),
+        m('i.material-icons',
+          {
+            onclick: () => {
+              if (this.height === DRAG_HANDLE_HEIGHT_PX) {
+                this.isClosed = false;
+                this.resize(DEFAULT_DETAILS_HEIGHT_PX);
+              } else {
+                this.isClosed = true;
+                this.resize(DRAG_HANDLE_HEIGHT_PX);
+              }
+              globals.rafScheduler.scheduleFullRedraw();
+            },
+            title
+          },
+          icon));
+  }
+}
+
+export class DetailsPanel implements m.ClassComponent {
+  private detailsHeight = DRAG_HANDLE_HEIGHT_PX;
+  // Used to set details panel to default height on selection.
+  private showDetailsPanel = true;
+
+  view() {
+    const detailsPanels: AnyAttrsVnode[] = [];
+    const curSelection = globals.state.currentSelection;
+    if (curSelection) {
+      switch (curSelection.kind) {
+        case 'NOTE':
+          detailsPanels.push(m(NotesEditorPanel, {
+            key: 'notes',
+            id: curSelection.id,
+          }));
+          break;
+        case 'SLICE':
+          detailsPanels.push(m(SliceDetailsPanel, {
+            key: 'slice',
+          }));
+          break;
+        case 'COUNTER':
+          detailsPanels.push(m(CounterDetailsPanel, {
+            key: 'counter',
+          }));
+          break;
+        case 'HEAP_PROFILE':
+          detailsPanels.push(m(HeapProfileDetailsPanel, {key: 'heap_profile'}));
+          break;
+        case 'CHROME_SLICE':
+          detailsPanels.push(m(ChromeSliceDetailsPanel));
+          break;
+        case 'THREAD_STATE':
+          detailsPanels.push(m(ThreadStatePanel, {
+            key: 'thread_state',
+            ts: curSelection.ts,
+            dur: curSelection.dur,
+            utid: curSelection.utid,
+            state: curSelection.state,
+            cpu: curSelection.cpu
+          }));
+          break;
+        default:
+          break;
+      }
+    } else if (hasLogs()) {
+      detailsPanels.push(m(LogPanel, {}));
+    }
+
+    const wasShowing = this.showDetailsPanel;
+    this.showDetailsPanel = detailsPanels.length > 0;
+    // Pop up details panel on first selection.
+    if (!wasShowing && this.showDetailsPanel &&
+        this.detailsHeight === DRAG_HANDLE_HEIGHT_PX) {
+      this.detailsHeight = DEFAULT_DETAILS_HEIGHT_PX;
+    }
+
+    return m(
+        '.details-content',
+        {
+          style: {
+            height: `${this.detailsHeight}px`,
+            display: this.showDetailsPanel ? null : 'none'
+          }
+        },
+        m(DragHandle, {
+          resize: (height: number) => {
+            this.detailsHeight = Math.max(height, DRAG_HANDLE_HEIGHT_PX);
+          },
+          height: this.detailsHeight,
+        }),
+        m('.details-panel-container',
+          m(PanelContainer,
+            {doesScroll: true, panels: detailsPanels, kind: 'DETAILS'})));
+  }
+}
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index 25f7991..85d1eff 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -95,6 +95,7 @@
   visibleTracks = new Set<string>();
   prevVisibleTracks = new Set<string>();
   searchIndex = -1;
+  scrollToTrackId: undefined|string|number = undefined;
   private scrollBarWidth: undefined|number = undefined;
 
   private _omniboxState: OmniboxState = {
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index b2e7f97..032cdb5 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -28,6 +28,7 @@
   dur?: number;
   priority?: number;
   endState?: string;
+  cpu?: number;
   id?: number;
   utid?: number;
   wakeupTs?: number;
diff --git a/ui/src/frontend/heap_profile_panel.ts b/ui/src/frontend/heap_profile_panel.ts
index d909fa8..938a80b 100644
--- a/ui/src/frontend/heap_profile_panel.ts
+++ b/ui/src/frontend/heap_profile_panel.ts
@@ -29,9 +29,10 @@
 
   view() {
     const heapDumpInfo = globals.heapDumpDetails;
-    if (heapDumpInfo && heapDumpInfo.ts && heapDumpInfo.allocated &&
-        heapDumpInfo.allocatedNotFreed && heapDumpInfo.tsNs &&
-        heapDumpInfo.pid) {
+    if (heapDumpInfo && heapDumpInfo.ts !== undefined &&
+        heapDumpInfo.allocated !== undefined &&
+        heapDumpInfo.allocatedNotFreed !== undefined &&
+        heapDumpInfo.tsNs !== undefined && heapDumpInfo.pid !== undefined) {
       this.ts = heapDumpInfo.tsNs;
       this.pid = heapDumpInfo.pid;
       return m(
@@ -57,8 +58,8 @@
                  ])],
               ),
           m('.explanation',
-            'Heap profile support is in beta. To explore a heap profile,',
-            ' download and open it in ',
+            'Heap profile support is in beta. If you need missing features, ',
+            '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'),
diff --git a/ui/src/frontend/slice_panel.ts b/ui/src/frontend/slice_panel.ts
index 80f5c7b..99191fe 100644
--- a/ui/src/frontend/slice_panel.ts
+++ b/ui/src/frontend/slice_panel.ts
@@ -14,6 +14,7 @@
 
 import * as m from 'mithril';
 
+import {Actions} from '../common/actions';
 import {drawDoubleHeadedArrow} from '../common/canvas_utils';
 import {translateState} from '../common/thread_state';
 import {timeToCode} from '../common/time';
@@ -21,37 +22,46 @@
 import {globals} from './globals';
 import {Panel, PanelSize} from './panel';
 
-
 export class SliceDetailsPanel extends Panel {
   view() {
     const sliceInfo = globals.sliceDetails;
-    if (!sliceInfo.utid) return;
+    if (sliceInfo.utid === undefined) return;
     const threadInfo = globals.threads.get(sliceInfo.utid);
 
-    if (threadInfo && sliceInfo.ts && sliceInfo.dur) {
+    if (threadInfo && sliceInfo.ts !== undefined &&
+        sliceInfo.dur !== undefined) {
       return m(
           '.details-panel',
           m('.details-panel-heading', `Slice Details:`),
-          m('.details-table', [m('table', [
-              m('tr', m('th', `PID`), m('td', `${threadInfo.pid}`)),
-              m('tr',
-                m('th', `Process name`),
-                m('td', `${threadInfo.procName}`)),
-              m('tr', m('th', `TID`), m('td', `${threadInfo.tid}`)),
-              m('tr',
-                m('th', `Thread name`),
-                m('td', `${threadInfo.threadName}`)),
-              m('tr',
-                m('th', `Start time`),
-                m('td', `${timeToCode(sliceInfo.ts)}`)),
-              m('tr',
-                m('th', `Duration`),
-                m('td', `${timeToCode(sliceInfo.dur)}`)),
-              m('tr', m('th', `Prio`), m('td', `${sliceInfo.priority}`)),
-              m('tr',
-                m('th', `End State`),
-                m('td', `${translateState(sliceInfo.endState)}`))
-            ])], ));
+          m(
+              '.details-table',
+              [m('table',
+                 [
+                   m('tr',
+                     m('th', `Process`),
+                     m('td', `${threadInfo.procName} [${threadInfo.pid}]`)),
+                   m('tr',
+                     m('th', `Thread`),
+                     m('td',
+                       `${threadInfo.threadName} [${threadInfo.tid}]`,
+                       m('i.material-icons',
+                         {
+                           onclick: () => this.goToThread(),
+                           title: 'Go to thread'
+                         },
+                         'call_made'))),
+                   m('tr',
+                     m('th', `Start time`),
+                     m('td', `${timeToCode(sliceInfo.ts)}`)),
+                   m('tr',
+                     m('th', `Duration`),
+                     m('td', `${timeToCode(sliceInfo.dur)}`)),
+                   m('tr', m('th', `Prio`), m('td', `${sliceInfo.priority}`)),
+                   m('tr',
+                     m('th', `End State`),
+                     m('td', `${translateState(sliceInfo.endState)}`))
+                 ])],
+              ));
     } else {
       return m(
           '.details-panel',
@@ -62,6 +72,41 @@
     }
   }
 
+  goToThread() {
+    const sliceInfo = globals.sliceDetails;
+    if (sliceInfo.utid === undefined) return;
+    const threadInfo = globals.threads.get(sliceInfo.utid);
+
+    if (sliceInfo.id === undefined || sliceInfo.ts === undefined ||
+        sliceInfo.dur === undefined || sliceInfo.cpu === undefined ||
+        threadInfo === undefined) {
+      return;
+    }
+    globals.makeSelection(Actions.selectThreadState({
+      utid: threadInfo.utid,
+      ts: sliceInfo.ts + globals.state.traceTime.startSec,
+      dur: sliceInfo.dur,
+      state: 'Running',
+      cpu: sliceInfo.cpu,
+    }));
+    let trackId: string|number|undefined;
+    let trackGroupId;
+    for (const track of Object.values(globals.state.tracks)) {
+      if (track.kind === 'ThreadStateTrack' &&
+          (track.config as {utid: number}).utid === threadInfo.utid) {
+        trackGroupId = track.trackGroup;
+        trackId = track.id;
+      }
+    }
+    // After the track exists in the dom, it will be scrolled to.
+    globals.frontendLocalState.scrollToTrackId = trackId;
+
+    if (trackGroupId && globals.state.trackGroups[trackGroupId].collapsed) {
+      globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId}));
+    }
+  }
+
+
   renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
     const details = globals.sliceDetails;
     // Show expanded details on the scheduling of the currently selected slice.
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index d60f4b4..1af0ef0 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -20,6 +20,7 @@
 import {globals} from './globals';
 import {drawGridLines} from './gridline_helper';
 import {Panel, PanelSize} from './panel';
+import {verticalScrollToTrack} from './scroll_helper';
 import {Track} from './track';
 import {TRACK_SHELL_WIDTH} from './track_constants';
 import {trackRegistry} from './track_registry';
@@ -187,6 +188,13 @@
           m(TrackContent, {track: attrs.track})
         ]);
   }
+
+  onupdate({attrs}: m.CVnode<TrackComponentAttrs>) {
+    if (globals.frontendLocalState.scrollToTrackId === attrs.trackState.id) {
+      verticalScrollToTrack(attrs.trackState.id);
+      globals.frontendLocalState.scrollToTrackId = undefined;
+    }
+  }
 }
 
 export interface TrackButtonAttrs {
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 53430c4..37c0279 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -15,25 +15,18 @@
 import * as m from 'mithril';
 
 import {Actions} from '../common/actions';
-import {LogExists, LogExistsKey} from '../common/logs';
 import {QueryResponse} from '../common/queries';
 import {TimeSpan} from '../common/time';
 
-import {ChromeSliceDetailsPanel} from './chrome_slice_panel';
 import {copyToClipboard} from './clipboard';
-import {CounterDetailsPanel} from './counter_panel';
-import {DragGestureHandler} from './drag_gesture_handler';
+import {DetailsPanel} from './details_panel';
 import {globals} from './globals';
-import {HeapProfileDetailsPanel} from './heap_profile_panel';
-import {LogPanel} from './logs_panel';
-import {NotesEditorPanel, NotesPanel} from './notes_panel';
+import {NotesPanel} from './notes_panel';
 import {OverviewTimelinePanel} from './overview_timeline_panel';
 import {createPage} from './pages';
 import {PanAndZoomHandler} from './pan_and_zoom_handler';
 import {Panel} from './panel';
 import {AnyAttrsVnode, PanelContainer} from './panel_container';
-import {SliceDetailsPanel} from './slice_panel';
-import {ThreadStatePanel} from './thread_state_panel';
 import {TickmarkPanel} from './tickmark_panel';
 import {TimeAxisPanel} from './time_axis_panel';
 import {computeZoom} from './time_scale';
@@ -43,17 +36,8 @@
 import {TrackPanel} from './track_panel';
 import {VideoPanel} from './video_panel';
 
-const DRAG_HANDLE_HEIGHT_PX = 28;
-const DEFAULT_DETAILS_HEIGHT_PX = 230 + DRAG_HANDLE_HEIGHT_PX;
-const UP_ICON = 'keyboard_arrow_up';
-const DOWN_ICON = 'keyboard_arrow_down';
 const SIDEBAR_WIDTH = 256;
 
-function hasLogs(): boolean {
-  const data = globals.trackDataStore.get(LogExistsKey) as LogExists;
-  return data && data.exists;
-}
-
 class QueryTable extends Panel {
   view() {
     const resp = globals.queryResults.get('command') as QueryResponse;
@@ -113,71 +97,6 @@
   renderCanvas() {}
 }
 
-interface DragHandleAttrs {
-  height: number;
-  resize: (height: number) => void;
-}
-
-class DragHandle implements m.ClassComponent<DragHandleAttrs> {
-  private dragStartHeight = 0;
-  private height = 0;
-  private resize: (height: number) => void = () => {};
-  private isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
-
-  oncreate({dom, attrs}: m.CVnodeDOM<DragHandleAttrs>) {
-    this.resize = attrs.resize;
-    this.height = attrs.height;
-    this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
-    const elem = dom as HTMLElement;
-    new DragGestureHandler(
-        elem,
-        this.onDrag.bind(this),
-        this.onDragStart.bind(this),
-        this.onDragEnd.bind(this));
-  }
-
-  onupdate({attrs}: m.CVnodeDOM<DragHandleAttrs>) {
-    this.resize = attrs.resize;
-    this.height = attrs.height;
-    this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
-  }
-
-  onDrag(_x: number, y: number) {
-    const newHeight = this.dragStartHeight + (DRAG_HANDLE_HEIGHT_PX / 2) - y;
-    this.isClosed = Math.floor(newHeight) <= DRAG_HANDLE_HEIGHT_PX;
-    this.resize(Math.floor(newHeight));
-    globals.rafScheduler.scheduleFullRedraw();
-  }
-
-  onDragStart(_x: number, _y: number) {
-    this.dragStartHeight = this.height;
-  }
-
-  onDragEnd() {}
-
-  view() {
-    const icon = this.isClosed ? UP_ICON : DOWN_ICON;
-    const title = this.isClosed ? 'Show panel' : 'Hide panel';
-    return m(
-        '.handle',
-        m('.handle-title', 'Current Selection'),
-        m('i.material-icons',
-          {
-            onclick: () => {
-              if (this.height === DRAG_HANDLE_HEIGHT_PX) {
-                this.isClosed = false;
-                this.resize(DEFAULT_DETAILS_HEIGHT_PX);
-              } else {
-                this.isClosed = true;
-                this.resize(DRAG_HANDLE_HEIGHT_PX);
-              }
-              globals.rafScheduler.scheduleFullRedraw();
-            },
-            title
-          },
-          icon));
-  }
-}
 
 // Checks if the mousePos is within 3px of the start or end of the
 // current selected time range.
@@ -206,9 +125,6 @@
 class TraceViewer implements m.ClassComponent {
   private onResize: () => void = () => {};
   private zoomContent?: PanAndZoomHandler;
-  private detailsHeight = DRAG_HANDLE_HEIGHT_PX;
-  // Used to set details panel to default height on selection.
-  private showDetailsPanel = true;
   // Used to prevent global deselection if a pan/drag select occurred.
   private keepCurrentSelection = false;
 
@@ -327,57 +243,6 @@
     }
     scrollingPanels.unshift(m(QueryTable, {key: 'query'}));
 
-    const detailsPanels: AnyAttrsVnode[] = [];
-    const curSelection = globals.state.currentSelection;
-    if (curSelection) {
-      switch (curSelection.kind) {
-        case 'NOTE':
-          detailsPanels.push(m(NotesEditorPanel, {
-            key: 'notes',
-            id: curSelection.id,
-          }));
-          break;
-        case 'SLICE':
-          detailsPanels.push(m(SliceDetailsPanel, {
-            key: 'slice',
-          }));
-          break;
-        case 'COUNTER':
-          detailsPanels.push(m(CounterDetailsPanel, {
-            key: 'counter',
-          }));
-          break;
-        case 'HEAP_PROFILE':
-          detailsPanels.push(m(HeapProfileDetailsPanel, {key: 'heap_profile'}));
-          break;
-        case 'CHROME_SLICE':
-          detailsPanels.push(m(ChromeSliceDetailsPanel));
-          break;
-        case 'THREAD_STATE':
-          detailsPanels.push(m(ThreadStatePanel, {
-            key: 'thread_state',
-            ts: curSelection.ts,
-            dur: curSelection.dur,
-            utid: curSelection.utid,
-            state: curSelection.state,
-            cpu: curSelection.cpu
-          }));
-          break;
-        default:
-          break;
-      }
-    } else if (hasLogs()) {
-      detailsPanels.push(m(LogPanel, {}));
-    }
-
-    const wasShowing = this.showDetailsPanel;
-    this.showDetailsPanel = detailsPanels.length > 0;
-    // Pop up details panel on first selection.
-    if (!wasShowing && this.showDetailsPanel &&
-        this.detailsHeight === DRAG_HANDLE_HEIGHT_PX) {
-      this.detailsHeight = DEFAULT_DETAILS_HEIGHT_PX;
-    }
-
     return m(
         '.page',
         m('.split-panel',
@@ -414,22 +279,7 @@
             (globals.state.videoEnabled && globals.state.video != null) ?
                 m(VideoPanel) :
                 null)),
-        m('.details-content',
-          {
-            style: {
-              height: `${this.detailsHeight}px`,
-              display: this.showDetailsPanel ? null : 'none'
-            }
-          },
-          m(DragHandle, {
-            resize: (height: number) => {
-              this.detailsHeight = Math.max(height, DRAG_HANDLE_HEIGHT_PX);
-            },
-            height: this.detailsHeight,
-          }),
-          m('.details-panel-container',
-            m(PanelContainer,
-              {doesScroll: true, panels: detailsPanels, kind: 'DETAILS'}))));
+        m(DetailsPanel));
   }
 }
 
diff --git a/ui/src/tracks/process_summary/controller.ts b/ui/src/tracks/process_summary/controller.ts
index a7a73aa..65835c1 100644
--- a/ui/src/tracks/process_summary/controller.ts
+++ b/ui/src/tracks/process_summary/controller.ts
@@ -64,7 +64,8 @@
     }
 
     // |resolution| is in s/px we want # ns for 10px window:
-    const bucketSizeNs = Math.round(resolution * 10 * 1e9);
+    // Max value with 1 so we don't end up with resolution 0.
+    const bucketSizeNs = Math.max(1, Math.round(resolution * 10 * 1e9));
     const windowStartNs = Math.floor(startNs / bucketSizeNs) * bucketSizeNs;
     const windowDurNs = Math.max(1, endNs - windowStartNs);