Merge "trace_processor: allow selecting rowmaps with smaller rowmaps"
diff --git a/bazel/rules.bzl b/bazel/rules.bzl
index 7fca94d..498f34a 100644
--- a/bazel/rules.bzl
+++ b/bazel/rules.bzl
@@ -22,7 +22,9 @@
 def default_cc_args():
     return {
         "deps": PERFETTO_CONFIG.deps.build_config,
-        "copts": [],
+        "copts": [
+            "-Wno-pragma-system-header-outside-header",
+        ],
         "includes": ["include"],
         "linkopts": select({
             "@perfetto//bazel:os_linux": ["-ldl", "-lrt", "-lpthread"],
diff --git a/include/perfetto/tracing/event_context.h b/include/perfetto/tracing/event_context.h
index da1607b..fe9c4bb 100644
--- a/include/perfetto/tracing/event_context.h
+++ b/include/perfetto/tracing/event_context.h
@@ -35,9 +35,15 @@
 //                       dbg->set_int_value(1234);
 //                     });
 //
-class EventContext {
+class PERFETTO_EXPORT EventContext {
  public:
   EventContext(EventContext&&) = default;
+
+  // For Chromium during the transition phase to the client library.
+  // TODO(eseckler): Remove once Chromium has switched to client lib entirely.
+  explicit EventContext(protos::pbzero::TrackEvent* event)
+      : event_(event), incremental_state_(nullptr) {}
+
   ~EventContext();
 
   protos::pbzero::TrackEvent* event() const { return event_; }
diff --git a/src/trace_processor/containers/bit_vector.h b/src/trace_processor/containers/bit_vector.h
index 58b9e23..5ef0f74 100644
--- a/src/trace_processor/containers/bit_vector.h
+++ b/src/trace_processor/containers/bit_vector.h
@@ -107,8 +107,8 @@
     PERFETTO_DCHECK(it != counts_.begin());
 
     // Go back one block to find the block which has the bit we are looking for.
-    uint16_t block_idx =
-        static_cast<uint16_t>(std::distance(counts_.begin(), it) - 1);
+    uint32_t block_idx =
+        static_cast<uint32_t>(std::distance(counts_.begin(), it) - 1);
 
     // Figure out how many set bits forward we are looking inside the block
     // by taking away the number of bits at the start of the block from n.
diff --git a/src/trace_processor/heap_profile_tracker.cc b/src/trace_processor/heap_profile_tracker.cc
index a666dad..4bf0756 100644
--- a/src/trace_processor/heap_profile_tracker.cc
+++ b/src/trace_processor/heap_profile_tracker.cc
@@ -45,23 +45,23 @@
     const SourceAllocation& alloc,
     const StackProfileTracker::InternLookup* intern_lookup) {
   SequenceState& sequence_state = sequence_state_[seq_id];
-  auto maybe_callstack_id =
-      stack_profile_tracker->FindCallstack(alloc.callstack_id, intern_lookup);
+  auto maybe_callstack_id = stack_profile_tracker->FindOrInsertCallstack(
+      alloc.callstack_id, intern_lookup);
   if (!maybe_callstack_id)
     return;
 
-  int64_t callstack_id = *maybe_callstack_id;
+  CallsiteId callstack_id = *maybe_callstack_id;
 
   UniquePid upid = context_->process_tracker->GetOrCreateProcess(
       static_cast<uint32_t>(alloc.pid));
 
   tables::HeapProfileAllocationTable::Row alloc_row{
-      alloc.timestamp, upid, callstack_id,
+      alloc.timestamp, upid, callstack_id.value,
       static_cast<int64_t>(alloc.alloc_count),
       static_cast<int64_t>(alloc.self_allocated)};
 
   tables::HeapProfileAllocationTable::Row free_row{
-      alloc.timestamp, upid, callstack_id,
+      alloc.timestamp, upid, callstack_id.value,
       -static_cast<int64_t>(alloc.free_count),
       -static_cast<int64_t>(alloc.self_freed)};
 
diff --git a/src/trace_processor/heap_profile_tracker.h b/src/trace_processor/heap_profile_tracker.h
index 1d121aa..93a2440 100644
--- a/src/trace_processor/heap_profile_tracker.h
+++ b/src/trace_processor/heap_profile_tracker.h
@@ -77,10 +77,10 @@
   struct SequenceState {
     std::vector<SourceAllocation> pending_allocs;
 
-    std::unordered_map<std::pair<UniquePid, int64_t>,
+    std::unordered_map<std::pair<UniquePid, CallsiteId>,
                        tables::HeapProfileAllocationTable::Row>
         prev_alloc;
-    std::unordered_map<std::pair<UniquePid, int64_t>,
+    std::unordered_map<std::pair<UniquePid, CallsiteId>,
                        tables::HeapProfileAllocationTable::Row>
         prev_free;
 
diff --git a/src/trace_processor/heap_profile_tracker_unittest.cc b/src/trace_processor/heap_profile_tracker_unittest.cc
index 91c3f93..d9d3e1d 100644
--- a/src/trace_processor/heap_profile_tracker_unittest.cc
+++ b/src/trace_processor/heap_profile_tracker_unittest.cc
@@ -181,11 +181,11 @@
 int64_t FindCallstack(const TraceStorage& storage,
                       int64_t depth,
                       int64_t parent,
-                      int64_t frame_id) {
+                      FrameId frame_id) {
   const auto& callsites = storage.stack_profile_callsite_table();
   for (uint32_t i = 0; i < callsites.row_count(); ++i) {
     if (callsites.depth()[i] == depth && callsites.parent_id()[i] == parent &&
-        callsites.frame_id()[i] == frame_id) {
+        callsites.frame_id()[i] == frame_id.value) {
       return static_cast<int64_t>(i);
     }
   }
@@ -327,7 +327,6 @@
     const StackProfileTracker::SourceCallstack& callstack = callstacks[i];
     for (size_t depth = 0; depth < callstack.size(); ++depth) {
       auto frame_id = spt->GetDatabaseFrameIdForTesting(callstack[depth]);
-      ASSERT_NE(frame_id, -1);
       int64_t self = FindCallstack(
           *context.storage, static_cast<int64_t>(depth), parent, frame_id);
       ASSERT_NE(self, -1);
diff --git a/src/trace_processor/importers/proto/packet_sequence_state.h b/src/trace_processor/importers/proto/packet_sequence_state.h
index 903f99d..81ec6e0 100644
--- a/src/trace_processor/importers/proto/packet_sequence_state.h
+++ b/src/trace_processor/importers/proto/packet_sequence_state.h
@@ -47,7 +47,7 @@
  public:
   InternedMessageView(TraceBlobView msg) : message_(std::move(msg)) {}
 
-  InternedMessageView(InternedMessageView&&) noexcept = default;
+  InternedMessageView(InternedMessageView&&) = default;
   InternedMessageView& operator=(InternedMessageView&&) = default;
 
   // Allow copy by cloning the TraceBlobView. This is required for
diff --git a/src/trace_processor/importers/proto/proto_trace_parser.cc b/src/trace_processor/importers/proto/proto_trace_parser.cc
index b2b852f..4c5566d 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser.cc
@@ -445,15 +445,15 @@
       break;
     }
 
-    auto maybe_callstack_id =
-        stack_profile_tracker.FindCallstack(*callstack_it, &intern_lookup);
+    auto maybe_callstack_id = stack_profile_tracker.FindOrInsertCallstack(
+        *callstack_it, &intern_lookup);
     if (!maybe_callstack_id) {
       context_->storage->IncrementStats(stats::stackprofile_parser_error);
       PERFETTO_ELOG("StreamingProfilePacket referencing invalid callstack!");
       continue;
     }
 
-    int64_t callstack_id = *maybe_callstack_id;
+    uint32_t callstack_id = maybe_callstack_id->value;
 
     tables::CpuProfileStackSampleTable::Row sample_row{
         sequence_state->state()->IncrementAndGetTrackEventTimeNs(*timestamp_it *
@@ -631,10 +631,10 @@
   protos::pbzero::ModuleSymbols::Decoder module_symbols(blob.data, blob.size);
   std::string hex_build_id = base::ToHex(module_symbols.build_id().data,
                                          module_symbols.build_id().size);
-  auto mapping_rows = context_->storage->FindMappingRow(
+  auto mapping_ids = context_->storage->FindMappingRow(
       context_->storage->InternString(module_symbols.path()),
       context_->storage->InternString(base::StringView(hex_build_id)));
-  if (mapping_rows.empty()) {
+  if (mapping_ids.empty()) {
     context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
     return;
   }
@@ -643,16 +643,14 @@
 
     uint32_t symbol_set_id = context_->storage->symbol_table().row_count();
     bool frame_found = false;
-    for (int64_t mapping_row : mapping_rows) {
-      std::vector<int64_t> frame_rows = context_->storage->FindFrameRow(
-          static_cast<size_t>(mapping_row), address_symbols.address());
+    for (MappingId mapping_id : mapping_ids) {
+      std::vector<FrameId> frame_ids = context_->storage->FindFrameIds(
+          mapping_id, address_symbols.address());
 
-      for (const int64_t frame_row : frame_rows) {
-        PERFETTO_DCHECK(frame_row >= 0);
-
-        uint32_t row_idx = static_cast<uint32_t>(frame_row);
+      for (const FrameId frame_id : frame_ids) {
         auto* frames = context_->storage->mutable_stack_profile_frame_table();
-        frames->mutable_symbol_set_id()->Set(row_idx, symbol_set_id);
+        uint32_t frame_row = *frames->id().IndexOf(frame_id);
+        frames->mutable_symbol_set_id()->Set(frame_row, symbol_set_id);
         frame_found = true;
       }
     }
diff --git a/src/trace_processor/importers/systrace/systrace_trace_parser.cc b/src/trace_processor/importers/systrace/systrace_trace_parser.cc
index 2ffaac4..d23581c 100644
--- a/src/trace_processor/importers/systrace/systrace_trace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_trace_parser.cc
@@ -27,6 +27,7 @@
 #include "src/trace_processor/track_tracker.h"
 
 #include <inttypes.h>
+#include <cctype>
 #include <string>
 #include <unordered_map>
 
@@ -37,9 +38,9 @@
 std::string SubstrTrim(const std::string& input) {
   std::string s = input;
   s.erase(s.begin(), std::find_if(s.begin(), s.end(),
-                                  [](int ch) { return !std::isspace(ch); }));
+                                  [](char ch) { return !std::isspace(ch); }));
   s.erase(std::find_if(s.rbegin(), s.rend(),
-                       [](int ch) { return !std::isspace(ch); })
+                       [](char ch) { return !std::isspace(ch); })
               .base(),
           s.end());
   return s;
diff --git a/src/trace_processor/stack_profile_tracker.cc b/src/trace_processor/stack_profile_tracker.cc
index b4f8f9b..63443f4 100644
--- a/src/trace_processor/stack_profile_tracker.cc
+++ b/src/trace_processor/stack_profile_tracker.cc
@@ -43,14 +43,14 @@
   string_map_.emplace(id, str.ToStdString());
 }
 
-base::Optional<int64_t> StackProfileTracker::AddMapping(
+base::Optional<MappingId> StackProfileTracker::AddMapping(
     SourceMappingId id,
     const SourceMapping& mapping,
     const InternLookup* intern_lookup) {
   std::string path;
   for (SourceStringId str_id : mapping.name_ids) {
-    auto opt_str =
-        FindString(str_id, intern_lookup, InternedStringType::kMappingPath);
+    auto opt_str = FindOrInsertString(str_id, intern_lookup,
+                                      InternedStringType::kMappingPath);
     if (!opt_str)
       break;
     path += "/" + *opt_str;
@@ -84,42 +84,40 @@
 
   tables::StackProfileMappingTable* mappings =
       context_->storage->mutable_stack_profile_mapping_table();
-  int64_t cur_row = -1;
+  base::Optional<MappingId> cur_id;
   auto it = mapping_idx_.find(row);
   if (it != mapping_idx_.end()) {
-    cur_row = it->second;
+    cur_id = it->second;
   } else {
-    std::vector<int64_t> db_mappings =
+    std::vector<MappingId> db_mappings =
         context_->storage->FindMappingRow(row.name, row.build_id);
-    for (const int64_t preexisting_mapping : db_mappings) {
-      PERFETTO_DCHECK(preexisting_mapping >= 0);
-      uint32_t preexisting_row_id = static_cast<uint32_t>(preexisting_mapping);
-      tables::StackProfileMappingTable::Row preexisting_row{
-          mappings->build_id()[preexisting_row_id],
-          mappings->exact_offset()[preexisting_row_id],
-          mappings->start_offset()[preexisting_row_id],
-          mappings->start()[preexisting_row_id],
-          mappings->end()[preexisting_row_id],
-          mappings->load_bias()[preexisting_row_id],
-          mappings->name()[preexisting_row_id]};
+    for (const MappingId preexisting_mapping : db_mappings) {
+      uint32_t preexisting_row = *mappings->id().IndexOf(preexisting_mapping);
+      tables::StackProfileMappingTable::Row preexisting_data{
+          mappings->build_id()[preexisting_row],
+          mappings->exact_offset()[preexisting_row],
+          mappings->start_offset()[preexisting_row],
+          mappings->start()[preexisting_row],
+          mappings->end()[preexisting_row],
+          mappings->load_bias()[preexisting_row],
+          mappings->name()[preexisting_row]};
 
-      if (row == preexisting_row) {
-        cur_row = preexisting_mapping;
+      if (row == preexisting_data) {
+        cur_id = preexisting_mapping;
       }
     }
-    if (cur_row == -1) {
+    if (!cur_id) {
       MappingId mapping_id = mappings->Insert(row);
-      uint32_t mapping_row = *mappings->id().IndexOf(mapping_id);
-      context_->storage->InsertMappingRow(row.name, row.build_id, mapping_row);
-      cur_row = mapping_row;
+      context_->storage->InsertMappingId(row.name, row.build_id, mapping_id);
+      cur_id = mapping_id;
     }
-    mapping_idx_.emplace(row, cur_row);
+    mapping_idx_.emplace(row, *cur_id);
   }
-  mappings_.emplace(id, cur_row);
-  return cur_row;
+  mapping_ids_.emplace(id, *cur_id);
+  return cur_id;
 }
 
-base::Optional<int64_t> StackProfileTracker::AddFrame(
+base::Optional<FrameId> StackProfileTracker::AddFrame(
     SourceFrameId id,
     const SourceFrame& frame,
     const InternLookup* intern_lookup) {
@@ -132,93 +130,92 @@
   }
   const StringId& str_id = opt_str_id.value();
 
-  auto maybe_mapping = FindMapping(frame.mapping_id, intern_lookup);
+  auto maybe_mapping = FindOrInsertMapping(frame.mapping_id, intern_lookup);
   if (!maybe_mapping) {
     context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
     PERFETTO_ELOG("Invalid mapping for frame %" PRIu64, id);
     return base::nullopt;
   }
-  int64_t mapping_row = *maybe_mapping;
+  MappingId mapping_id = *maybe_mapping;
 
-  tables::StackProfileFrameTable::Row row{str_id, mapping_row,
+  tables::StackProfileFrameTable::Row row{str_id, mapping_id.value,
                                           static_cast<int64_t>(frame.rel_pc)};
 
   auto* frames = context_->storage->mutable_stack_profile_frame_table();
 
-  int64_t cur_row = -1;
+  base::Optional<FrameId> cur_id;
   auto it = frame_idx_.find(row);
   if (it != frame_idx_.end()) {
-    cur_row = it->second;
+    cur_id = it->second;
   } else {
-    std::vector<int64_t> db_frames = context_->storage->FindFrameRow(
-        static_cast<size_t>(mapping_row), frame.rel_pc);
-    for (const int64_t preexisting_frame : db_frames) {
-      PERFETTO_DCHECK(preexisting_frame >= 0);
-      uint32_t preexisting_row_id = static_cast<uint32_t>(preexisting_frame);
+    std::vector<FrameId> db_frames =
+        context_->storage->FindFrameIds(mapping_id, frame.rel_pc);
+    for (const FrameId preexisting_frame : db_frames) {
+      uint32_t preexisting_row_id = preexisting_frame.value;
       tables::StackProfileFrameTable::Row preexisting_row{
           frames->name()[preexisting_row_id],
           frames->mapping()[preexisting_row_id],
           frames->rel_pc()[preexisting_row_id]};
 
       if (row == preexisting_row) {
-        cur_row = preexisting_frame;
+        cur_id = preexisting_frame;
       }
     }
-    if (cur_row == -1) {
-      auto new_id = frames->Insert(row);
-      cur_row = *frames->id().IndexOf(new_id);
-      context_->storage->InsertFrameRow(static_cast<size_t>(row.mapping),
-                                        static_cast<uint64_t>(row.rel_pc),
-                                        static_cast<uint32_t>(cur_row));
+    if (!cur_id) {
+      cur_id = frames->Insert(row);
+      context_->storage->InsertFrameRow(
+          mapping_id, static_cast<uint64_t>(row.rel_pc), *cur_id);
     }
-    frame_idx_.emplace(row, cur_row);
+    frame_idx_.emplace(row, *cur_id);
   }
-  frames_.emplace(id, cur_row);
-  return cur_row;
+  frame_ids_.emplace(id, *cur_id);
+  return cur_id;
 }
 
-base::Optional<int64_t> StackProfileTracker::AddCallstack(
+base::Optional<CallsiteId> StackProfileTracker::AddCallstack(
     SourceCallstackId id,
     const SourceCallstack& frame_ids,
     const InternLookup* intern_lookup) {
   // TODO(fmayer): This should be NULL.
-  int64_t parent_id = -1;
+  base::Optional<CallsiteId> parent_id;
   for (size_t depth = 0; depth < frame_ids.size(); ++depth) {
-    SourceFrameId frame_id = frame_ids[depth];
-    auto maybe_frame_row = FindFrame(frame_id, intern_lookup);
-    if (!maybe_frame_row) {
+    auto maybe_frame_id = FindOrInsertFrame(frame_ids[depth], intern_lookup);
+    if (!maybe_frame_id) {
       context_->storage->IncrementStats(stats::stackprofile_invalid_frame_id);
       PERFETTO_ELOG("Unknown frame in callstack; ignoring.");
       return base::nullopt;
     }
-    int64_t frame_row = *maybe_frame_row;
+    FrameId frame_id = *maybe_frame_id;
 
+    // TODO(fmayer): Store roots as having null parent_id instead of -1.
+    int64_t db_parent_id = -1;
+    if (parent_id)
+      db_parent_id = parent_id->value;
     tables::StackProfileCallsiteTable::Row row{static_cast<int64_t>(depth),
-                                               parent_id, frame_row};
+                                               db_parent_id, frame_id.value};
 
-    int64_t self_id;
+    CallsiteId self_id;
     auto callsite_it = callsite_idx_.find(row);
     if (callsite_it != callsite_idx_.end()) {
       self_id = callsite_it->second;
     } else {
       auto* callsite =
           context_->storage->mutable_stack_profile_callsite_table();
-      auto callsite_id = callsite->Insert(row);
-      self_id = callsite_id.value;
+      self_id = callsite->Insert(row);
       callsite_idx_.emplace(row, self_id);
     }
     parent_id = self_id;
   }
-  callstacks_.emplace(id, parent_id);
+  callstack_ids_.emplace(id, *parent_id);
   return parent_id;
 }
 
-int64_t StackProfileTracker::GetDatabaseFrameIdForTesting(
+FrameId StackProfileTracker::GetDatabaseFrameIdForTesting(
     SourceFrameId frame_id) {
-  auto it = frames_.find(frame_id);
-  if (it == frames_.end()) {
+  auto it = frame_ids_.find(frame_id);
+  if (it == frame_ids_.end()) {
     PERFETTO_DLOG("Invalid frame.");
-    return -1;
+    return {};
   }
   return it->second;
 }
@@ -230,14 +227,14 @@
   if (id == 0)
     return GetEmptyStringId();
 
-  auto opt_str = FindString(id, intern_lookup, type);
+  auto opt_str = FindOrInsertString(id, intern_lookup, type);
   if (!opt_str)
     return GetEmptyStringId();
 
   return context_->storage->InternString(base::StringView(*opt_str));
 }
 
-base::Optional<std::string> StackProfileTracker::FindString(
+base::Optional<std::string> StackProfileTracker::FindOrInsertString(
     SourceStringId id,
     const InternLookup* intern_lookup,
     StackProfileTracker::InternedStringType type) {
@@ -262,12 +259,12 @@
   return it->second;
 }
 
-base::Optional<int64_t> StackProfileTracker::FindMapping(
+base::Optional<MappingId> StackProfileTracker::FindOrInsertMapping(
     SourceMappingId mapping_id,
     const InternLookup* intern_lookup) {
-  base::Optional<int64_t> res;
-  auto it = mappings_.find(mapping_id);
-  if (it == mappings_.end()) {
+  base::Optional<MappingId> res;
+  auto it = mapping_ids_.find(mapping_id);
+  if (it == mapping_ids_.end()) {
     if (intern_lookup) {
       auto interned_mapping = intern_lookup->GetMapping(mapping_id);
       if (interned_mapping) {
@@ -277,19 +274,19 @@
     }
     context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
     PERFETTO_ELOG("Unknown mapping %" PRIu64 " : %zu", mapping_id,
-                  mappings_.size());
+                  mapping_ids_.size());
     return res;
   }
   res = it->second;
   return res;
 }
 
-base::Optional<int64_t> StackProfileTracker::FindFrame(
+base::Optional<FrameId> StackProfileTracker::FindOrInsertFrame(
     SourceFrameId frame_id,
     const InternLookup* intern_lookup) {
-  base::Optional<int64_t> res;
-  auto it = frames_.find(frame_id);
-  if (it == frames_.end()) {
+  base::Optional<FrameId> res;
+  auto it = frame_ids_.find(frame_id);
+  if (it == frame_ids_.end()) {
     if (intern_lookup) {
       auto interned_frame = intern_lookup->GetFrame(frame_id);
       if (interned_frame) {
@@ -298,19 +295,20 @@
       }
     }
     context_->storage->IncrementStats(stats::stackprofile_invalid_frame_id);
-    PERFETTO_DLOG("Unknown frame %" PRIu64 " : %zu", frame_id, frames_.size());
+    PERFETTO_DLOG("Unknown frame %" PRIu64 " : %zu", frame_id,
+                  frame_ids_.size());
     return res;
   }
   res = it->second;
   return res;
 }
 
-base::Optional<int64_t> StackProfileTracker::FindCallstack(
+base::Optional<CallsiteId> StackProfileTracker::FindOrInsertCallstack(
     SourceCallstackId callstack_id,
     const InternLookup* intern_lookup) {
-  base::Optional<int64_t> res;
-  auto it = callstacks_.find(callstack_id);
-  if (it == callstacks_.end()) {
+  base::Optional<CallsiteId> res;
+  auto it = callstack_ids_.find(callstack_id);
+  if (it == callstack_ids_.end()) {
     auto interned_callstack = intern_lookup->GetCallstack(callstack_id);
     if (interned_callstack) {
       res = AddCallstack(callstack_id, *interned_callstack, intern_lookup);
@@ -318,7 +316,7 @@
     }
     context_->storage->IncrementStats(stats::stackprofile_invalid_callstack_id);
     PERFETTO_DLOG("Unknown callstack %" PRIu64 " : %zu", callstack_id,
-                  callstacks_.size());
+                  callstack_ids_.size());
     return res;
   }
   res = it->second;
@@ -327,9 +325,9 @@
 
 void StackProfileTracker::ClearIndices() {
   string_map_.clear();
-  mappings_.clear();
-  callstacks_.clear();
-  frames_.clear();
+  mapping_ids_.clear();
+  callstack_ids_.clear();
+  frame_ids_.clear();
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/stack_profile_tracker.h b/src/trace_processor/stack_profile_tracker.h
index 7862809..575715c 100644
--- a/src/trace_processor/stack_profile_tracker.h
+++ b/src/trace_processor/stack_profile_tracker.h
@@ -39,6 +39,41 @@
 };
 
 template <>
+struct hash<std::pair<uint32_t, perfetto::trace_processor::CallsiteId>> {
+  using argument_type =
+      std::pair<uint32_t, perfetto::trace_processor::CallsiteId>;
+  using result_type = size_t;
+
+  result_type operator()(const argument_type& p) const {
+    return std::hash<uint32_t>{}(p.first) ^
+           std::hash<uint32_t>{}(p.second.value);
+  }
+};
+
+template <>
+struct hash<std::pair<uint32_t, perfetto::trace_processor::MappingId>> {
+  using argument_type =
+      std::pair<uint32_t, perfetto::trace_processor::MappingId>;
+  using result_type = size_t;
+
+  result_type operator()(const argument_type& p) const {
+    return std::hash<uint32_t>{}(p.first) ^
+           std::hash<uint32_t>{}(p.second.value);
+  }
+};
+
+template <>
+struct hash<std::pair<uint32_t, perfetto::trace_processor::FrameId>> {
+  using argument_type = std::pair<uint32_t, perfetto::trace_processor::FrameId>;
+  using result_type = size_t;
+
+  result_type operator()(const argument_type& p) const {
+    return std::hash<uint32_t>{}(p.first) ^
+           std::hash<uint32_t>{}(p.second.value);
+  }
+};
+
+template <>
 struct hash<std::vector<uint64_t>> {
   using argument_type = std::vector<uint64_t>;
   using result_type = size_t;
@@ -119,19 +154,19 @@
   ~StackProfileTracker();
 
   void AddString(SourceStringId, base::StringView);
-  base::Optional<int64_t> AddMapping(
+  base::Optional<MappingId> AddMapping(
       SourceMappingId,
       const SourceMapping&,
       const InternLookup* intern_lookup = nullptr);
-  base::Optional<int64_t> AddFrame(SourceFrameId,
+  base::Optional<FrameId> AddFrame(SourceFrameId,
                                    const SourceFrame&,
                                    const InternLookup* intern_lookup = nullptr);
-  base::Optional<int64_t> AddCallstack(
+  base::Optional<CallsiteId> AddCallstack(
       SourceCallstackId,
       const SourceCallstack&,
       const InternLookup* intern_lookup = nullptr);
 
-  int64_t GetDatabaseFrameIdForTesting(SourceFrameId);
+  FrameId GetDatabaseFrameIdForTesting(SourceFrameId);
 
   // Gets the row number of string / mapping / frame / callstack previously
   // added through AddString / AddMapping/ AddFrame / AddCallstack.
@@ -147,15 +182,18 @@
       SourceStringId,
       const InternLookup* intern_lookup,
       InternedStringType type);
-  base::Optional<std::string> FindString(SourceStringId,
-                                         const InternLookup* intern_lookup,
-                                         InternedStringType type);
-  base::Optional<int64_t> FindMapping(SourceMappingId,
-                                      const InternLookup* intern_lookup);
-  base::Optional<int64_t> FindFrame(SourceFrameId,
-                                    const InternLookup* intern_lookup);
-  base::Optional<int64_t> FindCallstack(SourceCallstackId,
-                                        const InternLookup* intern_lookup);
+  base::Optional<std::string> FindOrInsertString(
+      SourceStringId,
+      const InternLookup* intern_lookup,
+      InternedStringType type);
+  base::Optional<MappingId> FindOrInsertMapping(
+      SourceMappingId,
+      const InternLookup* intern_lookup);
+  base::Optional<FrameId> FindOrInsertFrame(SourceFrameId,
+                                            const InternLookup* intern_lookup);
+  base::Optional<CallsiteId> FindOrInsertCallstack(
+      SourceCallstackId,
+      const InternLookup* intern_lookup);
 
   // Clear indices when they're no longer needed.
   void ClearIndices();
@@ -164,16 +202,21 @@
   StringId GetEmptyStringId();
 
   std::unordered_map<SourceStringId, std::string> string_map_;
-  std::unordered_map<SourceMappingId, int64_t> mappings_;
-  std::unordered_map<SourceFrameId, int64_t> frames_;
-  std::unordered_map<SourceCallstackId, int64_t> callstacks_;
+
+  // Mapping from ID of mapping / frame / callstack in original trace and the
+  // index in the respective table it was inserted into.
+  std::unordered_map<SourceMappingId, MappingId> mapping_ids_;
+  std::unordered_map<SourceFrameId, FrameId> frame_ids_;
+  std::unordered_map<SourceCallstackId, CallsiteId> callstack_ids_;
 
   // TODO(oysteine): Share these indices between the StackProfileTrackers,
   // since they're not sequence-specific.
-  std::unordered_map<tables::StackProfileMappingTable::Row, int64_t>
+  //
+  // Mapping from content of database row to the index of the raw.
+  std::unordered_map<tables::StackProfileMappingTable::Row, MappingId>
       mapping_idx_;
-  std::unordered_map<tables::StackProfileFrameTable::Row, int64_t> frame_idx_;
-  std::unordered_map<tables::StackProfileCallsiteTable::Row, int64_t>
+  std::unordered_map<tables::StackProfileFrameTable::Row, FrameId> frame_idx_;
+  std::unordered_map<tables::StackProfileCallsiteTable::Row, CallsiteId>
       callsite_idx_;
 
   TraceProcessorContext* const context_;
diff --git a/src/trace_processor/tables/macros_internal.h b/src/trace_processor/tables/macros_internal.h
index 71895b2..2a4f47f 100644
--- a/src/trace_processor/tables/macros_internal.h
+++ b/src/trace_processor/tables/macros_internal.h
@@ -264,6 +264,7 @@
       explicit constexpr DefinedId(uint32_t v) : value(v) {}                  \
                                                                               \
       bool operator==(const DefinedId& o) const { return o.value == value; }  \
+      bool operator<(const DefinedId& o) const { return value < o.value; }    \
                                                                               \
       uint32_t value;                                                         \
     };                                                                        \
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index 2970d97..db6ac60 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -73,6 +73,10 @@
 
 using MappingId = tables::StackProfileMappingTable::Id;
 
+using FrameId = tables::StackProfileFrameTable::Id;
+
+using CallsiteId = tables::StackProfileCallsiteTable::Id;
+
 using MetadataId = tables::MetadataTable::Id;
 
 using RawId = tables::RawTable::Id;
@@ -612,7 +616,8 @@
   std::pair<int64_t, int64_t> GetTraceTimestampBoundsNs() const;
 
   // TODO(lalitm): remove this when we have a better home.
-  std::vector<int64_t> FindMappingRow(StringId name, StringId build_id) const {
+  std::vector<MappingId> FindMappingRow(StringId name,
+                                        StringId build_id) const {
     auto it = stack_profile_mapping_index_.find(std::make_pair(name, build_id));
     if (it == stack_profile_mapping_index_.end())
       return {};
@@ -620,13 +625,14 @@
   }
 
   // TODO(lalitm): remove this when we have a better home.
-  void InsertMappingRow(StringId name, StringId build_id, uint32_t row) {
+  void InsertMappingId(StringId name, StringId build_id, MappingId row) {
     auto pair = std::make_pair(name, build_id);
     stack_profile_mapping_index_[pair].emplace_back(row);
   }
 
   // TODO(lalitm): remove this when we have a better home.
-  std::vector<int64_t> FindFrameRow(size_t mapping_row, uint64_t rel_pc) const {
+  std::vector<FrameId> FindFrameIds(MappingId mapping_row,
+                                    uint64_t rel_pc) const {
     auto it =
         stack_profile_frame_index_.find(std::make_pair(mapping_row, rel_pc));
     if (it == stack_profile_frame_index_.end())
@@ -635,7 +641,7 @@
   }
 
   // TODO(lalitm): remove this when we have a better home.
-  void InsertFrameRow(size_t mapping_row, uint64_t rel_pc, uint32_t row) {
+  void InsertFrameRow(MappingId mapping_row, uint64_t rel_pc, FrameId row) {
     auto pair = std::make_pair(mapping_row, rel_pc);
     stack_profile_frame_index_[pair].emplace_back(row);
   }
@@ -698,11 +704,11 @@
 
   // TODO(lalitm): remove this when we find a better home for this.
   using MappingKey = std::pair<StringId /* name */, StringId /* build id */>;
-  std::map<MappingKey, std::vector<int64_t>> stack_profile_mapping_index_;
+  std::map<MappingKey, std::vector<MappingId>> stack_profile_mapping_index_;
 
   // TODO(lalitm): remove this when we find a better home for this.
-  using FrameKey = std::pair<size_t /* mapping row */, uint64_t /* rel_pc */>;
-  std::map<FrameKey, std::vector<int64_t>> stack_profile_frame_index_;
+  using FrameKey = std::pair<MappingId, uint64_t /* rel_pc */>;
+  std::map<FrameKey, std::vector<FrameId>> stack_profile_frame_index_;
 
   // One entry for each unique string in the trace.
   StringPool string_pool_;
diff --git a/src/tracing/event_context.cc b/src/tracing/event_context.cc
index 2126e2c..a4f1ade 100644
--- a/src/tracing/event_context.cc
+++ b/src/tracing/event_context.cc
@@ -29,6 +29,9 @@
       incremental_state_(incremental_state) {}
 
 EventContext::~EventContext() {
+  if (!trace_packet_)
+    return;
+
   // When the track event is finalized (i.e., the context is destroyed), we
   // should flush any newly seen interned data to the trace. The data has
   // earlier been written to a heap allocated protobuf message
diff --git a/ui/src/frontend/help_modal.ts b/ui/src/frontend/help_modal.ts
index 39ff0e4..1227d47 100644
--- a/ui/src/frontend/help_modal.ts
+++ b/ui/src/frontend/help_modal.ts
@@ -35,41 +35,39 @@
   helpModelOpen = true;
   showModal({
     title: 'Perfetto Help',
-    content:
-        m('.help',
-          m('h2', 'Navigation'),
-          m(
-              'table',
-              m(
-                  'tr',
-                  m('td', keycap('w'), '/', keycap('s')),
-                  m('td', 'Zoom in/out'),
-                  ),
-              m(
-                  'tr',
-                  m('td', keycap('a'), '/', keycap('d')),
-                  m('td', 'Pan left/right'),
-                  ),
-              ),
-          m('h2', 'Mouse Controls'),
-          m('table',
-            m('tr', m('td', 'Click'), m('td', 'Select event')),
-            m('tr', m('td', 'Ctrl + Scroll wheel'), m('td', 'Zoom in/out')),
-            m('tr', m('td', 'Click + Drag'), m('td', 'Pan left/right')),
+    content: m(
+        '.help',
+        m('h2', 'Navigation'),
+        m(
+            'table',
+            m(
+                'tr',
+                m('td', keycap('w'), '/', keycap('s')),
+                m('td', 'Zoom in/out'),
+                ),
+            m(
+                'tr',
+                m('td', keycap('a'), '/', keycap('d')),
+                m('td', 'Pan left/right'),
+                ),
+            ),
+        m('h2', 'Mouse Controls'),
+        m('table',
+          m('tr', m('td', 'Click'), m('td', 'Select event')),
+          m('tr', m('td', 'Ctrl + Scroll wheel'), m('td', 'Zoom in/out')),
+          m('tr', m('td', 'Click + Drag'), m('td', 'Select area')),
+          m('tr', m('td', 'Shift + Click + Drag'), m('td', 'Pan left/right'))),
+        m('h2', 'Other'),
+        m(
+            'table',
             m('tr',
-              m('td', 'Shift + Click + Drag'),
-              m('td', 'Select a time span'))),
-          m('h2', 'Other'),
-          m(
-              'table',
-              m('tr',
-                m('td', keycap('f'), ' (with event selected)'),
-                m('td', 'Scroll + zoom to current selection')),
-              m('tr',
-                m('td', keycap('m'), ' (with event selected)'),
-                m('td', 'Select time span of event')),
-              m('tr', m('td', keycap('?')), m('td', 'Show help')),
-              )),
+              m('td', keycap('f'), ' (with event selected)'),
+              m('td', 'Scroll + zoom to current selection')),
+            m('tr',
+              m('td', keycap('m'), ' (with event selected)'),
+              m('td', 'Select time span of event')),
+            m('tr', m('td', keycap('?')), m('td', 'Show help')),
+            )),
     buttons: [],
   }).finally(() => {
     helpModelOpen = false;
diff --git a/ui/src/frontend/pan_and_zoom_handler.ts b/ui/src/frontend/pan_and_zoom_handler.ts
index 9d2bda1..1f504b4 100644
--- a/ui/src/frontend/pan_and_zoom_handler.ts
+++ b/ui/src/frontend/pan_and_zoom_handler.ts
@@ -44,8 +44,8 @@
 const WHEEL_ZOOM_SPEED = -0.02;
 
 const EDITING_RANGE_CURSOR = 'ew-resize';
-const SHIFT_CURSOR = 'text';
-const DEFAULT_CURSOR = 'default';
+const DRAG_CURSOR = 'text';
+const PAN_CURSOR = 'move';
 
 enum Pan {
   None = 0,
@@ -94,28 +94,29 @@
   private contentOffsetX: number;
   private onPanned: (movedPx: number) => void;
   private onZoomed: (zoomPositionPx: number, zoomRatio: number) => void;
-  private shouldDrag: (currentPx: number) => boolean;
-  private onDrag:
+  private editSelection: (currentPx: number) => boolean;
+  private onSelection:
       (dragStartX: number, dragStartY: number, prevX: number, currentX: number,
        currentY: number, editing: boolean) => void;
 
   constructor(
-      {element, contentOffsetX, onPanned, onZoomed, shouldDrag, onDrag}: {
-        element: HTMLElement,
-        contentOffsetX: number,
-        onPanned: (movedPx: number) => void,
-        onZoomed: (zoomPositionPx: number, zoomRatio: number) => void,
-        shouldDrag: (currentPx: number) => boolean,
-        onDrag:
-            (dragStartX: number, dragStartY: number, prevX: number,
-             currentX: number, currentY: number, editing: boolean) => void,
-      }) {
+      {element, contentOffsetX, onPanned, onZoomed, editSelection, onSelection}:
+          {
+            element: HTMLElement,
+            contentOffsetX: number,
+            onPanned: (movedPx: number) => void,
+            onZoomed: (zoomPositionPx: number, zoomRatio: number) => void,
+            editSelection: (currentPx: number) => boolean,
+            onSelection:
+                (dragStartX: number, dragStartY: number, prevX: number,
+                 currentX: number, currentY: number, editing: boolean) => void,
+          }) {
     this.element = element;
     this.contentOffsetX = contentOffsetX;
     this.onPanned = onPanned;
     this.onZoomed = onZoomed;
-    this.shouldDrag = shouldDrag;
-    this.onDrag = onDrag;
+    this.editSelection = editSelection;
+    this.onSelection = onSelection;
 
     document.body.addEventListener('keydown', this.boundOnKeyDown);
     document.body.addEventListener('keyup', this.boundOnKeyUp);
@@ -125,16 +126,14 @@
     let prevX = -1;
     let dragStartX = -1;
     let dragStartY = -1;
-    let drag = false;
+    let edit = false;
     new DragGestureHandler(
         this.element,
         (x, y) => {
-          // If we started our drag on a time range boundary or shift is down
-          // then we are drag selecting rather than panning.
-          if (drag || this.shiftDown) {
-            this.onDrag(dragStartX, dragStartY, prevX, x, y, !this.shiftDown);
-          } else {
+          if (this.shiftDown) {
             this.onPanned(prevX - x);
+          } else {
+            this.onSelection(dragStartX, dragStartY, prevX, x, y, edit);
           }
           prevX = x;
         },
@@ -142,19 +141,18 @@
           prevX = x;
           dragStartX = x;
           dragStartY = y;
-          drag = this.shouldDrag(x);
+          edit = this.editSelection(x);
           // Set the cursor style based on where the cursor is when the drag
           // starts.
-          if (drag) {
+          if (edit) {
             this.element.style.cursor = EDITING_RANGE_CURSOR;
-          } else if (this.shiftDown) {
-            this.element.style.cursor = SHIFT_CURSOR;
+          } else if (!this.shiftDown) {
+            this.element.style.cursor = DRAG_CURSOR;
           }
         },
         () => {
           // Reset the cursor now the drag has ended.
-          this.element.style.cursor =
-              this.shiftDown ? SHIFT_CURSOR : DEFAULT_CURSOR;
+          this.element.style.cursor = this.shiftDown ? PAN_CURSOR : DRAG_CURSOR;
           dragStartX = -1;
           dragStartY = -1;
         });
@@ -213,14 +211,13 @@
     // the cursor flickering between styles if you drag fast and get too
     // far from the current time range.
     if (e.buttons === 0) {
-      if (!this.shouldDrag(this.mousePositionX)) {
-        this.element.style.cursor =
-            this.shiftDown ? SHIFT_CURSOR : DEFAULT_CURSOR;
-      } else {
+      if (this.editSelection(this.mousePositionX)) {
         this.element.style.cursor = EDITING_RANGE_CURSOR;
+      } else {
+        this.element.style.cursor = this.shiftDown ? PAN_CURSOR : DRAG_CURSOR;
       }
     }
-    if (this.shiftDown) {
+    if (!this.shiftDown) {
       const pos = this.mousePositionX - TRACK_SHELL_WIDTH;
       const ts = globals.frontendLocalState.timeScale.pxToTime(pos);
       globals.frontendLocalState.setHoveredTimestamp(ts);
@@ -283,17 +280,15 @@
     if (down === this.shiftDown) return;
     this.shiftDown = down;
     if (this.shiftDown) {
+      globals.frontendLocalState.setHoveredTimestamp(-1);
+      this.element.style.cursor = PAN_CURSOR;
+    } else {
       if (this.mousePositionX) {
-        this.element.style.cursor = SHIFT_CURSOR;
+        this.element.style.cursor = DRAG_CURSOR;
         const pos = this.mousePositionX - TRACK_SHELL_WIDTH;
         const ts = globals.frontendLocalState.timeScale.pxToTime(pos);
         globals.frontendLocalState.setHoveredTimestamp(ts);
       }
-    } else {
-      globals.frontendLocalState.setHoveredTimestamp(-1);
-      this.element.style.cursor = DEFAULT_CURSOR;
     }
-
-    globals.frontendLocalState.setShowTimeSelectPreview(this.shiftDown);
   }
 }
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 9d2eaa5..3c2e31f 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -234,6 +234,16 @@
     const ctx = assertExists(this.ctx);
     const canvas = assertExists(ctx.canvas);
     canvas.style.height = `${this.canvasHeight}px`;
+
+    // If're we're non-scrolling canvas and the scroll-limiter should always
+    // have the same height. Enforce this by explicitly setting the height.
+    if (!this.attrs.doesScroll) {
+      const scrollLimiter = canvas.parentElement;
+      if (scrollLimiter) {
+        scrollLimiter.style.height = `${this.canvasHeight}px`;
+      }
+    }
+
     const dpr = window.devicePixelRatio;
     // On non-MacOS if there is a solid scroll bar it can cover important
     // pixels, reduce the size of the canvas so it doesn't overlap with
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 49e6d22..6dc5475 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -179,10 +179,10 @@
         frontendLocalState.updateVisibleTime(newSpan);
         globals.rafScheduler.scheduleRedraw();
       },
-      shouldDrag: (currentPx: number) => {
+      editSelection: (currentPx: number) => {
         return onTimeRangeBoundary(currentPx) !== null;
       },
-      onDrag: (
+      onSelection: (
           dragStartX: number,
           dragStartY: number,
           prevX: number,