Merge "Project import generated by Copybara."
diff --git a/BUILD b/BUILD
index 4448932..08bc590 100644
--- a/BUILD
+++ b/BUILD
@@ -953,6 +953,7 @@
 perfetto_filegroup(
     name = "src_trace_processor_db_db",
     srcs = [
+        "src/trace_processor/db/base_id.h",
         "src/trace_processor/db/column.cc",
         "src/trace_processor/db/column.h",
         "src/trace_processor/db/compare.h",
diff --git a/BUILD.gn b/BUILD.gn
index 201e340..d18c71c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -92,7 +92,9 @@
   import("gn/perfetto_unittests.gni")
   test("perfetto_unittests") {
     if (is_fuchsia) {
-      use_cfv2 = false
+      additional_manifest_fragments = [
+        "//build/config/fuchsia/test/network.shard.test-cml",
+      ]
     }
     deps = perfetto_unittests_targets
   }
diff --git a/CHANGELOG b/CHANGELOG
index ab2f7bf..db4ed93 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -13,6 +13,8 @@
       an ISO8601 string.
     * Changed 'trace_to_text' to be named 'traceconv'. The source
       location also moved from 'tools/trace_to_text' to 'src/traceconv'.
+    * Changed compiler flags, added -mavx2. The previous patch in v22.0 was
+      supposed to add AVX2 support but added only AVX.
   UI:
     *
   SDK:
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index 42e35b8..c7f6469 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -147,9 +147,8 @@
   # For now the IPC layer is conservatively not enabled on Chromium+Windows
   # builds.
   enable_perfetto_ipc =
-      !is_fuchsia && !is_nacl &&
-      (perfetto_build_standalone || perfetto_build_with_android ||
-       (build_with_chromium && !is_win))
+      !is_nacl && (perfetto_build_standalone || perfetto_build_with_android ||
+                   (build_with_chromium && !is_win) || is_fuchsia)
 
   # Makes the heap profiling daemon target reachable. It works only on Android,
   # but is built on Linux as well for test/compiler coverage.
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index 4b401d4..33fd7aa 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -247,7 +247,7 @@
       # When updating these flags, the CheckCpuOptimizations() in utils.cc must
       # be updated accordingly.
       cflags += [
-        "-mavx",
+        "-mavx2",
         "-mpopcnt",
         "-msse4.2",
       ]
diff --git a/include/perfetto/ext/base/unix_socket.h b/include/perfetto/ext/base/unix_socket.h
index c849da6..dd90423 100644
--- a/include/perfetto/ext/base/unix_socket.h
+++ b/include/perfetto/ext/base/unix_socket.h
@@ -362,7 +362,8 @@
   // User ID of the peer, as returned by the kernel. If the client disconnects
   // and the socket goes into the kDisconnected state, it retains the uid of
   // the last peer.
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
   uid_t peer_uid_posix(bool skip_check_for_testing = false) const {
     PERFETTO_DCHECK((!is_listening() && peer_uid_ != kInvalidUid) ||
                     skip_check_for_testing);
@@ -416,7 +417,8 @@
   State state_ = State::kDisconnected;
   SockPeerCredMode peer_cred_mode_ = SockPeerCredMode::kDefault;
 
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
   uid_t peer_uid_ = kInvalidUid;
 #endif
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
diff --git a/include/perfetto/ext/ipc/basic_types.h b/include/perfetto/ext/ipc/basic_types.h
index 3931f05..191a5a8 100644
--- a/include/perfetto/ext/ipc/basic_types.h
+++ b/include/perfetto/ext/ipc/basic_types.h
@@ -33,10 +33,13 @@
 using ClientID = uint64_t;
 using RequestID = uint64_t;
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
 // AF_UNIX on Windows is supported only on Windows 10 from build 17063.
 // Also it doesn't bring major advantages compared to a TCP socket.
 // See go/perfetto-win .
+// AF_UNIX sockets are not supported on Fuchsia. The typical IPC flow involves
+// adoption of connected kernel sockets.
 constexpr bool kUseTCPSocket = true;
 #else
 // On Android, Linux, Mac use a AF_UNIX socket.
diff --git a/infra/perfetto-get.appspot.com/main.py b/infra/perfetto-get.appspot.com/main.py
index e4b14a8..015e922 100644
--- a/infra/perfetto-get.appspot.com/main.py
+++ b/infra/perfetto-get.appspot.com/main.py
@@ -22,6 +22,7 @@
        '+/master/%s?format=TEXT'
 
 RESOURCES = {
+    'tracebox': 'tools/tracebox',
     'traceconv': 'tools/traceconv',
     'trace_processor': 'tools/trace_processor',
 }
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 81b6b62..850b6df 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/src/base/utils.cc b/src/base/utils.cc
index 0b08ece..0ec6cdf 100644
--- a/src/base/utils.cc
+++ b/src/base/utils.cc
@@ -99,12 +99,21 @@
       (ecx & (1u << 28)) &&  // AVX supported in hardware
       ((GetXCR0EAX() & xcr0_avx_mask) == xcr0_avx_mask);
 
-  if (!have_sse4_2 || !have_popcnt || !have_avx) {
+  // Get level 7 features (eax = 7 and ecx= 0), to check for AVX2 support.
+  // (See Intel 64 and IA-32 Architectures Software Developer's Manual
+  //  Volume 2A: Instruction Set Reference, A-M CPUID).
+  PERFETTO_GETCPUID(eax, ebx, ecx, edx, 7, 0);
+  const bool have_avx2 = have_avx && ((ebx >> 5) & 0x1);
+
+  if (!have_sse4_2 || !have_popcnt || !have_avx2) {
     fprintf(
         stderr,
-        "This executable requires a cpu that supports SSE4.2 and AVX2.\n"
-        "Rebuild with enable_perfetto_x64_cpu_opt=false (ebx=%x, ecx=%x).\n",
-        ebx, ecx);
+        "This executable requires a X86_64 cpu that supports SSE4.2 and AVX2.\n"
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+        "On MacOS, this might be caused by running x86_64 binaries on arm64.\n"
+        "See https://github.com/google/perfetto/issues/294 for more.\n"
+#endif
+        "Rebuild with enable_perfetto_x64_cpu_opt=false.\n");
     _exit(126);
   }
 }
diff --git a/src/ipc/BUILD.gn b/src/ipc/BUILD.gn
index 39843f1..d093978 100644
--- a/src/ipc/BUILD.gn
+++ b/src/ipc/BUILD.gn
@@ -117,6 +117,13 @@
     "host_impl_unittest.cc",
     "test/ipc_integrationtest.cc",
   ]
+
+  if (is_fuchsia) {
+    deps += [
+      "//third_party/fuchsia-sdk/sdk/pkg/zx",
+      "//third_party/fuchsia-sdk/sdk/pkg/fdio",
+    ]
+  }
 }
 
 perfetto_proto_library("test_messages_@TYPE@") {
diff --git a/src/ipc/client_impl_unittest.cc b/src/ipc/client_impl_unittest.cc
index 0a31a8c..0f46459 100644
--- a/src/ipc/client_impl_unittest.cc
+++ b/src/ipc/client_impl_unittest.cc
@@ -340,8 +340,9 @@
   ASSERT_EQ(kNumReplies, replies_seen);
 }
 
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-// File descriptor sending over IPC is not supported on Windows.
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
+// File descriptor sending over IPC is not supported on Windows or Fuchsia.
 TEST_F(ClientImplTest, ReceiveFileDescriptor) {
   auto* host_svc = host_->AddFakeService("FakeSvc");
   auto* host_method = host_svc->AddFakeMethod("FakeMethod1");
diff --git a/src/ipc/host_impl.cc b/src/ipc/host_impl.cc
index c182cd1..7304702 100644
--- a/src/ipc/host_impl.cc
+++ b/src/ipc/host_impl.cc
@@ -43,7 +43,8 @@
 base::CrashKey g_crash_key_uid("ipc_uid");
 
 uid_t GetPosixPeerUid(base::UnixSocket* sock) {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
   base::ignore_result(sock);
   // Unsupported. Must be != kInvalidUid or the PacketValidator will fail.
   return 0;
diff --git a/src/ipc/host_impl_unittest.cc b/src/ipc/host_impl_unittest.cc
index e1bbee7..4956a4f 100644
--- a/src/ipc/host_impl_unittest.cc
+++ b/src/ipc/host_impl_unittest.cc
@@ -321,7 +321,8 @@
   task_runner_->RunUntilCheckpoint("on_reply_received");
 }
 
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
 // File descriptor sending over IPC is not supported on Windows.
 TEST_F(HostImplTest, SendFileDescriptor) {
   FakeService* fake_service = new FakeService("FakeService");
diff --git a/src/ipc/test/test_socket.h b/src/ipc/test/test_socket.h
index eaafd73..29e9058 100644
--- a/src/ipc/test/test_socket.h
+++ b/src/ipc/test/test_socket.h
@@ -41,7 +41,8 @@
   inline void Destroy();
 };
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
 
 const char* TestSocket::name() {
   uint64_t hash = 5381;
diff --git a/src/trace_processor/containers/nullable_vector.h b/src/trace_processor/containers/nullable_vector.h
index 537e822..44bf4f6 100644
--- a/src/trace_processor/containers/nullable_vector.h
+++ b/src/trace_processor/containers/nullable_vector.h
@@ -118,14 +118,6 @@
     valid_.Insert(size_++);
   }
 
-  // Adds a null value to the NullableVector.
-  void AppendNull() {
-    if (mode_ == Mode::kDense) {
-      data_.emplace_back();
-    }
-    size_++;
-  }
-
   // Adds the given optional value to the NullableVector.
   void Append(base::Optional<T> val) {
     if (val) {
@@ -167,6 +159,13 @@
  private:
   explicit NullableVector(Mode mode) : mode_(mode) {}
 
+  void AppendNull() {
+    if (mode_ == Mode::kDense) {
+      data_.emplace_back();
+    }
+    size_++;
+  }
+
   Mode mode_ = Mode::kSparse;
 
   std::deque<T> data_;
diff --git a/src/trace_processor/containers/nullable_vector_unittest.cc b/src/trace_processor/containers/nullable_vector_unittest.cc
index 37a8b78..1db8d3f 100644
--- a/src/trace_processor/containers/nullable_vector_unittest.cc
+++ b/src/trace_processor/containers/nullable_vector_unittest.cc
@@ -26,7 +26,7 @@
   NullableVector<int64_t> sv;
   sv.Append(10);
   sv.Append(20);
-  sv.AppendNull();
+  sv.Append(base::nullopt);
   sv.Append(40);
 
   ASSERT_FALSE(sv.IsDense());
@@ -41,8 +41,8 @@
   NullableVector<int64_t> sv;
   sv.Append(10);
   sv.Append(20);
-  sv.AppendNull();
-  sv.AppendNull();
+  sv.Append(base::nullopt);
+  sv.Append(base::nullopt);
   sv.Append(40);
 
   sv.Set(0, 15);
@@ -74,10 +74,10 @@
   auto sv = NullableVector<int64_t>::Dense();
 
   sv.Append(0);
-  sv.AppendNull();
+  sv.Append(base::nullopt);
   sv.Append(2);
   sv.Append(3);
-  sv.AppendNull();
+  sv.Append(base::nullopt);
 
   ASSERT_TRUE(sv.IsDense());
   ASSERT_EQ(sv.Get(0), 0);
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index ebbd478..fa695e5 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -16,6 +16,7 @@
 
 source_set("db") {
   sources = [
+    "base_id.h",
     "column.cc",
     "column.h",
     "compare.h",
diff --git a/src/trace_processor/db/base_id.h b/src/trace_processor/db/base_id.h
new file mode 100644
index 0000000..75d479e
--- /dev/null
+++ b/src/trace_processor/db/base_id.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include <stdint.h>
+#include <type_traits>
+
+#ifndef SRC_TRACE_PROCESSOR_DB_BASE_ID_H_
+#define SRC_TRACE_PROCESSOR_DB_BASE_ID_H_
+
+namespace perfetto {
+namespace trace_processor {
+
+// Id type which can be used as a base for strongly typed ids.
+// TypedColumn has support for storing descendents of BaseId seamlessly
+// in a Column.
+struct BaseId {
+  BaseId() = default;
+  explicit constexpr BaseId(uint32_t v) : value(v) {}
+
+  bool operator==(const BaseId& o) const { return o.value == value; }
+  bool operator!=(const BaseId& o) const { return !(*this == o); }
+  bool operator<(const BaseId& o) const { return value < o.value; }
+
+  uint32_t value;
+};
+static_assert(std::is_trivially_destructible<BaseId>::value,
+              "Inheritance used without trivial destruction");
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_BASE_ID_H_
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index c3071b8..88928f4 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -30,22 +30,6 @@
 namespace perfetto {
 namespace trace_processor {
 
-// Id type which can be used as a base for strongly typed ids.
-// TypedColumn has support for storing descendents of BaseId seamlessly
-// in a Column.
-struct BaseId {
-  BaseId() = default;
-  explicit constexpr BaseId(uint32_t v) : value(v) {}
-
-  bool operator==(const BaseId& o) const { return o.value == value; }
-  bool operator!=(const BaseId& o) const { return !(*this == o); }
-  bool operator<(const BaseId& o) const { return value < o.value; }
-
-  uint32_t value;
-};
-static_assert(std::is_trivially_destructible<BaseId>::value,
-              "Inheritance used without trivial destruction");
-
 // Represents the possible filter operations on a column.
 enum class FilterOp {
   kEq,
@@ -300,41 +284,6 @@
     PERFETTO_FATAL("For GCC");
   }
 
-  // Sets the value of the column at the given |row|.
-  void Set(uint32_t row, SqlValue value) {
-    PERFETTO_CHECK(value.type == type());
-    switch (type_) {
-      case ColumnType::kInt32: {
-        mutable_nullable_vector<int32_t>()->Set(
-            row, static_cast<int32_t>(value.long_value));
-        break;
-      }
-      case ColumnType::kUint32: {
-        mutable_nullable_vector<uint32_t>()->Set(
-            row, static_cast<uint32_t>(value.long_value));
-        break;
-      }
-      case ColumnType::kInt64: {
-        mutable_nullable_vector<int64_t>()->Set(
-            row, static_cast<int64_t>(value.long_value));
-        break;
-      }
-      case ColumnType::kDouble: {
-        mutable_nullable_vector<double>()->Set(row, value.double_value);
-        break;
-      }
-      case ColumnType::kString: {
-        PERFETTO_FATAL(
-            "Setting a generic value on a string column is not implemented");
-      }
-      case ColumnType::kId: {
-        PERFETTO_FATAL("Cannot set value on a id column");
-      }
-      case ColumnType::kDummy:
-        PERFETTO_FATAL("Set not allowed on dummy column");
-    }
-  }
-
   // Sorts |idx| in ascending or descending order (determined by |desc|) based
   // on the contents of this column.
   void StableSort(bool desc, std::vector<uint32_t>* idx) const;
diff --git a/src/trace_processor/db/typed_column.h b/src/trace_processor/db/typed_column.h
index 605a02a..7522df0 100644
--- a/src/trace_processor/db/typed_column.h
+++ b/src/trace_processor/db/typed_column.h
@@ -120,22 +120,14 @@
     return Column::ToSqlValueType<serialized_type>();
   }
 
-  // Reinterpret cast a Column to TypedColumn or crash if that is likely to be
-  // unsafe.
-  static const TypedColumn<T>* FromColumn(const Column* column) {
-    // While casting from a base to derived without constructing as a derived is
-    // technically UB, in practice, this is at the heart of protozero (see
-    // Message::BeginNestedMessage) so we use it here.
-    static_assert(sizeof(TypedColumn<T>) == sizeof(Column),
-                  "TypedColumn cannot introduce extra state.");
+  // Cast a Column to TypedColumn or crash if that is unsafe.
+  static TypedColumn<T>* FromColumn(Column* column) {
+    return FromColumnInternal<TypedColumn<T>>(column);
+  }
 
-    if (column->IsColumnType<serialized_type>() &&
-        (column->IsNullable() == TH::is_optional) && !column->IsId()) {
-      return static_cast<const TypedColumn<T>*>(column);
-    } else {
-      PERFETTO_FATAL("Unsafe to convert Column TypedColumn (%s)",
-                     column->name());
-    }
+  // Cast a Column to TypedColumn or crash if that is unsafe.
+  static const TypedColumn<T>* FromColumn(const Column* column) {
+    return FromColumnInternal<const TypedColumn<T>>(column);
   }
 
   // Public for use by macro tables.
@@ -158,6 +150,23 @@
  private:
   friend class Table;
 
+  template <typename Output, typename Input>
+  static Output* FromColumnInternal(Input* column) {
+    // While casting from a base to derived without constructing as a derived is
+    // technically UB, in practice, this is at the heart of protozero (see
+    // Message::BeginNestedMessage) so we use it here.
+    static_assert(sizeof(TypedColumn<T>) == sizeof(Column),
+                  "TypedColumn cannot introduce extra state.");
+
+    if (column->template IsColumnType<serialized_type>() &&
+        (column->IsNullable() == TH::is_optional) && !column->IsId()) {
+      return static_cast<Output*>(column);
+    } else {
+      PERFETTO_FATAL("Unsafe to convert Column TypedColumn (%s)",
+                     column->name());
+    }
+  }
+
   static SqlValue ToValue(double value) { return SqlValue::Double(value); }
   static SqlValue ToValue(uint32_t value) { return SqlValue::Long(value); }
   static SqlValue ToValue(int64_t value) { return SqlValue::Long(value); }
diff --git a/src/trace_processor/db/typed_column_internal.h b/src/trace_processor/db/typed_column_internal.h
index 5e2f618..9001ef6 100644
--- a/src/trace_processor/db/typed_column_internal.h
+++ b/src/trace_processor/db/typed_column_internal.h
@@ -17,7 +17,9 @@
 #ifndef SRC_TRACE_PROCESSOR_DB_TYPED_COLUMN_INTERNAL_H_
 #define SRC_TRACE_PROCESSOR_DB_TYPED_COLUMN_INTERNAL_H_
 
-#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/containers/nullable_vector.h"
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/base_id.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/importers/common/args_tracker.cc b/src/trace_processor/importers/common/args_tracker.cc
index 1b6e0e9..b0036cf 100644
--- a/src/trace_processor/importers/common/args_tracker.cc
+++ b/src/trace_processor/importers/common/args_tracker.cc
@@ -67,20 +67,23 @@
   std::stable_sort(args_.begin(), args_.end(), comparator);
 
   for (uint32_t i = 0; i < args_.size();) {
-    const auto& arg = args_[i];
-    Column* column = arg.column;
-    auto row = arg.row;
+    const GlobalArgsTracker::Arg& arg = args_[i];
+    auto* col = arg.column;
+    uint32_t row = arg.row;
 
     uint32_t next_rid_idx = i + 1;
-    while (next_rid_idx < args_.size() &&
-           column == args_[next_rid_idx].column &&
+    while (next_rid_idx < args_.size() && col == args_[next_rid_idx].column &&
            row == args_[next_rid_idx].row) {
       next_rid_idx++;
     }
 
     ArgSetId set_id =
         context_->global_args_tracker->AddArgSet(&args_[0], i, next_rid_idx);
-    column->Set(row, SqlValue::Long(set_id));
+    if (col->IsNullable()) {
+      TypedColumn<base::Optional<uint32_t>>::FromColumn(col)->Set(row, set_id);
+    } else {
+      TypedColumn<uint32_t>::FromColumn(col)->Set(row, set_id);
+    }
 
     i = next_rid_idx;
   }
diff --git a/src/trace_processor/importers/common/clock_tracker.cc b/src/trace_processor/importers/common/clock_tracker.cc
index 7642777..2bb0ff2 100644
--- a/src/trace_processor/importers/common/clock_tracker.cc
+++ b/src/trace_processor/importers/common/clock_tracker.cc
@@ -200,8 +200,7 @@
     // Expore all the adjacent clocks.
     // The lower_bound() below returns an iterator to the first edge that starts
     // on |cur_clock_id|. The edges are sorted by (src, target, hash).
-    for (auto it = std::lower_bound(graph_.begin(), graph_.end(),
-                                    ClockGraphEdge(cur_clock_id, 0, 0));
+    for (auto it = graph_.lower_bound(ClockGraphEdge(cur_clock_id, 0, 0));
          it != graph_.end() && std::get<0>(*it) == cur_clock_id; ++it) {
       ClockId next_clock_id = std::get<1>(*it);
       SnapshotHash hash = std::get<2>(*it);
diff --git a/src/trace_processor/metrics/sql/android/startup/launches_minsdk33.sql b/src/trace_processor/metrics/sql/android/startup/launches_minsdk33.sql
index f3773bb..25ea8cd 100644
--- a/src/trace_processor/metrics/sql/android/startup/launches_minsdk33.sql
+++ b/src/trace_processor/metrics/sql/android/startup/launches_minsdk33.sql
@@ -30,12 +30,14 @@
 CREATE VIEW launch_complete_events AS
 SELECT
   STR_SPLIT(completed, ':completed:', 0) id,
-  STR_SPLIT(completed, ':completed:', 1) package_name
+  STR_SPLIT(completed, ':completed:', 1) package_name,
+  MIN(ts)
 FROM (
-  SELECT SUBSTR(name, 19) completed
+  SELECT ts, SUBSTR(name, 19) completed
   FROM slice
   WHERE dur = 0 AND name GLOB 'launchingActivity#*:completed:*'
-);
+)
+GROUP BY 1, 2;
 
 INSERT INTO launches(id, ts, ts_end, dur, package)
 SELECT
diff --git a/src/tracing/ipc/posix_shared_memory.cc b/src/tracing/ipc/posix_shared_memory.cc
index 3a59948..a21a290 100644
--- a/src/tracing/ipc/posix_shared_memory.cc
+++ b/src/tracing/ipc/posix_shared_memory.cc
@@ -18,7 +18,8 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) ||   \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
 
 #include <fcntl.h>
 #include <stdint.h>
diff --git a/src/tracing/ipc/posix_shared_memory.h b/src/tracing/ipc/posix_shared_memory.h
index 18c7dd7..0ba1a31 100644
--- a/src/tracing/ipc/posix_shared_memory.h
+++ b/src/tracing/ipc/posix_shared_memory.h
@@ -21,7 +21,8 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
 
 #include <stddef.h>
 
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 4788112..2293a73 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -608,6 +608,11 @@
     return ReadSlicesFromTrace(tracing_session->ReadTraceBlocking());
   }
 
+  std::vector<std::string> StopSessionAndReadSlicesFromTrace(
+      TestTracingSessionHandle* tracing_session) {
+    return ReadSlicesFromTrace(StopSessionAndReturnBytes(tracing_session));
+  }
+
   std::vector<std::string> ReadSlicesFromTrace(
       const std::vector<char>& raw_trace) {
     EXPECT_GE(raw_trace.size(), 0u);
@@ -956,10 +961,9 @@
   // the producer name filter was cleared.
   TRACE_EVENT_BEGIN("foo", "EventIncluded");
   TRACE_EVENT_END("foo");
-  tracing_session->get()->StopBlocking();
 
   // Verify that only the second event is in the trace data.
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
   std::string trace(raw_trace.data(), raw_trace.size());
   EXPECT_THAT(trace, Not(HasSubstr("EventFilteredOut")));
   EXPECT_THAT(trace, HasSubstr("EventIncluded"));
@@ -1251,13 +1255,7 @@
         perfetto::TraceTimestamp{kClockIdIncremental, kInstantEvent2Time},
         empty_lambda);
 
-    perfetto::TrackEvent::Flush();
-    tracing_session->get()->StopBlocking();
-
-    std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-
-    perfetto::protos::gen::Trace trace;
-    ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+    auto trace = StopSessionAndReturnParsedTrace(tracing_session);
     uint64_t absolute_timestamp = 0;
     uint64_t prv_timestamp = 0;
     int event_count = 0;
@@ -1339,8 +1337,7 @@
   TRACE_EVENT_BEGIN("bar", "Enabled");
   TRACE_EVENT_END("bar");
 
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
   std::string trace(raw_trace.data(), raw_trace.size());
   // TODO(skyostil): Come up with a nicer way to verify trace contents.
   EXPECT_THAT(trace, HasSubstr("Enabled"));
@@ -1477,8 +1474,7 @@
   TRACE_EVENT_BEGIN("bar", "DisabledEventFromMain");
   TRACE_EVENT_END("bar");
 
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
   std::string trace(raw_trace.data(), raw_trace.size());
   EXPECT_THAT(trace, HasSubstr("FooEventFromMain"));
   EXPECT_THAT(trace, Not(HasSubstr("DisabledEventFromMain")));
@@ -1547,9 +1543,7 @@
   });
   thread.join();
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
   std::string trace(raw_trace.data(), raw_trace.size());
   EXPECT_THAT(trace, HasSubstr("EventInDynamicCategory"));
   EXPECT_THAT(trace, Not(HasSubstr("EventInDisabledDynamicCategory")));
@@ -1559,8 +1553,7 @@
   EXPECT_THAT(trace,
               Not(HasSubstr("EventInSecondStaticallyNamedDynamicCategory")));
 
-  tracing_session2->get()->StopBlocking();
-  raw_trace = tracing_session2->get()->ReadTraceBlocking();
+  raw_trace = StopSessionAndReturnBytes(tracing_session2);
   trace = std::string(raw_trace.data(), raw_trace.size());
   EXPECT_THAT(trace, Not(HasSubstr("EventInDynamicCategory")));
   EXPECT_THAT(trace, Not(HasSubstr("EventInDisabledDynamicCategory")));
@@ -1739,13 +1732,8 @@
       static_cast<int32_t>(perfetto::base::GetThreadId()));
   desc.mutable_chrome_process()->set_process_priority(123);
   perfetto::TrackEvent::SetTrackDescriptor(track, std::move(desc));
-  perfetto::TrackEvent::Flush();
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   uint32_t main_thread_sequence = GetMainThreadPacketSequenceId(trace);
   bool found_desc = false;
@@ -1799,12 +1787,7 @@
   });
   thread.join();
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   // Check that the track uuids match on the begin and end events.
   const auto track = perfetto::Track(async_id);
@@ -1875,12 +1858,7 @@
                       perfetto::TraceTimestamp{kMyClockId, kTimestamp});
   TRACE_EVENT_INSTANT("foo", "EventWithNormalTime");
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   // Check that both the clock id and the timestamp got written together with
   // the packet. Note that we don't check the actual clock sync behavior here
@@ -1907,12 +1885,7 @@
 
   TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0("cat", "Name", 1,
                                                MyThreadId(456), MyTimestamp{0});
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   // Check that we wrote a track descriptor for the custom thread track, and
   // that the event was associated with that track.
@@ -1952,12 +1925,7 @@
   INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP(
       TRACE_EVENT_PHASE_INSTANT, "cat", "Name", 0, MyThreadId{789},
       MyTimestamp{0}, TRACE_EVENT_FLAG_HAS_PROCESS_ID);
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   // Check that the event has a pid_override matching MyThread above.
   bool found_event = false;
@@ -1981,12 +1949,7 @@
 
   // Emit an event on a custom track.
   TRACE_EVENT_INSTANT("bar", "Event", perfetto::Track(8086));
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   // Check that the descriptor was written before the event.
   std::set<uint64_t> seen_descriptors;
@@ -2020,12 +1983,7 @@
   TRACE_EVENT_INSTANT("bar", "InstantEvent", track, kInstantEventTime,
                       empty_lambda);
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   int event_count = 0;
   for (const auto& packet : trace.packet()) {
@@ -2065,12 +2023,7 @@
   TRACE_EVENT_BEGIN("bar", "Event", track, kBeginEventTime);
   TRACE_EVENT_END("bar", track, kEndEventTime);
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   int event_count = 0;
   for (const auto& packet : trace.packet()) {
@@ -2106,12 +2059,7 @@
   std::thread thread([&] { TRACE_EVENT_END("bar", track); });
   thread.join();
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   // Check that a descriptor for the track was emitted.
   bool found_descriptor = false;
@@ -2145,15 +2093,10 @@
                     });
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  std::string trace(raw_trace.data(), raw_trace.size());
-
-  perfetto::protos::gen::Trace parsed_trace;
-  ASSERT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   bool found_args = false;
-  for (const auto& packet : parsed_trace.packet()) {
+  for (const auto& packet : trace.packet()) {
     if (!packet.has_track_event())
       continue;
     const auto& track_event = packet.track_event();
@@ -2185,15 +2128,10 @@
                     perfetto::protos::pbzero::TrackEvent::kFlowIds, flow_ids);
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  std::string trace(raw_trace.data(), raw_trace.size());
-
-  perfetto::protos::gen::Trace parsed_trace;
-  ASSERT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   bool found_args = false;
-  for (const auto& packet : parsed_trace.packet()) {
+  for (const auto& packet : trace.packet()) {
     if (!packet.has_track_event())
       continue;
     const auto& track_event = packet.track_event();
@@ -2282,8 +2220,7 @@
                     LogMessage());
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 }
 
 TEST_P(PerfettoApiTest, TrackEventThreadTime) {
@@ -2305,19 +2242,13 @@
     TRACE_EVENT_END("foo");
     TRACE_EVENT_END("foo");
 
-    tracing_session->get()->StopBlocking();
-
-    std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-
-    perfetto::protos::gen::Trace parsed_trace;
-    EXPECT_TRUE(
-        parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+    auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
     bool found_counter_track_descriptor = false;
     uint64_t thread_time_counter_uuid = 0;
     uint64_t default_counter_uuid = 0;
     std::unordered_set<std::string> event_names;
-    for (const auto& packet : parsed_trace.packet()) {
+    for (const auto& packet : trace.packet()) {
       if (packet.has_track_descriptor() &&
           packet.track_descriptor().has_counter()) {
         EXPECT_FALSE(found_counter_track_descriptor);
@@ -2376,9 +2307,7 @@
                     LogMessage(), "arg", "value");
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
   std::string trace(raw_trace.data(), raw_trace.size());
 
   // Find typed argument.
@@ -2386,7 +2315,7 @@
 
   // Find untyped argument.
   EXPECT_THAT(ReadSlicesFromTrace(raw_trace),
-              ElementsAre("B:foo.E(arg=(string)value)"));
+              ElementsAre("B:foo.E(arg=(string)value)", "E"));
 }
 
 TEST_P(PerfettoApiTest, TrackEventArgs_UntypedAndTyped) {
@@ -2399,16 +2328,14 @@
                     LogMessage());
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find typed argument.
   CheckLogMessagePresent(raw_trace);
 
   // Find untyped argument.
   EXPECT_THAT(ReadSlicesFromTrace(raw_trace),
-              ElementsAre("B:foo.E(arg=(string)value)"));
+              ElementsAre("B:foo.E(arg=(string)value)", "E"));
 }
 
 TEST_P(PerfettoApiTest, TrackEventArgs_UntypedAndRefLambda) {
@@ -2419,16 +2346,14 @@
   TRACE_EVENT_BEGIN("foo", "E", "arg", "value", GetWriteLogMessageRefLambda());
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find typed argument.
   CheckLogMessagePresent(raw_trace);
 
   // Find untyped argument.
   EXPECT_THAT(ReadSlicesFromTrace(raw_trace),
-              ElementsAre("B:foo.E(arg=(string)value)"));
+              ElementsAre("B:foo.E(arg=(string)value)", "E"));
 }
 
 TEST_P(PerfettoApiTest, TrackEventArgs_RefLambdaAndUntyped) {
@@ -2439,16 +2364,14 @@
   TRACE_EVENT_BEGIN("foo", "E", GetWriteLogMessageRefLambda(), "arg", "value");
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find typed argument.
   CheckLogMessagePresent(raw_trace);
 
   // Find untyped argument.
   EXPECT_THAT(ReadSlicesFromTrace(raw_trace),
-              ElementsAre("B:foo.E(arg=(string)value)"));
+              ElementsAre("B:foo.E(arg=(string)value)", "E"));
 }
 
 TEST_P(PerfettoApiTest, TrackEventArgs_RefLambdaAndTyped) {
@@ -2464,16 +2387,14 @@
       perfetto::protos::pbzero::TrackEvent::kLogMessage, LogMessage());
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find typed argument.
   CheckLogMessagePresent(raw_trace);
 
   // Find untyped argument.
   EXPECT_THAT(ReadSlicesFromTrace(raw_trace),
-              ElementsAre("B:foo.E(arg=(string)value)"));
+              ElementsAre("B:foo.E(arg=(string)value)", "E"));
 }
 
 TEST_P(PerfettoApiTest, TrackEventArgs_TypedAndRefLambda) {
@@ -2488,16 +2409,14 @@
                     });
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find typed argument.
   CheckLogMessagePresent(raw_trace);
 
   // Find untyped argument.
   EXPECT_THAT(ReadSlicesFromTrace(raw_trace),
-              ElementsAre("B:foo.E(arg=(string)value)"));
+              ElementsAre("B:foo.E(arg=(string)value)", "E"));
 }
 
 TEST_P(PerfettoApiTest, TrackEventArgs_RefLambdaAndRefLambda) {
@@ -2515,13 +2434,12 @@
       });
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find untyped arguments.
-  EXPECT_THAT(ReadSlicesFromTrace(raw_trace),
-              ElementsAre("B:foo.E(arg1=(string)value1,arg2=(string)value2)"));
+  EXPECT_THAT(
+      ReadSlicesFromTrace(raw_trace),
+      ElementsAre("B:foo.E(arg1=(string)value1,arg2=(string)value2)", "E"));
 }
 
 TEST_P(PerfettoApiTest, TrackEventArgs_RefLambdaAndLambda) {
@@ -2539,13 +2457,12 @@
       });
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find untyped arguments.
-  EXPECT_THAT(ReadSlicesFromTrace(raw_trace),
-              ElementsAre("B:foo.E(arg1=(string)value1,arg2=(string)value2)"));
+  EXPECT_THAT(
+      ReadSlicesFromTrace(raw_trace),
+      ElementsAre("B:foo.E(arg1=(string)value1,arg2=(string)value2)", "E"));
 }
 
 TEST_P(PerfettoApiTest, TrackEventArgs_RefLambda) {
@@ -2558,13 +2475,11 @@
   });
   TRACE_EVENT_END("foo");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find untyped argument.
   EXPECT_THAT(ReadSlicesFromTrace(raw_trace),
-              ElementsAre("B:foo.E(arg=(string)value)"));
+              ElementsAre("B:foo.E(arg=(string)value)", "E"));
 }
 
 TEST_P(PerfettoApiTest, TrackEventArgs_Flow_Global) {
@@ -2575,9 +2490,7 @@
   TRACE_EVENT_INSTANT("foo", "E1", perfetto::Flow::Global(42));
   TRACE_EVENT_INSTANT("foo", "E2", perfetto::TerminatingFlow::Global(42));
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find typed argument.
   CheckTypedArguments(
@@ -2602,13 +2515,11 @@
   }
   { TRACE_EVENT("foo", "E3", perfetto::TerminatingFlow::Global(3)); }
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
   EXPECT_THAT(ReadSlicesFromTrace(raw_trace),
               ElementsAre("B:foo.E1(flow_ids=1,2,3)", "E",
                           "B:foo.E2(flow_ids=1)(terminating_flow_ids=2)", "E",
-                          "B:foo.E3(terminating_flow_ids=3)"));
+                          "B:foo.E3(terminating_flow_ids=3)", "E"));
 }
 
 TEST_P(PerfettoApiTest, TrackEventArgs_Flow_ProcessScoped) {
@@ -2620,9 +2531,7 @@
   TRACE_EVENT_INSTANT("foo", "E2", perfetto::TerminatingFlow::ProcessScoped(1));
   TRACE_EVENT_INSTANT("foo", "Flush");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find typed arguments.
   CheckTypedArguments(raw_trace, "E1",
@@ -2648,9 +2557,7 @@
   TRACE_EVENT_INSTANT("foo", "E2", perfetto::TerminatingFlow::FromPointer(ptr));
   TRACE_EVENT_INSTANT("foo", "Flush");
 
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
 
   // Find typed arguments.
   CheckTypedArguments(raw_trace, "E1",
@@ -2888,10 +2795,7 @@
     TRACE_EVENT("test", "AnotherEvent");
     TRACE_EVENT("foo", "DisabledEvent");
   }
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(
       slices,
       ElementsAre("B:test.TestEventWithArgs", "E", "B:test.SingleLineTestEvent",
@@ -2921,10 +2825,7 @@
                 });
   }
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
   EXPECT_GE(raw_trace.size(), 0u);
 
   bool found_extension = false;
@@ -2964,10 +2865,7 @@
         std::vector<int>{42});
   }
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
   EXPECT_GE(raw_trace.size(), 0u);
 
   bool found_extension = false;
