Merge "Trace Redaction - Common Redact FTrace Event Modifications" into main
diff --git a/Android.bp b/Android.bp
index 403ffaa..928eee1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5694,6 +5694,7 @@
         "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
+        "protos/perfetto/trace/chrome/chrome_trigger.proto",
         "protos/perfetto/trace/chrome/v8.proto",
     ],
 }
@@ -5713,6 +5714,7 @@
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.gen.cc",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.gen.cc",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.gen.cc",
+        "external/perfetto/protos/perfetto/trace/chrome/chrome_trigger.gen.cc",
         "external/perfetto/protos/perfetto/trace/chrome/v8.gen.cc",
     ],
 }
@@ -5732,6 +5734,7 @@
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.gen.h",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.gen.h",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.gen.h",
+        "external/perfetto/protos/perfetto/trace/chrome/chrome_trigger.gen.h",
         "external/perfetto/protos/perfetto/trace/chrome/v8.gen.h",
     ],
     export_include_dirs: [
@@ -5747,6 +5750,7 @@
         "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
+        "protos/perfetto/trace/chrome/chrome_trigger.proto",
         "protos/perfetto/trace/chrome/v8.proto",
     ],
 }
@@ -5765,6 +5769,7 @@
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.pb.cc",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pb.cc",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pb.cc",
+        "external/perfetto/protos/perfetto/trace/chrome/chrome_trigger.pb.cc",
         "external/perfetto/protos/perfetto/trace/chrome/v8.pb.cc",
     ],
 }
@@ -5783,6 +5788,7 @@
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.pb.h",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pb.h",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pb.h",
+        "external/perfetto/protos/perfetto/trace/chrome/chrome_trigger.pb.h",
         "external/perfetto/protos/perfetto/trace/chrome/v8.pb.h",
     ],
     export_include_dirs: [
@@ -5798,6 +5804,7 @@
         "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
+        "protos/perfetto/trace/chrome/chrome_trigger.proto",
         "protos/perfetto/trace/chrome/v8.proto",
     ],
 }
@@ -5817,6 +5824,7 @@
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/chrome/chrome_trigger.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/chrome/v8.pbzero.cc",
     ],
 }
