Merge "Sticky header tracks"
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index 6c8106c..2995192 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -1013,7 +1013,6 @@
     "android-unwinding/libunwindstack/ElfInterfaceArm.cpp",
     "android-unwinding/libunwindstack/Global.cpp",
     "android-unwinding/libunwindstack/JitDebug.cpp",
-    "android-unwinding/libunwindstack/LocalUnwinder.cpp",
     "android-unwinding/libunwindstack/MapInfo.cpp",
     "android-unwinding/libunwindstack/Maps.cpp",
     "android-unwinding/libunwindstack/Memory.cpp",
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index fb26bbc..63b058f 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -9097,6 +9097,9 @@
     UNWIND_ERROR_SYSTEM_CALL = 9;
     UNWIND_ERROR_THREAD_TIMEOUT = 10;
     UNWIND_ERROR_THREAD_DOES_NOT_EXIST = 11;
+    UNWIND_ERROR_BAD_ARCH = 12;
+    UNWIND_ERROR_MAPS_PARSE = 13;
+    UNWIND_ERROR_INVALID_PARAMETER = 14;
   }
 }
 
diff --git a/protos/perfetto/trace/profiling/profile_packet.proto b/protos/perfetto/trace/profiling/profile_packet.proto
index f599f17..d58e6e7 100644
--- a/protos/perfetto/trace/profiling/profile_packet.proto
+++ b/protos/perfetto/trace/profiling/profile_packet.proto
@@ -271,6 +271,9 @@
     UNWIND_ERROR_SYSTEM_CALL = 9;
     UNWIND_ERROR_THREAD_TIMEOUT = 10;
     UNWIND_ERROR_THREAD_DOES_NOT_EXIST = 11;
+    UNWIND_ERROR_BAD_ARCH = 12;
+    UNWIND_ERROR_MAPS_PARSE = 13;
+    UNWIND_ERROR_INVALID_PARAMETER = 14;
   }
 }
 
diff --git a/src/profiling/common/unwind_support.cc b/src/profiling/common/unwind_support.cc
index 48ff5cf..a070ee2 100644
--- a/src/profiling/common/unwind_support.cc
+++ b/src/profiling/common/unwind_support.cc
@@ -161,6 +161,12 @@
       return "THREAD_DOES_NOT_EXIST";
     case unwindstack::ERROR_THREAD_TIMEOUT:
       return "THREAD_TIMEOUT";
+    case unwindstack::ERROR_BAD_ARCH:
+      return "BAD_ARCH";
+    case unwindstack::ERROR_MAPS_PARSE:
+      return "MAPS_PARSE";
+    case unwindstack::ERROR_INVALID_PARAMETER:
+      return "INVALID_PARAMETER";
   }
 }
 
diff --git a/src/profiling/perf/event_reader.cc b/src/profiling/perf/event_reader.cc
index de05f8b..3c6271a 100644
--- a/src/profiling/perf/event_reader.cc
+++ b/src/profiling/perf/event_reader.cc
@@ -135,7 +135,9 @@
   ret.metadata_page_ = reinterpret_cast<perf_event_mmap_page*>(mmap_addr);
   ret.data_buf_ = reinterpret_cast<char*>(mmap_addr) + base::kPageSize;
   PERFETTO_CHECK(ret.metadata_page_->data_offset == base::kPageSize);
-  PERFETTO_CHECK(ret.metadata_page_->data_size = ret.data_buf_sz_);
+  PERFETTO_CHECK(ret.metadata_page_->data_size == ret.data_buf_sz_);
+
+  PERFETTO_DCHECK(IsPowerOfTwo(ret.data_buf_sz_));
 
   return base::make_optional(std::move(ret));
 }
@@ -178,8 +180,6 @@
 
   // event wrapped - reconstruct it, and return a pointer to the buffer
   if (read_pos + evt_size > data_buf_sz_) {
-    PERFETTO_DCHECK(read_pos + evt_size !=
-                    ((read_pos + evt_size) & (data_buf_sz_ - 1)));
     PERFETTO_DLOG("PerfRingBuffer: returning reconstructed event");
 
     size_t prefix_sz = data_buf_sz_ - read_pos;
@@ -189,9 +189,6 @@
     return &reconstructed_record_[0];
   } else {
     // usual case - contiguous sample
-    PERFETTO_DCHECK(read_pos + evt_size ==
-                    ((read_pos + evt_size) & (data_buf_sz_ - 1)));
-
     return data_buf_ + read_pos;
   }
 }
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index 22c4a6d..8582885 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -284,6 +284,12 @@
       return Profiling::UNWIND_ERROR_THREAD_TIMEOUT;
     case unwindstack::ERROR_THREAD_DOES_NOT_EXIST:
       return Profiling::UNWIND_ERROR_THREAD_DOES_NOT_EXIST;
+    case unwindstack::ERROR_BAD_ARCH:
+      return Profiling::UNWIND_ERROR_BAD_ARCH;
+    case unwindstack::ERROR_MAPS_PARSE:
+      return Profiling::UNWIND_ERROR_MAPS_PARSE;
+    case unwindstack::ERROR_INVALID_PARAMETER:
+      return Profiling::UNWIND_ERROR_INVALID_PARAMETER;
   }
   return Profiling::UNWIND_ERROR_UNKNOWN;
 }
diff --git a/src/trace_processor/importers/proto/profile_packet_utils.h b/src/trace_processor/importers/proto/profile_packet_utils.h
index 2dde388..08c8a46 100644
--- a/src/trace_processor/importers/proto/profile_packet_utils.h
+++ b/src/trace_processor/importers/proto/profile_packet_utils.h
@@ -113,6 +113,12 @@
         return "thread_timeout";
       case Profiling::UNWIND_ERROR_THREAD_DOES_NOT_EXIST:
         return "thread_does_not_exist";
+      case Profiling::UNWIND_ERROR_BAD_ARCH:
+        return "bad_arch";
+      case Profiling::UNWIND_ERROR_MAPS_PARSE:
+        return "maps_parse";
+      case Profiling::UNWIND_ERROR_INVALID_PARAMETER:
+        return "invalid_parameter";
     }
     return "unknown";  // switch should be complete, but gcc needs a hint
   }
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc
index e6dd7c6..4d202e5 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.cc
+++ b/src/trace_processor/importers/proto/proto_trace_reader.cc
@@ -234,7 +234,7 @@
     PERFETTO_ELOG(
         "It is strongly recommended to have flush_period_ms set when "
         "write_into_file is turned on. This trace will be loaded fully "
-        "into memory before sorting which increases the likliehoold of "
+        "into memory before sorting which increases the likelihood of "
         "OOMs.");
   }
 }
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index 7e9accd..a8c5854 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -217,7 +217,9 @@
       break;
     }
 
