Merge "Move ScopedReadMmap to ext/base" into main
diff --git a/Android.bp b/Android.bp
index dc3f8de..ece4ad0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -494,6 +494,7 @@
         ":perfetto_include_perfetto_ext_base_base",
         ":perfetto_include_perfetto_ext_base_version",
         ":perfetto_include_perfetto_ext_ipc_ipc",
+        ":perfetto_include_perfetto_ext_protozero_protozero",
         ":perfetto_include_perfetto_ext_traced_sys_stats_counters",
         ":perfetto_include_perfetto_ext_traced_traced",
         ":perfetto_include_perfetto_ext_tracing_core_core",
@@ -1358,6 +1359,7 @@
         ":perfetto_include_perfetto_ext_base_base",
         ":perfetto_include_perfetto_ext_base_version",
         ":perfetto_include_perfetto_ext_ipc_ipc",
+        ":perfetto_include_perfetto_ext_protozero_protozero",
         ":perfetto_include_perfetto_ext_traced_sys_stats_counters",
         ":perfetto_include_perfetto_ext_traced_traced",
         ":perfetto_include_perfetto_ext_tracing_core_core",
@@ -1656,6 +1658,7 @@
         ":perfetto_include_perfetto_ext_base_base",
         ":perfetto_include_perfetto_ext_base_version",
         ":perfetto_include_perfetto_ext_ipc_ipc",
+        ":perfetto_include_perfetto_ext_protozero_protozero",
         ":perfetto_include_perfetto_ext_traced_sys_stats_counters",
         ":perfetto_include_perfetto_ext_traced_traced",
         ":perfetto_include_perfetto_ext_tracing_core_core",
@@ -2025,6 +2028,11 @@
     name: "perfetto_include_perfetto_ext_ipc_ipc",
 }
 
+// GN: //include/perfetto/ext/protozero:protozero
+filegroup {
+    name: "perfetto_include_perfetto_ext_protozero_protozero",
+}
+
 // GN: //include/perfetto/ext/trace_processor:demangle
 filegroup {
     name: "perfetto_include_perfetto_ext_trace_processor_demangle",
@@ -2155,6 +2163,7 @@
         ":perfetto_include_perfetto_ext_base_base",
         ":perfetto_include_perfetto_ext_base_version",
         ":perfetto_include_perfetto_ext_ipc_ipc",
+        ":perfetto_include_perfetto_ext_protozero_protozero",
         ":perfetto_include_perfetto_ext_trace_processor_demangle",
         ":perfetto_include_perfetto_ext_trace_processor_export_json",
         ":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -13661,6 +13670,7 @@
         ":perfetto_include_perfetto_ext_base_threading_threading",
         ":perfetto_include_perfetto_ext_base_version",
         ":perfetto_include_perfetto_ext_ipc_ipc",
+        ":perfetto_include_perfetto_ext_protozero_protozero",
         ":perfetto_include_perfetto_ext_trace_processor_demangle",
         ":perfetto_include_perfetto_ext_trace_processor_export_json",
         ":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -14212,6 +14222,7 @@
         ":perfetto_include_perfetto_ext_base_base",
         ":perfetto_include_perfetto_ext_base_version",
         ":perfetto_include_perfetto_ext_ipc_ipc",
+        ":perfetto_include_perfetto_ext_protozero_protozero",
         ":perfetto_include_perfetto_ext_traced_sys_stats_counters",
         ":perfetto_include_perfetto_ext_traced_traced",
         ":perfetto_include_perfetto_ext_tracing_core_core",
@@ -14527,6 +14538,7 @@
         ":perfetto_include_perfetto_ext_base_base",
         ":perfetto_include_perfetto_ext_base_http_http",
         ":perfetto_include_perfetto_ext_base_version",
+        ":perfetto_include_perfetto_ext_protozero_protozero",
         ":perfetto_include_perfetto_ext_trace_processor_demangle",
         ":perfetto_include_perfetto_ext_trace_processor_export_json",
         ":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -14765,6 +14777,7 @@
         ":perfetto_include_perfetto_base_base",
         ":perfetto_include_perfetto_ext_base_base",
         ":perfetto_include_perfetto_ext_base_version",
+        ":perfetto_include_perfetto_ext_protozero_protozero",
         ":perfetto_include_perfetto_ext_trace_processor_demangle",
         ":perfetto_include_perfetto_ext_trace_processor_export_json",
         ":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
diff --git a/BUILD b/BUILD
index 8e771b0..367a2fe 100644
--- a/BUILD
+++ b/BUILD
@@ -287,6 +287,7 @@
     hdrs = [
         ":include_perfetto_base_base",
         ":include_perfetto_ext_base_base",
+        ":include_perfetto_ext_protozero_protozero",
         ":include_perfetto_ext_trace_processor_demangle",
         ":include_perfetto_ext_trace_processor_export_json",
         ":include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -474,6 +475,7 @@
         ":include_perfetto_base_base",
         ":include_perfetto_ext_base_base",
         ":include_perfetto_ext_ipc_ipc",
+        ":include_perfetto_ext_protozero_protozero",
         ":include_perfetto_ext_traced_sys_stats_counters",
         ":include_perfetto_ext_traced_traced",
         ":include_perfetto_ext_tracing_core_core",
@@ -651,6 +653,14 @@
     ],
 )
 