@@ -5836,6 +5844,7 @@
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.pbzero.h",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pbzero.h",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/chrome/chrome_trigger.pbzero.h",
         "external/perfetto/protos/perfetto/trace/chrome/v8.pbzero.h",
     ],
     export_include_dirs: [
@@ -5919,6 +5928,7 @@
         "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
+        "protos/perfetto/trace/chrome/chrome_trigger.proto",
         "protos/perfetto/trace/chrome/v8.proto",
         "protos/perfetto/trace/clock_snapshot.proto",
         "protos/perfetto/trace/etw/etw.proto",
@@ -13803,6 +13813,7 @@
         "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
+        "protos/perfetto/trace/chrome/chrome_trigger.proto",
         "protos/perfetto/trace/chrome/v8.proto",
         "protos/perfetto/trace/clock_snapshot.proto",
         "protos/perfetto/trace/etw/etw.proto",
diff --git a/BUILD b/BUILD
index 032036c..2e137b7 100644
--- a/BUILD
+++ b/BUILD
@@ -4646,6 +4646,7 @@
         "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
+        "protos/perfetto/trace/chrome/chrome_trigger.proto",
         "protos/perfetto/trace/chrome/v8.proto",
     ],
     visibility = [
diff --git a/include/perfetto/base/build_config.h b/include/perfetto/base/build_config.h
index a0e6bb9..5f7a7bd 100644
--- a/include/perfetto/base/build_config.h
+++ b/include/perfetto/base/build_config.h
@@ -139,6 +139,12 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_ARM64() 0
 #endif
 
+#if defined(__x86_64__) || defined(_M_X64)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_X86_64() 1
+#else
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_X86_64() 0
+#endif
+
 // perfetto_build_flags.h contains the tweakable build flags defined via GN.
 // - In GN builds (e.g., standalone, chromium, v8) this file is generated at
 //   build time via the gen_rule //gn/gen_buildflags.
diff --git a/include/perfetto/base/time.h b/include/perfetto/base/time.h
index 3e5b101..13e575c 100644
--- a/include/perfetto/base/time.h
+++ b/include/perfetto/base/time.h
@@ -37,6 +37,14 @@
 #include <emscripten/emscripten.h>
 #endif
 
+#if PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_X86_64)
+#if PERFETTO_BUILDFLAG(PERFETTO_COMPILER_MSVC)
+#include <intrin.h>
+#else
+#include <x86intrin.h>
+#endif
+#endif
+
 namespace perfetto {
 namespace base {
 
@@ -272,6 +280,12 @@
   return TimeGm(&tms);
 }
 
+#if PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_X86_64)
+inline uint64_t Rdtsc() {
+  return static_cast<uint64_t>(__rdtsc());
+}
+#endif
+
 std::optional<int32_t> GetTimezoneOffsetMins();
 
 }  // namespace base
diff --git a/protos/perfetto/common/builtin_clock.proto b/protos/perfetto/common/builtin_clock.proto
index dba9ec9..6b1ecc1 100644
--- a/protos/perfetto/common/builtin_clock.proto
+++ b/protos/perfetto/common/builtin_clock.proto
@@ -26,11 +26,8 @@
   BUILTIN_CLOCK_MONOTONIC_COARSE = 4;
   BUILTIN_CLOCK_MONOTONIC_RAW = 5;
   BUILTIN_CLOCK_BOOTTIME = 6;
+  BUILTIN_CLOCK_TSC = 9;
   BUILTIN_CLOCK_MAX_ID = 63;
 
   reserved 7, 8;
-
-  // An internal CL (ag/16521245) has taken this for BUILTIN_CLOCK_TSC.
-  // That might get upstreamed later on. Avoid diverging on this ID in future.
-  reserved 9;
 }
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 6623597..2fd3c55 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -339,13 +339,10 @@
   BUILTIN_CLOCK_MONOTONIC_COARSE = 4;
   BUILTIN_CLOCK_MONOTONIC_RAW = 5;
   BUILTIN_CLOCK_BOOTTIME = 6;
+  BUILTIN_CLOCK_TSC = 9;
   BUILTIN_CLOCK_MAX_ID = 63;
 
   reserved 7, 8;
-
-  // An internal CL (ag/16521245) has taken this for BUILTIN_CLOCK_TSC.
-  // That might get upstreamed later on. Avoid diverging on this ID in future.
-  reserved 9;
 }
 
 // End of protos/perfetto/common/builtin_clock.proto
diff --git a/protos/perfetto/trace/chrome/BUILD.gn b/protos/perfetto/trace/chrome/BUILD.gn
index 0df6b88..b9599aa 100644
--- a/protos/perfetto/trace/chrome/BUILD.gn
+++ b/protos/perfetto/trace/chrome/BUILD.gn
@@ -19,6 +19,7 @@
     "chrome_benchmark_metadata.proto",
     "chrome_metadata.proto",
     "chrome_trace_event.proto",
+    "chrome_trigger.proto",
     "v8.proto",
   ]
 }
diff --git a/protos/perfetto/trace/chrome/chrome_trace_packet.proto b/protos/perfetto/trace/chrome/chrome_trace_packet.proto
index ae0201d..17f1e68 100644
--- a/protos/perfetto/trace/chrome/chrome_trace_packet.proto
+++ b/protos/perfetto/trace/chrome/chrome_trace_packet.proto
@@ -28,6 +28,7 @@
 import "protos/perfetto/common/trace_stats.proto";
 import "protos/perfetto/config/trace_config.proto";
 import "protos/perfetto/trace/chrome/chrome_trace_event.proto";
+import "protos/perfetto/trace/chrome/chrome_trigger.proto";
 import "protos/perfetto/trace/clock_snapshot.proto";
 import "protos/perfetto/trace/interned_data/interned_data.proto";
 import "protos/perfetto/trace/profiling/profile_packet.proto";