-    case 'N': {
+    case 'N':
+    case 'T':
+    case 'U': {
       StringId name_id = context_->storage->InternString(point.name);
       StringId track_name_id = context_->storage->InternString(point.str_value);
       UniquePid upid =
@@ -225,9 +227,21 @@
       auto track_set_id =
           context_->async_track_set_tracker->InternProcessTrackSet(
               upid, track_name_id);
-      TrackId track_id =
-          context_->async_track_set_tracker->Scoped(track_set_id, ts, 0);
-      context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name_id, 0);
+
+      if (point.phase == 'N') {
+        TrackId track_id =
+            context_->async_track_set_tracker->Scoped(track_set_id, ts, 0);
+        context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name_id,
+                                        0);
+      } else if (point.phase == 'T') {
+        TrackId track_id = context_->async_track_set_tracker->Begin(
+            track_set_id, point.int_value);
+        context_->slice_tracker->Begin(ts, track_id, kNullStringId, name_id);
+      } else if (point.phase == 'U') {
+        TrackId track_id = context_->async_track_set_tracker->End(
+            track_set_id, point.int_value);
+        context_->slice_tracker->End(ts, track_id);
+      }
       break;
     }
 
diff --git a/src/trace_processor/importers/systrace/systrace_parser.h b/src/trace_processor/importers/systrace/systrace_parser.h
index c6e506a..af0f21f 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.h
+++ b/src/trace_processor/importers/systrace/systrace_parser.h
@@ -75,6 +75,22 @@
                               std::move(track_name));
   }
 
+  static SystraceTracePoint T(uint32_t tgid,
+                              base::StringView track_name,
+                              base::StringView name,
+                              int64_t cookie) {
+    return SystraceTracePoint('T', tgid, std::move(name), cookie,
+                              std::move(track_name));
+  }
+
+  static SystraceTracePoint U(uint32_t tgid,
+                              base::StringView track_name,
+                              base::StringView name,
+                              int64_t cookie) {
+    return SystraceTracePoint('U', tgid, std::move(name), cookie,
+                              std::move(track_name));
+  }
+
   SystraceTracePoint(char p,
                      uint32_t tg,
                      base::StringView n,
@@ -82,18 +98,18 @@
                      base::StringView s)
       : phase(p), tgid(tg), name(std::move(n)), int_value(v), str_value(s) {}
 
-  // Phase can be one of B, E, C, S, F.
+  // Phase can be one of B, E, C, S, F, I, N, T, U.
   char phase = '\0';
 
   uint32_t tgid = 0;
 
-  // For phase = 'B' and phase = 'C' only.
+  // For phase = B, C, S, F, N, U, T, U.
   base::StringView name;
 
-  // For phase = 'C' (counter value) and 'B', 'F' (async cookie).
+  // For phase = C (counter value) and B, S, F, N, T, U (async cookie).
   int64_t int_value = 0;
 
-  // For phase = 'N' (instant on track)
+  // For phase = N, T, U (track name)
   base::StringView str_value;
 
   // Visible for unittesting.
@@ -107,17 +123,19 @@
 };
 
 // We have to handle trace_marker events of a few different types:
-// 1. some random text
-// 2. B|1636|pokeUserActivity
-// 3. E|1636
-// 4. C|1636|wq:monitor|0
-// 5. S|1636|frame_capture|123
-// 6. F|1636|frame_capture|456
+// 1.   some random text
+// 2.   B|1636|pokeUserActivity
+// 3.   E|1636
+// 4.   C|1636|wq:monitor|0
+// 5.   S|1636|frame_capture|123
+// 6.   F|1636|frame_capture|456
+// 7.   C|3209|TransfersBytesPendingOnDisk-value|0|Blob
+// 8.   I|4820|instant
+// 9.   N|1938|track_name|instant_name
+// 10.  T|1339|track_name|slice_name|789
+// 11.  U|6890|track_name|slice_name|135
 // Counters emitted by chromium can have a further "category group" appended
 // ("Blob" in the example below). We ignore the category group.
-// 7. C|3209|TransfersBytesPendingOnDisk-value|0|Blob
-// 8. I|4820|instant
-// 9. N|1938|track_name|instant_name
 inline SystraceParseResult ParseSystraceTracePoint(
     base::StringView str_untrimmed,
     SystraceTracePoint* out) {
@@ -219,6 +237,20 @@
       out->int_value = *maybe_value;
       return SystraceParseResult::kSuccess;
     }
+    case 'T':    // Begin of async slice on track.
+    case 'U': {  // End of async slice on track.
+      auto f2_track_name = read_next_field();
+      auto f3_name = read_next_field();
+      auto maybe_cookie = base::StringToInt64(read_next_field().ToStdString());
+      if (PERFETTO_UNLIKELY(!has_tgid || f2_track_name.empty() ||
+                            f3_name.empty() || !maybe_cookie)) {
+        return SystraceParseResult::kFailure;
+      }
+      out->name = f3_name;
+      out->str_value = f2_track_name;
+      out->int_value = *maybe_cookie;
+      return SystraceParseResult::kSuccess;
+    }
     default:
       if (str.find("trace_event_clock_sync:") == 0)
         return SystraceParseResult::kUnsupported;
diff --git a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
index be0d801..07431c6 100644
--- a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
@@ -108,6 +108,32 @@
             Result::kUnsupported);
 }
 