+# GN target: //include/perfetto/ext/protozero:protozero
+perfetto_filegroup(
+    name = "include_perfetto_ext_protozero_protozero",
+    srcs = [
+        "include/perfetto/ext/protozero/proto_ring_buffer.h",
+    ],
+)
+
 # GN target: //include/perfetto/ext/trace_processor/importers/memory_tracker:memory_tracker
 perfetto_filegroup(
     name = "include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -1310,7 +1320,6 @@
     name = "src_protozero_proto_ring_buffer",
     srcs = [
         "src/protozero/proto_ring_buffer.cc",
-        "src/protozero/proto_ring_buffer.h",
     ],
 )
 
@@ -5717,6 +5726,7 @@
     srcs = [
         ":include_perfetto_base_base",
         ":include_perfetto_ext_base_base",
+        ":include_perfetto_ext_protozero_protozero",
         ":include_perfetto_ext_trace_processor_demangle",
         ":include_perfetto_ext_trace_processor_export_json",
         ":include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -5942,6 +5952,7 @@
     srcs = [
         ":include_perfetto_base_base",
         ":include_perfetto_ext_base_base",
+        ":include_perfetto_ext_protozero_protozero",
         ":include_perfetto_ext_trace_processor_demangle",
         ":include_perfetto_ext_trace_processor_export_json",
         ":include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
diff --git a/include/perfetto/ext/base/version.h b/include/perfetto/ext/base/version.h
index 212424a..ad78372 100644
--- a/include/perfetto/ext/base/version.h
+++ b/include/perfetto/ext/base/version.h
@@ -20,9 +20,27 @@
 namespace perfetto {
 namespace base {
 
-// The returned pointer is a static string is safe to pass around.
+// The returned pointer is a static string and safe to pass around.
+// Returns a human readable string currently of the approximate form:
+// Perfetto v42.1-deadbeef0 (deadbeef03c641e4b4ea9cf38e9b5696670175a9)
+// However you should not depend on the format of this string.
+// It maybe not be possible to determine the version. In which case the
+// string will be of the approximate form:
+// Perfetto v0.0 (unknown)
 const char* GetVersionString();
 
+// The returned pointer is a static string and safe to pass around.
+// Returns the short code used to identity the version:
+// v42.1-deadbeef0
+// It maybe not be possible to determine the version. In which case
+// this returns nullptr.
+// This can be compared with equality to other
+// version codes to detect matched builds (for example to see if
+// trace_processor_shell and the UI were built at the same revision)
+// but you should not attempt to parse it as the format may change
+// without warning.
+const char* GetVersionCode();
+
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/include/perfetto/ext/protozero/BUILD.gn b/include/perfetto/ext/protozero/BUILD.gn
new file mode 100644
index 0000000..5354e2c
--- /dev/null
+++ b/include/perfetto/ext/protozero/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../../gn/perfetto.gni")
+
+source_set("protozero") {
+  public_deps = [ "../base" ]
+  deps = [ "../../../../gn:default_deps" ]
+  sources = [ "proto_ring_buffer.h" ]
+}
diff --git a/src/protozero/proto_ring_buffer.h b/include/perfetto/ext/protozero/proto_ring_buffer.h
similarity index 96%
rename from src/protozero/proto_ring_buffer.h
rename to include/perfetto/ext/protozero/proto_ring_buffer.h
index 6459426..8db542c 100644
--- a/src/protozero/proto_ring_buffer.h
+++ b/include/perfetto/ext/protozero/proto_ring_buffer.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_PROTOZERO_PROTO_RING_BUFFER_H_
-#define SRC_PROTOZERO_PROTO_RING_BUFFER_H_
+#ifndef INCLUDE_PERFETTO_EXT_PROTOZERO_PROTO_RING_BUFFER_H_
+#define INCLUDE_PERFETTO_EXT_PROTOZERO_PROTO_RING_BUFFER_H_
 
 #include <stdint.h>
 
@@ -150,4 +150,4 @@
 
 }  // namespace protozero
 
-#endif  // SRC_PROTOZERO_PROTO_RING_BUFFER_H_
+#endif  // INCLUDE_PERFETTO_EXT_PROTOZERO_PROTO_RING_BUFFER_H_
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index c96caa0..1e579ca 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -38,9 +38,18 @@
 
 enum TraceProcessorApiVersion {
   // This variable has been introduced in v15 and is used to deal with API
-  // mismatches between UI and trace_processor_shell --httpd. Increment this
-  // every time a new feature that the UI depends on is being introduced (e.g.
-  // new tables, new SQL operators, metrics that are required by the UI).
+  // mismatches between UI and trace_processor_shell --httpd.
+  //
+  // Prior to API version 11 this was incremented every time a new
+  // feature that the UI depended on was introduced (e.g. new tables,
+  // new SQL operators, metrics that are required by the UI, etc).
+  // This:
+  // a. Tended to be forgotten
+  // b. Still led to issues when the TP dropped *backwards*
+  //    compatibility of a feature (since we checked TP >= UI
+  //    TRACE_PROCESSOR_CURRENT_API_VERSION).
+  // Now the UI attempts to redirect the user to the matched version
+  // of the UI if one exists.
   // See also StatusResult.api_version (below).
   // Changes:
   // 7. Introduce GUESS_CPU_SIZE
@@ -48,10 +57,12 @@
   // 9. Add get_thread_state_summary_for_interval.
   // 10. Add 'slice_is_ancestor' to stdlib.
   // 11. Removal of experimental module from stdlib.
-  TRACE_PROCESSOR_CURRENT_API_VERSION = 11;
+  // 12. Changed UI to be more aggresive about version matching.
+  //     Added version_code.
+  TRACE_PROCESSOR_CURRENT_API_VERSION = 12;
 }
 
-// At lowest level, the wire-format of the RPC procol is a linear sequence of
+// At lowest level, the wire-format of the RPC protocol is a linear sequence of
 // TraceProcessorRpc messages on each side of the byte pipe
 // Each message is prefixed by a tag (field = 1, type = length delimited) and a
 // varint encoding its size (this is so the whole stream can also be read /
@@ -236,6 +247,15 @@
   // The API version is incremented every time a change that the UI depends
   // on is introduced (e.g. adding a new table that the UI queries).
   optional int32 api_version = 3;
+
+  // Typically something like "v42.1-deadbeef0", but could be just
+  // "v42", "v0.0", or unset for binaries built from Bazel or other
+  // build configurations. This can be compared with equality to other
+  // version codes to detect matched builds (for example to see if
+  // trace_processor_shell and the UI were built at the same revision)
+  // but you should not attempt to parse it as the format may change
+  // without warning.
+  optional string version_code = 4;
 }
 
 // Input for the /compute_metric endpoint.
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index 261ab0b..ce0bc66 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/src/base/version.cc b/src/base/version.cc
index 18569d3..e989534 100644
--- a/src/base/version.cc
+++ b/src/base/version.cc
@@ -23,18 +23,26 @@
 #if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
 #include "perfetto_version.gen.h"
 #else
-#define PERFETTO_VERSION_STRING() "v0.0"
+#define PERFETTO_VERSION_STRING() nullptr
 #define PERFETTO_VERSION_SCM_REVISION() "unknown"
 #endif
 
 namespace perfetto {
 namespace base {
 
+const char* GetVersionCode() {
+  return PERFETTO_VERSION_STRING();
+}
+
 const char* GetVersionString() {
   static const char* version_str = [] {
     static constexpr size_t kMaxLen = 256;
+    const char* version_code = PERFETTO_VERSION_STRING();
+    if (version_code == nullptr) {
+      version_code = "v0.0";
+    }
     char* version = new char[kMaxLen + 1];
-    snprintf(version, kMaxLen, "Perfetto %s (%s)", PERFETTO_VERSION_STRING(),
+    snprintf(version, kMaxLen, "Perfetto %s (%s)", version_code,
              PERFETTO_VERSION_SCM_REVISION());
     return version;
   }();
diff --git a/src/protozero/BUILD.gn b/src/protozero/BUILD.gn
index 0082a8c..4d940bc 100644
--- a/src/protozero/BUILD.gn
+++ b/src/protozero/BUILD.gn
@@ -45,15 +45,13 @@
 }
 
 source_set("proto_ring_buffer") {
+  public_deps = [ "../../include/perfetto/ext/protozero" ]
   deps = [
     ":protozero",
     "../../gn:default_deps",
     "../base",
   ]
-  sources = [
-    "proto_ring_buffer.cc",
-    "proto_ring_buffer.h",
-  ]
+  sources = [ "proto_ring_buffer.cc" ]
 }
 
 perfetto_unittest_source_set("unittests") {
diff --git a/src/protozero/proto_ring_buffer.cc b/src/protozero/proto_ring_buffer.cc
index 6f82a42..7b12a1b 100644
--- a/src/protozero/proto_ring_buffer.cc
+++ b/src/protozero/proto_ring_buffer.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/protozero/proto_ring_buffer.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/paged_memory.h"
diff --git a/src/protozero/proto_ring_buffer_unittest.cc b/src/protozero/proto_ring_buffer_unittest.cc
index 30cc3f1..5766d0a 100644
--- a/src/protozero/proto_ring_buffer_unittest.cc
+++ b/src/protozero/proto_ring_buffer_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/protozero/proto_ring_buffer.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
 
 #include <stdint.h>
 #include <sys/types.h>
diff --git a/src/protozero/test/proto_ring_buffer_benchmark.cc b/src/protozero/test/proto_ring_buffer_benchmark.cc
index b65da3b..0c0f7f9 100644
--- a/src/protozero/test/proto_ring_buffer_benchmark.cc
+++ b/src/protozero/test/proto_ring_buffer_benchmark.cc
@@ -18,8 +18,8 @@
 #include <string>
 
 #include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
 #include "src/base/test/utils.h"
-#include "src/protozero/proto_ring_buffer.h"
 
 static void BM_ProtoRingBufferReadLargeChunks(benchmark::State& state) {
   std::string trace_data;
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index 68e0732..58fb245 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -24,11 +24,11 @@
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/version.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
 #include "perfetto/ext/trace_processor/rpc/query_result_serializer.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/protozero/scattered_stream_writer.h"
 #include "perfetto/trace_processor/trace_processor.h"
-#include "src/protozero/proto_ring_buffer.h"
 #include "src/trace_processor/tp_metatrace.h"
 
 #include "protos/perfetto/trace_processor/trace_processor.pbzero.h"
@@ -491,6 +491,10 @@
   protozero::HeapBuffered<protos::pbzero::StatusResult> status;
   status->set_loaded_trace_name(trace_processor_->GetCurrentTraceName());
   status->set_human_readable_version(base::GetVersionString());
+  const char* version_code = base::GetVersionCode();
+  if (version_code) {
+    status->set_version_code(version_code);
+  }
   status->set_api_version(protos::pbzero::TRACE_PROCESSOR_CURRENT_API_VERSION);
   return status.SerializeAsArray();
 }
diff --git a/src/trace_processor/rpc/rpc.h b/src/trace_processor/rpc/rpc.h
index c2ef645..c10d4bf 100644
--- a/src/trace_processor/rpc/rpc.h
+++ b/src/trace_processor/rpc/rpc.h
@@ -24,9 +24,9 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/status.h"
-#include "src/protozero/proto_ring_buffer.h"
 
 namespace perfetto {
 
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 4d38bd6..51e95d6 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -434,7 +434,19 @@
 DbSqliteTable::Cursor::Cursor(DbSqliteTable* sqlite_table, QueryCache* cache)
     : SqliteTable::BaseCursor(sqlite_table),
       db_sqlite_table_(sqlite_table),
-      cache_(cache) {}
+      cache_(cache) {
+  switch (db_sqlite_table_->context_->computation) {
+    case TableComputation::kStatic:
+      upstream_table_ = db_sqlite_table_->context_->static_table;
+      break;
+    case TableComputation::kRuntime:
+      upstream_table_ = db_sqlite_table_->runtime_table_;
+      break;
+    case TableComputation::kTableFunction: {
+      break;
+    }
+  }
+}
 DbSqliteTable::Cursor::~Cursor() = default;
 
 void DbSqliteTable::Cursor::TryCacheCreateSortedTable(
@@ -535,17 +547,7 @@
   // Setup the upstream table based on the computation state.
   switch (db_sqlite_table_->context_->computation) {
     case TableComputation::kStatic:
-      // If we have a static table, just set the upstream table to be the static
-      // table.
-      upstream_table_ = db_sqlite_table_->context_->static_table;
-
-      // Tries to create a sorted cached table which can be used to speed up
-      // filters below.
-      TryCacheCreateSortedTable(qc, history);
-      break;
     case TableComputation::kRuntime:
-      upstream_table_ = db_sqlite_table_->runtime_table_;
-
       // Tries to create a sorted cached table which can be used to speed up
       // filters below.
       TryCacheCreateSortedTable(qc, history);
diff --git a/src/traceconv/trace_to_text.cc b/src/traceconv/trace_to_text.cc
index 1deac65..ef7e03a 100644
--- a/src/traceconv/trace_to_text.cc
+++ b/src/traceconv/trace_to_text.cc
@@ -19,7 +19,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
-#include "src/protozero/proto_ring_buffer.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
 #include "src/traceconv/trace.descriptor.h"
 #include "src/traceconv/utils.h"
 
diff --git a/src/traced/probes/statsd_client/statsd_binder_data_source.h b/src/traced/probes/statsd_client/statsd_binder_data_source.h
index 6268eb7..19aa5f5 100644
--- a/src/traced/probes/statsd_client/statsd_binder_data_source.h
+++ b/src/traced/probes/statsd_client/statsd_binder_data_source.h
@@ -22,10 +22,10 @@
 #include "perfetto/base/task_runner.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/weak_ptr.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/trace_writer.h"
 #include "perfetto/tracing/core/forward_decls.h"
-#include "src/protozero/proto_ring_buffer.h"
 #include "src/traced/probes/probes_data_source.h"
 
 namespace perfetto {
diff --git a/ui/src/assets/modal.scss b/ui/src/assets/modal.scss
index f7ed990..25e7706 100644
--- a/ui/src/assets/modal.scss
+++ b/ui/src/assets/modal.scss
@@ -127,7 +127,7 @@
     background-color: #e6e6e6;
     color: rgba(0, 0, 0, 0.8);
     border: 2px solid transparent;
-    border-radius: 4px;
+    border-radius: $pf-border-radius;
     cursor: pointer;
     text-transform: none;
     overflow: visible;
diff --git a/ui/src/frontend/aggregation_tab.ts b/ui/src/frontend/aggregation_tab.ts
index 44166f0..30dd743 100644
--- a/ui/src/frontend/aggregation_tab.ts
+++ b/ui/src/frontend/aggregation_tab.ts
@@ -21,14 +21,21 @@
 import {Button, ButtonBar} from '../widgets/button';
 import {raf} from '../core/raf_scheduler';
 import {EmptyState} from '../widgets/empty_state';
+import {FlowEventsAreaSelectedPanel} from './flow_events_panel';
+import {PivotTable} from './pivot_table';
+
+interface View {
+  key: string;
+  name: string;
+  content: m.Children;
+};
 
 class AreaDetailsPanel implements m.ClassComponent {
   private currentTab: string|undefined = undefined;
 
-  private getCurrentAggType(): string|undefined {
-    const types = Array.from(globals.aggregateDataStore.entries())
-      .filter(([_, value]) => !isEmptyData(value))
-      .map(([type, _]) => type);
+  private getCurrentView(): string|undefined {
+    const types = this.getViews()
+      .map(({key}) => key);
 
     if (types.length === 0) {
       return undefined;
@@ -45,47 +52,80 @@
     return this.currentTab;
   }
 
-  view(_: m.Vnode): m.Children {
-    const aggregationButtons = Array.from(globals.aggregateDataStore.entries())
-      .filter(([_, value]) => !isEmptyData(value))
-      .map(([type, value]) => {
-        return m(Button,
-          {
-            onclick: () => {
-              this.currentTab = type;
-              raf.scheduleFullRedraw();
-            },
-            key: type,
-            label: value.tabName,
-            active: this.getCurrentAggType() === type,
-            minimal: true,
-          },
-        );
+  private getViews(): View[] {
+    const views = [];
+
+    for (const [key, value] of globals.aggregateDataStore.entries()) {
+      if (!isEmptyData(value)) {
+        views.push({
+          key: value.tabName,
+          name: value.tabName,
+          content: m(AggregationPanel, {kind: key, key, data: value}),
+        });
+      }
+    }
+
+    // Add this after all aggregation panels, to make it appear after 'Slices'
+    if (globals.selectedFlows.length > 0) {
+      views.push({
+        key: 'selected_flows',
+        name: 'Flow Events',
+        content: m(FlowEventsAreaSelectedPanel),
       });
+    }
 
-    const content = this.renderAggregationContent();
+    const pivotTableState = globals.state.nonSerializableState.pivotTable;
+    if (pivotTableState.selectionArea !== undefined) {
+      views.push({
+        key: 'pivot_table',
+        name: 'Pivot Table',
+        content: m(PivotTable, {
+          selectionArea:
+              pivotTableState.selectionArea,
+        }),
+      });
+    }
 
+    return views;
+  }
+
+  view(_: m.Vnode): m.Children {
+    const views = this.getViews();
+    const currentViewKey = this.getCurrentView();
+
+    const aggregationButtons = views.map(({key, name}) => {
+      return m(Button,
+        {
+          onclick: () => {
+            this.currentTab = key;
+            raf.scheduleFullRedraw();
+          },
+          key,
+          label: name,
+          active: currentViewKey === key,
+          minimal: true,
+        },
+      );
+    });
+
+    if (currentViewKey === undefined) {
+      return this.renderEmptyState();
+    }
+
+    const content = views.find(({key}) => key === currentViewKey)?.content;
     if (content === undefined) {
       return this.renderEmptyState();
     }
 
     return m(DetailsShell,
       {
-        title: 'Aggregate',
+        title: 'Area Selection',
         description: m(ButtonBar, aggregationButtons),
       },
       content,
     );
   }
 
-  private renderAggregationContent(): m.Children {
-    const currentTab = this.getCurrentAggType();
-    if (currentTab === undefined) return undefined;
-
-    const data = globals.aggregateDataStore.get(currentTab);
-    return m(AggregationPanel, {kind: currentTab, data});
-  }
-
   private renderEmptyState(): m.Children {
     return m(EmptyState, {
       className: 'pf-noselection',
diff --git a/ui/src/frontend/router.ts b/ui/src/frontend/router.ts
index 29028b0..c6c316b 100644
--- a/ui/src/frontend/router.ts
+++ b/ui/src/frontend/router.ts
@@ -308,4 +308,40 @@
       throw new Error('History rewriting livelock');
     }
   }
+
+  static getUrlForVersion(versionCode: string): string {
+    const url = `${window.location.origin}/${versionCode}/`;
+    return url;
+  }
+
+  static async isVersionAvailable(versionCode: string):
+        Promise<string|undefined> {
+    if (versionCode === '') {
+      return undefined;
+    }
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 1000);
+    const url = Router.getUrlForVersion(versionCode);
+    let r;
+    try {
+      r = await fetch(url, {signal: controller.signal});
+    } catch (e) {
+      console.error(`No UI version for ${versionCode} at ${url}. This is an error if ${versionCode} is a released Perfetto version`);
+      return undefined;
+    } finally {
+      clearTimeout(timeoutId);
+    }
+    if (!r.ok) {
+      return undefined;
+    }
+    return url;
+  }
+
+  static navigateToVersion(versionCode: string): void {
+    const url = Router.getUrlForVersion(versionCode);
+    if (url === undefined) {
+      throw new Error(`No URL known for UI version ${versionCode}.`);
+    }
+    window.location.replace(url);
+  }
 }
diff --git a/ui/src/frontend/rpc_http_dialog.ts b/ui/src/frontend/rpc_http_dialog.ts
index f29c846..97c186e 100644
--- a/ui/src/frontend/rpc_http_dialog.ts
+++ b/ui/src/frontend/rpc_http_dialog.ts
@@ -20,6 +20,7 @@
 import {StatusResult, TraceProcessorApiVersion} from '../protos';
 import {HttpRpcEngine} from '../trace_processor/http_rpc_engine';
 import {showModal} from '../widgets/modal';
+import {Router} from './router';
 
 import {globals} from './globals';
 import {publishHttpRpcState} from './publish';
@@ -27,9 +28,9 @@
 const CURRENT_API_VERSION =
     TraceProcessorApiVersion.TRACE_PROCESSOR_CURRENT_API_VERSION;
 
-const PROMPT = () => `Trace Processor Native Accelerator detected on ` +
-`${HttpRpcEngine.hostAndPort} with:
-$loadedTraceName
+function getPromptMessage(tpStatus: StatusResult): string {
+  return `Trace Processor Native Accelerator detected on ${HttpRpcEngine.hostAndPort} with:
+${tpStatus.loadedTraceName}
 
 YES, use loaded trace:
 Will load from the current state of Trace Processor. If you did run
@@ -46,13 +47,11 @@
 Using the native accelerator has some minor caveats:
 - Only one tab can be using the accelerator.
 - Sharing, downloading and conversion-to-legacy aren't supported.
-- You may encounter UI errors if the Trace Processor version you are using is
-too old. Get the latest version from get.perfetto.dev/trace_processor.
 `;
+}
 
-
-const MSG_TOO_OLD = () => `The Trace Processor instance on ` +
-`${HttpRpcEngine.hostAndPort} is too old.
+function getIncompatibleRpcMessage(tpStatus: StatusResult): string {
+  return `The Trace Processor instance on ${HttpRpcEngine.hostAndPort} is too old.
 
 This UI requires TraceProcessor features that are not present in the
 Trace Processor native accelerator you are currently running.
@@ -64,14 +63,88 @@
 chmod +x ./trace_processor
 ./trace_processor --httpd
 
-UI version: ${VERSION}
-TraceProcessor RPC API required: ${CURRENT_API_VERSION} or higher
+UI version code: ${VERSION}
+UI RPC API: ${CURRENT_API_VERSION}
 
-TraceProcessor version: $tpVersion
-RPC API: $tpApi
+Trace processor version: ${tpStatus.humanReadableVersion}
+Trace processor version code: ${tpStatus.versionCode}
+Trace processor RPC API: ${tpStatus.apiVersion}
 `;
+}
 
-let forceUseOldVersion = false;
+function getVersionMismatchMessage(tpStatus: StatusResult): string {
+  return `The trace processor instance on ${HttpRpcEngine.hostAndPort} is a different build from the UI.
+
+This may cause problems. Where possible it is better to use the matched version of the UI.
+You can do this by clicking the button below.
+
+UI version code: ${VERSION}
+UI RPC API: ${CURRENT_API_VERSION}
+
+Trace processor version: ${tpStatus.humanReadableVersion}
+Trace processor version code: ${tpStatus.versionCode}
+Trace processor RPC API: ${tpStatus.apiVersion}
+`;
+}
+
+// The flow is fairly complicated:
+// +-----------------------------------+
+// |        User loads the UI          |
+// +-----------------+-----------------+
+//                   |
+// +-----------------+-----------------+
+// |   Is trace_processor present at   |
+// |   HttpRpcEngine.hostAndPort?      |
+// +--------------------------+--------+
+//    |No                     |Yes
+//    |        +--------------+-------------------------------+
+//    |        |  Does version code of UI and TP match?       |
+//    |        +--------------+----------------------------+--+
+//    |                       |No                          |Yes
+//    |                       |                            |
+//    |                       |                            |
+//    |         +-------------+-------------+              |
+//    |         |Is a build of the UI at the|              |
+//    |         |TP version code existant   |              |
+//    |         |and reachable?             |              |
+//    |         +---+----------------+------+              |
+//    |             | No             | Yes                 |
+//    |             |                |                     |
+//    |             |       +--------+-------+             |
+//    |             |       |Dialog: Mismatch|             |
+//    |             |       |Load matched UI +-------------------------------+
+//    |             |       |Continue        +-+           |                 |
+//    |             |       +----------------+ |           |                 |
+//    |             |                          |           |                 |
+//    |      +------+--------------------------+----+      |                 |
+//    |      |TP RPC version >= UI RPC version      |      |                 |
+//    |      +----+-------------------+-------------+      |                 |
+//    |           | No                |Yes                 |                 |
+//    |      +----+--------------+    |                    |                 |
+//    |      |Dialog: Bad RPC    |    |                    |                 |
+//    |  +---+Use built-in WASM  |    |                    |                 |
+//    |  |   |Continue anyway    +----|                    |                 |
+//    |  |   +-------------------+    |        +-----------+-----------+     |
+//    |  |                            +--------+TP has preloaded trace?|     |
+//    |  |                                     +-+---------------+-----+     |
+//    |  |                                       |No             |Yes        |
+//    |  |                                       |  +---------------------+  |
+//    |  |                                       |  | Dialog: Preloaded?  |  |
+//    |  |                                       +--+ YES, use loaded trace  |
+//    |  |                                 +--------| YES, but reset state|  |
+//    |  |  +---------------------------------------| NO, Use builtin Wasm|  |
+//    |  |  |                              |     |  +---------------------+  |
+//    |  |  |                              |     |                           |
+//    |  |  |                           Reset TP |                           |
+//    |  |  |                              |     |                           |
+//    |  |  |                              |     |                           |
+//  Show the UI                         Show the UI                  Link to
+//  (WASM mode)                         (RPC mode)                   matched UI
+
+// There are three options in the end:
+// - Show the UI (WASM mode)
+// - Show the UI (RPC mode)
+// - Redirect to a matched version of the UI
 
 // Try to connect to the external Trace Processor HTTP RPC accelerator (if
 // available, often it isn't). If connected it will populate the
@@ -83,73 +156,184 @@
 export async function CheckHttpRpcConnection(): Promise<void> {
   const state = await HttpRpcEngine.checkConnection();
   publishHttpRpcState(state);
-  if (!state.connected) return;
+  if (!state.connected) {
+    // No RPC = exit immediately to the WASM UI.
+    return;
+  }
   const tpStatus = assertExists(state.status);
 
-  if (tpStatus.apiVersion < CURRENT_API_VERSION) {
-    await showDialogTraceProcessorTooOld(tpStatus);
-    if (!forceUseOldVersion) return;
+  function forceWasm() {
+    globals.dispatch(Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
   }
 
+  // Check short version:
+  if (tpStatus.versionCode !== '' && tpStatus.versionCode !== VERSION) {
+    const url = await Router.isVersionAvailable(tpStatus.versionCode);
+    if (url !== undefined) {
+      // If matched UI available show a dialog asking the user to
+      // switch.
+      const result = await showDialogVersionMismatch(tpStatus, url);
+      switch (result) {
+      case MismatchedVersionDialog.Dismissed:
+      case MismatchedVersionDialog.UseMatchingUi:
+        Router.navigateToVersion(tpStatus.versionCode);
+        return;
+      case MismatchedVersionDialog.UseMismatchedRpc:
+        break;
+      case MismatchedVersionDialog.UseWasm:
+        forceWasm();
+        return;
+      default:
+        const x: never = result;
+        throw new Error(`Unsupported result ${x}`);
+      }
+    }
+  }
+
+  // Check the RPC version:
+  if (tpStatus.apiVersion < CURRENT_API_VERSION) {
+    const result = await showDialogIncompatibleRPC(tpStatus);
+    switch (result) {
+    case IncompatibleRpcDialogResult.Dismissed:
+    case IncompatibleRpcDialogResult.UseWasm:
+      forceWasm();
+      return;
+    case IncompatibleRpcDialogResult.UseIncompatibleRpc:
+      break;
+    default:
+      const x: never = result;
+      throw new Error(`Unsupported result ${x}`);
+    }
+  }
+
+  // Check if pre-loaded:
   if (tpStatus.loadedTraceName) {
     // If a trace is already loaded in the trace processor (e.g., the user
     // launched trace_processor_shell -D trace_file.pftrace), prompt the user to
     // initialize the UI with the already-loaded trace.
-    return showDialogToUsePreloadedTrace(tpStatus);
+    const result = await showDialogToUsePreloadedTrace(tpStatus);
+    switch (result) {
+    case PreloadedDialogResult.Dismissed:
+    case PreloadedDialogResult.UseRpcWithPreloadedTrace:
+      globals.dispatch(Actions.openTraceFromHttpRpc({}));
+      return;
+    case PreloadedDialogResult.UseRpc:
+      // Resetting state is the default.
+      return;
+    case PreloadedDialogResult.UseWasm:
+      forceWasm();
+      return;
+    default:
+      const x: never = result;
+      throw new Error(`Unsupported result ${x}`);
+    }
   }
 }
 
-async function showDialogTraceProcessorTooOld(tpStatus: StatusResult) {
-  return showModal({
-    title: 'Your Trace Processor binary is outdated',
-    content:
-        m('.modal-pre',
-          MSG_TOO_OLD().replace('$tpVersion', tpStatus.humanReadableVersion)
-            .replace('$tpApi', `${tpStatus.apiVersion}`)),
+enum MismatchedVersionDialog {
+  UseMatchingUi = 'useMatchingUi',
+  UseWasm = 'useWasm',
+  UseMismatchedRpc = 'useMismatchedRpc',
+  Dismissed = 'dismissed',
+}
+
+async function showDialogVersionMismatch(tpStatus: StatusResult,
+  url: string):
+            Promise<MismatchedVersionDialog> {
+  let result = MismatchedVersionDialog.Dismissed;
+  await showModal({
+    title: 'Version mismatch',
+    content: m('.modal-pre', getVersionMismatchMessage(tpStatus)),
+    buttons: [
+      {
+        primary: true,
+        text: `Open ${url}`,
+        action: () => {
+          result = MismatchedVersionDialog.UseMatchingUi;
+        },
+      },
+      {
+        text: 'Use builtin Wasm',
+        action: () => {
+          result = MismatchedVersionDialog.UseWasm;
+        },
+      },
+      {
+        text: 'Use mismatched version regardless (might crash)',
+        action: () => {
+          result = MismatchedVersionDialog.UseMismatchedRpc;
+        },
+      },
+    ],
+  });
+  return result;
+}
+
+enum IncompatibleRpcDialogResult {
+  UseWasm = 'useWasm',
+  UseIncompatibleRpc = 'useIncompatibleRpc',
+  Dismissed = 'dismissed',
+}
+
+async function showDialogIncompatibleRPC(tpStatus: StatusResult):
+      Promise<IncompatibleRpcDialogResult> {
+  let result = IncompatibleRpcDialogResult.Dismissed;
+  await showModal({
+    title: 'Incompatible RPC version',
+    content: m('.modal-pre', getIncompatibleRpcMessage(tpStatus)),
     buttons: [
       {
         text: 'Use builtin Wasm',
         primary: true,
         action: () => {
-          globals.dispatch(
-            Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
+          result = IncompatibleRpcDialogResult.UseWasm;
         },
       },
       {
-        text: 'Use old version regardless (might crash)',
-        primary: false,
+        text: 'Use old version regardless (will crash)',
         action: () => {
-          forceUseOldVersion = true;
+          result = IncompatibleRpcDialogResult.UseIncompatibleRpc;
         },
       },
     ],
   });
+  return result;
 }
 
-async function showDialogToUsePreloadedTrace(tpStatus: StatusResult) {
-  return showModal({
-    title: 'Use Trace Processor Native Acceleration?',
-    content:
-        m('.modal-pre',
-          PROMPT().replace('$loadedTraceName', tpStatus.loadedTraceName)),
+enum PreloadedDialogResult {
+  UseRpcWithPreloadedTrace = 'useRpcWithPreloadedTrace',
+  UseRpc = 'useRpc',
+  UseWasm = 'useWasm',
+  Dismissed = 'dismissed',
+}
+
+async function showDialogToUsePreloadedTrace(tpStatus: StatusResult):
+    Promise<PreloadedDialogResult> {
+  let result = PreloadedDialogResult.Dismissed;
+  await showModal({
+    title: 'Use trace processor native acceleration?',
+    content: m('.modal-pre', getPromptMessage(tpStatus)),
     buttons: [
       {
         text: 'YES, use loaded trace',
         primary: true,
         action: () => {
-          globals.dispatch(Actions.openTraceFromHttpRpc({}));
+          result = PreloadedDialogResult.UseRpcWithPreloadedTrace;
         },
       },
       {
         text: 'YES, but reset state',
+        action: () => {
+          result = PreloadedDialogResult.UseRpc;
+        },
       },
       {
-        text: 'NO, Use builtin Wasm',
+        text: 'NO, Use builtin WASM',
         action: () => {
-          globals.dispatch(
-            Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
+          result = PreloadedDialogResult.UseWasm;
         },
       },
     ],
   });
+  return result;
 }
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
index cbd5c9d..eb596c2 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
@@ -110,7 +110,7 @@
               sum(stall_backend_mem) as total_stall_backend_mem,
               sum(l3_cache_miss) as total_l3_cache_miss
             FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
-          'target thread ipc statistic'
+          'target thread ipc statistic',
         );
       },
     });
diff --git a/ui/src/tracks/flows/index.ts b/ui/src/tracks/flows/index.ts
deleted file mode 100644
index 626c671..0000000
--- a/ui/src/tracks/flows/index.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (C) 2024 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {
-  FlowEventsAreaSelectedPanel,
-  FlowEventsPanel,
-} from '../../frontend/flow_events_panel';
-import {globals} from '../../frontend/globals';
-import {
-  Plugin,
-  PluginContext,
-  PluginContextTrace,
-  PluginDescriptor,
-} from '../../public';
-
-class FlowsPlugin implements Plugin {
-  onActivate(_ctx: PluginContext): void {}
-
-  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
-    const tabUri = 'perfetto.Flows#FlowEvents';
-    ctx.registerTab({
-      isEphemeral: false,
-      uri: tabUri,
-      content: {
-        render: () => {
-          const selection = globals.state.currentSelection;
-          if (selection?.kind === 'AREA') {
-            return m(FlowEventsAreaSelectedPanel);
-          } else {
-            return m(FlowEventsPanel);
-          }
-        },
-        getTitle: () => 'Flow Events',
-      },
-    });
-
-    ctx.registerCommand({
-      id: 'perfetto.Flows#ShowFlowsTab',
-      name: `Show Flows Tab`,
-      callback: () => {
-        ctx.tabs.showTab(tabUri);
-      },
-    });
-  }
-}
-
-export const plugin: PluginDescriptor = {
-  pluginId: 'perfetto.Flows',
-  plugin: FlowsPlugin,
-};
diff --git a/ui/src/tracks/pivot_table/index.ts b/ui/src/tracks/pivot_table/index.ts
deleted file mode 100644
index e9d14cd..0000000
--- a/ui/src/tracks/pivot_table/index.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2024 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {PIVOT_TABLE_REDUX_FLAG} from '../../controller/pivot_table_controller';
-import {globals} from '../../frontend/globals';
-import {PivotTable} from '../../frontend/pivot_table';
-import {
-  Plugin,
-  PluginContext,
-  PluginContextTrace,
-  PluginDescriptor,
-} from '../../public';
-
-class PivotTablePlugin implements Plugin {
-  onActivate(_ctx: PluginContext): void {}
-
-  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
-    if (PIVOT_TABLE_REDUX_FLAG.get()) {
-      ctx.registerTab({
-        isEphemeral: false,
-        uri: 'perfetto.PivotTable#PivotTable',
-        content: {
-          render: () => m(PivotTable, {
-            selectionArea:
-                globals.state.nonSerializableState.pivotTable.selectionArea,
-          }),
-          getTitle: () => 'Pivot Table',
-        },
-      });
-    }
-  }
-}
-
-export const plugin: PluginDescriptor = {
-  pluginId: 'perfetto.PivotTable',
-  plugin: PivotTablePlugin,
-};