@@ -52,6 +53,7 @@
   ThreadDescriptor thread_descriptor = 44;
   StreamingProfilePacket streaming_profile_packet = 54;
   ProfiledFrameSymbols profiled_frame_symbols = 55;
+  ChromeTrigger chrome_trigger = 109;
 
   // The original trace config.
   TraceConfig trace_config = 33;
diff --git a/protos/perfetto/trace/chrome/chrome_trigger.proto b/protos/perfetto/trace/chrome/chrome_trigger.proto
new file mode 100644
index 0000000..9662e92
--- /dev/null
+++ b/protos/perfetto/trace/chrome/chrome_trigger.proto
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+
+package perfetto.protos;
+
+// When a TracingSession receives a trigger it records the boot time
+// nanoseconds in the TracePacket's timestamp field. We emit this data so
+// filtering can be done on triggers received in the trace.
+message ChromeTrigger {
+  // Name of the trigger which was received.
+  optional string trigger_name = 1;
+  // MD5 hash of the trigger name.
+  optional string trigger_hash = 2;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index c8b4c9e..4988bfd 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -339,13 +339,10 @@
   BUILTIN_CLOCK_MONOTONIC_COARSE = 4;
   BUILTIN_CLOCK_MONOTONIC_RAW = 5;
   BUILTIN_CLOCK_BOOTTIME = 6;
+  BUILTIN_CLOCK_TSC = 9;
   BUILTIN_CLOCK_MAX_ID = 63;
 
   reserved 7, 8;
-
-  // An internal CL (ag/16521245) has taken this for BUILTIN_CLOCK_TSC.
-  // That might get upstreamed later on. Avoid diverging on this ID in future.
-  reserved 9;
 }
 
 // End of protos/perfetto/common/builtin_clock.proto
@@ -6001,6 +5998,20 @@
 
 // End of protos/perfetto/trace/chrome/chrome_trace_event.proto
 
+// Begin of protos/perfetto/trace/chrome/chrome_trigger.proto
+
+// When a TracingSession receives a trigger it records the boot time
+// nanoseconds in the TracePacket's timestamp field. We emit this data so
+// filtering can be done on triggers received in the trace.
+message ChromeTrigger {
+  // Name of the trigger which was received.
+  optional string trigger_name = 1;
+  // MD5 hash of the trigger name.
+  optional string trigger_hash = 2;
+}
+
+// End of protos/perfetto/trace/chrome/chrome_trigger.proto
+
 // Begin of protos/perfetto/trace/chrome/v8.proto
 
 // These are the protos for the V8 data source.
@@ -14680,7 +14691,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 14 (up to 15).
-// Next id: 108.
+// Next id: 109.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -14721,6 +14732,7 @@
     AndroidLogPacket android_log = 39;
     SystemInfo system_info = 45;
     Trigger trigger = 46;
+    ChromeTrigger chrome_trigger = 109;
     PackagesList packages_list = 47;
     ChromeBenchmarkMetadata chrome_benchmark_metadata = 48;
     PerfettoMetatrace perfetto_metatrace = 49;
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index 2547824..93fb7d3 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -37,6 +37,7 @@
 import "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto";
 import "protos/perfetto/trace/chrome/chrome_metadata.proto";
 import "protos/perfetto/trace/chrome/chrome_trace_event.proto";
+import "protos/perfetto/trace/chrome/chrome_trigger.proto";
 import "protos/perfetto/trace/chrome/v8.proto";
 import "protos/perfetto/trace/clock_snapshot.proto";
 import "protos/perfetto/trace/etw/etw_event_bundle.proto";
@@ -102,7 +103,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 14 (up to 15).
-// Next id: 108.
+// Next id: 109.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -143,6 +144,7 @@
     AndroidLogPacket android_log = 39;
     SystemInfo system_info = 45;
     Trigger trigger = 46;
+    ChromeTrigger chrome_trigger = 109;
     PackagesList packages_list = 47;
     ChromeBenchmarkMetadata chrome_benchmark_metadata = 48;
     PerfettoMetatrace perfetto_metatrace = 49;