+TEST(SystraceParserTest, AsyncTrackEvents) {
+  SystraceTracePoint result{};
+  ASSERT_EQ(ParseSystraceTracePoint("T", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("U", &result), Result::kFailure);
+
+  ASSERT_EQ(ParseSystraceTracePoint("T||test|test|", &result),
+            Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("T|123|test||", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("T|123||test|", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("T|123|track|event|", &result),
+            Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("T|123|track|event|456", &result),
+            Result::kSuccess);
+  EXPECT_EQ(result, SystraceTracePoint::T(123, "track", "event", 456));
+
+  ASSERT_EQ(ParseSystraceTracePoint("U||test|test|", &result),
+            Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("U|123|test||", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("U|123||test|", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("U|123|track|event|", &result),
+            Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("U|123|track|event|456", &result),
+            Result::kSuccess);
+  EXPECT_EQ(result, SystraceTracePoint::U(123, "track", "event", 456));
+}
+
 }  // namespace
 }  // namespace systrace_utils
 }  // namespace trace_processor
diff --git a/src/trace_processor/metrics/sql/android/android_sysui_cuj_surfaceflinger.sql b/src/trace_processor/metrics/sql/android/android_sysui_cuj_surfaceflinger.sql
index f88ed0f..9a93741 100644
--- a/src/trace_processor/metrics/sql/android/android_sysui_cuj_surfaceflinger.sql
+++ b/src/trace_processor/metrics/sql/android/android_sysui_cuj_surfaceflinger.sql
@@ -146,7 +146,7 @@
     slice.ts + slice.dur AS ts_end
   FROM slice
   JOIN android_sysui_cuj_sf_main_thread_track main_track ON slice.track_id = main_track.id
-  WHERE slice.dur > 0 AND slice.name = 'composite';
+  WHERE slice.dur > 0 AND slice.name GLOB 'composite*';
 
 DROP VIEW IF EXISTS android_sysui_cuj_surfaceflinger_commit_composite_frames_in_cuj;
 CREATE VIEW android_sysui_cuj_surfaceflinger_commit_composite_frames_in_cuj AS
diff --git a/test/trace_processor/parsing/android_async_slice.textproto b/test/trace_processor/atrace/android_async_slice.textproto
similarity index 100%
rename from test/trace_processor/parsing/android_async_slice.textproto
rename to test/trace_processor/atrace/android_async_slice.textproto
diff --git a/test/trace_processor/parsing/android_b2b_async_begin.textproto b/test/trace_processor/atrace/android_b2b_async_begin.textproto
similarity index 100%
rename from test/trace_processor/parsing/android_b2b_async_begin.textproto
rename to test/trace_processor/atrace/android_b2b_async_begin.textproto
diff --git a/test/trace_processor/parsing/android_b2b_async_begin_list_slices.out b/test/trace_processor/atrace/android_b2b_async_begin_list_slices.out
similarity index 100%
rename from test/trace_processor/parsing/android_b2b_async_begin_list_slices.out
rename to test/trace_processor/atrace/android_b2b_async_begin_list_slices.out
diff --git a/test/trace_processor/atrace/async_track_atrace.py b/test/trace_processor/atrace/async_track_atrace.py
new file mode 100644
index 0000000..121f6d3
--- /dev/null
+++ b/test/trace_processor/atrace/async_track_atrace.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+# Copyright (C) 2022 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.
+
+from os import sys, path
+
+import synth_common
+
+trace = synth_common.create_trace()
+
+trace.add_packet()
+trace.add_process(pid=1, ppid=0, cmdline="p1")
+trace.add_process(pid=2, ppid=1, cmdline="p2")
+
+trace.add_thread(tid=1, tgid=1, cmdline='p1', name='p1')
+trace.add_thread(tid=2, tgid=2, cmdline='p2', name='p2')
+
+trace.add_ftrace_packet(cpu=0)
+trace.add_print(ts=50, tid=1, buf='T|1|track|ev|1024\n')
+trace.add_print(ts=55, tid=1, buf='T|1|track|ev|2048\n')
+trace.add_print(ts=60, tid=2, buf='T|2|track|ev|1024\n')
+trace.add_print(ts=65, tid=2, buf='U|2|track|ev|1024\n')
+trace.add_print(ts=70, tid=1, buf='U|1|track|ev|2048\n')
+trace.add_print(ts=75, tid=1, buf='U|1|track|ev|1024\n')
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/atrace/async_track_atrace_process_track_slices.out b/test/trace_processor/atrace/async_track_atrace_process_track_slices.out
new file mode 100644
index 0000000..3023fcf
--- /dev/null
+++ b/test/trace_processor/atrace/async_track_atrace_process_track_slices.out
@@ -0,0 +1,4 @@
+"ts","dur","pid","slice_name","track_name"
+50,25,1,"ev","track"
+55,15,1,"ev","track"
+60,5,2,"ev","track"
diff --git a/test/trace_processor/parsing/bad_print.systrace b/test/trace_processor/atrace/bad_print.systrace
similarity index 100%
rename from test/trace_processor/parsing/bad_print.systrace
rename to test/trace_processor/atrace/bad_print.systrace
diff --git a/test/trace_processor/parsing/bad_print.textproto b/test/trace_processor/atrace/bad_print.textproto
similarity index 100%
rename from test/trace_processor/parsing/bad_print.textproto
rename to test/trace_processor/atrace/bad_print.textproto
diff --git a/test/trace_processor/parsing/bad_print_systrace_list_slices.out b/test/trace_processor/atrace/bad_print_systrace_list_slices.out
similarity index 100%
rename from test/trace_processor/parsing/bad_print_systrace_list_slices.out
rename to test/trace_processor/atrace/bad_print_systrace_list_slices.out
diff --git a/test/trace_processor/parsing/bad_print_textproto_list_slices.out b/test/trace_processor/atrace/bad_print_textproto_list_slices.out
similarity index 100%
rename from test/trace_processor/parsing/bad_print_textproto_list_slices.out
rename to test/trace_processor/atrace/bad_print_textproto_list_slices.out
diff --git a/test/trace_processor/atrace/index b/test/trace_processor/atrace/index
new file mode 100644
index 0000000..32a8119
--- /dev/null
+++ b/test/trace_processor/atrace/index
@@ -0,0 +1,13 @@
+# Check error handling when parsing print events.
+bad_print.textproto ../common/list_slices.sql bad_print_textproto_list_slices.out
+bad_print.systrace ../common/list_slices.sql bad_print_systrace_list_slices.out
+instant_atrace.py instant_with_thread.sql instant_atrace_instant_with_thread.out
+instant_async_atrace.py instant_async.sql instant_async_atrace_instant_async.out
+
+# Match legacy Catapult behaviour when we see multiple S events b2b with the same cookie
+# name and upid.
+android_b2b_async_begin.textproto ../common/list_slices.sql android_b2b_async_begin_list_slices.out
+
+# Android userspace async slices
+android_async_slice.textproto ../common/process_track_slices.sql process_track_slices_android_async_slice.out
+async_track_atrace.py ../common/process_track_slices.sql async_track_atrace_process_track_slices.out
diff --git a/test/trace_processor/parsing/instant_async.sql b/test/trace_processor/atrace/instant_async.sql
similarity index 100%
rename from test/trace_processor/parsing/instant_async.sql
rename to test/trace_processor/atrace/instant_async.sql
diff --git a/test/trace_processor/parsing/instant_async_atrace.py b/test/trace_processor/atrace/instant_async_atrace.py
similarity index 100%
rename from test/trace_processor/parsing/instant_async_atrace.py
rename to test/trace_processor/atrace/instant_async_atrace.py
diff --git a/test/trace_processor/parsing/instant_async_atrace_instant_async.out b/test/trace_processor/atrace/instant_async_atrace_instant_async.out
similarity index 100%
rename from test/trace_processor/parsing/instant_async_atrace_instant_async.out
rename to test/trace_processor/atrace/instant_async_atrace_instant_async.out
diff --git a/test/trace_processor/parsing/instant_atrace.py b/test/trace_processor/atrace/instant_atrace.py
similarity index 100%
rename from test/trace_processor/parsing/instant_atrace.py
rename to test/trace_processor/atrace/instant_atrace.py
diff --git a/test/trace_processor/parsing/instant_atrace_instant_with_thread.out b/test/trace_processor/atrace/instant_atrace_instant_with_thread.out
similarity index 100%
rename from test/trace_processor/parsing/instant_atrace_instant_with_thread.out
rename to test/trace_processor/atrace/instant_atrace_instant_with_thread.out
diff --git a/test/trace_processor/parsing/instant_with_thread.sql b/test/trace_processor/atrace/instant_with_thread.sql
similarity index 100%
rename from test/trace_processor/parsing/instant_with_thread.sql
rename to test/trace_processor/atrace/instant_with_thread.sql
diff --git a/test/trace_processor/parsing/process_track_slices_android_async_slice.out b/test/trace_processor/atrace/process_track_slices_android_async_slice.out
similarity index 100%
rename from test/trace_processor/parsing/process_track_slices_android_async_slice.out
rename to test/trace_processor/atrace/process_track_slices_android_async_slice.out
diff --git a/test/trace_processor/parsing/list_slices.sql b/test/trace_processor/common/list_slices.sql
similarity index 96%
rename from test/trace_processor/parsing/list_slices.sql
rename to test/trace_processor/common/list_slices.sql
index 211ae89..55b0d72 100644
--- a/test/trace_processor/parsing/list_slices.sql
+++ b/test/trace_processor/common/list_slices.sql
@@ -1,2 +1,2 @@
 SELECT ts, dur, name
-FROM slice
\ No newline at end of file
+FROM slice
diff --git a/test/trace_processor/parsing/process_track_slices.sql b/test/trace_processor/common/process_track_slices.sql
similarity index 100%
rename from test/trace_processor/parsing/process_track_slices.sql
rename to test/trace_processor/common/process_track_slices.sql
diff --git a/test/trace_processor/include_index b/test/trace_processor/include_index
index 593e338..29a7b41 100644
--- a/test/trace_processor/include_index
+++ b/test/trace_processor/include_index
@@ -1,3 +1,4 @@
+atrace/index
 camera/index
 chrome/index
 cros/index
diff --git a/test/trace_processor/parsing/index b/test/trace_processor/parsing/index
index 4913f0b..9952dea 100644
--- a/test/trace_processor/parsing/index
+++ b/test/trace_processor/parsing/index
@@ -56,9 +56,6 @@
 # Mm Event
 ../../data/mm_event.pb mm_event.sql mm_event.out
 
-# Android userspace async slices
-android_async_slice.textproto process_track_slices.sql process_track_slices_android_async_slice.out
-
 # Check the systrace conversion code in the raw table.
 # Print events
 ../../data/lmk_userspace.pb print_systrace.sql print_systrace_lmk_userspace.out
@@ -75,16 +72,6 @@
 ../../data/systrace.html systrace_html.sql systrace_html.out
 ../../data/trailing_empty.systrace sched_smoke.sql sched_smoke_trailing_empty.out
 
-# Check error handling when parsing print events.
-bad_print.textproto list_slices.sql bad_print_textproto_list_slices.out
-bad_print.systrace list_slices.sql bad_print_systrace_list_slices.out
-instant_atrace.py instant_with_thread.sql instant_atrace_instant_with_thread.out
-instant_async_atrace.py instant_async.sql instant_async_atrace_instant_async.out
-
-# Match legacy Catapult behaviour when we see multiple S events b2b with the same cookie
-# name and upid.
-android_b2b_async_begin.textproto list_slices.sql android_b2b_async_begin_list_slices.out
-
 # LMK handling
 kernel_lmk.py lmk.sql lmk_kernel_lmk.out
 ../../data/lmk_userspace.pb lmk.sql lmk_userspace_lmk.out
diff --git a/tools/install-build-deps b/tools/install-build-deps
index a9004f0..de21e94 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -231,7 +231,7 @@
     Dependency(
         'buildtools/android-unwinding',
         'https://android.googlesource.com/platform/system/unwinding.git',
-        'b61aebb7f7afaad9b775a59dcd0ea6289fc4325a', 'all', 'all'),
+        '215644709bd7bbda4c05a9db4d55e96be61bcf64', 'all', 'all'),
     Dependency('buildtools/android-logging',
                'https://android.googlesource.com/platform/system/logging.git',
                '7b36b566c9113fc703d68f76e8f40c0c2432481c', 'all', 'all'),
diff --git a/ui/src/assets/trace_info_page.scss b/ui/src/assets/trace_info_page.scss
index 84ee72f..a91dcf9 100644
--- a/ui/src/assets/trace_info_page.scss
+++ b/ui/src/assets/trace_info_page.scss
@@ -76,6 +76,10 @@
           background-color: rgba(0, 0, 0, 0.04);
         }
 