@@ -3003,10 +2901,7 @@
 
   TRACE_EVENT_INSTANT("test", "TestEvent");
   TRACE_EVENT_INSTANT("test", "AnotherEvent");
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(slices, ElementsAre("I:test.TestEvent", "I:test.AnotherEvent"));
 }
 
@@ -3017,10 +2912,7 @@
 
   TRACE_EVENT_INSTANT("test", "ThreadEvent");
   TRACE_EVENT_INSTANT("test", "GlobalEvent", perfetto::Track::Global(0u));
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(slices,
               ElementsAre("I:test.ThreadEvent", "[track=0]I:test.GlobalEvent"));
 }
@@ -3034,12 +2926,10 @@
   int* ptr = reinterpret_cast<int*>(2);
   TRACE_EVENT_INSTANT("test", "Event",
                       perfetto::Track::FromPointer(ptr, parent_track));
-  perfetto::TrackEvent::Flush();
 
   perfetto::Track track(reinterpret_cast<uintptr_t>(ptr), parent_track);
 
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(slices, ElementsAre("[track=" + std::to_string(track.uuid) +
                                   "]I:test.Event"));
 }
@@ -3068,14 +2958,11 @@
                         perfetto::Track::ThreadScoped(&num));
   });
   t2.join();
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace parsed_trace;
-  ASSERT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   std::unordered_map<std::string, uint64_t> track_uuid_map;
-  for (auto packet : parsed_trace.packet()) {
+  for (auto packet : trace.packet()) {
     if (packet.has_interned_data()) {
       for (auto& ename : packet.interned_data().event_names()) {
         track_uuid_map.emplace(ename.name(), packet.track_event().track_uuid());
@@ -3107,10 +2994,7 @@
       ctx.AddDebugAnnotation("debug_name", "debug_value");
     });
     TRACE_EVENT_BEGIN("test", "Event3");
-    perfetto::TrackEvent::Flush();
-    tracing_session->get()->StopBlocking();
-
-    auto slices = ReadSlicesFromTrace(tracing_session->get());
+    auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
     ASSERT_EQ(3u, slices.size());
     if (flag) {
       EXPECT_EQ("B:test.Event2", slices[1]);
@@ -3150,10 +3034,7 @@
   TRACE_EVENT_BEGIN("test", "E", [&](perfetto::EventContext ctx) {
     ctx.AddDebugAnnotation("debug_annotation", "value");
   });
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(
       slices,
       ElementsAre(
@@ -3178,10 +3059,7 @@
   TRACE_EVENT_BEGIN("test", "E", "custom_arg", MyDebugAnnotation());
   TRACE_EVENT_BEGIN("test", "E", "normal_arg", "x", "custom_arg",
                     std::move(owned_annotation));
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(
       slices,
       ElementsAre(
@@ -3221,10 +3099,7 @@
   TRACE_EVENT_BEGIN("test", "E", "raw_arg", MyRawDebugAnnotation());
   TRACE_EVENT_BEGIN("test", "E", "plain_arg", 42, "raw_arg",
                     MyRawDebugAnnotation());
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(
       slices,
       ElementsAre("B:test.E(raw_arg=(nested)nested_value)",
@@ -3237,10 +3112,7 @@
   tracing_session->get()->StartBlocking();
 
   TRACE_EVENT_BEGIN("test", "E", "arg1", 1, "arg2", 2, "arg3", 3);
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(slices,
               ElementsAre("B:test.E(arg1=(int)1,arg2=(int)2,arg3=(int)3)"));
 }
@@ -3258,17 +3130,10 @@
       "test", "E", "key", "value", [](perfetto::EventContext ctx) {
         ctx.event()->set_log_message()->set_source_location_iid(42);
       });
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  std::string trace(raw_trace.data(), raw_trace.size());
-
-  perfetto::protos::gen::Trace parsed_trace;
-  ASSERT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   bool found_args = false;
-  for (const auto& packet : parsed_trace.packet()) {
+  for (const auto& packet : trace.packet()) {
     if (!packet.has_track_event())
       continue;
     const auto& track_event = packet.track_event();
@@ -3298,20 +3163,13 @@
         ctx->set_source_location_iid(42);
       });
 
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  std::string trace(raw_trace.data(), raw_trace.size());
-
-  perfetto::protos::gen::Trace parsed_trace;
-  ASSERT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
   std::vector<std::string> interned_debug_annotation_names;
   std::vector<std::string> interned_debug_annotation_proto_type_names;
 
   bool found_args = false;
-  for (const auto& packet : parsed_trace.packet()) {
+  for (const auto& packet : trace.packet()) {
     if (packet.has_interned_data()) {
       for (const auto& interned_name :
            packet.interned_data().debug_annotation_names()) {
@@ -3363,10 +3221,7 @@
   for (int i = 0; i < 3; i++)
     TRACE_EVENT_BEGIN0("test", i % 2 ? "Odd" : "Even");
 
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(slices, ElementsAre("B:test.Even", "B:test.Odd", "B:test.Even",
                                   "B:test.Even", "B:test.Odd", "B:test.Even"));
 }
@@ -3419,9 +3274,7 @@
     perfetto::DynamicCategory dyn{"dynamic,bar"};
     TRACE_EVENT_BEGIN(dyn, "DynamicGroupBarEvent");
 
-    perfetto::TrackEvent::Flush();
-    tracing_session->get()->StopBlocking();
-    auto slices = ReadSlicesFromTrace(tracing_session->get());
+    auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
     tracing_session->session.reset();
     return slices;
   };
@@ -3672,16 +3525,8 @@
     packet = ctx.NewTracePacket();
   });
 
-  EXPECT_TRUE(tracing_session->get()->FlushBlocking());
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
 
-  // Deliberately doing ReadTraceBlocking() before StopBlocking() to avoid
-  // hitting the auto scrape-on-stop behavior of the service.
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  tracing_session->get()->StopBlocking();
-
-  ASSERT_GE(raw_trace.size(), 0u);
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
   bool test_packet_found = false;
   for (const auto& packet : trace.packet()) {
     if (!packet.has_for_testing())
@@ -4338,9 +4183,7 @@
   TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP_AND_FLAGS0(
       "cat", "LegacyAsync3", 9001, MyTimestamp{6}, TRACE_EVENT_FLAG_NONE);
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(
       slices,
       ElementsAre(
@@ -4375,9 +4218,7 @@
   std::unique_ptr<MyDebugAnnotation> owned_annotation(new MyDebugAnnotation());
   TRACE_EVENT_BEGIN1("cat", "LegacyEvent", "arg", std::move(owned_annotation));
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(slices,
               ElementsAre("B:cat.LegacyEvent(arg=(json){\"key\": 123})",
                           "B:cat.LegacyEvent(arg=(json){\"key\": 123})"));
@@ -4396,9 +4237,7 @@
   std::unique_ptr<MyDebugAnnotation> owned_annotation(new MyDebugAnnotation());
   TRACE_EVENT_BEGIN1("cat", "LegacyEvent", "arg", std::move(owned_annotation));
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(slices,
               ElementsAre("B:cat.LegacyEvent(arg=(json){\"key\": 123})"));
 
@@ -4419,9 +4258,7 @@
       "cat", "WithScope",
       TRACE_ID_WITH_SCOPE("scope string", TRACE_ID_GLOBAL(0x4000)));
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(slices, ElementsAre("Legacy_S(unscoped_id=4096):cat.UnscopedId",
                                   "Legacy_S(local_id=8192):cat.LocalId",
                                   "Legacy_S(global_id=12288):cat.GlobalId",
@@ -4444,15 +4281,10 @@
   TRACE_EVENT_NESTABLE_ASYNC_END0("cat", "bar", TRACE_ID_WITH_SCOPE("bar", 2));
   TRACE_EVENT_NESTABLE_ASYNC_END0("cat", "bar", TRACE_ID_WITH_SCOPE("bar", 1));
   TRACE_EVENT_NESTABLE_ASYNC_END0("cat", "foo", TRACE_ID_WITH_SCOPE("foo", 1));
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace parsed_trace;
-  ASSERT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
   using LegacyEvent = perfetto::protos::gen::TrackEvent::LegacyEvent;
   std::vector<const LegacyEvent*> legacy_events;
-  for (const auto& packet : parsed_trace.packet()) {
+  for (const auto& packet : trace.packet()) {
     if (packet.has_track_event() && packet.track_event().has_legacy_event()) {
       legacy_events.push_back(&packet.track_event().legacy_event());
     }
@@ -4495,9 +4327,7 @@
                            TRACE_EVENT_FLAG_FLOW_IN, "step", "End");
   }
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(slices,
               ElementsAre("B(bind_id=1234)(flow_direction=2):cat.LatencyInfo."
                           "Flow(step=(string)Begin)",
@@ -4991,9 +4821,7 @@
   { TRACE_EVENT1("cat", "ScopedLegacy", "value", obj.value); }
   obj.mutex.Unlock();
 
-  perfetto::TrackEvent::Flush();
-  tracing_session->get()->StopBlocking();
-  auto slices = ReadSlicesFromTrace(tracing_session->get());
+  auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   EXPECT_THAT(slices, ElementsAre("I:cat.Instant(value=(int)1)",
                                   "[track=0]I:cat.InstantLegacy(value=(int)1)",
                                   "B:cat.Scoped(value=(int)1)", "E",
@@ -5033,12 +4861,7 @@
   TRACE_COUNTER("cat", track1, 98);
   TRACE_COUNTER("cat", track2, 1084);
 
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
   std::unordered_map<uint64_t, std::string> counter_names;
   // Map(Counter name -> counter values)
   std::unordered_map<std::string, std::vector<int64_t>> values;
@@ -5094,13 +4917,7 @@
   TRACE_COUNTER("cat",
                 perfetto::CounterTrack("Power", "GW").set_category("dmc"),
                 MyTimestamp(1985u), 1.21f);
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
   std::map<uint64_t, std::string> counter_names;
   std::vector<std::string> counter_samples;
   for (const auto& packet : trace.packet()) {
@@ -5151,13 +4968,7 @@
 
   // Emit an empty event.
   PERFETTO_INTERNAL_ADD_EMPTY_EVENT();
-  perfetto::TrackEvent::Flush();
-
-  tracing_session->get()->StopBlocking();
-  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
-
-  perfetto::protos::gen::Trace trace;
-  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  auto trace = StopSessionAndReturnParsedTrace(tracing_session);
   auto it = std::find_if(trace.packet().begin(), trace.packet().end(),
                          [](const perfetto::protos::gen::TracePacket& packet) {
                            return packet.has_trace_stats();
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index 01c1207..fba2bca 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -190,12 +190,17 @@
         break;
       case TPM.TPM_COMPUTE_METRIC:
         const metricRes = assertExists(rpc.metricResult) as ComputeMetricResult;
+        const pendingComputeMetric =
+            assertExists(this.pendingComputeMetrics.shift());
         if (metricRes.error && metricRes.error.length > 0) {
-          throw new QueryError(`ComputeMetric() error: ${metricRes.error}`, {
-            query: 'COMPUTE_METRIC',
-          });
+          const error =
+              new QueryError(`ComputeMetric() error: ${metricRes.error}`, {
+                query: 'COMPUTE_METRIC',
+              });
+          pendingComputeMetric.reject(error);
+        } else {
+          pendingComputeMetric.resolve(metricRes);
         }
-        assertExists(this.pendingComputeMetrics.shift()).resolve(metricRes);
         break;
       default:
         console.log(
diff --git a/ui/src/controller/pivot_table_redux_controller.ts b/ui/src/controller/pivot_table_redux_controller.ts
index c72ce02..833b7a9 100644
--- a/ui/src/controller/pivot_table_redux_controller.ts
+++ b/ui/src/controller/pivot_table_redux_controller.ts
@@ -141,6 +141,7 @@
 // Controller responsible for showing the panel with pivot table, as well as
 // executing its queries and post-processing query results.
 export class PivotTableReduxController extends Controller<{}> {
+  static detailsCount = 0;
   engine: Engine;
   lastQueryAreaId = '';
   lastQueryAreaTracks = new Set<string>();
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index 357f246..8aa5dc8 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -169,6 +169,24 @@
   }
 }
 
+// For queries that are supposed to be displayed in the bottom bar, return a
+// name for a tab. Otherwise, return null.
+function userVisibleQueryName(id: string): string|null {
+  if (id === 'command') {
+    return 'Omnibox Query';
+  }
+  if (id === 'analyze-page-query') {
+    return 'Standalone Query';
+  }
+  if (id.startsWith('command_')) {
+    return 'Pinned Query';
+  }
+  if (id.startsWith('pivot_table_details_')) {
+    return 'Pivot Table Details';
+  }
+  return null;
+}
+
 export class DetailsPanel implements m.ClassComponent {
   private detailsHeight = DEFAULT_DETAILS_HEIGHT_PX;
 
@@ -273,16 +291,25 @@
       });
     }
 
-    if (globals.queryResults.has('command')) {
+    const queryResults = [];
+    for (const queryId of globals.queryResults.keys()) {
+      const readableName = userVisibleQueryName(queryId);
+      if (readableName !== null) {
+        queryResults.push({queryId, name: readableName});
+      }
+    }
+
+    for (const {queryId, name} of queryResults) {
       const count =
-          (globals.queryResults.get('command') as QueryResponse).rows.length;
+          (globals.queryResults.get(queryId) as QueryResponse).rows.length;
       detailsPanels.push({
-        key: 'query_result',
-        name: `Query Result (${count})`,
-        vnode: m(QueryTable, {key: 'query', queryId: 'command'})
+        key: `query_result_${queryId}`,
+        name: `${name} (${count})`,
+        vnode: m(QueryTable, {key: `query_${queryId}`, queryId})
       });
     }
 
+
     if (globals.state.nonSerializableState.pivotTableRedux.selectionArea !==
         null) {
       detailsPanels.push({
diff --git a/ui/src/frontend/pivot_table_redux.ts b/ui/src/frontend/pivot_table_redux.ts
index d0ed91b..84ec43b 100644
--- a/ui/src/frontend/pivot_table_redux.ts
+++ b/ui/src/frontend/pivot_table_redux.ts
@@ -25,7 +25,10 @@
   PivotTableReduxResult,
   SortDirection
 } from '../common/state';
-import {PivotTree} from '../controller/pivot_table_redux_controller';
+import {
+  PivotTableReduxController,
+  PivotTree
+} from '../controller/pivot_table_redux_controller';
 
 import {globals} from './globals';
 import {Panel} from './panel';
@@ -179,7 +182,8 @@
               // 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({
-                queryId: 'command',
+                queryId: `pivot_table_details_${
+                    PivotTableReduxController.detailsCount++}`,
                 query,
               }));
             }
diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts
index aa148cb..53339c0 100644
--- a/ui/src/frontend/publish.ts
+++ b/ui/src/frontend/publish.ts
@@ -147,7 +147,7 @@
 
 export function publishQueryResult(args: {id: string, data?: {}}) {
   globals.queryResults.set(args.id, args.data);
-  globals.dispatch(Actions.setCurrentTab({tab: 'query_result'}));
+  globals.dispatch(Actions.setCurrentTab({tab: `query_result_${args.id}`}));
   globals.publishRedraw();
 }
 
diff --git a/ui/src/frontend/topbar.ts b/ui/src/frontend/topbar.ts
index d816249..ccb3a56 100644
--- a/ui/src/frontend/topbar.ts
+++ b/ui/src/frontend/topbar.ts
@@ -34,6 +34,7 @@
 
 let mode: Mode = SEARCH;
 let displayStepThrough = false;
+let queryCount = 0;
 
 function onKeyDown(e: Event) {
   const event = (e as KeyboardEvent);
@@ -59,6 +60,12 @@
   if (mode === SEARCH && key === 'Enter') {
     txt.blur();
   }
+
+  if (mode === COMMAND && key === 'Enter') {
+    const queryId =
+        (event.metaKey || event.ctrlKey) ? `command_${queryCount++}` : 'command';
+    globals.dispatch(Actions.executeQuery({queryId, query: txt.value}));
+  }
 }
 
 function onKeyUp(e: Event) {
@@ -75,10 +82,6 @@
     globals.rafScheduler.scheduleFullRedraw();
     return;
   }
-  if (mode === COMMAND && key === 'Enter') {
-    globals.dispatch(
-        Actions.executeQuery({queryId: 'command', query: txt.value}));
-  }
 }
 
 class Omnibox implements m.ClassComponent {