diff --git a/src/base/http/http_server.cc b/src/base/http/http_server.cc
index 2efe9f7..bf99e25 100644
--- a/src/base/http/http_server.cc
+++ b/src/base/http/http_server.cc
@@ -60,7 +60,7 @@
                               SockType::kStream);
   bool ipv4_listening = sock4_ && sock4_->is_listening();
   if (!ipv4_listening) {
-    PERFETTO_PLOG("Failed to listen on IPv4 socket");
+    PERFETTO_PLOG("Failed to listen on IPv4 socket: \"%s\"", ipv4_addr.c_str());
     sock4_.reset();
   }
 
@@ -68,7 +68,7 @@
                               SockType::kStream);
   bool ipv6_listening = sock6_ && sock6_->is_listening();
   if (!ipv6_listening) {
-    PERFETTO_PLOG("Failed to listen on IPv6 socket");
+    PERFETTO_PLOG("Failed to listen on IPv6 socket: \"%s\"", ipv6_addr.c_str());
     sock6_.reset();
   }
 }
diff --git a/src/trace_processor/importers/proto/metadata_module.cc b/src/trace_processor/importers/proto/metadata_module.cc
index 3374043..9f2cb30 100644
--- a/src/trace_processor/importers/proto/metadata_module.cc
+++ b/src/trace_processor/importers/proto/metadata_module.cc
@@ -26,6 +26,7 @@
 #include "src/trace_processor/util/protozero_to_text.h"
 
 #include "protos/perfetto/config/trace_config.pbzero.h"
+#include "protos/perfetto/trace/chrome/chrome_trigger.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "protos/perfetto/trace/trace_uuid.pbzero.h"
 #include "protos/perfetto/trace/trigger.pbzero.h"
@@ -87,11 +88,14 @@
     int64_t ts,
     const TracePacketData&,
     uint32_t field_id) {
+  // We handle triggers at parse time rather at tokenization because
+  // we add slices to tables which need to happen post-sorting.
   if (field_id == TracePacket::kTriggerFieldNumber) {
-    // We handle triggers at parse time rather at tokenization because
-    // we add slices to tables which need to happen post-sorting.
     ParseTrigger(ts, decoder.trigger());
   }
+  if (field_id == TracePacket::kChromeTriggerFieldNumber) {
+    ParseChromeTrigger(ts, decoder.chrome_trigger());
+  }
 }
 
 void MetadataModule::ParseTrigger(int64_t ts, ConstBytes blob) {
@@ -116,6 +120,20 @@
       });
 }
 
+void MetadataModule::ParseChromeTrigger(int64_t ts, ConstBytes blob) {
+  protos::pbzero::ChromeTrigger::Decoder trigger(blob.data, blob.size);
+  StringId cat_id = kNullStringId;
+  TrackId track_id = context_->track_tracker->GetOrCreateTriggerTrack();
+  StringId name_id;
+  if (trigger.has_trigger_name()) {
+    name_id = context_->storage->InternString(trigger.trigger_name());
+  } else {
+    name_id = context_->storage->InternString(trigger.trigger_hash());
+  }
+  context_->slice_tracker->Scoped(ts, track_id, cat_id, name_id,
+                                  /* duration = */ 0);
+}
+
 void MetadataModule::ParseTraceUuid(ConstBytes blob) {
   // If both the TraceUuid packet and TraceConfig.trace_uuid_msb/lsb are set,
   // the former (which is emitted first) takes precedence. This is because the
diff --git a/src/trace_processor/importers/proto/metadata_module.h b/src/trace_processor/importers/proto/metadata_module.h
index 49e0384..31cbd42 100644
--- a/src/trace_processor/importers/proto/metadata_module.h
+++ b/src/trace_processor/importers/proto/metadata_module.h
@@ -47,6 +47,7 @@
 
  private:
   void ParseTrigger(int64_t ts, ConstBytes);
+  void ParseChromeTrigger(int64_t ts, ConstBytes);
   void ParseTraceUuid(ConstBytes);
 
   TraceProcessorContext* context_;
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index 97969be..3e2b9ab 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -625,12 +625,12 @@
     // Set up the rest of the tracefs state, without starting it.
     // Notes:
     // * resizing buffers can be quite slow (up to hundreds of ms).
-    // * resizing buffers doesn't clear their existing contents, which matters
-    // to the preserve_ftrace_buffer option.
+    // * resizing buffers may truncate existing contents if the new size is
+    // smaller, which matters to the preserve_ftrace_buffer option.
     if (!request.preserve_ftrace_buffer()) {
       SetupClock(request);
+      SetupBufferSize(request);
     }
-    SetupBufferSize(request);
   }
 
   std::set<GroupAndName> events = GetFtraceEvents(request, table_);
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
index d5d7f05..3ef5635 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
@@ -1385,5 +1385,28 @@
   ASSERT_FALSE(model.SetupConfig(/* id= */ 73, config));
 }
 