+        td.name {
+          min-width: 150px;
+        }
+
         td {
           font-family: var(--monospace-font);
           font-size: 12px;
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 8366f65..1f8ca17 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -15,7 +15,6 @@
 import {Draft} from 'immer';
 
 import {assertExists, assertTrue} from '../base/logging';
-import {randomColor} from '../common/colorizer';
 import {RecordConfig} from '../controller/record_config_types';
 import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames/common';
 import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices/common';
@@ -33,6 +32,7 @@
 } from '../tracks/process_scheduling/common';
 import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common';
 
+import {randomColor} from './colorizer';
 import {createEmptyState} from './empty_state';
 import {DEFAULT_VIEWING_OPTION, PERF_SAMPLES_KEY} from './flamegraph_util';
 import {
@@ -134,11 +134,18 @@
   return index;
 }
 
+function generateNextId(draft: StateDraft): string {
+  const nextId = String(Number(draft.nextId) + 1);
+  draft.nextId = nextId;
+  return nextId;
+}
+
 export const StateActions = {
 
   openTraceFromFile(state: StateDraft, args: {file: File}): void {
     clearTraceState(state);
-    const id = `${state.nextId++}`;
+    const id = generateNextId(state);
+    state.currentEngineId = id;
     state.engines[id] = {
       id,
       ready: false,
@@ -148,7 +155,8 @@
 
   openTraceFromBuffer(state: StateDraft, args: PostedTrace): void {
     clearTraceState(state);
-    const id = `${state.nextId++}`;
+    const id = generateNextId(state);
+    state.currentEngineId = id;
     state.engines[id] = {
       id,
       ready: false,
@@ -158,7 +166,8 @@
 
   openTraceFromUrl(state: StateDraft, args: {url: string}): void {
     clearTraceState(state);
-    const id = `${state.nextId++}`;
+    const id = generateNextId(state);
+    state.currentEngineId = id;
     state.engines[id] = {
       id,
       ready: false,
@@ -168,7 +177,8 @@
 
   openTraceFromHttpRpc(state: StateDraft, _args: {}): void {
     clearTraceState(state);
-    const id = `${state.nextId++}`;
+    const id = generateNextId(state);
+    state.currentEngineId = id;
     state.engines[id] = {
       id,
       ready: false,
@@ -198,7 +208,7 @@
 
   addTracks(state: StateDraft, args: {tracks: AddTrackArgs[]}) {
     args.tracks.forEach(track => {
-      const id = track.id === undefined ? `${state.nextId++}` : track.id;
+      const id = track.id === undefined ? generateNextId(state) : track.id;
       track.id = id;
       state.tracks[id] = track as TrackState;
       this.fillUiTrackIdByTraceTrackId(state, track as TrackState, id);
@@ -214,7 +224,7 @@
     id?: string; engineId: string; kind: string; name: string;
     trackGroup?: string; config: {}; trackKindPriority: TrackKindPriority;
   }): void {
-    const id = args.id !== undefined ? args.id : `${state.nextId++}`;
+    const id = args.id !== undefined ? args.id : generateNextId(state);
     state.tracks[id] = {
       id,
       engineId: args.engineId,
@@ -252,7 +262,7 @@
   addDebugTrack(state: StateDraft, args: {engineId: string, name: string}):
       void {
         if (state.debugTrackId !== undefined) return;
-        const trackId = `${state.nextId++}`;
+        const trackId = generateNextId(state);
         state.debugTrackId = trackId;
         this.addTrack(state, {
           id: trackId,
@@ -334,11 +344,11 @@
 
   executeQuery(
       state: StateDraft,
-      args: {queryId: string; engineId: string; query: string}): void {
+      args: {queryId: string; query: string, engineId?: string}): void {
     state.queries[args.queryId] = {
       id: args.queryId,
-      engineId: args.engineId,
       query: args.query,
+      engineId: args.engineId
     };
   },
 
@@ -433,7 +443,7 @@
 
   createPermalink(state: StateDraft, args: {isRecordingConfig: boolean}): void {
     state.permalink = {
-      requestId: `${state.nextId++}`,
+      requestId: generateNextId(state),
       hash: undefined,
       isRecordingConfig: args.isRecordingConfig
     };
@@ -447,7 +457,7 @@
       },
 
   loadPermalink(state: StateDraft, args: {hash: string}): void {
-    state.permalink = {requestId: `${state.nextId++}`, hash: args.hash};
+    state.permalink = {requestId: generateNextId(state), hash: args.hash};
   },
 
   clearPermalink(state: StateDraft, _: {}): void {
@@ -497,7 +507,7 @@
   },
 
   addNote(state: StateDraft, args: {timestamp: number, color: string}): void {
-    const id = `${state.nextNoteId++}`;
+    const id = generateNextId(state);
     state.notes[id] = {
       noteType: 'DEFAULT',
       id,
@@ -515,7 +525,7 @@
             state.currentSelection.kind !== 'AREA') {
           return;
         }
-        const id = args.persistent ? `${state.nextNoteId++}` : '0';
+        const id = args.persistent ? generateNextId(state) : '0';
         const color = args.persistent ? args.color : '#344596';
         state.notes[id] = {
           noteType: 'AREA',
@@ -539,7 +549,7 @@
   },
 
   markArea(state: StateDraft, args: {area: Area, persistent: boolean}): void {
-    const areaId = `${state.nextAreaId++}`;
+    const areaId = generateNextId(state);
     assertTrue(args.area.endSec >= args.area.startSec);
     state.areas[areaId] = {
       id: areaId,
@@ -547,11 +557,11 @@
       endSec: args.area.endSec,
       tracks: args.area.tracks
     };
-    const id = args.persistent ? `${state.nextNoteId++}` : '0';
+    const noteId = args.persistent ? generateNextId(state) : '0';
     const color = args.persistent ? randomColor() : '#344596';
-    state.notes[id] = {
+    state.notes[noteId] = {
       noteType: 'AREA',
-      id,
+      id: noteId,
       areaId,
       color,
       text: '',
@@ -768,7 +778,7 @@
   },
 
   selectArea(state: StateDraft, args: {area: Area}): void {
-    const areaId = `${state.nextAreaId++}`;
+    const areaId = generateNextId(state);
     assertTrue(args.area.endSec >= args.area.startSec);
     state.areas[areaId] = {
       id: areaId,
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index 53e8f65..66689b8 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -259,7 +259,6 @@
 
 test('open trace', () => {
   const state = createEmptyState();
-  state.nextId = 100;
   const recordConfig = state.recordConfig;
   const after = produce(state, draft => {
     StateActions.openTraceFromUrl(draft, {
@@ -268,7 +267,6 @@
   });
 
   const engineKeys = Object.keys(after.engines);
-  expect(after.nextId).toBe(101);
   expect(engineKeys.length).toBe(1);
   expect((after.engines[engineKeys[0]].source as TraceUrlSource).url)
       .toBe('https://example.com/bar');
@@ -316,15 +314,17 @@
 
 test('setEngineReady', () => {
   const state = createEmptyState();
-  state.nextId = 100;
+  let latestEngineId = 'dummy value will be replaced';
+  state.currentEngineId = '100';
   const after = produce(state, draft => {
     StateActions.openTraceFromUrl(draft, {
       url: 'https://example.com/bar',
     });
+    latestEngineId = assertExists(draft.currentEngineId);
     StateActions.setEngineReady(
-        draft, {engineId: '100', ready: true, mode: 'WASM'});
+        draft, {engineId: latestEngineId, ready: true, mode: 'WASM'});
   });
-  expect(after.engines['100'].ready).toBe(true);
+  expect(after.engines[latestEngineId].ready).toBe(true);
 });
 
 test('sortTracksByPriority', () => {
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index ac287d2..c24567b 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -32,9 +32,8 @@
 export function createEmptyState(): State {
   return {
     version: STATE_VERSION,
-    nextId: 0,
-    nextNoteId: 1,  // 0 is reserved for ephemeral area marking.
-    nextAreaId: 0,
+    currentEngineId: undefined,
+    nextId: '-1',
     newEngineMode: 'USE_HTTP_RPC_IF_AVAILABLE',
     engines: {},
     traceTime: {...defaultTraceTime},
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index ef7edcd..6447767 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -80,7 +80,11 @@
 // serialisation+deserialisation.
 // 15: Added state for Pivot Table V2
 // 16: Added boolean tracking if the flamegraph modal was dismissed
-export const STATE_VERSION = 16;
+// 17:
+// - add currentEngineId to track the id of the current engine
+// - remove nextNoteId, nextAreaId and use nextId as a unique counter for all
+//   indexing except the indexing of the engines
+export const STATE_VERSION = 17;
 
 export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
 
@@ -180,7 +184,7 @@
 
 export interface QueryConfig {
   id: string;
-  engineId: string;
+  engineId?: string;
   query: string;
 }
 
@@ -393,9 +397,8 @@
   // tslint:disable-next-line:no-any
   [key: string]: any;
   version: number;
-  nextId: number;
-  nextNoteId: number;
-  nextAreaId: number;
+  currentEngineId?: string;
+  nextId: string;
 
   /**
    * State of the ConfigEditor.
diff --git a/ui/src/common/state_unittest.ts b/ui/src/common/state_unittest.ts
index 046f864..b26ac71 100644
--- a/ui/src/common/state_unittest.ts
+++ b/ui/src/common/state_unittest.ts
@@ -17,7 +17,7 @@
 
 test('createEmptyState', () => {
   const state: State = createEmptyState();
-  expect(state.nextId).toEqual(0);
+  expect(state.currentEngineId).toEqual(undefined);
 });
 
 test('getContainingTrackId', () => {
diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts
index cfb87dd..31a7010 100644
--- a/ui/src/controller/permalink_controller.ts
+++ b/ui/src/controller/permalink_controller.ts
@@ -26,6 +26,7 @@
   saveTrace,
   toSha256
 } from '../common/upload_utils';
+import {globals as frontendGlobals} from '../frontend/globals';
 import {publishConversionJobStatusUpdate} from '../frontend/publish';
 import {Router} from '../frontend/router';
 
@@ -120,7 +121,7 @@
     if (isRecordingConfig) {
       uploadState = globals.state.recordConfig;
     } else {
-      const engine = assertExists(Object.values(globals.state.engines)[0]);
+      const engine = assertExists(frontendGlobals.getCurrentEngine());
       let dataToUpload: File|ArrayBuffer|undefined = undefined;
       let traceName = `trace ${engine.id}`;
       if (engine.source.type === 'FILE') {
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index fb40021..62e9e59 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -186,6 +186,16 @@
 
         // Create a QueryController for each query.
         for (const queryId of Object.keys(globals.state.queries)) {
+          // If the expected engineId was not specified in the query, we
+          // assume it's `state.currentEngineId`. The engineId is not specified
+          // for instances with queries created prior to the creation of the
+          // first engine.
+          const expectedEngineId = globals.state.queries[queryId].engineId ||
+              frontendGlobals.state.currentEngineId;
+          // Check that we are executing the query on the correct engine.
+          if (expectedEngineId !== engine.id) {
+            continue;
+          }
           const queryArgs: QueryControllerArgs = { queryId, engine };
           childControllers.push(Child(queryId, QueryController, queryArgs));
         }
diff --git a/ui/src/frontend/analyze_page.ts b/ui/src/frontend/analyze_page.ts
index c464651..cd9e05e 100644
--- a/ui/src/frontend/analyze_page.ts
+++ b/ui/src/frontend/analyze_page.ts
@@ -45,8 +45,7 @@
         query = query.substring(selectionStart, selectionEnd);
       }
       if (!query) return;
-      globals.dispatch(
-          Actions.executeQuery({engineId: '0', queryId: QUERY_ID, query}));
+      globals.dispatch(Actions.executeQuery({queryId: QUERY_ID, query}));
     }
 
     if (event.code === 'Tab') {
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index aa763bd..fa3f223 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -14,6 +14,7 @@
 
 import * as m from 'mithril';
 
+import {assertExists} from '../base/logging';
 import {TraceUrlSource} from '../common/state';
 import {saveTrace} from '../common/upload_utils';
 
@@ -74,7 +75,7 @@
   const errTitle = errLog.split('\n', 1)[0].substr(0, 80);
   const userDescription = '';
   let checked = false;
-  const engine = Object.values(globals.state.engines)[0];
+  const engine = globals.getCurrentEngine();
 
   const shareTraceSection: m.Vnode[] = [];
   if (isShareable() && !urlExists()) {
@@ -83,7 +84,7 @@
           checked,
           oninput: (ev: InputEvent) => {
             checked = (ev.target as HTMLInputElement).checked;
-            if (checked && engine.source.type === 'FILE') {
+            if (checked && engine && engine.source.type === 'FILE') {
               saveTrace(engine.source.file).then(url => {
                 const errMessage = createErrorMessage(errLog, checked, url);
                 renderModal(
@@ -152,7 +153,7 @@
 
 // If there is a trace URL to share, we don't have to show the upload checkbox.
 function urlExists() {
-  const engine = Object.values(globals.state.engines)[0];
+  const engine = globals.getCurrentEngine();
   return engine !== undefined &&
       (engine.source.type === 'ARRAY_BUFFER' || engine.source.type === 'URL') &&
       engine.source.url !== undefined;
@@ -160,11 +161,12 @@
 
 function createErrorMessage(errLog: string, checked: boolean, url?: string) {
   let errMessage = '';
-  const engine = Object.values(globals.state.engines)[0];
+  const engine = globals.getCurrentEngine();
   if (checked && url !== undefined) {
     errMessage += `Trace: ${url}`;
   } else if (urlExists()) {
-    errMessage += `Trace: ${(engine.source as TraceUrlSource).url}`;
+    errMessage +=
+        `Trace: ${(assertExists(engine).source as TraceUrlSource).url}`;
   } else {
     errMessage += 'To assist with debugging please attach or link to the ' +
         'trace you were viewing.';
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index 368ab32..866ed87 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -248,7 +248,7 @@
   }
 
   downloadPprof() {
-    const engine = Object.values(globals.state.engines)[0];
+    const engine = globals.getCurrentEngine();
     if (!engine) return;
     getCurrentTrace()
         .then(file => {
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 71e2a34..a7662bf 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -24,7 +24,7 @@
 import {Engine} from '../common/engine';
 import {MetricResult} from '../common/metric_data';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
-import {CallsiteInfo, State} from '../common/state';
+import {CallsiteInfo, EngineConfig, State} from '../common/state';
 import {fromNs, toNs} from '../common/time';
 
 import {Analytics, initAnalytics} from './analytics';
@@ -514,6 +514,13 @@
     return resolution;
   }
 
+  getCurrentEngine(): EngineConfig|undefined {
+    if (!this.state.currentEngineId) {
+      return undefined;
+    }
+    return this.state.engines[this.state.currentEngineId];
+  }
+
   makeSelection(action: DeferredAction<{}>, tabToOpen = 'current_selection') {
     // A new selection should cancel the current search selection.
     globals.dispatch(Actions.setSearchIndex({index: -1}));
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 15ad0cf..c0977ce 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -323,9 +323,8 @@
 
     // Don't allow postMessage or opening trace from route when the user says
     // that they want to reuse the already loaded trace in trace processor.
-    const values = Object.values(globals.state.engines);
-    if (values.length > 0 &&
-        globals.state.engines[values.length - 1].source.type === 'HTTP_RPC') {
+    const engine = globals.getCurrentEngine();
+    if (engine && engine.source.type === 'HTTP_RPC') {
       return;
     }
 
diff --git a/ui/src/frontend/pivot_table_redux.ts b/ui/src/frontend/pivot_table_redux.ts
index 2bdae1b..262ebcf 100644
--- a/ui/src/frontend/pivot_table_redux.ts
+++ b/ui/src/frontend/pivot_table_redux.ts
@@ -15,7 +15,6 @@
  */
 
 import * as m from 'mithril';
-
 import {GenericSet} from '../base/generic_set';
 import {sqliteString} from '../base/string_utils';
 import {Actions} from '../common/actions';
@@ -171,7 +170,6 @@
               // TODO(ddrone): the UI of running query as if it was a canned or
               // custom query is a temporary one, replace with a proper UI.
               globals.dispatch(Actions.executeQuery({
-                engineId: '0',
                 queryId: 'command',
                 query,
               }));
diff --git a/ui/src/frontend/record_config.ts b/ui/src/frontend/record_config.ts
index 847a270..a200b47 100644
--- a/ui/src/frontend/record_config.ts
+++ b/ui/src/frontend/record_config.ts
@@ -195,12 +195,8 @@
   recordTargetOS: string|null;
 
   constructor() {
-    this.recordTargetOS = null;
-    const savedTarget =
+    this.recordTargetOS =
         window.localStorage.getItem(LOCAL_STORAGE_RECORD_TARGET_OS_KEY);
-    if (typeof savedTarget === 'string') {
-      this.recordTargetOS = savedTarget;
-    }
   }
 
   get(): string|null {
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 653ee61..e33f333 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -113,7 +113,6 @@
   return (e: Event) => {
     e.preventDefault();
     globals.dispatch(Actions.executeQuery({
-      engineId: '0',
       queryId: 'command',
       query,
     }));
@@ -124,7 +123,7 @@
   return (e: Event) => {
     e.preventDefault();
     globals.dispatch(Actions.addDebugTrack({
-      engineId: Object.keys(globals.state.engines)[0],
+      engineId: assertExists(globals.state.currentEngineId),
       name: 'Debug Slices',
     }));
   };
@@ -355,7 +354,7 @@
 
 export async function getCurrentTrace(): Promise<Blob> {
   // Caller must check engine exists.
-  const engine = assertExists(Object.values(globals.state.engines)[0]);
+  const engine = assertExists(globals.getCurrentEngine());
   const src = engine.source;
   if (src.type === 'ARRAY_BUFFER') {
     return new Blob([src.buffer]);
@@ -411,8 +410,7 @@
 }
 
 export function isTraceLoaded(): boolean {
-  const engine = Object.values(globals.state.engines)[0];
-  return engine !== undefined;
+  return globals.getCurrentEngine() !== undefined;
 }
 
 function openTraceUrl(url: string): (e: Event) => void {
@@ -539,7 +537,7 @@
 
 function shareTrace(e: Event) {
   e.preventDefault();
-  const engine = assertExists(Object.values(globals.state.engines)[0]);
+  const engine = assertExists(globals.getCurrentEngine());
   const traceUrl = (engine.source as (TraceArrayBufferSource)).url || '';
 
   // If the trace is not shareable (has been pushed via postMessage()) but has
@@ -579,7 +577,7 @@
   if (!isDownloadable() || !isTraceLoaded()) return;
   globals.logging.logEvent('Trace Actions', 'Download trace');
 
-  const engine = Object.values(globals.state.engines)[0];
+  const engine = globals.getCurrentEngine();
   if (!engine) return;
   let url = '';
   let fileName = `trace${TRACE_SUFFIX}`;
diff --git a/ui/src/frontend/slice_panel.ts b/ui/src/frontend/slice_panel.ts
index 1f649c6..65beeb2 100644
--- a/ui/src/frontend/slice_panel.ts
+++ b/ui/src/frontend/slice_panel.ts
@@ -17,6 +17,22 @@
 import {globals, SliceDetails} from './globals';
 import {Panel} from './panel';
 
+// To display process or thread, we want to concatenate their name with ID, but
+// either can be undefined and all the cases need to be considered carefully to
+// avoid `undefined undefined` showing up in the UI. This function does such
+// concatenation.
+//
+// Result can be undefined if both name and process are, in this case result is
+// not going to be displayed in the UI.
+function getDisplayName(name: string|undefined, id: number|undefined): string|
+    undefined {
+  if (name === undefined) {
+    return id === undefined ? undefined : `${id}`;
+  } else {
+    return id === undefined ? name : `${name} ${id}`;
+  }
+}
+
 export abstract class SlicePanel extends Panel {
   protected computeDuration(ts: number, dur: number): string {
     return toNs(dur) === -1 ?
@@ -26,8 +42,8 @@
 
   protected getProcessThreadDetails(sliceInfo: SliceDetails) {
     return new Map<string, string|undefined>([
-      ['Thread', `${sliceInfo.threadName} ${sliceInfo.tid}`],
-      ['Process', `${sliceInfo.processName} ${sliceInfo.pid}`],
+      ['Thread', getDisplayName(sliceInfo.threadName, sliceInfo.tid)],
+      ['Process', getDisplayName(sliceInfo.processName, sliceInfo.pid)],
       ['User ID', sliceInfo.uid ? String(sliceInfo.uid) : undefined],
       ['Package name', sliceInfo.packageName],
       [
diff --git a/ui/src/frontend/topbar.ts b/ui/src/frontend/topbar.ts
index 9cb29bf..d816249 100644
--- a/ui/src/frontend/topbar.ts
+++ b/ui/src/frontend/topbar.ts
@@ -15,7 +15,6 @@
 import * as m from 'mithril';
 
 import {Actions} from '../common/actions';
-import {EngineConfig} from '../common/state';
 import * as version from '../gen/perfetto_version';
 
 import {globals} from './globals';
@@ -77,8 +76,8 @@
     return;
   }
   if (mode === COMMAND && key === 'Enter') {
-    globals.dispatch(Actions.executeQuery(
-        {engineId: '0', queryId: 'command', query: txt.value}));
+    globals.dispatch(
+        Actions.executeQuery({queryId: 'command', query: txt.value}));
   }
 }
 
@@ -176,9 +175,9 @@
 
   loadingAnimation() {
     if (this.progressBar === undefined) return;
-    const engine: EngineConfig = globals.state.engines['0'];
-    if ((engine !== undefined && !engine.ready) ||
-        globals.numQueuedQueries > 0 || taskTracker.hasPendingTasks()) {
+    const engine = globals.getCurrentEngine();
+    if ((engine && !engine.ready) || globals.numQueuedQueries > 0 ||
+        taskTracker.hasPendingTasks()) {
       this.progressBar.classList.add('progress-anim');
     } else {
       this.progressBar.classList.remove('progress-anim');
diff --git a/ui/src/frontend/trace_attrs.ts b/ui/src/frontend/trace_attrs.ts
index 9cc7292..5162469 100644
--- a/ui/src/frontend/trace_attrs.ts
+++ b/ui/src/frontend/trace_attrs.ts
@@ -19,11 +19,10 @@
 }
 
 export function isDownloadable() {
-  const engine = Object.values(globals.state.engines)[0];
+  const engine = globals.getCurrentEngine();
   if (engine && engine.source.type === 'ARRAY_BUFFER' &&
       engine.source.localOnly) {
     return false;
   }
-  if (engine && engine.source.type === 'HTTP_RPC') return false;
-  return true;
+  return !(engine && engine.source.type === 'HTTP_RPC');
 }
\ No newline at end of file
diff --git a/ui/src/frontend/trace_info_page.ts b/ui/src/frontend/trace_info_page.ts
index b1ba4d3..2758bb9 100644
--- a/ui/src/frontend/trace_info_page.ts
+++ b/ui/src/frontend/trace_info_page.ts
@@ -40,7 +40,6 @@
     if (!this.queryDispatched) {
       this.queryDispatched = true;
       globals.dispatch(Actions.executeQuery({
-        engineId: '0',
         queryId: attrs.queryId,
         query: `select name, value, cast(ifnull(idx, '') as text) as idx,
                 description, severity, source from stats
@@ -64,7 +63,7 @@
       const idx = row.idx !== '' ? `[${row.idx}]` : '';
       tableRows.push(m(
           'tr',
-          m('td', {title: row.description}, `${row.name}${idx}`, help),
+          m('td.name', {title: row.description}, `${row.name}${idx}`, help),
           m('td', `${row.value}`),
           m('td', `${row.severity} (${row.source})`),
           ));
@@ -103,7 +102,6 @@
     if (!this.queryDispatched) {
       this.queryDispatched = true;
       globals.dispatch(Actions.executeQuery({
-        engineId: '0',
         queryId: this.QUERY_ID,
         query: `select name, ifnull(str_value, cast(int_value as text)) as value
                 from metadata order by name`,
@@ -119,7 +117,7 @@
     for (const row of resp.rows) {
       tableRows.push(m(
           'tr',
-          m('td', `${row.name}`),
+          m('td.name', `${row.name}`),
           m('td', `${row.value}`),
           ));
     }
@@ -144,7 +142,6 @@
     if (!this.queryDispatched) {
       this.queryDispatched = true;
       globals.dispatch(Actions.executeQuery({
-        engineId: '0',
         queryId: this.QUERY_ID,
         query: `select package_name, version_code, debuggable,
                 profileable_from_shell from package_list`,
@@ -160,7 +157,7 @@
     for (const row of resp.rows) {
       tableRows.push(m(
           'tr',
-          m('td', `${row.package_name}`),
+          m('td.name', `${row.package_name}`),
           m('td', `${row.version_code}`),
           m('td',
             `${row.debuggable ? 'debuggable' : ''} ${