+TEST_F(FtraceConfigMuxerTest, PreserveFtraceBufferNotSetBufferSizeKb) {
+  auto fake_table = CreateFakeTable();
+  NiceMock<MockFtraceProcfs> ftrace;
+  FtraceConfigMuxer model(&ftrace, fake_table.get(), GetSyscallTable(), {},
+                          /* secondary_instance= */ false);
+
+  FtraceConfig config = CreateFtraceConfig({"sched/sched_switch"});
+
+  config.set_preserve_ftrace_buffer(true);
+  EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
+      .WillOnce(Return('1'));
+  ON_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .WillByDefault(Return("[local] global boot"));
+  EXPECT_CALL(ftrace, ReadFileIntoString("/root/trace_clock"))
+      .Times(AnyNumber());
+  EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", _)).Times(0);
+  EXPECT_CALL(ftrace,
+              WriteToFile("/root/events/sched/sched_switch/enable", "1"));
+
+  FtraceConfigId id = 44;
+  ASSERT_TRUE(model.SetupConfig(id, config));
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/src/tracing/core/clock_snapshots.cc b/src/tracing/core/clock_snapshots.cc
index 98fe7c5..a4fe6c0 100644
--- a/src/tracing/core/clock_snapshots.cc
+++ b/src/tracing/core/clock_snapshots.cc
@@ -66,6 +66,12 @@
       ClockReading(protos::pbzero::BUILTIN_CLOCK_MONOTONIC, wall_time_ns));
 #endif
 
+#if PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_X86_64)
+  // X86-specific but OS-independent TSC clocksource
+  snapshot_data.push_back(
+      ClockReading(protos::pbzero::BUILTIN_CLOCK_TSC, base::Rdtsc()));
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_X86_64)
+
   return snapshot_data;
 }
 
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 4f16d80..f99ae5c 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -256,7 +256,7 @@
     args: {
       name: string;
       id: string;
-      summaryTrackKey: string;
+      summaryTrackKey?: string;
       collapsed: boolean;
       fixedOrdering?: boolean;
     },
@@ -265,7 +265,8 @@
       name: args.name,
       id: args.id,
       collapsed: args.collapsed,
-      tracks: [args.summaryTrackKey],
+      tracks: [],
+      summaryTrack: args.summaryTrackKey,
       fixedOrdering: args.fixedOrdering,
     };
   },
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index 56c204c..b31d2ae 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -133,8 +133,8 @@
     });
   });
 
-  expect(afterTrackAdd.trackGroups['123-123-123'].tracks[0]).toBe('s');
-  expect(afterTrackAdd.trackGroups['123-123-123'].tracks[1]).toBe('1');
+  expect(afterTrackAdd.trackGroups['123-123-123'].summaryTrack).toBe('s');
+  expect(afterTrackAdd.trackGroups['123-123-123'].tracks[0]).toBe('1');
 });
 
 test('reorder tracks', () => {
@@ -346,7 +346,7 @@
 
   // High Priority tracks should be sorted before Low Priority tracks:
   // 'b' appears twice because it's the summary track
-  expect(after.trackGroups['g'].tracks).toEqual(['a', 'b', 'b']);
+  expect(after.trackGroups['g'].tracks).toEqual(['a', 'b']);
 });
 
 test('sortTracksByPriorityAndKindAndName', () => {
@@ -406,7 +406,6 @@
   expect(after.trackGroups['g'].tracks).toEqual([
     'a',
     'b',
-    'b',
     'c',
     'd',
     'e',
@@ -456,7 +455,7 @@
     StateActions.sortThreadTracks(draft, {});
   });
 
-  expect(after.trackGroups['g'].tracks).toEqual(['a', 'a', 'c', 'b']);
+  expect(after.trackGroups['g'].tracks).toEqual(['a', 'c', 'b']);
 });
 
 test('perf samples open flamegraph', () => {
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 43f205e..396df65 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -148,7 +148,8 @@
 // 49. Remove currentTab, which is only relevant to TabsV1.
 // 50. Remove ftrace filter state.
 // 51. Changed structure of FlamegraphState.expandedCallsiteByViewingOption.
-export const STATE_VERSION = 51;
+// 52. Update track group state - don't make the summary track the first track.
+export const STATE_VERSION = 52;
 
 export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
 
@@ -290,6 +291,7 @@
   collapsed: boolean;
   tracks: string[]; // Child track ids.
   fixedOrdering?: boolean; // Render tracks without sorting.
+  summaryTrack: string | undefined;
 }
 
 export interface EngineConfig {
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index 06e6823..3f942e9 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -41,7 +41,6 @@
   ACTUAL_FRAMES_SLICE_TRACK_KIND,
   EXPECTED_FRAMES_SLICE_TRACK_KIND,
 } from '../tracks/frames';
-import {NULL_TRACK_URI} from '../tracks/null_track';
 import {decideTracks as screenshotDecideTracks} from '../tracks/screenshots';
 import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
 
@@ -233,19 +232,8 @@
           parentIdToGroupId.set(parentTrackId, trackGroup);
 
           const parentName = getTrackName({name: rawParentName, kind});
-
-          const summaryTrackKey = uuidv4();
-          this.tracksToAdd.push({
-            uri: NULL_TRACK_URI,
-            key: summaryTrackKey,
-            trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
-            trackGroup: undefined,
-            name: parentName,
-          });
-
           this.addTrackGroupActions.push(
             Actions.addTrackGroup({
-              summaryTrackKey: summaryTrackKey,
               name: parentName,
               id: trackGroup,
               collapsed: true,
@@ -406,18 +394,7 @@
 
     for (const [key, value] of devMap) {
       const groupName = group + key;
-      const summaryTrackKey = uuidv4();
-
-      this.tracksToAdd.push({
-        uri: NULL_TRACK_URI,
-        key: summaryTrackKey,
-        trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
-        name: groupName,
-        trackGroup: undefined,
-      });
-
       const addGroup = Actions.addTrackGroup({
-        summaryTrackKey,
         name: groupName,
         id: value,
         collapsed: true,
@@ -456,18 +433,7 @@
 
     for (const [key, value] of devMap) {
       const groupName = key;
-      const summaryTrackKey = uuidv4();
-
-      this.tracksToAdd.push({
-        uri: NULL_TRACK_URI,
-        key: summaryTrackKey,
-        trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
-        name: groupName,
-        trackGroup: undefined,
-      });
-
       const addGroup = Actions.addTrackGroup({
-        summaryTrackKey,
         name: groupName,
         id: value,
         collapsed: true,
@@ -492,9 +458,6 @@
         ) {
           continue;
         }
-        if (track.uri === NULL_TRACK_URI) {
-          continue;
-        }
         if (groupUuid === undefined) {
           groupUuid = uuidv4();
         }
@@ -503,17 +466,7 @@
     }
 
     if (groupUuid !== undefined) {
-      const summaryTrackKey = uuidv4();
-      this.tracksToAdd.push({
-        uri: NULL_TRACK_URI,
-        key: summaryTrackKey,
-        trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
-        name: groupName,
-        trackGroup: undefined,
-      });
-
       const addGroup = Actions.addTrackGroup({
-        summaryTrackKey,
         name: groupName,
         id: groupUuid,
         collapsed: true,
@@ -540,9 +493,6 @@
       ) {
         continue;
       }
-      if (track.uri === NULL_TRACK_URI) {
-        continue;
-      }
       let allowlisted = false;
       for (const regex of ALLOWLIST_REGEXES) {
         allowlisted = allowlisted || regex.test(track.name);
@@ -557,17 +507,7 @@
     }
 
     if (groupUuid !== undefined) {
-      const summaryTrackKey = uuidv4();
-      this.tracksToAdd.push({
-        uri: NULL_TRACK_URI,
-        key: summaryTrackKey,
-        trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
-        name: groupName,
-        trackGroup: undefined,
-      });
-
       const addGroup = Actions.addTrackGroup({
-        summaryTrackKey,
         name: groupName,
         id: groupUuid,
         collapsed: true,
@@ -587,9 +527,6 @@
         ) {
           continue;
         }
-        if (track.uri === NULL_TRACK_URI) {
-          continue;
-        }
         if (groupUuid === undefined) {
           groupUuid = uuidv4();
         }
@@ -598,17 +535,7 @@
     }
 
     if (groupUuid !== undefined) {
-      const summaryTrackKey = uuidv4();
-      this.tracksToAdd.push({
-        uri: NULL_TRACK_URI,
-        key: summaryTrackKey,
-        trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
-        name: groupName,
-        trackGroup: undefined,
-      });
-
       const addGroup = Actions.addTrackGroup({
-        summaryTrackKey: summaryTrackKey,
         name: groupName,
         id: groupUuid,
         collapsed: true,
@@ -962,7 +889,7 @@
     });
 
     // Map From [name] -> [uuid, key]
-    const groupMap = new Map<string, [string, string]>();
+    const groupMap = new Map<string, string>();
 
     for (; it.valid(); it.next()) {
       if (it.name == null || it.uid == null) {
@@ -975,16 +902,7 @@
 
       const groupUuid = `uid-track-group${rawName}`;
       if (groupMap.get(rawName) === undefined) {
-        const summaryTrackKey = uuidv4();
-        this.tracksToAdd.push({
-          uri: NULL_TRACK_URI,
-          trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
-          name: `UID Tracks`,
-          trackGroup: undefined,
-          key: summaryTrackKey,
-        });
-
-        groupMap.set(rawName, [groupUuid, summaryTrackKey]);
+        groupMap.set(rawName, groupUuid);
       }
 
       this.tracksToAdd.push({
@@ -995,12 +913,11 @@
       });
     }
 
-    for (const [name, [groupUuid, summaryTrackKey]] of groupMap) {
+    for (const [name, groupUuid] of groupMap) {
       const addGroup = Actions.addTrackGroup({
         name: name,
         id: groupUuid,
         collapsed: true,
-        summaryTrackKey,
       });
       this.addTrackGroupActions.push(addGroup);
     }
@@ -1589,25 +1506,12 @@
         if (uuid) {
           groupUuid = uuid;
         } else {
-          console.log(`Creating group "${groupName}"`);
-
-          // Add the summary track
-          const summaryTrackKey = uuidv4();
-          this.tracksToAdd.push({
-            uri: NULL_TRACK_URI,
-            trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
-            name: groupName,
-            trackGroup: undefined,
-            key: summaryTrackKey,
-          });
-
           // Add the group
           groupUuid = uuidv4();
           const addGroup = Actions.addTrackGroup({
             name: groupName,
             id: groupUuid,
             collapsed: true,
-            summaryTrackKey,
             fixedOrdering: true,
           });
           this.addTrackGroupActions.push(addGroup);
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 34d1752..73decc9 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -226,24 +226,31 @@
     );
 
     for (const group of Object.values(globals.state.trackGroups)) {
-      const key = group.tracks[0];
-      const trackBundle = this.resolveTrack(key);
-      const headerPanel = new TrackGroupPanel({
-        trackGroupId: group.id,
-        key: `trackgroup-${group.id}`,
-        trackFSM: trackBundle.trackFSM,
-        labels: trackBundle.labels,
-        tags: trackBundle.tags,
-        collapsed: group.collapsed,
-        title: group.name,
-      });
+      const key = group.summaryTrack;
+      let headerPanel;
+      if (key) {
+        const trackBundle = this.resolveTrack(key);
+        headerPanel = new TrackGroupPanel({
+          trackGroupId: group.id,
+          key: `trackgroup-${group.id}`,
+          trackFSM: trackBundle.trackFSM,
+          labels: trackBundle.labels,
+          tags: trackBundle.tags,
+          collapsed: group.collapsed,
+          title: group.name,
+        });
+      } else {
+        headerPanel = new TrackGroupPanel({
+          trackGroupId: group.id,
+          key: `trackgroup-${group.id}`,
+          collapsed: group.collapsed,
+          title: group.name,
+        });
+      }
 
       const childTracks: Panel[] = [];
-      // The first track is the summary track, and is displayed as part of the
-      // group panel, we don't want to display it twice so we start from 1.
       if (!group.collapsed) {
-        for (let i = 1; i < group.tracks.length; ++i) {
-          const key = group.tracks[i];
+        for (const key of group.tracks) {
           const trackBundle = this.resolveTrack(key);
           const panel = new TrackPanel({
             trackKey: key,
diff --git a/ui/src/tracks/chrome_scroll_jank/index.ts b/ui/src/tracks/chrome_scroll_jank/index.ts
index 212c3fa..41917bb 100644
--- a/ui/src/tracks/chrome_scroll_jank/index.ts
+++ b/ui/src/tracks/chrome_scroll_jank/index.ts
@@ -26,11 +26,9 @@
   PluginContext,
   PluginContextTrace,
   PluginDescriptor,
-  PrimaryTrackSortKey,
 } from '../../public';
 import {Engine, EngineProxy} from '../../trace_processor/engine';
 import {CustomSqlDetailsPanelConfig} from '../custom_sql_table_slices';
-import {NULL_TRACK_URI} from '../null_track';
 
 import {ChromeTasksScrollJankTrack} from './chrome_tasks_scroll_jank_track';
 import {EventLatencySliceDetailsPanel} from './event_latency_details_panel';
@@ -138,20 +136,10 @@
   const eventLatencies = await addLatencyTracks();
   result.tracksToAdd = result.tracksToAdd.concat(eventLatencies.tracksToAdd);
 
-  const summaryTrackKey = uuidv4();
-  result.tracksToAdd.push({
-    uri: NULL_TRACK_URI,
-    trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
-    name: '', // TODO(stevegolton): We should probably put some name here.
-    trackGroup: undefined,
-    key: summaryTrackKey,
-  });
-
   const addTrackGroup = Actions.addTrackGroup({
     name: 'Chrome Scroll Jank',
     id: SCROLL_JANK_GROUP_ID,
     collapsed: false,
-    summaryTrackKey,
     fixedOrdering: true,
   });
 
diff --git a/ui/src/tracks/null_track/index.ts b/ui/src/tracks/null_track/index.ts
deleted file mode 100644
index 540550e..0000000
--- a/ui/src/tracks/null_track/index.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-// 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.
-
-import {
-  Plugin,
-  PluginContext,
-  PluginContextTrace,
-  PluginDescriptor,
-  Track,
-} from '../../public';
-
-export const NULL_TRACK_URI = 'perfetto.NullTrack';
-export const NULL_TRACK_KIND = 'NullTrack';
-
-export class NullTrack implements Track {
-  getHeight(): number {
-    return 30;
-  }
-
-  render(): void {}
-}
-
-class NullTrackPlugin implements Plugin {
-  onActivate(_ctx: PluginContext): void {}
-
-  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
-    // TODO(stevegolton): This is not the right way to handle blank tracks,
-    // instead we should probably just render some blank element at render time
-    // if no track uri is supplied.
-    ctx.registerTrack({
-      uri: NULL_TRACK_URI,
-      displayName: 'Null Track',
-      kind: NULL_TRACK_KIND,
-      trackFactory: () => new NullTrack(),
-    });
-  }
-}
-
-export const plugin: PluginDescriptor = {
-  pluginId: 'perfetto.NullTrack',
-  plugin: NullTrackPlugin,
-};