Merge "[chrome] Register MetadataModule for kChromeTriggerFieldNumber" into main
diff --git a/Android.bp b/Android.bp
index 1683b53..6cb0053 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1320,6 +1320,7 @@
         "protos/perfetto/config/android/android_system_property_config.proto",
         "protos/perfetto/config/android/network_trace_config.proto",
         "protos/perfetto/config/android/packages_list_config.proto",
+        "protos/perfetto/config/android/pixel_modem_config.proto",
         "protos/perfetto/config/android/protolog_config.proto",
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
@@ -2344,6 +2345,7 @@
         ":perfetto_src_trace_processor_importers_common_parser_types",
         ":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
         ":perfetto_src_trace_processor_importers_etw_full",
+        ":perfetto_src_trace_processor_importers_etw_minimal",
         ":perfetto_src_trace_processor_importers_ftrace_ftrace_descriptors",
         ":perfetto_src_trace_processor_importers_ftrace_full",
         ":perfetto_src_trace_processor_importers_ftrace_minimal",
@@ -2889,6 +2891,7 @@
         "protos/perfetto/config/android/android_system_property_config.proto",
         "protos/perfetto/config/android/network_trace_config.proto",
         "protos/perfetto/config/android/packages_list_config.proto",
+        "protos/perfetto/config/android/pixel_modem_config.proto",
         "protos/perfetto/config/android/protolog_config.proto",
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
@@ -2916,6 +2919,7 @@
         "external/perfetto/protos/perfetto/config/android/android_system_property_config.gen.cc",
         "external/perfetto/protos/perfetto/config/android/network_trace_config.gen.cc",
         "external/perfetto/protos/perfetto/config/android/packages_list_config.gen.cc",
+        "external/perfetto/protos/perfetto/config/android/pixel_modem_config.gen.cc",
         "external/perfetto/protos/perfetto/config/android/protolog_config.gen.cc",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.gen.cc",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.gen.cc",
@@ -2943,6 +2947,7 @@
         "external/perfetto/protos/perfetto/config/android/android_system_property_config.gen.h",
         "external/perfetto/protos/perfetto/config/android/network_trace_config.gen.h",
         "external/perfetto/protos/perfetto/config/android/packages_list_config.gen.h",
+        "external/perfetto/protos/perfetto/config/android/pixel_modem_config.gen.h",
         "external/perfetto/protos/perfetto/config/android/protolog_config.gen.h",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.gen.h",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.gen.h",
@@ -2965,6 +2970,7 @@
         "protos/perfetto/config/android/android_system_property_config.proto",
         "protos/perfetto/config/android/network_trace_config.proto",
         "protos/perfetto/config/android/packages_list_config.proto",
+        "protos/perfetto/config/android/pixel_modem_config.proto",
         "protos/perfetto/config/android/protolog_config.proto",
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
@@ -2991,6 +2997,7 @@
         "external/perfetto/protos/perfetto/config/android/android_system_property_config.pb.cc",
         "external/perfetto/protos/perfetto/config/android/network_trace_config.pb.cc",
         "external/perfetto/protos/perfetto/config/android/packages_list_config.pb.cc",
+        "external/perfetto/protos/perfetto/config/android/pixel_modem_config.pb.cc",
         "external/perfetto/protos/perfetto/config/android/protolog_config.pb.cc",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.pb.cc",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.pb.cc",
@@ -3017,6 +3024,7 @@
         "external/perfetto/protos/perfetto/config/android/android_system_property_config.pb.h",
         "external/perfetto/protos/perfetto/config/android/network_trace_config.pb.h",
         "external/perfetto/protos/perfetto/config/android/packages_list_config.pb.h",
+        "external/perfetto/protos/perfetto/config/android/pixel_modem_config.pb.h",
         "external/perfetto/protos/perfetto/config/android/protolog_config.pb.h",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.pb.h",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.pb.h",
@@ -3039,6 +3047,7 @@
         "protos/perfetto/config/android/android_system_property_config.proto",
         "protos/perfetto/config/android/network_trace_config.proto",
         "protos/perfetto/config/android/packages_list_config.proto",
+        "protos/perfetto/config/android/pixel_modem_config.proto",
         "protos/perfetto/config/android/protolog_config.proto",
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
@@ -3066,6 +3075,7 @@
         "external/perfetto/protos/perfetto/config/android/android_system_property_config.pbzero.cc",
         "external/perfetto/protos/perfetto/config/android/network_trace_config.pbzero.cc",
         "external/perfetto/protos/perfetto/config/android/packages_list_config.pbzero.cc",
+        "external/perfetto/protos/perfetto/config/android/pixel_modem_config.pbzero.cc",
         "external/perfetto/protos/perfetto/config/android/protolog_config.pbzero.cc",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.pbzero.cc",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.pbzero.cc",
@@ -3093,6 +3103,7 @@
         "external/perfetto/protos/perfetto/config/android/android_system_property_config.pbzero.h",
         "external/perfetto/protos/perfetto/config/android/network_trace_config.pbzero.h",
         "external/perfetto/protos/perfetto/config/android/packages_list_config.pbzero.h",
+        "external/perfetto/protos/perfetto/config/android/pixel_modem_config.pbzero.h",
         "external/perfetto/protos/perfetto/config/android/protolog_config.pbzero.h",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.pbzero.h",
         "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.pbzero.h",
@@ -3226,6 +3237,7 @@
         "protos/perfetto/config/android/android_system_property_config.proto",
         "protos/perfetto/config/android/network_trace_config.proto",
         "protos/perfetto/config/android/packages_list_config.proto",
+        "protos/perfetto/config/android/pixel_modem_config.proto",
         "protos/perfetto/config/android/protolog_config.proto",
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
@@ -5394,6 +5406,7 @@
         "protos/perfetto/trace/android/initial_display_state.proto",
         "protos/perfetto/trace/android/network_trace.proto",
         "protos/perfetto/trace/android/packages_list.proto",
+        "protos/perfetto/trace/android/pixel_modem_events.proto",
         "protos/perfetto/trace/android/protolog.proto",
         "protos/perfetto/trace/android/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
@@ -5426,6 +5439,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/network_trace.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/packages_list.gen.cc",
+        "external/perfetto/protos/perfetto/trace/android/pixel_modem_events.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/protolog.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/shell_transition.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.gen.cc",
@@ -5458,6 +5472,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.gen.h",
         "external/perfetto/protos/perfetto/trace/android/network_trace.gen.h",
         "external/perfetto/protos/perfetto/trace/android/packages_list.gen.h",
+        "external/perfetto/protos/perfetto/trace/android/pixel_modem_events.gen.h",
         "external/perfetto/protos/perfetto/trace/android/protolog.gen.h",
         "external/perfetto/protos/perfetto/trace/android/shell_transition.gen.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.gen.h",
@@ -5485,6 +5500,7 @@
         "protos/perfetto/trace/android/initial_display_state.proto",
         "protos/perfetto/trace/android/network_trace.proto",
         "protos/perfetto/trace/android/packages_list.proto",
+        "protos/perfetto/trace/android/pixel_modem_events.proto",
         "protos/perfetto/trace/android/protolog.proto",
         "protos/perfetto/trace/android/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
@@ -5516,6 +5532,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/network_trace.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/packages_list.pb.cc",
+        "external/perfetto/protos/perfetto/trace/android/pixel_modem_events.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/protolog.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/shell_transition.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pb.cc",
@@ -5547,6 +5564,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.pb.h",
         "external/perfetto/protos/perfetto/trace/android/network_trace.pb.h",
         "external/perfetto/protos/perfetto/trace/android/packages_list.pb.h",
+        "external/perfetto/protos/perfetto/trace/android/pixel_modem_events.pb.h",
         "external/perfetto/protos/perfetto/trace/android/protolog.pb.h",
         "external/perfetto/protos/perfetto/trace/android/shell_transition.pb.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pb.h",
@@ -5611,6 +5629,7 @@
         "protos/perfetto/trace/android/initial_display_state.proto",
         "protos/perfetto/trace/android/network_trace.proto",
         "protos/perfetto/trace/android/packages_list.proto",
+        "protos/perfetto/trace/android/pixel_modem_events.proto",
         "protos/perfetto/trace/android/protolog.proto",
         "protos/perfetto/trace/android/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
@@ -5643,6 +5662,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/network_trace.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/packages_list.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/android/pixel_modem_events.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/protolog.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/shell_transition.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pbzero.cc",
@@ -5675,6 +5695,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/network_trace.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/packages_list.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/android/pixel_modem_events.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/protolog.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/shell_transition.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pbzero.h",
@@ -5882,6 +5903,7 @@
         "protos/perfetto/config/android/android_system_property_config.proto",
         "protos/perfetto/config/android/network_trace_config.proto",
         "protos/perfetto/config/android/packages_list_config.proto",
+        "protos/perfetto/config/android/pixel_modem_config.proto",
         "protos/perfetto/config/android/protolog_config.proto",
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
@@ -5920,6 +5942,7 @@
         "protos/perfetto/trace/android/initial_display_state.proto",
         "protos/perfetto/trace/android/network_trace.proto",
         "protos/perfetto/trace/android/packages_list.proto",
+        "protos/perfetto/trace/android/pixel_modem_events.proto",
         "protos/perfetto/trace/android/protolog.proto",
         "protos/perfetto/trace/android/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
@@ -11484,11 +11507,20 @@
 filegroup {
     name: "perfetto_src_trace_processor_importers_etw_full",
     srcs: [
-        "src/trace_processor/importers/etw/etw_module.cc",
+        "src/trace_processor/importers/etw/etw_module_impl.cc",
+        "src/trace_processor/importers/etw/etw_parser.cc",
         "src/trace_processor/importers/etw/etw_tokenizer.cc",
     ],
 }
 
+// GN: //src/trace_processor/importers/etw:minimal
+filegroup {
+    name: "perfetto_src_trace_processor_importers_etw_minimal",
+    srcs: [
+        "src/trace_processor/importers/etw/etw_module.cc",
+    ],
+}
+
 // GN: //src/trace_processor/importers/ftrace:ftrace_descriptors
 filegroup {
     name: "perfetto_src_trace_processor_importers_ftrace_ftrace_descriptors",
@@ -12879,6 +12911,7 @@
         "src/trace_redaction/scrub_process_stats.cc",
         "src/trace_redaction/scrub_process_trees.cc",
         "src/trace_redaction/scrub_trace_packet.cc",
+        "src/trace_redaction/suspend_resume.cc",
         "src/trace_redaction/trace_redaction_framework.cc",
         "src/trace_redaction/trace_redactor.cc",
     ],
@@ -12900,6 +12933,7 @@
         "src/trace_redaction/redact_process_free_unittest.cc",
         "src/trace_redaction/redact_sched_switch_unittest.cc",
         "src/trace_redaction/redact_task_newtask_unittest.cc",
+        "src/trace_redaction/suspend_resume_unittest.cc",
     ],
 }
 
@@ -13746,8 +13780,8 @@
 }
 
 // GN: [//protos/perfetto/trace:non_minimal_source_set, //protos/perfetto/trace:minimal_source_set]
-java_library {
-    name: "perfetto_trace_java_protos",
+filegroup {
+    name: "perfetto_trace_filegroup_proto",
     srcs: [
         "protos/perfetto/common/android_energy_consumer_descriptor.proto",
         "protos/perfetto/common/android_log_constants.proto",
@@ -13774,6 +13808,7 @@
         "protos/perfetto/config/android/android_system_property_config.proto",
         "protos/perfetto/config/android/network_trace_config.proto",
         "protos/perfetto/config/android/packages_list_config.proto",
+        "protos/perfetto/config/android/pixel_modem_config.proto",
         "protos/perfetto/config/android/protolog_config.proto",
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
@@ -13812,6 +13847,225 @@
         "protos/perfetto/trace/android/initial_display_state.proto",
         "protos/perfetto/trace/android/network_trace.proto",
         "protos/perfetto/trace/android/packages_list.proto",
+        "protos/perfetto/trace/android/pixel_modem_events.proto",
+        "protos/perfetto/trace/android/protolog.proto",
+        "protos/perfetto/trace/android/shell_transition.proto",
+        "protos/perfetto/trace/android/surfaceflinger_common.proto",
+        "protos/perfetto/trace/android/surfaceflinger_layers.proto",
+        "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
+        "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",
+        "protos/perfetto/trace/etw/etw_event.proto",
+        "protos/perfetto/trace/etw/etw_event_bundle.proto",
+        "protos/perfetto/trace/extension_descriptor.proto",
+        "protos/perfetto/trace/filesystem/inode_file_map.proto",
+        "protos/perfetto/trace/ftrace/android_fs.proto",
+        "protos/perfetto/trace/ftrace/binder.proto",
+        "protos/perfetto/trace/ftrace/block.proto",
+        "protos/perfetto/trace/ftrace/cgroup.proto",
+        "protos/perfetto/trace/ftrace/clk.proto",
+        "protos/perfetto/trace/ftrace/cma.proto",
+        "protos/perfetto/trace/ftrace/compaction.proto",
+        "protos/perfetto/trace/ftrace/cpuhp.proto",
+        "protos/perfetto/trace/ftrace/cros_ec.proto",
+        "protos/perfetto/trace/ftrace/dma_fence.proto",
+        "protos/perfetto/trace/ftrace/dmabuf_heap.proto",
+        "protos/perfetto/trace/ftrace/dpu.proto",
+        "protos/perfetto/trace/ftrace/drm.proto",
+        "protos/perfetto/trace/ftrace/ext4.proto",
+        "protos/perfetto/trace/ftrace/f2fs.proto",
+        "protos/perfetto/trace/ftrace/fastrpc.proto",
+        "protos/perfetto/trace/ftrace/fence.proto",
+        "protos/perfetto/trace/ftrace/filemap.proto",
+        "protos/perfetto/trace/ftrace/ftrace.proto",
+        "protos/perfetto/trace/ftrace/ftrace_event.proto",
+        "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
+        "protos/perfetto/trace/ftrace/ftrace_stats.proto",
+        "protos/perfetto/trace/ftrace/g2d.proto",
+        "protos/perfetto/trace/ftrace/generic.proto",
+        "protos/perfetto/trace/ftrace/gpu_mem.proto",
+        "protos/perfetto/trace/ftrace/gpu_scheduler.proto",
+        "protos/perfetto/trace/ftrace/hyp.proto",
+        "protos/perfetto/trace/ftrace/i2c.proto",
+        "protos/perfetto/trace/ftrace/ion.proto",
+        "protos/perfetto/trace/ftrace/ipi.proto",
+        "protos/perfetto/trace/ftrace/irq.proto",
+        "protos/perfetto/trace/ftrace/kmem.proto",
+        "protos/perfetto/trace/ftrace/kvm.proto",
+        "protos/perfetto/trace/ftrace/lowmemorykiller.proto",
+        "protos/perfetto/trace/ftrace/lwis.proto",
+        "protos/perfetto/trace/ftrace/mali.proto",
+        "protos/perfetto/trace/ftrace/mdss.proto",
+        "protos/perfetto/trace/ftrace/mm_event.proto",
+        "protos/perfetto/trace/ftrace/net.proto",
+        "protos/perfetto/trace/ftrace/oom.proto",
+        "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
+        "protos/perfetto/trace/ftrace/power.proto",
+        "protos/perfetto/trace/ftrace/printk.proto",
+        "protos/perfetto/trace/ftrace/raw_syscalls.proto",
+        "protos/perfetto/trace/ftrace/regulator.proto",
+        "protos/perfetto/trace/ftrace/rpm.proto",
+        "protos/perfetto/trace/ftrace/samsung.proto",
+        "protos/perfetto/trace/ftrace/sched.proto",
+        "protos/perfetto/trace/ftrace/scm.proto",
+        "protos/perfetto/trace/ftrace/sde.proto",
+        "protos/perfetto/trace/ftrace/signal.proto",
+        "protos/perfetto/trace/ftrace/skb.proto",
+        "protos/perfetto/trace/ftrace/sock.proto",
+        "protos/perfetto/trace/ftrace/sync.proto",
+        "protos/perfetto/trace/ftrace/synthetic.proto",
+        "protos/perfetto/trace/ftrace/systrace.proto",
+        "protos/perfetto/trace/ftrace/task.proto",
+        "protos/perfetto/trace/ftrace/tcp.proto",
+        "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
+        "protos/perfetto/trace/ftrace/thermal.proto",
+        "protos/perfetto/trace/ftrace/trusty.proto",
+        "protos/perfetto/trace/ftrace/ufs.proto",
+        "protos/perfetto/trace/ftrace/v4l2.proto",
+        "protos/perfetto/trace/ftrace/virtio_gpu.proto",
+        "protos/perfetto/trace/ftrace/virtio_video.proto",
+        "protos/perfetto/trace/ftrace/vmscan.proto",
+        "protos/perfetto/trace/ftrace/workqueue.proto",
+        "protos/perfetto/trace/gpu/gpu_counter_event.proto",
+        "protos/perfetto/trace/gpu/gpu_log.proto",
+        "protos/perfetto/trace/gpu/gpu_render_stage_event.proto",
+        "protos/perfetto/trace/gpu/vulkan_api_event.proto",
+        "protos/perfetto/trace/gpu/vulkan_memory_event.proto",
+        "protos/perfetto/trace/interned_data/interned_data.proto",
+        "protos/perfetto/trace/memory_graph.proto",
+        "protos/perfetto/trace/perfetto/perfetto_metatrace.proto",
+        "protos/perfetto/trace/perfetto/tracing_service_event.proto",
+        "protos/perfetto/trace/power/android_energy_estimation_breakdown.proto",
+        "protos/perfetto/trace/power/android_entity_state_residency.proto",
+        "protos/perfetto/trace/power/battery_counters.proto",
+        "protos/perfetto/trace/power/power_rails.proto",
+        "protos/perfetto/trace/profiling/deobfuscation.proto",
+        "protos/perfetto/trace/profiling/heap_graph.proto",
+        "protos/perfetto/trace/profiling/profile_common.proto",
+        "protos/perfetto/trace/profiling/profile_packet.proto",
+        "protos/perfetto/trace/profiling/smaps.proto",
+        "protos/perfetto/trace/ps/process_stats.proto",
+        "protos/perfetto/trace/ps/process_tree.proto",
+        "protos/perfetto/trace/remote_clock_sync.proto",
+        "protos/perfetto/trace/statsd/statsd_atom.proto",
+        "protos/perfetto/trace/sys_stats/sys_stats.proto",
+        "protos/perfetto/trace/system_info.proto",
+        "protos/perfetto/trace/system_info/cpu_info.proto",
+        "protos/perfetto/trace/test_event.proto",
+        "protos/perfetto/trace/test_extensions.proto",
+        "protos/perfetto/trace/trace.proto",
+        "protos/perfetto/trace/trace_packet.proto",
+        "protos/perfetto/trace/trace_packet_defaults.proto",
+        "protos/perfetto/trace/trace_uuid.proto",
+        "protos/perfetto/trace/track_event/chrome_active_processes.proto",
+        "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
+        "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
+        "protos/perfetto/trace/track_event/chrome_content_settings_event_info.proto",
+        "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
+        "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
+        "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
+        "protos/perfetto/trace/track_event/chrome_latency_info.proto",
+        "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
+        "protos/perfetto/trace/track_event/chrome_message_pump.proto",
+        "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
+        "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
+        "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
+        "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
+        "protos/perfetto/trace/track_event/chrome_user_event.proto",
+        "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
+        "protos/perfetto/trace/track_event/counter_descriptor.proto",
+        "protos/perfetto/trace/track_event/debug_annotation.proto",
+        "protos/perfetto/trace/track_event/log_message.proto",
+        "protos/perfetto/trace/track_event/pixel_modem.proto",
+        "protos/perfetto/trace/track_event/process_descriptor.proto",
+        "protos/perfetto/trace/track_event/range_of_interest.proto",
+        "protos/perfetto/trace/track_event/screenshot.proto",
+        "protos/perfetto/trace/track_event/source_location.proto",
+        "protos/perfetto/trace/track_event/task_execution.proto",
+        "protos/perfetto/trace/track_event/thread_descriptor.proto",
+        "protos/perfetto/trace/track_event/track_descriptor.proto",
+        "protos/perfetto/trace/track_event/track_event.proto",
+        "protos/perfetto/trace/translation/translation_table.proto",
+        "protos/perfetto/trace/trigger.proto",
+        "protos/perfetto/trace/ui_state.proto",
+    ],
+}
+
+// GN: [//protos/perfetto/trace:non_minimal_source_set, //protos/perfetto/trace:minimal_source_set]
+java_library {
+    name: "perfetto_trace_java_protos",
+    srcs: [
+        "protos/perfetto/common/android_energy_consumer_descriptor.proto",
+        "protos/perfetto/common/android_log_constants.proto",
+        "protos/perfetto/common/builtin_clock.proto",
+        "protos/perfetto/common/commit_data_request.proto",
+        "protos/perfetto/common/data_source_descriptor.proto",
+        "protos/perfetto/common/descriptor.proto",
+        "protos/perfetto/common/ftrace_descriptor.proto",
+        "protos/perfetto/common/gpu_counter_descriptor.proto",
+        "protos/perfetto/common/interceptor_descriptor.proto",
+        "protos/perfetto/common/observable_events.proto",
+        "protos/perfetto/common/perf_events.proto",
+        "protos/perfetto/common/protolog_common.proto",
+        "protos/perfetto/common/sys_stats_counters.proto",
+        "protos/perfetto/common/trace_stats.proto",
+        "protos/perfetto/common/tracing_service_capabilities.proto",
+        "protos/perfetto/common/tracing_service_state.proto",
+        "protos/perfetto/common/track_event_descriptor.proto",
+        "protos/perfetto/config/android/android_game_intervention_list_config.proto",
+        "protos/perfetto/config/android/android_input_event_config.proto",
+        "protos/perfetto/config/android/android_log_config.proto",
+        "protos/perfetto/config/android/android_polled_state_config.proto",
+        "protos/perfetto/config/android/android_sdk_sysprop_guard_config.proto",
+        "protos/perfetto/config/android/android_system_property_config.proto",
+        "protos/perfetto/config/android/network_trace_config.proto",
+        "protos/perfetto/config/android/packages_list_config.proto",
+        "protos/perfetto/config/android/pixel_modem_config.proto",
+        "protos/perfetto/config/android/protolog_config.proto",
+        "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
+        "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
+        "protos/perfetto/config/chrome/chrome_config.proto",
+        "protos/perfetto/config/chrome/scenario_config.proto",
+        "protos/perfetto/config/chrome/v8_config.proto",
+        "protos/perfetto/config/data_source_config.proto",
+        "protos/perfetto/config/etw/etw_config.proto",
+        "protos/perfetto/config/ftrace/ftrace_config.proto",
+        "protos/perfetto/config/gpu/gpu_counter_config.proto",
+        "protos/perfetto/config/gpu/vulkan_memory_config.proto",
+        "protos/perfetto/config/inode_file/inode_file_config.proto",
+        "protos/perfetto/config/interceptor_config.proto",
+        "protos/perfetto/config/interceptors/console_config.proto",
+        "protos/perfetto/config/power/android_power_config.proto",
+        "protos/perfetto/config/process_stats/process_stats_config.proto",
+        "protos/perfetto/config/profiling/heapprofd_config.proto",
+        "protos/perfetto/config/profiling/java_hprof_config.proto",
+        "protos/perfetto/config/profiling/perf_event_config.proto",
+        "protos/perfetto/config/statsd/atom_ids.proto",
+        "protos/perfetto/config/statsd/statsd_tracing_config.proto",
+        "protos/perfetto/config/stress_test_config.proto",
+        "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+        "protos/perfetto/config/system_info/system_info.proto",
+        "protos/perfetto/config/test_config.proto",
+        "protos/perfetto/config/trace_config.proto",
+        "protos/perfetto/config/track_event/track_event_config.proto",
+        "protos/perfetto/trace/android/android_game_intervention_list.proto",
+        "protos/perfetto/trace/android/android_input_event.proto",
+        "protos/perfetto/trace/android/android_log.proto",
+        "protos/perfetto/trace/android/android_system_property.proto",
+        "protos/perfetto/trace/android/camera_event.proto",
+        "protos/perfetto/trace/android/frame_timeline_event.proto",
+        "protos/perfetto/trace/android/gpu_mem_event.proto",
+        "protos/perfetto/trace/android/graphics_frame_event.proto",
+        "protos/perfetto/trace/android/initial_display_state.proto",
+        "protos/perfetto/trace/android/network_trace.proto",
+        "protos/perfetto/trace/android/packages_list.proto",
+        "protos/perfetto/trace/android/pixel_modem_events.proto",
         "protos/perfetto/trace/android/protolog.proto",
         "protos/perfetto/trace/android/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
@@ -14322,6 +14576,7 @@
         ":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
         ":perfetto_src_trace_processor_importers_common_unittests",
         ":perfetto_src_trace_processor_importers_etw_full",
+        ":perfetto_src_trace_processor_importers_etw_minimal",
         ":perfetto_src_trace_processor_importers_ftrace_ftrace_descriptors",
         ":perfetto_src_trace_processor_importers_ftrace_full",
         ":perfetto_src_trace_processor_importers_ftrace_minimal",
@@ -15049,6 +15304,7 @@
         ":perfetto_src_trace_processor_importers_common_parser_types",
         ":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
         ":perfetto_src_trace_processor_importers_etw_full",
+        ":perfetto_src_trace_processor_importers_etw_minimal",
         ":perfetto_src_trace_processor_importers_ftrace_ftrace_descriptors",
         ":perfetto_src_trace_processor_importers_ftrace_full",
         ":perfetto_src_trace_processor_importers_ftrace_minimal",
@@ -15269,6 +15525,7 @@
         ":perfetto_src_trace_processor_importers_common_common",
         ":perfetto_src_trace_processor_importers_common_parser_types",
         ":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
+        ":perfetto_src_trace_processor_importers_etw_minimal",
         ":perfetto_src_trace_processor_importers_ftrace_minimal",
         ":perfetto_src_trace_processor_importers_fuchsia_fuchsia_record",
         ":perfetto_src_trace_processor_importers_json_minimal",
@@ -15421,6 +15678,7 @@
         ":perfetto_src_trace_processor_importers_common_parser_types",
         ":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
         ":perfetto_src_trace_processor_importers_etw_full",
+        ":perfetto_src_trace_processor_importers_etw_minimal",
         ":perfetto_src_trace_processor_importers_ftrace_ftrace_descriptors",
         ":perfetto_src_trace_processor_importers_ftrace_full",
         ":perfetto_src_trace_processor_importers_ftrace_minimal",
@@ -16107,11 +16365,10 @@
     ],
 }
 
-// TODO(b/315118713): use list of proto file sources instead of merged proto
 gensrcs {
     name: "perfetto_trace_javastream_protos",
     srcs: [
-        "protos/perfetto/trace/perfetto_trace.proto",
+        ":perfetto_trace_filegroup_proto",
     ],
     tools: [
         "aprotoc",
diff --git a/Android.bp.extras b/Android.bp.extras
index 3aab5af..3ebf51e 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -172,11 +172,10 @@
     ],
 }
 
-// TODO(b/315118713): use list of proto file sources instead of merged proto
 gensrcs {
     name: "perfetto_trace_javastream_protos",
     srcs: [
-        "protos/perfetto/trace/perfetto_trace.proto",
+        ":perfetto_trace_filegroup_proto",
     ],
     tools: [
         "aprotoc",
diff --git a/BUILD b/BUILD
index 4e316ed..12fe725 100644
--- a/BUILD
+++ b/BUILD
@@ -225,6 +225,7 @@
         ":src_trace_processor_importers_common_parser_types",
         ":src_trace_processor_importers_common_trace_parser_hdr",
         ":src_trace_processor_importers_etw_full",
+        ":src_trace_processor_importers_etw_minimal",
         ":src_trace_processor_importers_ftrace_ftrace_descriptors",
         ":src_trace_processor_importers_ftrace_full",
         ":src_trace_processor_importers_ftrace_minimal",
@@ -1526,13 +1527,24 @@
 perfetto_filegroup(
     name = "src_trace_processor_importers_etw_full",
     srcs = [
-        "src/trace_processor/importers/etw/etw_module.cc",
-        "src/trace_processor/importers/etw/etw_module.h",
+        "src/trace_processor/importers/etw/etw_module_impl.cc",
+        "src/trace_processor/importers/etw/etw_module_impl.h",
+        "src/trace_processor/importers/etw/etw_parser.cc",
+        "src/trace_processor/importers/etw/etw_parser.h",
         "src/trace_processor/importers/etw/etw_tokenizer.cc",
         "src/trace_processor/importers/etw/etw_tokenizer.h",
     ],
 )
 
+# GN target: //src/trace_processor/importers/etw:minimal
+perfetto_filegroup(
+    name = "src_trace_processor_importers_etw_minimal",
+    srcs = [
+        "src/trace_processor/importers/etw/etw_module.cc",
+        "src/trace_processor/importers/etw/etw_module.h",
+    ],
+)
+
 # GN target: //src/trace_processor/importers/ftrace:ftrace_descriptors
 perfetto_filegroup(
     name = "src_trace_processor_importers_ftrace_ftrace_descriptors",
@@ -3881,6 +3893,7 @@
         "protos/perfetto/config/android/android_system_property_config.proto",
         "protos/perfetto/config/android/network_trace_config.proto",
         "protos/perfetto/config/android/packages_list_config.proto",
+        "protos/perfetto/config/android/pixel_modem_config.proto",
         "protos/perfetto/config/android/protolog_config.proto",
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
@@ -4592,6 +4605,7 @@
         "protos/perfetto/trace/android/initial_display_state.proto",
         "protos/perfetto/trace/android/network_trace.proto",
         "protos/perfetto/trace/android/packages_list.proto",
+        "protos/perfetto/trace/android/pixel_modem_events.proto",
         "protos/perfetto/trace/android/protolog.proto",
         "protos/perfetto/trace/android/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
@@ -5708,6 +5722,7 @@
         ":src_trace_processor_importers_common_parser_types",
         ":src_trace_processor_importers_common_trace_parser_hdr",
         ":src_trace_processor_importers_etw_full",
+        ":src_trace_processor_importers_etw_minimal",
         ":src_trace_processor_importers_ftrace_ftrace_descriptors",
         ":src_trace_processor_importers_ftrace_full",
         ":src_trace_processor_importers_ftrace_minimal",
@@ -5877,6 +5892,7 @@
         ":src_trace_processor_importers_common_parser_types",
         ":src_trace_processor_importers_common_trace_parser_hdr",
         ":src_trace_processor_importers_etw_full",
+        ":src_trace_processor_importers_etw_minimal",
         ":src_trace_processor_importers_ftrace_ftrace_descriptors",
         ":src_trace_processor_importers_ftrace_full",
         ":src_trace_processor_importers_ftrace_minimal",
@@ -6104,6 +6120,7 @@
         ":src_trace_processor_importers_common_parser_types",
         ":src_trace_processor_importers_common_trace_parser_hdr",
         ":src_trace_processor_importers_etw_full",
+        ":src_trace_processor_importers_etw_minimal",
         ":src_trace_processor_importers_ftrace_ftrace_descriptors",
         ":src_trace_processor_importers_ftrace_full",
         ":src_trace_processor_importers_ftrace_minimal",
diff --git a/OWNERS b/OWNERS
index 3c29a20..b2a1331 100644
--- a/OWNERS
+++ b/OWNERS
@@ -7,7 +7,6 @@
 skyostil@google.com
 
 # UI, Ftrace interop, traced_probes, protozero, Android internals.
-hjd@google.com
 
 # Trace Processor, metrics, infra.
 lalitm@google.com
diff --git a/bazel/proto_gen.bzl b/bazel/proto_gen.bzl
index cd31d44..c5fd127 100644
--- a/bazel/proto_gen.bzl
+++ b/bazel/proto_gen.bzl
@@ -26,6 +26,11 @@
         for dep in ctx.attr.deps
         for f in dep[ProtoInfo].transitive_imports.to_list()
     ]
+    proto_paths = [
+        f
+        for dep in ctx.attr.deps
+        for f in dep[ProtoInfo].transitive_proto_path.to_list()
+    ]
 
     proto_path = "."
 
@@ -58,8 +63,10 @@
         out_files += [ctx.actions.declare_file(base_path + ".%s.cc" % suffix)]
 
     arguments = [
-        "--proto_path=" + proto_path,
+        "--proto_path=" + proto_path
+        for proto_path in proto_paths
     ]
+
     plugin_deps = []
     if ctx.attr.plugin:
         wrap_arg = ctx.attr.wrapper_namespace
diff --git a/docs/contributing/build-instructions.md b/docs/contributing/build-instructions.md
index c192898..85025d6 100644
--- a/docs/contributing/build-instructions.md
+++ b/docs/contributing/build-instructions.md
@@ -535,6 +535,7 @@
     "./ui",
   ],
   "prettier.configPath": "ui/.prettierrc.yml",
+  "typescript.preferences.importModuleSpecifier": "relative",
   "[typescript]": {
     "editor.defaultFormatter": "esbenp.prettier-vscode"
   },
diff --git a/docs/data-sources/java-heap-profiler.md b/docs/data-sources/java-heap-profiler.md
index 01af1f5..4056436 100644
--- a/docs/data-sources/java-heap-profiler.md
+++ b/docs/data-sources/java-heap-profiler.md
@@ -68,9 +68,9 @@
 select distinct graph_sample_ts, upid from heap_graph_object
 ```
 
-graph_sample_ts     |        upid        |
---------------------|--------------------|
-     56785646801    |         1          |
+|graph_sample_ts     |        upid        |
+|--------------------|--------------------|
+|     56785646801    |         1          |
 
 We can then use them to get the flamegraph data.
 
diff --git a/infra/OWNERS b/infra/OWNERS
index a4b3fef..0178ed0 100644
--- a/infra/OWNERS
+++ b/infra/OWNERS
@@ -1,3 +1,2 @@
-hjd@google.com
 lalitm@google.com
 primiano@google.com
diff --git a/protos/perfetto/config/android/BUILD.gn b/protos/perfetto/config/android/BUILD.gn
index 0af8024..ec4f7ce 100644
--- a/protos/perfetto/config/android/BUILD.gn
+++ b/protos/perfetto/config/android/BUILD.gn
@@ -26,6 +26,7 @@
     "android_system_property_config.proto",
     "network_trace_config.proto",
     "packages_list_config.proto",
+    "pixel_modem_config.proto",
     "protolog_config.proto",
     "surfaceflinger_layers_config.proto",
     "surfaceflinger_transactions_config.proto",
diff --git a/protos/perfetto/config/android/pixel_modem_config.proto b/protos/perfetto/config/android/pixel_modem_config.proto
new file mode 100644
index 0000000..d3efc59
--- /dev/null
+++ b/protos/perfetto/config/android/pixel_modem_config.proto
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+// Data source that records events from the modem.
+message PixelModemConfig {
+  // Event group to record, as defined by the modem.
+  enum EventGroup {
+    EVENT_GROUP_UNKNOWN = 0;
+
+    // Events suitable for low bandwidth tracing only.
+    EVENT_GROUP_LOW_BANDWIDTH = 1;
+
+    // Events suitable for high and low bandwidth tracing.
+    EVENT_GROUP_HIGH_AND_LOW_BANDWIDTH = 2;
+  }
+
+  optional EventGroup event_group = 1;
+
+  // If set, record only events with these hashes.
+  repeated int64 pigweed_hash_allow_list = 2;
+
+  // If set and allow_list is not set, deny events with these hashes.
+  repeated int64 pigweed_hash_deny_list = 3;
+}
diff --git a/protos/perfetto/config/data_source_config.proto b/protos/perfetto/config/data_source_config.proto
index e9a2594..3b8e860 100644
--- a/protos/perfetto/config/data_source_config.proto
+++ b/protos/perfetto/config/data_source_config.proto
@@ -26,6 +26,7 @@
 import "protos/perfetto/config/android/android_sdk_sysprop_guard_config.proto";
 import "protos/perfetto/config/android/network_trace_config.proto";
 import "protos/perfetto/config/android/packages_list_config.proto";
+import "protos/perfetto/config/android/pixel_modem_config.proto";
 import "protos/perfetto/config/android/protolog_config.proto";
 import "protos/perfetto/config/android/surfaceflinger_layers_config.proto";
 import "protos/perfetto/config/android/surfaceflinger_transactions_config.proto";
@@ -49,7 +50,7 @@
 import "protos/perfetto/config/system_info/system_info.proto";
 
 // The configuration that is passed to each data source when starting tracing.
-// Next id: 129
+// Next id: 130
 message DataSourceConfig {
   enum SessionInitiator {
     SESSION_INITIATOR_UNSPECIFIED = 0;
@@ -197,6 +198,9 @@
   // Data source name: android.input.inputevent
   optional AndroidInputEventConfig android_input_event_config = 128 [lazy = true];
 
+  // Data source name: android.pixel.modem
+  optional PixelModemConfig pixel_modem_config = 129 [lazy = true];
+
   // This is a fallback mechanism to send a free-form text config to the
   // producer. In theory this should never be needed. All the code that
   // is part of the platform (i.e. traced service) is supposed to *not* truncate
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 2fd3c55..7777d1c 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -660,6 +660,32 @@
 
 // End of protos/perfetto/config/android/packages_list_config.proto
 
+// Begin of protos/perfetto/config/android/pixel_modem_config.proto
+
+// Data source that records events from the modem.
+message PixelModemConfig {
+  // Event group to record, as defined by the modem.
+  enum EventGroup {
+    EVENT_GROUP_UNKNOWN = 0;
+
+    // Events suitable for low bandwidth tracing only.
+    EVENT_GROUP_LOW_BANDWIDTH = 1;
+
+    // Events suitable for high and low bandwidth tracing.
+    EVENT_GROUP_HIGH_AND_LOW_BANDWIDTH = 2;
+  }
+
+  optional EventGroup event_group = 1;
+
+  // If set, record only events with these hashes.
+  repeated int64 pigweed_hash_allow_list = 2;
+
+  // If set and allow_list is not set, deny events with these hashes.
+  repeated int64 pigweed_hash_deny_list = 3;
+}
+
+// End of protos/perfetto/config/android/pixel_modem_config.proto
+
 // Begin of protos/perfetto/common/protolog_common.proto
 
 enum ProtoLogLevel {
@@ -3231,7 +3257,7 @@
 // Begin of protos/perfetto/config/data_source_config.proto
 
 // The configuration that is passed to each data source when starting tracing.
-// Next id: 129
+// Next id: 130
 message DataSourceConfig {
   enum SessionInitiator {
     SESSION_INITIATOR_UNSPECIFIED = 0;
@@ -3379,6 +3405,9 @@
   // Data source name: android.input.inputevent
   optional AndroidInputEventConfig android_input_event_config = 128 [lazy = true];
 
+  // Data source name: android.pixel.modem
+  optional PixelModemConfig pixel_modem_config = 129 [lazy = true];
+
   // This is a fallback mechanism to send a free-form text config to the
   // producer. In theory this should never be needed. All the code that
   // is part of the platform (i.e. traced service) is supposed to *not* truncate
diff --git a/protos/perfetto/ipc/OWNERS b/protos/perfetto/ipc/OWNERS
index a10c8b8..bead02d 100644
--- a/protos/perfetto/ipc/OWNERS
+++ b/protos/perfetto/ipc/OWNERS
@@ -4,6 +4,5 @@
 
 set noparent
 eseckler@google.com
-hjd@google.com
 primiano@google.com
 skyostil@google.com
diff --git a/protos/perfetto/trace/android/BUILD.gn b/protos/perfetto/trace/android/BUILD.gn
index c88ca28..1910078 100644
--- a/protos/perfetto/trace/android/BUILD.gn
+++ b/protos/perfetto/trace/android/BUILD.gn
@@ -29,6 +29,7 @@
     "initial_display_state.proto",
     "network_trace.proto",
     "packages_list.proto",
+    "pixel_modem_events.proto",
     "protolog.proto",
     "shell_transition.proto",
     "surfaceflinger_common.proto",
diff --git a/protos/perfetto/trace/android/pixel_modem_events.proto b/protos/perfetto/trace/android/pixel_modem_events.proto
new file mode 100644
index 0000000..f014311
--- /dev/null
+++ b/protos/perfetto/trace/android/pixel_modem_events.proto
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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;
+
+message PixelModemEvents {
+  // Pigweed-format dehydrated events.
+  repeated bytes events = 1;
+
+  // Timestamps of the events, converted to CLOCK_BOOTTIME. The first
+  // timestamp is the absolute timestamp of the first event. Subsequent
+  // timestamps are deltas from the previous timestamp.
+  // The nth entry from `events` gets the nth entry here.
+  repeated uint64 event_time_nanos = 2;
+}
+
+// NB: this is not emitted in the trace but can be prepended later.
+message PixelModemTokenDatabase {
+  // Pigweed-format database to allow event rehydration.
+  optional bytes database = 1;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index f949e69..81977bf 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -660,6 +660,32 @@
 
 // End of protos/perfetto/config/android/packages_list_config.proto
 
+// Begin of protos/perfetto/config/android/pixel_modem_config.proto
+
+// Data source that records events from the modem.
+message PixelModemConfig {
+  // Event group to record, as defined by the modem.
+  enum EventGroup {
+    EVENT_GROUP_UNKNOWN = 0;
+
+    // Events suitable for low bandwidth tracing only.
+    EVENT_GROUP_LOW_BANDWIDTH = 1;
+
+    // Events suitable for high and low bandwidth tracing.
+    EVENT_GROUP_HIGH_AND_LOW_BANDWIDTH = 2;
+  }
+
+  optional EventGroup event_group = 1;
+
+  // If set, record only events with these hashes.
+  repeated int64 pigweed_hash_allow_list = 2;
+
+  // If set and allow_list is not set, deny events with these hashes.
+  repeated int64 pigweed_hash_deny_list = 3;
+}
+
+// End of protos/perfetto/config/android/pixel_modem_config.proto
+
 // Begin of protos/perfetto/common/protolog_common.proto
 
 enum ProtoLogLevel {
@@ -3231,7 +3257,7 @@
 // Begin of protos/perfetto/config/data_source_config.proto
 
 // The configuration that is passed to each data source when starting tracing.
-// Next id: 129
+// Next id: 130
 message DataSourceConfig {
   enum SessionInitiator {
     SESSION_INITIATOR_UNSPECIFIED = 0;
@@ -3379,6 +3405,9 @@
   // Data source name: android.input.inputevent
   optional AndroidInputEventConfig android_input_event_config = 128 [lazy = true];
 
+  // Data source name: android.pixel.modem
+  optional PixelModemConfig pixel_modem_config = 129 [lazy = true];
+
   // This is a fallback mechanism to send a free-form text config to the
   // producer. In theory this should never be needed. All the code that
   // is part of the platform (i.e. traced service) is supposed to *not* truncate
@@ -4996,6 +5025,27 @@
 
 // End of protos/perfetto/trace/android/packages_list.proto
 
+// Begin of protos/perfetto/trace/android/pixel_modem_events.proto
+
+message PixelModemEvents {
+  // Pigweed-format dehydrated events.
+  repeated bytes events = 1;
+
+  // Timestamps of the events, converted to CLOCK_BOOTTIME. The first
+  // timestamp is the absolute timestamp of the first event. Subsequent
+  // timestamps are deltas from the previous timestamp.
+  // The nth entry from `events` gets the nth entry here.
+  repeated uint64 event_time_nanos = 2;
+}
+
+// NB: this is not emitted in the trace but can be prepended later.
+message PixelModemTokenDatabase {
+  // Pigweed-format database to allow event rehydration.
+  optional bytes database = 1;
+}
+
+// End of protos/perfetto/trace/android/pixel_modem_events.proto
+
 // Begin of protos/perfetto/trace/android/protolog.proto
 
 /* represents a single log entry */
@@ -14721,7 +14771,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 14 (up to 15).
-// Next id: 109.
+// Next id: 112.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -14861,6 +14911,9 @@
     // Clock synchronization with remote machines.
     RemoteClockSync remote_clock_sync = 107;
 
+    PixelModemEvents pixel_modem_events = 110;
+    PixelModemTokenDatabase pixel_modem_token_database = 111;
+
     // This field is only used for testing.
     // In previous versions of this proto this field had the id 268435455
     // This caused many problems:
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index 93fb7d3..62d7e6f 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -30,6 +30,7 @@
 import "protos/perfetto/trace/android/initial_display_state.proto";
 import "protos/perfetto/trace/android/network_trace.proto";
 import "protos/perfetto/trace/android/packages_list.proto";
+import "protos/perfetto/trace/android/pixel_modem_events.proto";
 import "protos/perfetto/trace/android/protolog.proto";
 import "protos/perfetto/trace/android/shell_transition.proto";
 import "protos/perfetto/trace/android/surfaceflinger_layers.proto";
@@ -103,7 +104,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 14 (up to 15).
-// Next id: 109.
+// Next id: 112.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -243,6 +244,9 @@
     // Clock synchronization with remote machines.
     RemoteClockSync remote_clock_sync = 107;
 
+    PixelModemEvents pixel_modem_events = 110;
+    PixelModemTokenDatabase pixel_modem_token_database = 111;
+
     // This field is only used for testing.
     // In previous versions of this proto this field had the id 268435455
     // This caused many problems:
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 882d6ff..bea5d6e 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -596,7 +596,7 @@
   optional ShutdownBehavior shutdown_behavior = 4;
 }
 
-// TODO(crbug.com/1258495): Add more information.
+// TODO(crbug.com/40797026): Add more information.
 message BackForwardCacheCanStoreDocumentResult {
   enum BackForwardCacheNotRestoredReason {
     NOT_MAIN_FRAME = 1;
@@ -722,7 +722,7 @@
     TASK_TYPE_WORKER_THREAD_TASK_QUEUE_COMPOSITOR = 48;
     TASK_TYPE_COMPOSITOR_THREAD_TASK_QUEUE_INPUT = 49;
 
-    // TODO(crbug.com/860545): Obsolete. Remove.
+    // TODO(crbug.com/40583778): Obsolete. Remove.
     TASK_TYPE_NETWORKING_WITH_URL_LOADER_ANNOTATION = 50;
 
     TASK_TYPE_WORKER_ANIMATION = 51;
@@ -1835,9 +1835,27 @@
   optional int64 draw_estimate_delta_us = 7;
 }
 
+message WebViewStartup {
+  optional bool from_ui_thread = 1;
+  // This enum must be kept in sync with WebViewChromiumAwInit.CallSite
+  enum CallSite {
+    GET_AW_TRACING_CONTROLLER = 0;
+    GET_AW_PROXY_CONTROLLER = 1;
+    WEBVIEW_INSTANCE = 2;
+    GET_STATICS = 3;
+    GET_DEFAULT_GEOLOCATION_PERMISSIONS = 4;
+    GET_DEFAULT_SERVICE_WORKER_CONTROLLER = 5;
+    GET_WEB_ICON_DATABASE = 6;
+    GET_DEFAULT_WEB_STORAGE = 7;
+    GET_DEFAULT_WEBVIEW_DATABASE = 8;
+    GET_TRACING_CONTROLLER = 9;
+  }
+  optional CallSite call_site = 2;
+}
+
 message ChromeTrackEvent {
   // Extension range for Chrome: 1000-1999
-  // Next ID: 1063
+  // Next ID: 1064
   extend TrackEvent {
     optional ChromeAppState chrome_app_state = 1000;
 
@@ -1970,5 +1988,7 @@
     optional ViewClassName view_class_name = 1061;
 
     optional ChromeCompositorSchedulerStateV2 cc_scheduler_state = 1062;
+
+    optional WebViewStartup webview_startup = 1063;
   }
 }
diff --git a/python/perfetto/trace_uri_resolver/resolver.py b/python/perfetto/trace_uri_resolver/resolver.py
index bcdbf93..32f224d 100644
--- a/python/perfetto/trace_uri_resolver/resolver.py
+++ b/python/perfetto/trace_uri_resolver/resolver.py
@@ -190,11 +190,13 @@
   """Creates an the args dictionary from a trace URI.
 
     URIs have the form:
-    android_ci:day=2021-01-01;devices=blueline,crosshatch;key>=value
+    android_ci:day=2021-01-01;devices=blueline,crosshatch;key>=value;\
+    version>=1;version<5
 
     This is converted to a dictionary of the form:
     {'day': '2021-01-01', 'id': ['blueline', 'crosshatch'],
-    'key': ConstraintClass('value', Op.GE)}
+    'key': ConstraintClass('value', Op.GE),
+    'version': [ConstraintClass(1, Op.GE), ConstraintClass(5, Op.LT)]}
   """
   _, args_str = util.parse_trace_uri(uri)
   if not args_str:
@@ -206,13 +208,14 @@
     (key, op, value) = _parse_arg(arg)
     lst = value.split(',')
     if len(lst) > 1:
-      args_dict[key] = lst
+      args_dict_value = lst
     else:
-      args_dict[key] = value
+      args_dict_value = value
 
     if key not in type_hints:
       if op != ConstraintClass.Op.EQ:
         raise ValueError(f'{key} only supports "=" operator')
+      args_dict[key] = args_dict_value
       continue
     have_constraint = False
     type_hint = type_hints[key]
@@ -227,5 +230,12 @@
       raise ValueError('Operator other than "=" passed to argument which '
                        'does not have constraint type: ' + arg)
     if have_constraint:
-      args_dict[key] = ConstraintClass(args_dict[key], op)
+      if key not in args_dict:
+        args_dict[key] = ConstraintClass(args_dict_value, op)
+      else:
+        if isinstance(args_dict[key], ConstraintClass):
+          args_dict[key] = [args_dict[key]]
+        args_dict[key].append(ConstraintClass(args_dict_value, op))
+    else:
+      args_dict[key] = args_dict_value
   return args_dict
diff --git a/python/test/resolver_unittest.py b/python/test/resolver_unittest.py
index 5cdb9f9..5ac9f63 100644
--- a/python/test/resolver_unittest.py
+++ b/python/test/resolver_unittest.py
@@ -197,6 +197,22 @@
     self.assertEqual(
         _args_dict_from_uri('foo:key<v1', type_hints),
         {'key': ConstraintClass('v1', ConstraintClass.Op.LT)})
+    self.assertEqual(
+        _args_dict_from_uri('foo:key>v1;key<=v2', type_hints), {
+            'key': [
+                ConstraintClass('v1', ConstraintClass.Op.GT),
+                ConstraintClass('v2', ConstraintClass.Op.LE)
+            ]
+        })
+    self.assertEqual(
+        _args_dict_from_uri('foo:key>=v1;key<v4;key!=v2;key!=v3', type_hints), {
+            'key': [
+                ConstraintClass('v1', ConstraintClass.Op.GE),
+                ConstraintClass('v4', ConstraintClass.Op.LT),
+                ConstraintClass('v2', ConstraintClass.Op.NE),
+                ConstraintClass('v3', ConstraintClass.Op.NE),
+            ]
+        })
 
   def _check_resolver_result(self,
                              foo_res,
diff --git a/src/protozero/protoc_plugin/protozero_plugin.cc b/src/protozero/protoc_plugin/protozero_plugin.cc
index 5b08292..7a29776 100644
--- a/src/protozero/protoc_plugin/protozero_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_plugin.cc
@@ -371,6 +371,12 @@
           // name of this message is used to group them.
           std::string extension_name = extension->extension_scope()->name();
           extensions_[extension_name].push_back(extension);
+
+          if (extension->message_type()) {
+            // Emit a forward declaration of nested message types, as the outer
+            // class will refer to them when creating type aliases.
+            referenced_messages_.insert(extension->message_type());
+          }
         }
       } else {
         messages_.push_back(message);
@@ -755,6 +761,14 @@
       if (field->is_repeated() && !field->is_packed())
         has_nonpacked_repeated_fields = true;
     }
+    // Iterate over all fields in "extend" blocks.
+    for (int i = 0; i < message->extension_range_count(); ++i) {
+      const Descriptor::ExtensionRange* range = message->extension_range(i);
+      int candidate = range->end - 1;
+      if (candidate > kMaxDecoderFieldId)
+        continue;
+      max_field_id = std::max(max_field_id, candidate);
+    }
 
     std::string class_name = GetCppClassName(message) + "_Decoder";
     stub_h_->Print(
@@ -880,7 +894,8 @@
   }
 
   void GenerateConstantsForMessageFields(const Descriptor* message) {
-    const bool has_fields = (message->field_count() > 0);
+    const bool has_fields =
+        message->field_count() > 0 || message->extension_count() > 0;
 
     // Field number constants.
     if (has_fields) {
@@ -893,6 +908,15 @@
                        GetFieldNumberConstant(field), "id",
                        std::to_string(field->number()));
       }
+
+      for (int i = 0; i < message->extension_count(); ++i) {
+        const FieldDescriptor* field = message->extension(i);
+
+        stub_h_->Print("$name$ = $id$,\n", "name",
+                       GetFieldNumberConstant(field), "id",
+                       std::to_string(field->number()));
+      }
+
       stub_h_->Outdent();
       stub_h_->Print("};\n");
     }
@@ -1049,6 +1073,20 @@
       }
       GenerateFieldDescriptor(extension_name, field);
     }
+
+    if (!descriptors.empty()) {
+      stub_h_->Print("enum : int32_t {\n");
+      stub_h_->Indent();
+
+      for (const FieldDescriptor* field : descriptors) {
+        stub_h_->Print("$name$ = $id$,\n", "name",
+                       GetFieldNumberConstant(field), "id",
+                       std::to_string(field->number()));
+      }
+      stub_h_->Outdent();
+      stub_h_->Print("};\n");
+    }
+
     stub_h_->Outdent();
     stub_h_->Print("};\n");
   }
diff --git a/src/trace_processor/importers/common/process_tracker.cc b/src/trace_processor/importers/common/process_tracker.cc
index e1d23a3..f034867 100644
--- a/src/trace_processor/importers/common/process_tracker.cc
+++ b/src/trace_processor/importers/common/process_tracker.cc
@@ -15,11 +15,11 @@
  */
 
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/storage/stats.h"
 
-#include <cinttypes>
 #include <utility>
 
+#include "src/trace_processor/storage/stats.h"
+
 namespace perfetto {
 namespace trace_processor {
 
@@ -102,7 +102,7 @@
   // Remove the thread from the list of threads being tracked as any event after
   // this one should be ignored.
   auto& vector = tids_[tid];
-  vector.erase(std::remove(vector.begin(), vector.end(), utid));
+  vector.erase(std::remove(vector.begin(), vector.end(), utid), vector.end());
 
   auto opt_upid = thread_table->upid()[utid];
   if (!opt_upid.has_value() || process_table->pid()[*opt_upid] != tid)
@@ -536,13 +536,11 @@
 }
 
 void ProcessTracker::SetPidZeroIsUpidZeroIdleProcess() {
-  auto swapper_id = context_->storage->InternString("swapper");
-
   // Create a mapping from (t|p)id 0 -> u(t|p)id for the idle process.
   tids_.Insert(0, std::vector<UniqueTid>{swapper_utid_});
   pids_.Insert(0, swapper_upid_);
 
-  // Set the hardcoded, constant "swapper" thread name to the thread.
+  auto swapper_id = context_->storage->InternString("swapper");
   UpdateThreadName(0, swapper_id, ThreadNamePriority::kTraceProcessorConstant);
 }
 
diff --git a/src/trace_processor/importers/common/process_tracker.h b/src/trace_processor/importers/common/process_tracker.h
index 3808f99..e5ccd39 100644
--- a/src/trace_processor/importers/common/process_tracker.h
+++ b/src/trace_processor/importers/common/process_tracker.h
@@ -17,8 +17,11 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_PROCESS_TRACKER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_PROCESS_TRACKER_H_
 
-#include <tuple>
+#include <stdint.h>
+
+#include <optional>
 #include <unordered_set>
+#include <vector>
 
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/string_view.h"
@@ -35,6 +38,7 @@
 enum class ThreadNamePriority {
   kOther = 0,
   kFtrace = 1,
+  kEtwTrace = 1,
   kProcessTree = 2,
   kTrackDescriptorThreadType = 3,
   kTrackDescriptor = 4,
diff --git a/src/trace_processor/importers/common/thread_state_tracker.cc b/src/trace_processor/importers/common/thread_state_tracker.cc
index 37d26d1..2d98ffb 100644
--- a/src/trace_processor/importers/common/thread_state_tracker.cc
+++ b/src/trace_processor/importers/common/thread_state_tracker.cc
@@ -15,8 +15,10 @@
  */
 
 #include "src/trace_processor/importers/common/thread_state_tracker.h"
+
 #include <cstdint>
 #include <optional>
+
 #include "src/trace_processor/importers/common/process_tracker.h"
 
 namespace perfetto {
@@ -182,7 +184,10 @@
   // If common_flags contains TRACE_FLAG_HARDIRQ | TRACE_FLAG_SOFTIRQ, wakeup
   // was emitted in interrupt context.
   // See:
-  // https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/include/trace/trace_events.h
+  // https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/include/linux/trace_events.h
+  // TODO(rsavitski): we could also include TRACE_FLAG_NMI for a complete
+  // "interrupt context" meaning. But at the moment it's not necessary as this
+  // is used for sched_waking events, which are not emitted from NMI contexts.
   return common_flags & (0x08 | 0x10) ? 1 : 0;
 }
 
diff --git a/src/trace_processor/importers/common/trace_parser.cc b/src/trace_processor/importers/common/trace_parser.cc
index 6c586e0..608aca3 100644
--- a/src/trace_processor/importers/common/trace_parser.cc
+++ b/src/trace_processor/importers/common/trace_parser.cc
@@ -51,6 +51,9 @@
 void TraceParser::ParseInlineSchedWaking(uint32_t, int64_t, InlineSchedWaking) {
   PERFETTO_FATAL("Wrong parser type");
 }
+void TraceParser::ParseEtwEvent(uint32_t, int64_t, TracePacketData) {
+  PERFETTO_FATAL("Wrong parser type");
+}
 
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/common/trace_parser.h b/src/trace_processor/importers/common/trace_parser.h
index d948d27..9c7098f 100644
--- a/src/trace_processor/importers/common/trace_parser.h
+++ b/src/trace_processor/importers/common/trace_parser.h
@@ -43,6 +43,7 @@
   virtual void ParseTrackEvent(int64_t, TrackEventData);
   virtual void ParseSystraceLine(int64_t, SystraceLine);
 
+  virtual void ParseEtwEvent(uint32_t, int64_t, TracePacketData);
   virtual void ParseFtraceEvent(uint32_t, int64_t, TracePacketData);
   virtual void ParseInlineSchedSwitch(uint32_t, int64_t, InlineSchedSwitch);
   virtual void ParseInlineSchedWaking(uint32_t, int64_t, InlineSchedWaking);
diff --git a/src/trace_processor/importers/etw/BUILD.gn b/src/trace_processor/importers/etw/BUILD.gn
index d8985b1..23d8965 100644
--- a/src/trace_processor/importers/etw/BUILD.gn
+++ b/src/trace_processor/importers/etw/BUILD.gn
@@ -14,14 +14,30 @@
 
 import("../../../../gn/test.gni")
 
-source_set("full") {
+source_set("minimal") {
   sources = [
     "etw_module.cc",
     "etw_module.h",
+  ]
+  deps = [
+    "../../../../gn:default_deps",
+    "../common:parser_types",
+    "../common:trace_parser_hdr",
+    "../proto:proto_importer_module",
+  ]
+}
+
+source_set("full") {
+  sources = [
+    "etw_module_impl.cc",
+    "etw_module_impl.h",
+    "etw_parser.cc",
+    "etw_parser.h",
     "etw_tokenizer.cc",
     "etw_tokenizer.h",
   ]
   deps = [
+    ":minimal",
     "../../../../gn:default_deps",
     "../../../../protos/perfetto/common:zero",
     "../../../../protos/perfetto/trace:zero",
diff --git a/src/trace_processor/importers/etw/etw_module.cc b/src/trace_processor/importers/etw/etw_module.cc
index 508d2ca..a1a9d3f 100644
--- a/src/trace_processor/importers/etw/etw_module.cc
+++ b/src/trace_processor/importers/etw/etw_module.cc
@@ -15,37 +15,15 @@
  */
 
 #include "src/trace_processor/importers/etw/etw_module.h"
-#include "perfetto/base/build_config.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
-#include "src/trace_processor/importers/etw/etw_tokenizer.h"
 
-#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_processor/importers/common/parser_types.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-using perfetto::protos::pbzero::TracePacket;
-
-EtwModule::EtwModule(TraceProcessorContext* context) : tokenizer_(context) {
-  RegisterForField(TracePacket::kEtwEventsFieldNumber, context);
-}
-
-ModuleResult EtwModule::TokenizePacket(
-    const protos::pbzero::TracePacket::Decoder& decoder,
-    TraceBlobView* packet,
-    int64_t /*packet_timestamp*/,
-    PacketSequenceState* seq_state,
-    uint32_t field_id) {
-  switch (field_id) {
-    case TracePacket::kEtwEventsFieldNumber: {
-      auto etw_field = decoder.etw_events();
-      tokenizer_.TokenizeEtwBundle(
-          packet->slice(etw_field.data, etw_field.size), seq_state);
-      return ModuleResult::Handled();
-    }
-  }
-  return ModuleResult::Ignored();
-}
+void EtwModule::ParseEtwEventData(uint32_t /*cpu*/,
+                                  int64_t /*ts*/,
+                                  const TracePacketData&) {}
 
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/etw/etw_module.h b/src/trace_processor/importers/etw/etw_module.h
index 1624af1..5f80271 100644
--- a/src/trace_processor/importers/etw/etw_module.h
+++ b/src/trace_processor/importers/etw/etw_module.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -17,11 +17,7 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ETW_ETW_MODULE_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_ETW_ETW_MODULE_H_
 
-#include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "src/trace_processor/importers/common/parser_types.h"
-#include "src/trace_processor/importers/common/trace_parser.h"
-#include "src/trace_processor/importers/etw/etw_module.h"
-#include "src/trace_processor/importers/etw/etw_tokenizer.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 
 namespace perfetto {
@@ -29,17 +25,9 @@
 
 class EtwModule : public ProtoImporterModule {
  public:
-  explicit EtwModule(TraceProcessorContext* context);
-
-  ModuleResult TokenizePacket(
-      const protos::pbzero::TracePacket::Decoder& decoder,
-      TraceBlobView* packet,
-      int64_t packet_timestamp,
-      PacketSequenceState* state,
-      uint32_t field_id) override;
-
- private:
-  EtwTokenizer tokenizer_;
+  virtual void ParseEtwEventData(uint32_t cpu,
+                                 int64_t ts,
+                                 const TracePacketData& data);
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/etw/etw_module_impl.cc b/src/trace_processor/importers/etw/etw_module_impl.cc
new file mode 100644
index 0000000..7c44d04
--- /dev/null
+++ b/src/trace_processor/importers/etw/etw_module_impl.cc
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/etw/etw_module_impl.h"
+
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/etw/etw_tokenizer.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+using perfetto::protos::pbzero::TracePacket;
+
+EtwModuleImpl::EtwModuleImpl(TraceProcessorContext* context)
+    : tokenizer_(context), parser_(context) {
+  RegisterForField(TracePacket::kEtwEventsFieldNumber, context);
+}
+
+ModuleResult EtwModuleImpl::TokenizePacket(
+    const protos::pbzero::TracePacket::Decoder& decoder,
+    TraceBlobView* packet,
+    int64_t /*packet_timestamp*/,
+    PacketSequenceState* seq_state,
+    uint32_t field_id) {
+  switch (field_id) {
+    case TracePacket::kEtwEventsFieldNumber: {
+      auto etw_field = decoder.etw_events();
+      tokenizer_.TokenizeEtwBundle(
+          packet->slice(etw_field.data, etw_field.size), seq_state);
+      return ModuleResult::Handled();
+    }
+  }
+  return ModuleResult::Ignored();
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/etw/etw_module_impl.h b/src/trace_processor/importers/etw/etw_module_impl.h
new file mode 100644
index 0000000..af05fc8
--- /dev/null
+++ b/src/trace_processor/importers/etw/etw_module_impl.h
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ETW_ETW_MODULE_IMPL_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ETW_ETW_MODULE_IMPL_H_
+
+#include "src/trace_processor/importers/common/parser_types.h"
+#include "src/trace_processor/importers/etw/etw_module.h"
+#include "src/trace_processor/importers/etw/etw_parser.h"
+#include "src/trace_processor/importers/etw/etw_tokenizer.h"
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceBlobView;
+
+class EtwModuleImpl : public EtwModule {
+ public:
+  explicit EtwModuleImpl(TraceProcessorContext* context);
+
+  ModuleResult TokenizePacket(
+      const protos::pbzero::TracePacket::Decoder& decoder,
+      TraceBlobView* packet,
+      int64_t packet_timestamp,
+      PacketSequenceState* state,
+      uint32_t field_id) override;
+
+  void ParseEtwEventData(uint32_t cpu,
+                         int64_t ts,
+                         const TracePacketData& data) override {
+    util::Status res = parser_.ParseEtwEvent(cpu, ts, data);
+    if (!res.ok()) {
+      PERFETTO_ELOG("%s", res.message().c_str());
+    }
+  }
+
+ private:
+  EtwTokenizer tokenizer_;
+  EtwParser parser_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_ETW_ETW_MODULE_IMPL_H_
diff --git a/src/trace_processor/importers/etw/etw_parser.cc b/src/trace_processor/importers/etw/etw_parser.cc
new file mode 100644
index 0000000..99f790d
--- /dev/null
+++ b/src/trace_processor/importers/etw/etw_parser.cc
@@ -0,0 +1,140 @@
+/*
+ etw* 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.
+ */
+
+#include "src/trace_processor/importers/etw/etw_parser.h"
+
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/parser_types.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/sched_event_tracker.h"
+#include "src/trace_processor/importers/common/thread_state_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+
+#include "protos/perfetto/trace/etw/etw.pbzero.h"
+#include "protos/perfetto/trace/etw/etw_event.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+namespace {
+
+using protozero::ConstBytes;
+
+}  // namespace
+EtwParser::EtwParser(TraceProcessorContext* context) : context_(context) {}
+
+util::Status EtwParser::ParseEtwEvent(uint32_t cpu,
+                                      int64_t ts,
+                                      const TracePacketData& data) {
+  using protos::pbzero::EtwTraceEvent;
+  const TraceBlobView& event = data.packet;
+  protos::pbzero::EtwTraceEvent::Decoder decoder(event.data(), event.length());
+
+  if (decoder.has_c_switch()) {
+    ParseCswitch(ts, cpu, decoder.c_switch());
+  }
+
+  if (decoder.has_ready_thread()) {
+    ParseReadyThread(ts, decoder.ready_thread());
+  }
+
+  return util::OkStatus();
+}
+
+void EtwParser::ParseCswitch(int64_t timestamp, uint32_t cpu, ConstBytes blob) {
+  protos::pbzero::CSwitchEtwEvent::Decoder cs(blob.data, blob.size);
+  PushSchedSwitch(cpu, timestamp, cs.old_thread_id(), cs.old_thread_state(),
+                  cs.new_thread_id(), cs.new_thread_priority());
+}
+
+void EtwParser::ParseReadyThread(int64_t timestamp, ConstBytes blob) {
+  protos::pbzero::ReadyThreadEtwEvent::Decoder rt(blob.data, blob.size);
+  UniqueTid utid =
+      context_->process_tracker->GetOrCreateThread(rt.t_thread_id());
+  ThreadStateTracker::GetOrCreate(context_)->PushWakingEvent(timestamp, utid,
+                                                             utid);
+}
+
+void EtwParser::PushSchedSwitch(uint32_t cpu,
+                                int64_t ts,
+                                uint32_t prev_tid,
+                                int64_t prev_state,
+                                uint32_t next_tid,
+                                int32_t next_prio) {
+  // At this stage all events should be globally timestamp ordered.
+  if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(
+          ts, "etw_cswitch", stats::sched_switch_out_of_order)) {
+    return;
+  }
+
+  UniqueTid next_utid = context_->process_tracker->GetOrCreateThread(next_tid);
+
+  // First use this data to close the previous slice.
+  bool prev_pid_match_prev_next_pid = false;
+  auto* pending_sched = sched_event_state_.GetPendingSchedInfoForCpu(cpu);
+  uint32_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
+  StringId prev_state_string_id = TaskStateToStringId(prev_state);
+  if (prev_state_string_id == kNullStringId) {
+    context_->storage->IncrementStats(stats::task_state_invalid);
+  }
+  if (pending_slice_idx < std::numeric_limits<uint32_t>::max()) {
+    prev_pid_match_prev_next_pid = prev_tid == pending_sched->last_pid;
+    if (PERFETTO_LIKELY(prev_pid_match_prev_next_pid)) {
+      context_->sched_event_tracker->ClosePendingSlice(pending_slice_idx, ts,
+                                                       prev_state_string_id);
+    } else {
+      // If the pids are not consistent, make a note of this.
+      context_->storage->IncrementStats(stats::mismatched_sched_switch_tids);
+    }
+  }
+
+  auto new_slice_idx = context_->sched_event_tracker->AddStartSlice(
+      cpu, ts, next_utid, next_prio);
+
+  // Finally, update the info for the next sched switch on this CPU.
+  pending_sched->pending_slice_storage_idx = new_slice_idx;
+  pending_sched->last_pid = next_tid;
+  pending_sched->last_utid = next_utid;
+  pending_sched->last_prio = next_prio;
+
+  UniqueTid prev_utid = context_->process_tracker->GetOrCreateThread(prev_tid);
+
+  // Update the ThreadState table.
+  ThreadStateTracker::GetOrCreate(context_)->PushSchedSwitchEvent(
+      ts, cpu, prev_utid, prev_state_string_id, next_utid);
+}
+
+StringId EtwParser::TaskStateToStringId(int64_t task_state_int) {
+  const auto state = static_cast<uint8_t>(task_state_int);
+  // Mapping for the different Etw states with their string description.
+  std::map<uint8_t, base::StringView> etw_states_map = {
+      {0x00, "Initialized"},     // INITIALIZED
+      {0x01, "R"},               // READY
+      {0x02, "Running"},         // RUNNING
+      {0x03, "Stand By"},        // STANDBY
+      {0x04, "T"},               // TERMINATED
+      {0x05, "Waiting"},         // WAITING
+      {0x06, "Transition"},      // TRANSITION
+      {0x07, "Deferred Ready"},  // DEFERRED_READY
+  };
+
+  return etw_states_map.find(state) != etw_states_map.end()
+             ? context_->storage->InternString(etw_states_map[state])
+             : kNullStringId;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/etw/etw_parser.h b/src/trace_processor/importers/etw/etw_parser.h
new file mode 100644
index 0000000..380c6e8
--- /dev/null
+++ b/src/trace_processor/importers/etw/etw_parser.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ETW_ETW_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ETW_ETW_PARSER_H_
+
+#include "perfetto/protozero/field.h"
+#include "perfetto/trace_processor/status.h"
+#include "src/trace_processor/importers/common/parser_types.h"
+#include "src/trace_processor/importers/common/sched_event_state.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class EtwParser {
+ public:
+  explicit EtwParser(TraceProcessorContext* context);
+
+  util::Status ParseEtwEvent(uint32_t cpu,
+                             int64_t ts,
+                             const TracePacketData& data);
+
+ private:
+  void ParseCswitch(int64_t timestamp, uint32_t cpu, protozero::ConstBytes);
+  void ParseReadyThread(int64_t timestamp, protozero::ConstBytes);
+  void PushSchedSwitch(uint32_t cpu,
+                       int64_t timestamp,
+                       uint32_t prev_pid,
+                       int64_t prev_state,
+                       uint32_t next_pid,
+                       int32_t next_prio);
+  StringId TaskStateToStringId(int64_t task_state_int);
+
+  TraceProcessorContext* context_;
+
+  SchedEventState sched_event_state_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_ETW_ETW_PARSER_H_
diff --git a/src/trace_processor/importers/etw/etw_tokenizer.cc b/src/trace_processor/importers/etw/etw_tokenizer.cc
index f07d74c..2474454 100644
--- a/src/trace_processor/importers/etw/etw_tokenizer.cc
+++ b/src/trace_processor/importers/etw/etw_tokenizer.cc
@@ -50,9 +50,11 @@
   // in case the EtwTraceEvent does not contain the cpu.
   std::optional<uint32_t> bundle_cpu =
       decoder.has_cpu() ? std::make_optional(decoder.cpu()) : std::nullopt;
-  auto it = decoder.event();
-  return TokenizeEtwEvent(bundle_cpu, bundle.slice(it->data(), it->size()),
-                          state);
+
+  for (auto it = decoder.event(); it; ++it) {
+    TokenizeEtwEvent(bundle_cpu, bundle.slice(it->data(), it->size()), state);
+  }
+  return base::OkStatus();
 }
 
 PERFETTO_ALWAYS_INLINE
diff --git a/src/trace_processor/importers/etw/etw_tokenizer.h b/src/trace_processor/importers/etw/etw_tokenizer.h
index 1d93a14..6447ed2 100644
--- a/src/trace_processor/importers/etw/etw_tokenizer.h
+++ b/src/trace_processor/importers/etw/etw_tokenizer.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
index 2c0a962..a7b1ced 100644
--- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
@@ -74,8 +74,8 @@
                                         uint32_t next_pid,
                                         base::StringView next_comm,
                                         int32_t next_prio) {
-  if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(ts,
-      "sched_switch",stats::sched_switch_out_of_order)) {
+  if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(
+          ts, "sched_switch", stats::sched_switch_out_of_order)) {
     return;
   }
 
@@ -267,7 +267,6 @@
       ts, wakee_utid, curr_utid, common_flags);
 }
 
-PERFETTO_ALWAYS_INLINE
 void FtraceSchedEventTracker::AddRawSchedSwitchEvent(uint32_t cpu,
                                                      int64_t ts,
                                                      UniqueTid prev_utid,
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index 0f2f53a..d40a84b 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -100,6 +100,7 @@
     "../../util:profiler_util",
     "../common",
     "../common:parser_types",
+    "../etw:minimal",
     "../ftrace:minimal",
     "../json:minimal",
     "../memory_tracker:graph_processor",
diff --git a/src/trace_processor/importers/proto/additional_modules.cc b/src/trace_processor/importers/proto/additional_modules.cc
index 83b13e5..91645c3 100644
--- a/src/trace_processor/importers/proto/additional_modules.cc
+++ b/src/trace_processor/importers/proto/additional_modules.cc
@@ -15,7 +15,7 @@
  */
 
 #include "src/trace_processor/importers/proto/additional_modules.h"
-#include "src/trace_processor/importers/etw/etw_module.h"
+#include "src/trace_processor/importers/etw/etw_module_impl.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module_impl.h"
 #include "src/trace_processor/importers/proto/android_camera_event_module.h"
 #include "src/trace_processor/importers/proto/android_probes_module.h"
@@ -45,13 +45,14 @@
   context->modules.emplace_back(new MetadataModule(context));
   context->modules.emplace_back(new V8Module(context));
   context->modules.emplace_back(new WinscopeModule(context));
-  context->modules.emplace_back(new EtwModule(context));
 
-  // Ftrace module is special, because it has one extra method for parsing
-  // ftrace packets. So we need to store a pointer to it separately.
+  // Ftrace/Etw modules are special, because it has one extra method for parsing
+  // ftrace/etw packets. So we need to store a pointer to it separately.
   context->modules.emplace_back(new FtraceModuleImpl(context));
   context->ftrace_module =
       static_cast<FtraceModule*>(context->modules.back().get());
+  context->modules.emplace_back(new EtwModuleImpl(context));
+  context->etw_module = static_cast<EtwModule*>(context->modules.back().get());
 
   if (context->multi_machine_trace_manager) {
     context->multi_machine_trace_manager->EnableAdditionalModules(
diff --git a/src/trace_processor/importers/proto/default_modules.cc b/src/trace_processor/importers/proto/default_modules.cc
index ca18beb..b1cfc93 100644
--- a/src/trace_processor/importers/proto/default_modules.cc
+++ b/src/trace_processor/importers/proto/default_modules.cc
@@ -15,6 +15,7 @@
  */
 
 #include "src/trace_processor/importers/proto/default_modules.h"
+#include "src/trace_processor/importers/etw/etw_module.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
 #include "src/trace_processor/importers/proto/chrome_system_probes_module.h"
 #include "src/trace_processor/importers/proto/memory_tracker_snapshot_module.h"
@@ -28,10 +29,13 @@
 
 void RegisterDefaultModules(TraceProcessorContext* context) {
   context->modules.emplace_back(new FtraceModule());
-  // Ftrace module is special, because it has one extra method for parsing
-  // ftrace packets. So we need to store a pointer to it separately.
+  context->modules.emplace_back(new EtwModule());
+  // Ftrace and Etw modules are special, because they have an extra method for
+  // parsing the ftrace/etw packets. So we need to store a pointer to it
+  // separately.
   context->ftrace_module =
       static_cast<FtraceModule*>(context->modules.back().get());
+  context->etw_module = static_cast<EtwModule*>(context->modules.back().get());
 
   context->modules.emplace_back(new TrackEventModule(context));
   context->track_module =
diff --git a/src/trace_processor/importers/proto/proto_trace_parser.cc b/src/trace_processor/importers/proto/proto_trace_parser.cc
index 4043ec6..d81ef57 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser.cc
@@ -36,6 +36,7 @@
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/etw/etw_module.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
@@ -112,6 +113,18 @@
   context_->args_tracker->Flush();
 }
 
+void ProtoTraceParser::ParseEtwEvent(uint32_t cpu,
+                                     int64_t ts,
+                                     TracePacketData data) {
+  PERFETTO_DCHECK(context_->etw_module);
+  context_->etw_module->ParseEtwEventData(cpu, ts, data);
+
+  // TODO(lalitm): maybe move this to the flush method in the trace processor
+  // once we have it. This may reduce performance in the ArgsTracker though so
+  // needs to be handled carefully.
+  context_->args_tracker->Flush();
+}
+
 void ProtoTraceParser::ParseFtraceEvent(uint32_t cpu,
                                         int64_t ts,
                                         TracePacketData data) {
diff --git a/src/trace_processor/importers/proto/proto_trace_parser.h b/src/trace_processor/importers/proto/proto_trace_parser.h
index bfe5602..621bd02 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser.h
+++ b/src/trace_processor/importers/proto/proto_trace_parser.h
@@ -49,6 +49,10 @@
   void ParseTrackEvent(int64_t ts, TrackEventData data) override;
   void ParseTracePacket(int64_t ts, TracePacketData data) override;
 
+  void ParseEtwEvent(uint32_t cpu,
+                     int64_t /*ts*/,
+                     TracePacketData data) override;
+
   void ParseFtraceEvent(uint32_t cpu,
                         int64_t /*ts*/,
                         TracePacketData data) override;
diff --git a/src/trace_processor/perfetto_sql/stdlib/OWNERS b/src/trace_processor/perfetto_sql/stdlib/OWNERS
index 0479e4b..2d6db69 100644
--- a/src/trace_processor/perfetto_sql/stdlib/OWNERS
+++ b/src/trace_processor/perfetto_sql/stdlib/OWNERS
@@ -1,3 +1,5 @@
-# These can't be added inside chrome/ bacause the whole directory is
-# blown away on every import.
+# These can't be added inside chrome/ because the whole directory is
+# blown away on every import. Subdirectories of `chrome` will also
+# need CHROMIUM_OWNERS added here.
 per-file chrome/* = file://protos/third_party/CHROMIUM_OWNERS
+per-file chrome/scroll_jank/* = file://protos/third_party/CHROMIUM_OWNERS
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql
index f6e29f9..fcc8933 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql
@@ -2,6 +2,8 @@
 -- Use of this source code is governed by a BSD-style license that can be
 -- found in the LICENSE file.
 
+INCLUDE PERFETTO MODULE chrome.scroll_jank.utils;
+
 -- Defines slices for all of the individual scrolls in a trace based on the
 -- LatencyInfo-based scroll definition.
 --
@@ -19,36 +21,39 @@
   ts INT,
   -- The duration of the scroll.
   dur INT,
-  -- The earliest timestamp of the InputLatency::GestureScrollBegin for the
+  -- The earliest timestamp of the EventLatency slice of the GESTURE_SCROLL_BEGIN type for the
   -- corresponding scroll id.
   gesture_scroll_begin_ts INT,
-  -- The earliest timestamp of the InputLatency::GestureScrollEnd for the
+  -- The earliest timestamp of the EventLatency slice of the GESTURE_SCROLL_END type /
+  -- the latest timestamp of the EventLatency slice of the GESTURE_SCROLL_UPDATE type for the
   -- corresponding scroll id.
   gesture_scroll_end_ts INT
 ) AS
 WITH all_scrolls AS (
   SELECT
-    name,
-    ts,
-    dur,
-    extract_arg(arg_set_id, 'chrome_latency_info.gesture_scroll_id') AS scroll_id
-  FROM slice
-  WHERE name GLOB 'InputLatency::GestureScroll*'
-  AND extract_arg(arg_set_id, 'chrome_latency_info.gesture_scroll_id') IS NOT NULL
+    args.string_value AS name,
+    S.ts AS ts,
+    S.dur AS dur,
+    chrome_get_most_recent_scroll_begin_id(S.ts) AS scroll_id
+  FROM slice AS S JOIN args USING(arg_set_id)
+  WHERE name="EventLatency"
+  AND args.string_value GLOB "*GESTURE_SCROLL*"
 ),
 scroll_starts AS (
   SELECT
     scroll_id,
     MIN(ts) AS gesture_scroll_begin_ts
   FROM all_scrolls
-  WHERE name = 'InputLatency::GestureScrollBegin'
+  WHERE name = "GESTURE_SCROLL_BEGIN"
   GROUP BY scroll_id
-), scroll_ends AS (
+),
+scroll_ends AS (
   SELECT
     scroll_id,
-    MIN(ts) AS gesture_scroll_end_ts
+    MAX(ts) AS gesture_scroll_end_ts
   FROM all_scrolls
-  WHERE name = 'InputLatency::GestureScrollEnd'
+  WHERE name GLOB "*GESTURE_SCROLL_UPDATE"
+    OR name = "GESTURE_SCROLL_END"
   GROUP BY scroll_id
 )
 SELECT
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
index 74141e6..b7b6bad 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
@@ -103,3 +103,18 @@
 WHERE
   category GLOB "*scheduler.long_tasks*"
   AND name = $name;
+
+-- Extracts scroll id for the EventLatency slice at `ts`.
+CREATE PERFETTO FUNCTION chrome_get_most_recent_scroll_begin_id(
+  -- Timestamp of the EventLatency slice to get the scroll id for.
+  ts INT)
+-- The event_latency_id of the EventLatency slice with the type
+-- GESTURE_SCROLL_BEGIN that is the closest to `ts`.
+RETURNS INT AS
+SELECT EXTRACT_ARG(arg_set_id, "event_latency.event_latency_id")
+FROM slice
+WHERE name="EventLatency"
+AND EXTRACT_ARG(arg_set_id, "event_latency.event_type") = "GESTURE_SCROLL_BEGIN"
+AND ts<=$ts
+ORDER BY ts DESC
+LIMIT 1;
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/OWNERS b/src/trace_processor/perfetto_sql/stdlib/common/OWNERS
index a2788c4..0a16b3f 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/OWNERS
+++ b/src/trace_processor/perfetto_sql/stdlib/common/OWNERS
@@ -6,4 +6,3 @@
 
 # For emergency reviews
 primiano@google.com
-hjd@google.com
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/states.sql b/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
index 37f759a..f6ce66a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
@@ -46,6 +46,12 @@
 WHEN 'W' THEN 'Waking'
 WHEN 'P' THEN 'Parked'
 WHEN 'N' THEN 'No Load'
+-- ETW SPECIFIC STATES
+WHEN 'Stand By' THEN 'Stand By'
+WHEN 'Initialized' THEN 'Initialized'
+WHEN 'Waiting' THEN 'Waiting'
+WHEN 'Transition' THEN 'Transition'
+WHEN 'Deferred Ready' THEN 'Deferred Ready'
 ELSE $short_name
 END;
 
@@ -69,4 +75,4 @@
     WHEN 0 THEN ' (non-IO)'
     ELSE ''
   END
-);
\ No newline at end of file
+);
diff --git a/src/trace_processor/sorter/trace_sorter.cc b/src/trace_processor/sorter/trace_sorter.cc
index b937e2e..e8159c3 100644
--- a/src/trace_processor/sorter/trace_sorter.cc
+++ b/src/trace_processor/sorter/trace_sorter.cc
@@ -211,18 +211,21 @@
       return;
     case TimestampedEvent::Type::kInlineSchedSwitch:
     case TimestampedEvent::Type::kInlineSchedWaking:
-    case TimestampedEvent::Type::kFtraceEvent:
     case TimestampedEvent::Type::kEtwEvent:
+    case TimestampedEvent::Type::kFtraceEvent:
       PERFETTO_FATAL("Invalid event type");
   }
   PERFETTO_FATAL("For GCC");
 }
 
-void TraceSorter::ParseEtwPacket(TraceParser* /*parser*/,
-                                 uint32_t /*cpu*/,
+void TraceSorter::ParseEtwPacket(TraceParser* parser,
+                                 uint32_t cpu,
                                  const TimestampedEvent& event) {
+  TraceTokenBuffer::Id id = GetTokenBufferId(event);
   switch (static_cast<TimestampedEvent::Type>(event.event_type)) {
     case TimestampedEvent::Type::kEtwEvent:
+      parser->ParseEtwEvent(cpu, event.ts,
+                            token_buffer_.Extract<TracePacketData>(id));
       return;
     case TimestampedEvent::Type::kInlineSchedSwitch:
     case TimestampedEvent::Type::kInlineSchedWaking:
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index 3dc9cdf..be268d7 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -68,6 +68,8 @@
     "scrub_process_trees.h",
     "scrub_trace_packet.cc",
     "scrub_trace_packet.h",
+    "suspend_resume.cc",
+    "suspend_resume.h",
     "trace_redaction_framework.cc",
     "trace_redaction_framework.h",
     "trace_redactor.cc",
@@ -94,20 +96,22 @@
     "filter_ftrace_using_allowlist_integrationtest.cc",
     "filter_sched_waking_events_integrationtest.cc",
     "filter_task_rename_integrationtest.cc",
+    "prune_package_list_integrationtest.cc",
     "redact_sched_switch_integrationtest.cc",
     "scrub_ftrace_events_integrationtest.cc",
     "scrub_process_stats_integrationtest.cc",
     "scrub_process_trees_integrationtest.cc",
     "trace_redaction_integration_fixture.cc",
     "trace_redaction_integration_fixture.h",
-    "trace_redactor_integrationtest.cc",
   ]
   deps = [
     ":trace_redaction",
     "../../gn:default_deps",
     "../../gn:gtest_and_gmock",
     "../../include/perfetto/ext/base",
+    "../../protos/perfetto/trace:non_minimal_cpp",
     "../../protos/perfetto/trace:non_minimal_zero",
+    "../../protos/perfetto/trace/android:cpp",
     "../../protos/perfetto/trace/android:zero",
     "../../protos/perfetto/trace/ftrace:zero",
     "../../protos/perfetto/trace/ps:zero",
@@ -130,11 +134,13 @@
     "redact_process_free_unittest.cc",
     "redact_sched_switch_unittest.cc",
     "redact_task_newtask_unittest.cc",
+    "suspend_resume_unittest.cc",
   ]
   deps = [
     ":trace_redaction",
     "../../gn:default_deps",
     "../../gn:gtest_and_gmock",
+    "../../include/perfetto/ext/base:base",
     "../../include/perfetto/protozero:protozero",
     "../../protos/perfetto/config:cpp",
     "../../protos/perfetto/config:zero",
diff --git a/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc b/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
index 89946ae..e53770c 100644
--- a/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
+++ b/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
@@ -18,14 +18,11 @@
 #include <string>
 
 #include "perfetto/base/status.h"
-#include "perfetto/ext/base/file_utils.h"
 #include "src/base/test/status_matchers.h"
-#include "src/base/test/tmp_dir_tree.h"
-#include "src/base/test/utils.h"
 #include "src/trace_redaction/filter_ftrace_using_allowlist.h"
 #include "src/trace_redaction/populate_allow_lists.h"
 #include "src/trace_redaction/scrub_ftrace_events.h"
-#include "src/trace_redaction/trace_redaction_framework.h"
+#include "src/trace_redaction/trace_redaction_integration_fixture.h"
 #include "src/trace_redaction/trace_redactor.h"
 #include "test/gtest_and_gmock.h"
 
@@ -36,39 +33,15 @@
 
 namespace perfetto::trace_redaction {
 
-class FilterFtraceUsingAllowlistTest : public testing::Test {
+class FilterFtraceUsingAllowlistTest
+    : public testing::Test,
+      protected TraceRedactionIntegrationFixure {
  protected:
   void SetUp() override {
-    redactor_.emplace_build<PopulateAllowlists>();
-
-    auto* scrub_ftrace_events =
-        redactor_.emplace_transform<ScrubFtraceEvents>();
-    scrub_ftrace_events->emplace_back<FilterFtraceUsingAllowlist>();
-
-    src_trace_ =
-        base::GetTestDataPath("test/data/trace-redaction-general.pftrace");
-
-    dest_trace_ = tmp_dir_.AbsolutePath("dst.pftrace");
-  }
-
-  base::Status Redact() {
-    auto status = redactor_.Redact(src_trace_, dest_trace_, &context_);
-
-    // If redaction failed, the redactor should not have written the file to
-    // disk.
-    if (status.ok()) {
-      tmp_dir_.TrackFile("dst.pftrace");
-    }
-
-    return status;
-  }
-
-  base::StatusOr<std::string> LoadOriginal() const {
-    return ReadRawTrace(src_trace_);
-  }
-
-  base::StatusOr<std::string> LoadRedacted() const {
-    return ReadRawTrace(dest_trace_);
+    trace_redactor()->emplace_build<PopulateAllowlists>();
+    trace_redactor()
+        ->emplace_transform<ScrubFtraceEvents>()
+        ->emplace_back<FilterFtraceUsingAllowlist>();
   }
 
   // Parse the given buffer and gather field ids from across all events. This
@@ -100,25 +73,6 @@
 
     return event_ids;
   }
-
- private:
-  base::StatusOr<std::string> ReadRawTrace(const std::string& path) const {
-    std::string redacted_buffer;
-
-    if (base::ReadFile(path, &redacted_buffer)) {
-      return redacted_buffer;
-    }
-
-    return base::ErrStatus("Failed to read %s", path.c_str());
-  }
-
-  Context context_;
-  TraceRedactor redactor_;
-
-  base::TmpDirTree tmp_dir_;
-
-  std::string src_trace_;
-  std::string dest_trace_;
 };
 
 // This is not a test of FilterFtraceUsingAllowlist, but instead verifies of the
diff --git a/src/trace_redaction/filter_packet_using_allowlist.cc b/src/trace_redaction/filter_packet_using_allowlist.cc
index 5b01c4d..b1a4dc4 100644
--- a/src/trace_redaction/filter_packet_using_allowlist.cc
+++ b/src/trace_redaction/filter_packet_using_allowlist.cc
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#include <string>
-
 #include "perfetto/base/status.h"
 #include "perfetto/protozero/field.h"
 #include "src/trace_redaction/filter_packet_using_allowlist.h"
@@ -32,25 +30,11 @@
   return base::OkStatus();
 }
 
-bool FilterPacketUsingAllowlist::KeepPacket(const Context& context,
-                                            const std::string& bytes) const {
+bool FilterPacketUsingAllowlist::KeepField(
+    const Context& context,
+    const protozero::Field& field) const {
   PERFETTO_DCHECK(!context.trace_packet_allow_list.empty());
-
-  const auto& allow_list = context.trace_packet_allow_list;
-
-  protozero::ProtoDecoder decoder(bytes);
-
-  // A packet should only have one data type (proto oneof), but there are other
-  // values in the packet (e.g. timestamp). If one field is in the allowlist,
-  // then allow the whole trace packet.
-  for (auto field = decoder.ReadField(); field.valid();
-       field = decoder.ReadField()) {
-    if (allow_list.count(field.id()) != 0) {
-      return true;
-    }
-  }
-
-  return false;
+  return field.valid() && context.trace_packet_allow_list.count(field.id());
 }
 
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/filter_packet_using_allowlist.h b/src/trace_redaction/filter_packet_using_allowlist.h
index 7caa0ec..9205e94 100644
--- a/src/trace_redaction/filter_packet_using_allowlist.h
+++ b/src/trace_redaction/filter_packet_using_allowlist.h
@@ -31,8 +31,8 @@
  public:
   base::Status VerifyContext(const Context& context) const override;
 
-  bool KeepPacket(const Context& context,
-                  const std::string& bytes) const override;
+  bool KeepField(const Context& context,
+                 const protozero::Field& field) const override;
 };
 
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/filter_packet_using_allowlist_unittest.cc b/src/trace_redaction/filter_packet_using_allowlist_unittest.cc
index ffdb99f..fad6879 100644
--- a/src/trace_redaction/filter_packet_using_allowlist_unittest.cc
+++ b/src/trace_redaction/filter_packet_using_allowlist_unittest.cc
@@ -14,134 +14,59 @@
  * limitations under the License.
  */
 
-#include <string>
-
-#include "src/base/test/status_matchers.h"
 #include "src/trace_redaction/filter_packet_using_allowlist.h"
-#include "src/trace_redaction/scrub_trace_packet.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
 #include "test/gtest_and_gmock.h"
 
-#include "protos/perfetto/trace/ps/process_tree.gen.h"
-#include "protos/perfetto/trace/trace_packet.gen.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
+// TODO(vaage): These tests were used to test the filter-driver, but these tests
+// no longer do that. A new test suite should be created to test the driver code
+// with the different filters.
 namespace perfetto::trace_redaction {
 
-TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnErrorForNullPacket) {
-  ScrubTracePacket transform_;
-  transform_.emplace_back<FilterPacketUsingAllowlist>();
+namespace {
 
-  // Have something in the allow-list to avoid that error.
+constexpr auto kJustSomeFieldId =
+    protos::pbzero::TracePacket::kProcessTreeFieldNumber;
+
+}  // namespace
+
+TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnsErrorForEmptyAllowlist) {
   Context context;
-  context.trace_packet_allow_list.insert(
-      protos::pbzero::TracePacket::kProcessTreeFieldNumber);
 
-  ASSERT_FALSE(transform_.Transform(context, nullptr).ok());
+  FilterPacketUsingAllowlist filter;
+  auto status = filter.VerifyContext(context);
+
+  ASSERT_FALSE(status.ok()) << status.message();
 }
 
-TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnErrorForEmptyPacket) {
-  ScrubTracePacket transform_;
-  transform_.emplace_back<FilterPacketUsingAllowlist>();
-
-  // Have something in the allow-list to avoid that error.
+TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnsFalseForInvalidField) {
+  // Have something in the allow-list to avoid an error.
   Context context;
-  context.trace_packet_allow_list.insert(
-      protos::pbzero::TracePacket::kProcessTreeFieldNumber);
+  context.trace_packet_allow_list.insert(kJustSomeFieldId);
 
-  std::string packet_str = "";
+  protozero::Field invalid = {};
+  ASSERT_FALSE(invalid.valid());
 
-  ASSERT_FALSE(transform_.Transform(context, &packet_str).ok());
+  FilterPacketUsingAllowlist filter;
+  ASSERT_FALSE(filter.KeepField(context, invalid));
 }
 
-class FilterPacketUsingAllowlistTest : public testing::Test {
- protected:
-  void SetUp() override {
-    transform_.emplace_back<FilterPacketUsingAllowlist>();
-  }
+TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnsFalseForExcludedField) {
+  Context context;
+  context.trace_packet_allow_list.insert(kJustSomeFieldId);
 
-  base::StatusOr<std::string> Redact(const protos::gen::TracePacket& packet) {
-    auto str = packet.SerializeAsString();
-    auto status = transform_.Transform(context_, &str);
+  protozero::HeapBuffered<protos::pbzero::TracePacket> packet;
+  packet->set_timestamp(123456789);
 
-    if (status.ok()) {
-      return str;
-    }
+  auto buffer = packet.SerializeAsString();
 
-    return status;
-  }
+  protozero::ProtoDecoder decoder(buffer);
+  protozero::Field field = decoder.FindField(kJustSomeFieldId);
 
-  Context context_;
-
- private:
-  ScrubTracePacket transform_;
-};
-
-TEST_F(FilterPacketUsingAllowlistTest, ReturnErrorForEmptyAllowList) {
-  // The context will have no allow-list entries. ScrubTracePacket should fail.
-
-  protos::gen::TracePacket packet;
-
-  auto status = Redact(packet);
-  ASSERT_FALSE(status.ok()) << status.status().c_message();
-}
-
-// The whole packet should be dropped (cleared) when it has a data type not
-// included in the allow-list.
-TEST_F(FilterPacketUsingAllowlistTest, DropsOutsiderPacketType) {
-  protos::gen::TracePacket packet;
-  packet.set_timestamp(1234);
-  packet.mutable_android_camera_frame_event();  // Creates and sets data.
-
-  // Populate the allow-list with something that doesn't match the data in the
-  // packet.
-  context_.trace_packet_allow_list.insert(
-      protos::pbzero::TracePacket::kProcessTreeFieldNumber);
-
-  auto status = Redact(packet);
-  ASSERT_OK(status) << status.status().c_message();
-
-  ASSERT_TRUE(status->empty());
-}
-
-// Typically a trace packet should always have a data type (e.g. ProcessTree),
-// but it is possible that another transformation has cleared that data. If
-// that's the case, this primitive should treat it as an outsider.
-TEST_F(FilterPacketUsingAllowlistTest, DropsPacketsWithNoType) {
-  protos::gen::TracePacket packet;
-  packet.set_timestamp(1234);
-
-  std::string packet_str = packet.SerializeAsString();
-  ASSERT_GT(packet_str.size(), 0u);
-
-  context_.trace_packet_allow_list.insert(
-      protos::pbzero::TracePacket::kProcessTreeFieldNumber);
-
-  auto status = Redact(packet);
-  ASSERT_OK(status) << status.status().c_message();
-
-  ASSERT_TRUE(status->empty());
-}
-
-// A packet should not change (at all) if it's in the allow-list.
-TEST_F(FilterPacketUsingAllowlistTest, SkipsAllowedPacket) {
-  protos::gen::TracePacket packet;
-  packet.set_timestamp(1234);
-
-  // Add a process tree to the packet. Process trees are in the allow-list.
-  auto* process = packet.mutable_process_tree()->add_processes();
-  process->set_uid(0);
-  process->set_ppid(3);
-  process->set_pid(7);
-
-  context_.trace_packet_allow_list.insert(
-      protos::pbzero::TracePacket::kProcessTreeFieldNumber);
-
-  auto status = Redact(packet);
-  ASSERT_OK(status) << status.status().c_message();
-
-  // The transform shouldn't have changed the string, so the string before and
-  // after should match.
-  ASSERT_EQ(*status, packet.SerializeAsString());
+  FilterPacketUsingAllowlist filter;
+  ASSERT_FALSE(filter.KeepField(context, field));
 }
 
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 3fa0080..656fdef 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -34,6 +34,7 @@
 #include "src/trace_redaction/scrub_process_stats.h"
 #include "src/trace_redaction/scrub_process_trees.h"
 #include "src/trace_redaction/scrub_trace_packet.h"
+#include "src/trace_redaction/suspend_resume.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 #include "src/trace_redaction/trace_redactor.h"
 
@@ -51,6 +52,7 @@
 
   // Add all builders.
   redactor.emplace_build<PopulateAllowlists>();
+  redactor.emplace_build<AllowSuspendResume>();
   redactor.emplace_build<OptimizeTimeline>();
 
   // Add all transforms.
@@ -62,6 +64,7 @@
   scrub_ftrace_events->emplace_back<FilterPrintEvents>();
   scrub_ftrace_events->emplace_back<FilterSchedWakingEvents>();
   scrub_ftrace_events->emplace_back<FilterTaskRename>();
+  scrub_ftrace_events->emplace_back<FilterSuspendResume>();
 
   // Scrub packets and ftrace events first as they will remove the largest
   // chucks of data from the trace. This will reduce the amount of data that the
diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc
index 01da4a0..1c42bd1 100644
--- a/src/trace_redaction/populate_allow_lists.cc
+++ b/src/trace_redaction/populate_allow_lists.cc
@@ -25,8 +25,30 @@
 namespace perfetto::trace_redaction {
 
 base::Status PopulateAllowlists::Build(Context* context) const {
-  if (!context->trace_packet_allow_list.empty()) {
-    return base::ErrStatus("PopulateAllowlists: allow-list should be empty.");
+  // These fields are top-level fields that outside the "oneof data" field.
+  std::initializer_list<uint> required_trace_fields = {
+
+      protos::pbzero::TracePacket::kTimestampFieldNumber,
+      protos::pbzero::TracePacket::kTimestampClockIdFieldNumber,
+      protos::pbzero::TracePacket::kTrustedUidFieldNumber,
+      protos::pbzero::TracePacket::kTrustedPacketSequenceIdFieldNumber,
+      protos::pbzero::TracePacket::kTrustedPidFieldNumber,
+      protos::pbzero::TracePacket::kInternedDataFieldNumber,
+      protos::pbzero::TracePacket::kSequenceFlagsFieldNumber,
+
+      // DEPRECATED. Moved to SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED. So
+      // there is no reason to include it.
+      //
+      // protos::pbzero::TracePacket::incremental_state_cleared
+
+      protos::pbzero::TracePacket::kTracePacketDefaultsFieldNumber,
+      protos::pbzero::TracePacket::kPreviousPacketDroppedFieldNumber,
+      protos::pbzero::TracePacket::kFirstPacketOnSequenceFieldNumber,
+      protos::pbzero::TracePacket::kMachineIdFieldNumber,
+  };
+
+  for (auto item : required_trace_fields) {
+    context->trace_packet_allow_list.insert(item);
   }
 
   // TRACE PACKET NOTES
@@ -37,8 +59,7 @@
   //      constraints around keys or values, making fine-grain redaction
   //      difficult. Because this packet's value has no measurable, the safest
   //      option to drop the whole packet.
-
-  context->trace_packet_allow_list = {
+  std::initializer_list<uint> trace_packets = {
       protos::pbzero::TracePacket::kProcessTreeFieldNumber,
       protos::pbzero::TracePacket::kProcessStatsFieldNumber,
       protos::pbzero::TracePacket::kClockSnapshotFieldNumber,
@@ -59,13 +80,16 @@
       protos::pbzero::TracePacket::kPackagesListFieldNumber,
   };
 
-  context->ftrace_packet_allow_list = {
+  for (auto item : trace_packets) {
+    context->trace_packet_allow_list.insert(item);
+  }
+
+  std::initializer_list<uint> ftrace_events = {
       protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber,
       protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber,
       protos::pbzero::FtraceEvent::kCpuIdleFieldNumber,
       protos::pbzero::FtraceEvent::kSchedBlockedReasonFieldNumber,
       protos::pbzero::FtraceEvent::kSchedWakingFieldNumber,
-      protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber,
       protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber,
       protos::pbzero::FtraceEvent::kTaskRenameFieldNumber,
       protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber,
@@ -80,21 +104,9 @@
       protos::pbzero::FtraceEvent::kPrintFieldNumber,
   };
 
-  // TODO: Some ftrace fields should be retained, but they carry too much risk
-  // without additional redaction. This list should be configured in a build
-  // primitive so that they can be optionally included.
-  //
-  // protos::pbzero::FtraceEvent::kPrintFieldNumber,
-  //
-  // TODO: Some fields will create new packets (e.g. binder calls may create
-  // new spans. This is currently not supported (generated packets still
-  // need to be redacted).
-  //
-  // protos::pbzero::FtraceEvent::kBinderTransactionFieldNumber,
-  // protos::pbzero::FtraceEvent::kBinderTransactionReceivedFieldNumber,
-  // protos::pbzero::FtraceEvent::kBinderSetPriorityFieldNumber,
-  // protos::pbzero::FtraceEvent::kBinderLockedFieldNumber,
-  // protos::pbzero::FtraceEvent::kBinderUnlockFieldNumber,
+  for (auto item : ftrace_events) {
+    context->ftrace_packet_allow_list.insert(item);
+  }
 
   return base::OkStatus();
 }
diff --git a/src/trace_redaction/prune_package_list_integrationtest.cc b/src/trace_redaction/prune_package_list_integrationtest.cc
new file mode 100644
index 0000000..8fd056a
--- /dev/null
+++ b/src/trace_redaction/prune_package_list_integrationtest.cc
@@ -0,0 +1,162 @@
+/*
+ * 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.
+ */
+
+#include <cstdint>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "src/base/test/status_matchers.h"
+#include "src/trace_redaction/find_package_uid.h"
+#include "src/trace_redaction/prune_package_list.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+#include "src/trace_redaction/trace_redaction_integration_fixture.h"
+#include "src/trace_redaction/trace_redactor.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/android/packages_list.gen.h"
+#include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+
+// Set the package name to "just some package name". If a specific package name
+// is needed, the test it should overwrite this value.
+constexpr std::string_view kPackageName =
+    "com.Unity.com.unity.multiplayer.samples.coop";
+constexpr uint64_t kPackageUid = 10252;
+
+}  // namespace
+
+class PrunePackageListIntegrationTest
+    : public testing::Test,
+      protected TraceRedactionIntegrationFixure {
+ protected:
+  void SetUp() override {
+    context()->package_name = kPackageName;
+
+    trace_redactor()->emplace_collect<FindPackageUid>();
+    trace_redactor()->emplace_transform<PrunePackageList>();
+  }
+
+  std::vector<protos::gen::PackagesList::PackageInfo> GetPackageInfo(
+      const protos::pbzero::Trace::Decoder& trace) const {
+    std::vector<protos::gen::PackagesList::PackageInfo> packages;
+
+    for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+      protos::pbzero::TracePacket::Decoder packet(*packet_it);
+
+      if (!packet.has_packages_list()) {
+        continue;
+      }
+
+      protos::pbzero::PackagesList::Decoder list(packet.packages_list());
+
+      for (auto info = list.packages(); info; ++info) {
+        auto& item = packages.emplace_back();
+        item.ParseFromArray(info->data(), info->size());
+      }
+    }
+
+    return packages;
+  }
+
+  std::vector<std::string> GetPackageNames(
+      const protos::pbzero::Trace::Decoder& trace) const {
+    std::vector<std::string> names;
+
+    for (const auto& package : GetPackageInfo(trace)) {
+      if (package.has_name()) {
+        names.push_back(package.name());
+      }
+    }
+
+    return names;
+  }
+};
+
+// It is possible for two packages_list to appear in the trace. The
+// find_package_uid will stop after the first one is found. Package uids are
+// appear as n * 1,000,000 where n is some integer. It is also possible for two
+// packages_list to contain copies of each other - for example
+// "com.Unity.com.unity.multiplayer.samples.coop" appears in both packages_list.
+TEST_F(PrunePackageListIntegrationTest, FindsPackageAndFiltersPackageList) {
+  auto result = Redact();
+  ASSERT_OK(result) << result.message();
+
+  auto after_raw_trace = LoadRedacted();
+  ASSERT_OK(after_raw_trace) << after_raw_trace.status().message();
+
+  ASSERT_TRUE(context()->package_uid.has_value());
+  ASSERT_EQ(NormalizeUid(context()->package_uid.value()),
+            NormalizeUid(kPackageUid));
+
+  protos::pbzero::Trace::Decoder redacted_trace(after_raw_trace.value());
+  auto packages = GetPackageInfo(redacted_trace);
+
+  ASSERT_EQ(packages.size(), 2u);
+
+  for (const auto& package : packages) {
+    ASSERT_TRUE(package.has_name());
+    ASSERT_EQ(package.name(), kPackageName);
+
+    ASSERT_TRUE(package.has_uid());
+    ASSERT_EQ(NormalizeUid(package.uid()), NormalizeUid(kPackageUid));
+  }
+}
+
+// It is possible for multiple packages to share a uid. The names will appears
+// across multiple package lists. The only time the package name appears is in
+// the package list, so there is no way to differentiate these packages (only
+// the uid is used later), so each entry should remain.
+TEST_F(PrunePackageListIntegrationTest, RetainsAllInstancesOfUid) {
+  context()->package_name = "com.google.android.networkstack.tethering";
+
+  auto result = Redact();
+  ASSERT_OK(result) << result.message();
+
+  auto after_raw_trace = LoadRedacted();
+  ASSERT_OK(after_raw_trace) << after_raw_trace.status().message();
+
+  protos::pbzero::Trace::Decoder redacted_trace(after_raw_trace.value());
+  auto package_names = GetPackageNames(redacted_trace);
+
+  std::vector<std::string> expected_package_names = {
+      "com.google.android.cellbroadcastservice",
+      "com.google.android.cellbroadcastservice",
+      "com.google.android.networkstack",
+      "com.google.android.networkstack",
+      "com.google.android.networkstack.permissionconfig",
+      "com.google.android.networkstack.permissionconfig",
+      "com.google.android.networkstack.tethering",
+      "com.google.android.networkstack.tethering",
+  };
+
+  // Sort to make compare possible.
+  std::sort(expected_package_names.begin(), expected_package_names.end());
+  std::sort(package_names.begin(), package_names.end());
+
+  ASSERT_TRUE(std::equal(package_names.begin(), package_names.end(),
+                         expected_package_names.begin(),
+                         expected_package_names.end()));
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_ftrace_events_integrationtest.cc b/src/trace_redaction/scrub_ftrace_events_integrationtest.cc
index 7584b0f..50853c6 100644
--- a/src/trace_redaction/scrub_ftrace_events_integrationtest.cc
+++ b/src/trace_redaction/scrub_ftrace_events_integrationtest.cc
@@ -14,63 +14,37 @@
  * limitations under the License.
  */
 
-#include <string>
-#include <string_view>
 #include <vector>
 
 #include "perfetto/base/status.h"
-#include "perfetto/ext/base/file_utils.h"
 #include "src/base/test/status_matchers.h"
-#include "src/base/test/utils.h"
 #include "src/trace_redaction/scrub_ftrace_events.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
+#include "src/trace_redaction/trace_redaction_integration_fixture.h"
 #include "test/gtest_and_gmock.h"
 
 #include "protos/perfetto/trace//ftrace/ftrace_event.pbzero.h"
-#include "protos/perfetto/trace/android/packages_list.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto::trace_redaction {
 
-namespace {
-using FtraceEvent = protos::pbzero::FtraceEvent;
-using PackagesList = protos::pbzero::PackagesList;
-using PackageInfo = protos::pbzero::PackagesList::PackageInfo;
-using Trace = protos::pbzero::Trace;
-using TracePacket = protos::pbzero::TracePacket;
-
-constexpr std::string_view kTracePath =
-    "test/data/trace-redaction-general.pftrace";
-
 // Runs ScrubFtraceEvents over an actual trace, verifying packet integrity when
 // fields are removed.
-class ScrubFtraceEventsIntegrationTest : public testing::Test {
+class ScrubFtraceEventsIntegrationTest
+    : public testing::Test,
+      protected TraceRedactionIntegrationFixure {
  public:
   ScrubFtraceEventsIntegrationTest() = default;
   ~ScrubFtraceEventsIntegrationTest() override = default;
 
  protected:
   void SetUp() override {
-    src_trace_ = base::GetTestDataPath(std::string(kTracePath));
-    context_.ftrace_packet_allow_list.insert(
+    context()->ftrace_packet_allow_list.insert(
         protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber);
-  }
 
-  std::string src_trace_;
-
-  Context context_;  // Used for allowlist.
-  ScrubFtraceEvents transform_;
-
-  static base::StatusOr<std::string> ReadRawTrace(const std::string& path) {
-    std::string redacted_buffer;
-
-    if (base::ReadFile(path, &redacted_buffer)) {
-      return redacted_buffer;
-    }
-
-    return base::ErrStatus("Failed to read %s", path.c_str());
+    trace_redactor()->emplace_transform<ScrubFtraceEvents>();
   }
 
   // Gets spans for `event` messages that contain `sched_switch` messages.
@@ -137,23 +111,34 @@
 };
 
 TEST_F(ScrubFtraceEventsIntegrationTest, FindsPackageAndFiltersPackageList) {
-  const auto& src_file = src_trace_;
+  auto redacted = Redact();
+  ASSERT_OK(redacted) << redacted.message();
 
-  auto raw_src_trace = ReadRawTrace(src_file);
-  ASSERT_OK(raw_src_trace);
+  // Load source.
+  auto before_raw_trace = LoadOriginal();
+  ASSERT_OK(before_raw_trace) << before_raw_trace.status().message();
+  protos::pbzero::Trace::Decoder before_trace(before_raw_trace.value());
+  auto before_it = before_trace.packet();
 
-  protos::pbzero::Trace::Decoder source_trace(raw_src_trace.value());
+  // Load redacted.
+  auto after_raw_trace = LoadRedacted();
+  ASSERT_OK(after_raw_trace) << after_raw_trace.status().message();
+  protos::pbzero::Trace::Decoder after_trace(after_raw_trace.value());
+  auto after_it = after_trace.packet();
 
-  for (auto packet_it = source_trace.packet(); packet_it; ++packet_it) {
-    auto packet = packet_it->as_std_string();
-    ASSERT_OK(transform_.Transform(context_, &packet));
+  while (before_it && after_it) {
+    protos::pbzero::TracePacket::Decoder before_packet(*before_it);
+    protos::pbzero::TracePacket::Decoder after_packet(*after_it);
 
-    protos::pbzero::TracePacket::Decoder left_packet(*packet_it);
-    protos::pbzero::TracePacket::Decoder right_packet(packet);
+    ComparePackets(std::move(before_packet), std::move(after_packet));
 
-    ComparePackets(std::move(left_packet), std::move(right_packet));
+    ++before_it;
+    ++after_it;
   }
+
+  // Both should be at the end.
+  ASSERT_FALSE(before_it);
+  ASSERT_FALSE(after_it);
 }
 
-}  // namespace
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_trace_packet.cc b/src/trace_redaction/scrub_trace_packet.cc
index b8275e5..77ff55c 100644
--- a/src/trace_redaction/scrub_trace_packet.cc
+++ b/src/trace_redaction/scrub_trace_packet.cc
@@ -19,6 +19,9 @@
 #include "src/trace_redaction/scrub_trace_packet.h"
 
 #include "perfetto/base/status.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_redaction/proto_util.h"
 
 namespace perfetto::trace_redaction {
 
@@ -31,26 +34,29 @@
   }
 
   for (const auto& filter : filters_) {
-    auto status = filter->VerifyContext(context);
+    RETURN_IF_ERROR(filter->VerifyContext(context));
+  }
 
-    if (!status.ok()) {
-      return status;
+  protozero::HeapBuffered<protos::pbzero::TracePacket> new_packet;
+
+  protozero::ProtoDecoder decoder(*packet);
+
+  for (auto field = decoder.ReadField(); field.valid();
+       field = decoder.ReadField()) {
+    if (KeepEvent(context, field)) {
+      proto_util::AppendField(field, new_packet.get());
     }
   }
 
-  if (KeepEvent(context, *packet)) {
-    return base::OkStatus();
-  }
-
-  packet->clear();
+  packet->assign(new_packet.SerializeAsString());
   return base::OkStatus();
 }
 
-// Logical AND of all filters.
+// Logical AND all filters.
 bool ScrubTracePacket::KeepEvent(const Context& context,
-                                 const std::string& bytes) const {
+                                 const protozero::Field& field) const {
   for (const auto& filter : filters_) {
-    if (!filter->KeepPacket(context, bytes)) {
+    if (!filter->KeepField(context, field)) {
       return false;
     }
   }
diff --git a/src/trace_redaction/scrub_trace_packet.h b/src/trace_redaction/scrub_trace_packet.h
index 38dd2f1..331cccf 100644
--- a/src/trace_redaction/scrub_trace_packet.h
+++ b/src/trace_redaction/scrub_trace_packet.h
@@ -28,8 +28,10 @@
   // Checks if the context contains all neccessary parameters.
   virtual base::Status VerifyContext(const Context& context) const = 0;
 
-  virtual bool KeepPacket(const Context& context,
-                          const std::string& bytes) const = 0;
+  // Checks if the field should be pass onto the new packet. Checks are a
+  // logical AND, so all filters must return true.
+  virtual bool KeepField(const Context& context,
+                         const protozero::Field& field) const = 0;
 };
 
 class ScrubTracePacket : public TransformPrimitive {
@@ -43,7 +45,7 @@
   }
 
  private:
-  bool KeepEvent(const Context& context, const std::string& bytes) const;
+  bool KeepEvent(const Context& context, const protozero::Field& field) const;
 
   std::vector<std::unique_ptr<TracePacketFilter>> filters_;
 };
diff --git a/src/trace_redaction/suspend_resume.cc b/src/trace_redaction/suspend_resume.cc
new file mode 100644
index 0000000..243446f
--- /dev/null
+++ b/src/trace_redaction/suspend_resume.cc
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_redaction/suspend_resume.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/power.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status AllowSuspendResume::Build(Context* context) const {
+  context->ftrace_packet_allow_list.insert(
+      protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber);
+
+  // Values are taken from "suspend_period.textproto".
+  context->suspend_result_allow_list.insert("syscore_suspend");
+  context->suspend_result_allow_list.insert("syscore_resume");
+  context->suspend_result_allow_list.insert("timekeeping_freeze");
+
+  return base::OkStatus();
+}
+
+base::Status FilterSuspendResume::VerifyContext(const Context&) const {
+  // FilterSuspendResume could check if kSuspendResumeFieldNumber is present in
+  // ftrace_packet_allow_list and there are values in the
+  // suspend_result_allow_list, but would make it hard to enable/disable
+  // suspend-resume redaction.
+  return base::OkStatus();
+}
+
+// The ftrace event is passed in.
+bool FilterSuspendResume::KeepEvent(const Context& context,
+                                    protozero::ConstBytes bytes) const {
+  protozero::ProtoDecoder event_decoder(bytes);
+
+  auto suspend_resume = event_decoder.FindField(
+      protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber);
+
+  // It's not a suspend-resume event, defer the decision to another filter.
+  if (!suspend_resume.valid()) {
+    return true;
+  }
+
+  protozero::ProtoDecoder suspend_resume_decoder(suspend_resume.as_bytes());
+
+  auto action = suspend_resume_decoder.FindField(
+      protos::pbzero::SuspendResumeFtraceEvent::kActionFieldNumber);
+
+  return !action.valid() ||
+         context.suspend_result_allow_list.count(action.as_std_string());
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/suspend_resume.h b/src/trace_redaction/suspend_resume.h
new file mode 100644
index 0000000..09f7bd8
--- /dev/null
+++ b/src/trace_redaction/suspend_resume.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_REDACTION_SUSPEND_RESUME_H_
+#define SRC_TRACE_REDACTION_SUSPEND_RESUME_H_
+
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// Updates allowlists to include suspend-resume events and which events to allow
+// through.
+class AllowSuspendResume : public BuildPrimitive {
+ public:
+  base::Status Build(Context* context) const override;
+};
+
+// Filters ftrace events based on the suspend-resume event.
+class FilterSuspendResume : public FtraceEventFilter {
+ public:
+  base::Status VerifyContext(const Context& context) const override;
+
+  bool KeepEvent(const Context& context,
+                 protozero::ConstBytes bytes) const override;
+};
+
+}  // namespace perfetto::trace_redaction
+
+#endif  // SRC_TRACE_REDACTION_SUSPEND_RESUME_H_
diff --git a/src/trace_redaction/suspend_resume_unittest.cc b/src/trace_redaction/suspend_resume_unittest.cc
new file mode 100644
index 0000000..7cf498a
--- /dev/null
+++ b/src/trace_redaction/suspend_resume_unittest.cc
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_redaction/suspend_resume.h"
+#include "src/base/test/status_matchers.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/power.gen.h"
+
+namespace perfetto::trace_redaction {
+
+TEST(AllowSuspendResumeTest, UpdatesTracePacketAllowlist) {
+  Context context;
+
+  // Start with a non-empty allow-list item.
+  context.ftrace_packet_allow_list.insert(
+      protos::pbzero::FtraceEvent::kPrintFieldNumber);
+
+  ASSERT_EQ(context.ftrace_packet_allow_list.size(), 1u);
+
+  AllowSuspendResume allow;
+  auto status = allow.Build(&context);
+  ASSERT_OK(status) << status.message();
+
+  // Print should still be present. The allowlist should have been updated, not
+  // replaced.
+  ASSERT_EQ(context.ftrace_packet_allow_list.count(
+                protos::pbzero::FtraceEvent::kPrintFieldNumber),
+            1u);
+
+  ASSERT_EQ(context.ftrace_packet_allow_list.count(
+                protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber),
+            1u);
+}
+
+TEST(AllowSuspendResumeTest, UpdatesSuspendResumeAllowlist) {
+  Context context;
+
+  ASSERT_TRUE(context.suspend_result_allow_list.empty());
+
+  AllowSuspendResume allow;
+  auto status = allow.Build(&context);
+  ASSERT_OK(status) << status.message();
+
+  ASSERT_FALSE(context.suspend_result_allow_list.empty());
+}
+
+class SuspendResumeTest : public testing::Test {
+ protected:
+  void SetUp() {
+    AllowSuspendResume allow;
+    ASSERT_OK(allow.Build(&context_));
+  }
+
+  protos::gen::FtraceEvent CreateSuspendResumeEvent(
+      const std::string* action) const {
+    protos::gen::FtraceEvent event;
+    event.set_timestamp(1234);
+    event.set_pid(0);
+
+    auto* suspend_resume = event.mutable_suspend_resume();
+
+    if (action) {
+      suspend_resume->set_action(*action);
+    }
+
+    return event;
+  }
+
+  protos::gen::FtraceEvent CreateOtherEvent() const {
+    protos::gen::FtraceEvent event;
+    event.set_timestamp(1234);
+    event.set_pid(0);
+
+    auto* print = event.mutable_print();
+    print->set_buf("This is a message");
+
+    return event;
+  }
+
+  const Context& context() const { return context_; }
+
+ private:
+  Context context_;
+};
+
+// The suspend-resume filter is not responsible for non-suspend-resume events.
+// It should assume that another filter will handle it and it should just allow
+// those events through
+TEST_F(SuspendResumeTest, AcceptsOtherEvents) {
+  auto event = CreateOtherEvent();
+  auto event_array = event.SerializeAsArray();
+  protozero::ConstBytes event_bytes{event_array.data(), event_array.size()};
+
+  FilterSuspendResume filter;
+  ASSERT_TRUE(filter.KeepEvent(context(), event_bytes));
+}
+
+TEST_F(SuspendResumeTest, AcceptsEventsWithNoName) {
+  auto event = CreateSuspendResumeEvent(nullptr);
+  auto event_array = event.SerializeAsArray();
+  protozero::ConstBytes event_bytes{event_array.data(), event_array.size()};
+
+  Context context;
+
+  FilterSuspendResume filter;
+  ASSERT_TRUE(filter.KeepEvent(context, event_bytes));
+}
+
+TEST_F(SuspendResumeTest, AcceptsEventsWithValidName) {
+  // This value is from "src/trace_redaction/suspend_resume.cc".
+  std::string name = "syscore_suspend";
+
+  auto event = CreateSuspendResumeEvent(&name);
+  auto event_array = event.SerializeAsArray();
+  protozero::ConstBytes event_bytes{event_array.data(), event_array.size()};
+
+  FilterSuspendResume filter;
+  ASSERT_TRUE(filter.KeepEvent(context(), event_bytes));
+}
+
+TEST_F(SuspendResumeTest, RejectsEventsWithInvalidName) {
+  std::string name = "hello world";
+
+  auto event = CreateSuspendResumeEvent(&name);
+  auto event_array = event.SerializeAsArray();
+  protozero::ConstBytes event_bytes{event_array.data(), event_array.size()};
+
+  FilterSuspendResume filter;
+  ASSERT_FALSE(filter.KeepEvent(context(), event_bytes));
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/trace_redaction_framework.h b/src/trace_redaction/trace_redaction_framework.h
index c1c7e45..f741f9b 100644
--- a/src/trace_redaction/trace_redaction_framework.h
+++ b/src/trace_redaction/trace_redaction_framework.h
@@ -24,7 +24,6 @@
 
 #include "perfetto/base/flat_set.h"
 #include "perfetto/base/status.h"
-#include "perfetto/ext/base/status_or.h"
 #include "src/trace_redaction/process_thread_timeline.h"
 
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
@@ -159,6 +158,17 @@
   //      ftrace event
   base::FlatSet<uint32_t> ftrace_packet_allow_list;
 
+  //  message SuspendResumeFtraceEvent {
+  //    optional string action = 1 [(datapol.semantic_type) = ST_NOT_REQUIRED];
+  //    optional int32 val = 2;
+  //    optional uint32 start = 3 [(datapol.semantic_type) = ST_NOT_REQUIRED];
+  //  }
+  //
+  // The "action" in SuspendResumeFtraceEvent is a free-form string. There are
+  // some know and expected values. Those values are stored here and all events
+  // who's action value is not found here, the ftrace event will be dropped.
+  base::FlatSet<std::string> suspend_result_allow_list;
+
   // The timeline is a query-focused data structure that connects a pid to a
   // uid at specific point in time.
   //
diff --git a/src/trace_redaction/trace_redactor.cc b/src/trace_redaction/trace_redactor.cc
index fa56af4..60e8188 100644
--- a/src/trace_redaction/trace_redactor.cc
+++ b/src/trace_redaction/trace_redactor.cc
@@ -55,19 +55,13 @@
   trace_processor::TraceBlobView whole_view(
       trace_processor::TraceBlob::FromMmap(std::move(mapped)));
 
-  // TODO(vaage): Update other status code to use RETURN_IF_ERROR.
   RETURN_IF_ERROR(Collect(context, whole_view));
 
-  if (auto status = Build(context); !status.ok()) {
-    return status;
+  for (const auto& builder : builders_) {
+    RETURN_IF_ERROR(builder->Build(context));
   }
 
-  if (auto status = Transform(*context, whole_view, std::string(dest_filename));
-      !status.ok()) {
-    return status;
-  }
-
-  return base::OkStatus();
+  return Transform(*context, whole_view, std::string(dest_filename));
 }
 
 base::Status TraceRedactor::Collect(
@@ -94,16 +88,6 @@
   return base::OkStatus();
 }
 
-base::Status TraceRedactor::Build(Context* context) const {
-  for (const auto& builder : builders_) {
-    if (auto status = builder->Build(context); !status.ok()) {
-      return status;
-    }
-  }
-
-  return base::OkStatus();
-}
-
 base::Status TraceRedactor::Transform(
     const Context& context,
     const trace_processor::TraceBlobView& view,
@@ -128,10 +112,7 @@
         break;
       }
 
-      if (auto status = transformer->Transform(context, &packet);
-          !status.ok()) {
-        return status;
-      }
+      RETURN_IF_ERROR(transformer->Transform(context, &packet));
     }
 
     // The packet has been removed from the trace. Don't write an empty packet
diff --git a/src/trace_redaction/trace_redactor_integrationtest.cc b/src/trace_redaction/trace_redactor_integrationtest.cc
deleted file mode 100644
index a62359f..0000000
--- a/src/trace_redaction/trace_redactor_integrationtest.cc
+++ /dev/null
@@ -1,198 +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.
- */
-
-#include <cstdint>
-#include <string>
-#include <string_view>
-#include <vector>
-
-#include "perfetto/base/status.h"
-#include "perfetto/ext/base/file_utils.h"
-#include "src/base/test/status_matchers.h"
-#include "src/base/test/tmp_dir_tree.h"
-#include "src/base/test/utils.h"
-#include "src/trace_redaction/find_package_uid.h"
-#include "src/trace_redaction/prune_package_list.h"
-#include "src/trace_redaction/trace_redaction_framework.h"
-#include "src/trace_redaction/trace_redactor.h"
-#include "test/gtest_and_gmock.h"
-
-#include "protos/perfetto/trace/android/packages_list.pbzero.h"
-#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
-#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
-#include "protos/perfetto/trace/trace.pbzero.h"
-#include "protos/perfetto/trace/trace_packet.pbzero.h"
-
-namespace perfetto::trace_redaction {
-
-// TODO(vaage): Add tests for the untested and/or included primitives:
-//
-//              1. Scrub process tree
-
-namespace {
-using FtraceEvent = protos::pbzero::FtraceEvent;
-
-constexpr std::string_view kTracePath =
-    "test/data/trace-redaction-general.pftrace";
-
-// Set the package name to "just some package name". If a specific package name
-// is needed, the test it should overwrite this value.
-constexpr std::string_view kPackageName =
-    "com.Unity.com.unity.multiplayer.samples.coop";
-constexpr uint64_t kPackageUid = 10252;
-
-class TraceRedactorIntegrationTest : public testing::Test {
- protected:
-  void SetUp() override {
-    src_trace_ = base::GetTestDataPath(std::string(kTracePath));
-    context_.package_name = kPackageName;
-  }
-
-  const std::string& src_trace() const { return src_trace_; }
-
-  static base::StatusOr<std::string> ReadRawTrace(const std::string& path) {
-    std::string redacted_buffer;
-
-    if (base::ReadFile(path, &redacted_buffer)) {
-      return redacted_buffer;
-    }
-
-    return base::ErrStatus("Failed to read %s", path.c_str());
-  }
-
-  std::string src_trace_;
-  base::TmpDirTree tmp_dir_;
-
-  Context context_;
-  TraceRedactor redactor_;
-};
-
-class PackageListTraceRedactorIntegrationTest
-    : public TraceRedactorIntegrationTest {
- protected:
-  void SetUp() override {
-    TraceRedactorIntegrationTest::SetUp();
-
-    redactor_.emplace_collect<FindPackageUid>();
-    redactor_.emplace_transform<PrunePackageList>();
-  }
-
-  std::vector<protozero::ConstBytes> GetPackageInfos(
-      const protos::pbzero::Trace::Decoder& trace) const {
-    std::vector<protozero::ConstBytes> infos;
-
-    for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
-      protos::pbzero::TracePacket::Decoder packet_decoder(*packet_it);
-      if (packet_decoder.has_packages_list()) {
-        protos::pbzero::PackagesList::Decoder list_it(
-            packet_decoder.packages_list());
-        for (auto info_it = list_it.packages(); info_it; ++info_it) {
-          protos::pbzero::PackagesList::PackageInfo::Decoder info(*info_it);
-          infos.push_back(*info_it);
-        }
-      }
-    }
-
-    return infos;
-  }
-};
-
-TEST_F(PackageListTraceRedactorIntegrationTest,
-       FindsPackageAndFiltersPackageList) {
-  auto result = redactor_.Redact(
-      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
-
-  ASSERT_OK(result) << result.message();
-
-  tmp_dir_.TrackFile("dst.pftrace");
-
-  ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
-                       ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
-
-  protos::pbzero::Trace::Decoder redacted_trace(redacted_buffer);
-  std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
-
-  ASSERT_TRUE(context_.package_uid.has_value());
-  ASSERT_EQ(NormalizeUid(context_.package_uid.value()),
-            NormalizeUid(kPackageUid));
-
-  // It is possible for two packages_list to appear in the trace. The
-  // find_package_uid will stop after the first one is found. Package uids are
-  // appear as n * 1,000,000 where n is some integer. It is also possible for
-  // two packages_list to contain copies of each other - for example
-  // "com.Unity.com.unity.multiplayer.samples.coop" appears in both
-  // packages_list.
-  ASSERT_EQ(infos.size(), 2u);
-
-  std::vector<protos::pbzero::PackagesList::PackageInfo::Decoder> decoders;
-  decoders.emplace_back(infos[0]);
-  decoders.emplace_back(infos[1]);
-
-  for (auto& decoder : decoders) {
-    ASSERT_TRUE(decoder.has_name());
-    ASSERT_EQ(decoder.name().ToStdString(),
-              "com.Unity.com.unity.multiplayer.samples.coop");
-
-    ASSERT_TRUE(decoder.has_uid());
-    ASSERT_EQ(NormalizeUid(decoder.uid()), NormalizeUid(kPackageUid));
-  }
-}
-
-// It is possible for multiple packages to share a uid. The names will appears
-// across multiple package lists. The only time the package name appears is in
-// the package list, so there is no way to differentiate these packages (only
-// the uid is used later), so each entry should remain.
-TEST_F(PackageListTraceRedactorIntegrationTest, RetainsAllInstancesOfUid) {
-  context_.package_name = "com.google.android.networkstack.tethering";
-
-  auto result = redactor_.Redact(
-      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
-
-  ASSERT_OK(result) << result.message();
-
-  tmp_dir_.TrackFile("dst.pftrace");
-
-  ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
-                       ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
-
-  protos::pbzero::Trace::Decoder redacted_trace(redacted_buffer);
-  std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
-
-  ASSERT_EQ(infos.size(), 8u);
-
-  std::array<std::string, 8> package_names;
-
-  for (size_t i = 0; i < infos.size(); ++i) {
-    protos::pbzero::PackagesList::PackageInfo::Decoder info(infos[i]);
-    ASSERT_TRUE(info.has_name());
-    package_names[i] = info.name().ToStdString();
-  }
-
-  std::sort(package_names.begin(), package_names.end());
-  ASSERT_EQ(package_names[0], "com.google.android.cellbroadcastservice");
-  ASSERT_EQ(package_names[1], "com.google.android.cellbroadcastservice");
-  ASSERT_EQ(package_names[2], "com.google.android.networkstack");
-  ASSERT_EQ(package_names[3], "com.google.android.networkstack");
-  ASSERT_EQ(package_names[4],
-            "com.google.android.networkstack.permissionconfig");
-  ASSERT_EQ(package_names[5],
-            "com.google.android.networkstack.permissionconfig");
-  ASSERT_EQ(package_names[6], "com.google.android.networkstack.tethering");
-  ASSERT_EQ(package_names[7], "com.google.android.networkstack.tethering");
-}
-
-}  // namespace
-}  // namespace perfetto::trace_redaction
diff --git a/src/traced/probes/ftrace/OWNERS b/src/traced/probes/ftrace/OWNERS
index 66ecb6c..52d5e50 100644
--- a/src/traced/probes/ftrace/OWNERS
+++ b/src/traced/probes/ftrace/OWNERS
@@ -1,5 +1,4 @@
 # People knowledgeable with traced_probes <> ftrace integration.
-hjd@google.com
 primiano@google.com
 rsavitski@google.com
 skyostil@google.com
diff --git a/test/cts/OWNERS b/test/cts/OWNERS
index 10fd9fe..0a7edbb 100644
--- a/test/cts/OWNERS
+++ b/test/cts/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 323270
 ddiproietto@google.com
-hjd@google.com
 lalitm@google.com
 primiano@google.com
 rsavitski@google.com
diff --git a/test/trace_processor/diff_tests/stdlib/OWNERS b/test/trace_processor/diff_tests/stdlib/OWNERS
index 0479e4b..21e80a4 100644
--- a/test/trace_processor/diff_tests/stdlib/OWNERS
+++ b/test/trace_processor/diff_tests/stdlib/OWNERS
@@ -1,3 +1,3 @@
-# These can't be added inside chrome/ bacause the whole directory is
+# These can't be added inside chrome/ because the whole directory is
 # blown away on every import.
 per-file chrome/* = file://protos/third_party/CHROMIUM_OWNERS
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
index ae641d4..3f9e4b8 100755
--- a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
@@ -40,7 +40,7 @@
 
   def test_chrome_scrolls(self):
     return DiffTestBlueprint(
-        trace=Path('chrome_scroll_check.py'),
+        trace=DataPath('chrome_input_with_frame_view.pftrace'),
         query="""
         INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
 
@@ -55,15 +55,15 @@
         """,
         out=Csv("""
         "id","ts","dur","gesture_scroll_begin_ts","gesture_scroll_end_ts"
-        5678,0,55000000,0,45000000
-        5679,60000000,40000000,60000000,90000000
-        5680,80000000,30000000,80000000,100000000
-        5681,120000000,70000000,120000000,"[NULL]"
+        4328,1035865535981926,1255745000,1035865535981926,1035866753550926
+        4471,1035866799527926,1358505000,1035866799527926,1035868108723926
+        4620,1035868146266926,111786000,1035868146266926,1035868230937926
+        4652,1035868607429926,1517121000,1035868607429926,1035870086449926
         """))
 
   def test_chrome_scroll_intervals(self):
     return DiffTestBlueprint(
-        trace=Path('chrome_scroll_check.py'),
+        trace=DataPath('chrome_input_with_frame_view.pftrace'),
         query="""
         INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
 
@@ -76,9 +76,9 @@
         """,
         out=Csv("""
         "id","ts","dur"
-        1,0,55000000
-        2,60000000,50000000
-        3,120000000,70000000
+        1,1035865535981926,1255745000
+        2,1035866799527926,1458525000
+        3,1035868607429926,1517121000
         """))
 
   def test_chrome_scroll_input_offsets(self):
@@ -167,10 +167,10 @@
         """,
         out=Csv("""
         "scroll_id","missed_vsyncs","frame_count","presented_frame_count","janky_frame_count","janky_frame_percent"
-        4328,"[NULL]",109,110,0,0.000000
-        4471,"[NULL]",117,118,0,0.000000
-        4620,"[NULL]",5,4,0,0.000000
-        4652,1,122,122,1,0.820000
+        4328,"[NULL]",110,110,0,0.000000
+        4471,"[NULL]",118,118,0,0.000000
+        4620,"[NULL]",6,4,0,0.000000
+        4652,1,123,122,1,0.820000
         """))
 
   def test_chrome_scroll_jank_intervals_v3(self):
diff --git a/test/vts/OWNERS b/test/vts/OWNERS
index 10fd9fe..0a7edbb 100644
--- a/test/vts/OWNERS
+++ b/test/vts/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 323270
 ddiproietto@google.com
-hjd@google.com
 lalitm@google.com
 primiano@google.com
 rsavitski@google.com
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index c8db4ec..d71b0dc 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -121,13 +121,19 @@
 
 # Proto target groups which will be made public.
 proto_groups = {
-    'trace': [
-        '//protos/perfetto/trace:non_minimal_source_set',
-        '//protos/perfetto/trace:minimal_source_set'
-    ],
-    'config': [
-        '//protos/perfetto/config:source_set',
-    ],
+    'trace': {
+        'types': ['filegroup', 'lite'],
+        'targets': [
+            '//protos/perfetto/trace:non_minimal_source_set',
+            '//protos/perfetto/trace:minimal_source_set',
+        ]
+    },
+    'config': {
+        'types': ['lite'],
+        'targets': [
+            '//protos/perfetto/config:source_set',
+        ]
+    },
 }
 
 needs_libfts = [
@@ -956,23 +962,34 @@
 
 
 def create_proto_group_modules(blueprint, gn: GnParser, module_name: str,
-                               target_names):
-  # TODO(lalitm): today, we're only adding a Java lite module because that's
-  # the only one used in practice. In the future, if we need other target types
-  # (e.g. C++, Java full etc.) add them here.
-  bp_module_name = label_to_module_name(module_name) + '_java_protos'
-  module = Module('java_library', bp_module_name, bp_module_name)
-  module.comment = f'''GN: [{', '.join(target_names)}]'''
-  module.proto = {'type': 'lite', 'canonical_path_from_root': False}
+                               group):
+  target_names = group['targets']
+  module_types = group['types']
+  module_sources = set()
 
   for name in target_names:
     target = gn.get_target(name)
-    module.srcs.update(gn_utils.label_to_path(src) for src in target.sources)
+    module_sources.update(gn_utils.label_to_path(src) for src in target.sources)
     for dep_label in target.transitive_proto_deps():
       dep = gn.get_target(dep_label.name)
-      module.srcs.update(gn_utils.label_to_path(src) for src in dep.sources)
+      module_sources.update(gn_utils.label_to_path(src) for src in dep.sources)
 
-  blueprint.add_module(module)
+  for type in module_types:
+    if type == 'filegroup':
+      name = label_to_module_name(module_name) + '_filegroup_proto'
+      module = Module('filegroup', name, name)
+      module.comment = f'''GN: [{', '.join(target_names)}]'''
+      module.srcs = module_sources
+      blueprint.add_module(module)
+    elif type == 'lite':
+      name = label_to_module_name(module_name) + '_java_protos'
+      module = Module('java_library', name, name)
+      module.comment = f'''GN: [{', '.join(target_names)}]'''
+      module.proto = {'type': 'lite', 'canonical_path_from_root': False}
+      module.srcs = module_sources
+      blueprint.add_module(module)
+    else:
+      raise Error('Unhandled proto group type: {}'.format(group.type))
 
 
 def _get_cflags(target: GnParser.Target):
@@ -1228,8 +1245,8 @@
   # checker = gn_utils.ODRChecker(gn, target_name)
 
   # Add any proto groups to the blueprint.
-  for l_name, t_names in proto_groups.items():
-    create_proto_group_modules(blueprint, gn, l_name, t_names)
+  for name, group in proto_groups.items():
+    create_proto_group_modules(blueprint, gn, name, group)
 
   output = [
       """// Copyright (C) 2017 The Android Open Source Project
diff --git a/ui/OWNERS b/ui/OWNERS
index aaf587f..d6b4b3d 100644
--- a/ui/OWNERS
+++ b/ui/OWNERS
@@ -1,4 +1,3 @@
-hjd@google.com
 primiano@google.com
 stevegolton@google.com
 
diff --git a/ui/release/OWNERS b/ui/release/OWNERS
index 418a82a..4108764 100644
--- a/ui/release/OWNERS
+++ b/ui/release/OWNERS
@@ -1,6 +1,5 @@
 set noparent
 eseckler@google.com
-hjd@google.com
 lalitm@google.com
 primiano@google.com
 skyostil@google.com
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 5f4dd4d..c87709e 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -6,7 +6,7 @@
     },
     {
       "name": "canary",
-      "rev": "58439240cbad83689a1fbafd8234a379a56be7c1"
+      "rev": "9945c86178de704ff44bef7f64c020ff79aedd28"
     },
     {
       "name": "autopush",
diff --git a/ui/src/assets/viewer_page.scss b/ui/src/assets/viewer_page.scss
index c090dc9..690231e 100644
--- a/ui/src/assets/viewer_page.scss
+++ b/ui/src/assets/viewer_page.scss
@@ -42,7 +42,7 @@
     overflow: auto;
   }
 
-  .header {
+  .pf-timeline-header {
     display: flex;
     flex-direction: row;
     box-shadow: 1px 3px 15px rgba(23, 32, 44, 0.3);
diff --git a/ui/src/assets/widgets/button.scss b/ui/src/assets/widgets/button.scss
index a5e8f9a..65f6a56 100644
--- a/ui/src/assets/widgets/button.scss
+++ b/ui/src/assets/widgets/button.scss
@@ -18,8 +18,6 @@
   font-family: $pf-font;
   line-height: 1;
   user-select: none;
-  color: $pf-primary-foreground;
-  background: $pf-primary-background;
   transition: background $pf-anim-timing, box-shadow $pf-anim-timing;
   border-radius: $pf-border-radius;
   padding: 4px 8px;
@@ -42,45 +40,47 @@
     line-height: inherit;
   }
 
-  &:hover {
-    background: $pf-primary-background-hover;
-  }
-
-  &:active,
-  &.pf-active {
-    transition: none;
-    background: $pf-primary-background-active;
-    box-shadow: inset 1px 1px 4px #00000040;
-  }
-
   &:focus-visible {
     @include focus;
   }
 
+  background: $pf-minimal-background;
+  color: inherit;
+
+  &:hover {
+    background: $pf-minimal-background-hover;
+  }
+
+  &:active,
+  &.pf-active {
+    background: $pf-minimal-background-active;
+  }
+
   &[disabled] {
-    background: $pf-primary-background-disabled;
-    color: $pf-primary-foreground-disabled;
-    box-shadow: none;
+    color: $pf-minimal-foreground-disabled;
+    background: $pf-minimal-background-disabled;
     cursor: not-allowed;
   }
 
   // Remove default background in minimal mode, showing only the text
-  &.pf-minimal {
-    background: $pf-minimal-background;
-    color: inherit;
+  &.pf-intent-primary {
+    color: $pf-primary-foreground;
+    background: $pf-primary-background;
 
     &:hover {
-      background: $pf-minimal-background-hover;
+      background: $pf-primary-background-hover;
     }
 
     &:active,
     &.pf-active {
-      background: $pf-minimal-background-active;
+      transition: none;
+      background: $pf-primary-background-active;
+      box-shadow: inset 1px 1px 4px #00000040;
     }
-
     &[disabled] {
-      color: $pf-minimal-foreground-disabled;
-      background: $pf-minimal-background-disabled;
+      background: $pf-primary-background-disabled;
+      color: $pf-primary-foreground-disabled;
+      box-shadow: none;
       cursor: not-allowed;
     }
   }
diff --git a/ui/src/base/logging.ts b/ui/src/base/logging.ts
index 74cbe44..04e0bc7 100644
--- a/ui/src/base/logging.ts
+++ b/ui/src/base/logging.ts
@@ -134,6 +134,6 @@
 // 2) A compile time check where typescript asserts that the value passed can be
 // cast to the "never" type.
 // This is useful for ensuring we exhastively check union types.
-export function assertUnreachable(value: never) {
+export function assertUnreachable(value: never): never {
   throw new Error(`This code should not be reachable ${value as unknown}`);
 }
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/common/recordingV2/recording_config_utils.ts
index 334ad25..133d606 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/common/recordingV2/recording_config_utils.ts
@@ -769,7 +769,7 @@
   ) {
     const ds = new TraceConfig.DataSource();
     ds.config = new DataSourceConfig();
-    ds.config.name = 'windows.etw';
+    ds.config.name = 'org.chromium.etw_system';
     ds.config.etwConfig = new EtwConfig();
 
     const kernelFlags: EtwConfig.KernelFlag[] = [];
diff --git a/ui/src/common/thread_state.ts b/ui/src/common/thread_state.ts
index 6ef6188..d94625c 100644
--- a/ui/src/common/thread_state.ts
+++ b/ui/src/common/thread_state.ts
@@ -34,9 +34,18 @@
   ioWait: boolean | undefined = undefined,
 ) {
   if (state === undefined) return '';
-  if (state === 'Running') {
-    return state;
+
+  // Self describing states
+  switch (state) {
+    case 'Running':
+    case 'Initialized':
+    case 'Deferred Ready':
+    case 'Transition':
+    case 'Stand By':
+    case 'Waiting':
+      return state;
   }
+
   if (state === null) {
     return 'Unknown';
   }
diff --git a/ui/src/common/track_cache.ts b/ui/src/common/track_cache.ts
index 3b2eb41..5d05a13 100644
--- a/ui/src/common/track_cache.ts
+++ b/ui/src/common/track_cache.ts
@@ -180,6 +180,7 @@
 }
 
 enum TrackFSMState {
+  NotCreated = 'not_created',
   Creating = 'creating',
   Ready = 'ready',
   UpdatePending = 'update_pending',
@@ -200,20 +201,22 @@
   constructor(
     public track: Track,
     public desc: TrackDescriptor,
-    ctx: TrackContext,
+    private readonly ctx: TrackContext,
   ) {
-    this.state = TrackFSMState.Creating;
-    const result = this.track.onCreate?.(ctx);
-    Promise.resolve(result)
-      .then(() => this.onTrackCreated())
-      .catch((e) => {
-        this.error = e;
-        this.state = TrackFSMState.Error;
-      });
+    this.state = TrackFSMState.NotCreated;
   }
 
   update(): void {
     switch (this.state) {
+      case TrackFSMState.NotCreated:
+        Promise.resolve(this.track.onCreate?.(this.ctx))
+          .then(() => this.onTrackCreated())
+          .catch((e) => {
+            this.error = e;
+            this.state = TrackFSMState.Error;
+          });
+        this.state = TrackFSMState.Creating;
+        break;
       case TrackFSMState.Creating:
       case TrackFSMState.Updating:
         this.state = TrackFSMState.UpdatePending;
@@ -240,6 +243,10 @@
 
   destroy(): void {
     switch (this.state) {
+      case TrackFSMState.NotCreated:
+        // Nothing to do
+        this.state = TrackFSMState.Destroyed;
+        break;
       case TrackFSMState.Ready:
         // Don't bother awaiting this as the track can no longer be used.
         Promise.resolve(this.track.onDestroy?.()).catch(() => {
@@ -267,6 +274,7 @@
         this.track.onDestroy?.();
         this.state = TrackFSMState.Destroyed;
         break;
+      case TrackFSMState.Creating:
       case TrackFSMState.UpdatePending:
         const result = this.track.onUpdate?.();
         Promise.resolve(result)
@@ -277,9 +285,6 @@
           });
         this.state = TrackFSMState.Updating;
         break;
-      case TrackFSMState.Creating:
-        this.state = TrackFSMState.Ready;
-        break;
       case TrackFSMState.Error:
         break;
       default:
diff --git a/ui/src/common/track_cache_unittest.ts b/ui/src/common/track_cache_unittest.ts
index 2be2d55..599a748 100644
--- a/ui/src/common/track_cache_unittest.ts
+++ b/ui/src/common/track_cache_unittest.ts
@@ -63,8 +63,10 @@
   it('reuses tracks', async () => {
     const first = trackCache.resolveTrack('foo', td);
     trackCache.flushOldTracks();
+    first.update();
     const second = trackCache.resolveTrack('foo', td);
     trackCache.flushOldTracks();
+    second.update();
 
     // Ensure onCreate only called once
     expect(track.onCreate).toHaveBeenCalledTimes(1);
@@ -72,7 +74,8 @@
   });
 
   it('destroys tracks', async () => {
-    trackCache.resolveTrack('foo', td);
+    const t = trackCache.resolveTrack('foo', td);
+    t.update();
 
     // Double flush should destroy all tracks
     trackCache.flushOldTracks();
diff --git a/ui/src/frontend/aggregation_tab.ts b/ui/src/frontend/aggregation_tab.ts
index c6c46f6..d24fc1d 100644
--- a/ui/src/frontend/aggregation_tab.ts
+++ b/ui/src/frontend/aggregation_tab.ts
@@ -109,7 +109,6 @@
         key,
         label: name,
         active: currentViewKey === key,
-        minimal: true,
       });
     });
 
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 4bed3fc..0f11c59 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -804,6 +804,9 @@
         this.addRecentCommand(key);
         cmdMgr.runCommand(key);
       },
+      onGoBack: () => {
+        this.enterSearchMode(false);
+      },
     });
   }
 
@@ -822,6 +825,7 @@
       placeholder: ph,
       inputRef: App.OMNIBOX_INPUT_REF,
       extraClasses: 'query-mode',
+
       onInput: (value) => {
         this.queryText = value;
         raf.scheduleFullRedraw();
@@ -842,6 +846,9 @@
         this.enterSearchMode(false);
         raf.scheduleFullRedraw();
       },
+      onGoBack: () => {
+        this.enterSearchMode(false);
+      },
     });
   }
 
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index 3fe5c71..9996782 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -450,7 +450,7 @@
     return m(
       PopupMenu2,
       {
-        trigger: m(Button, {icon: 'show_chart', minimal: true, compact: true}),
+        trigger: m(Button, {icon: 'show_chart', compact: true}),
       },
       this.getCounterContextMenuItems(),
     );
@@ -963,6 +963,6 @@
   }
 
   get unit(): string {
-    return this.getCounterOptions().unit ?? '?';
+    return this.getCounterOptions().unit ?? '';
   }
 }
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 90471ed..f42d4b6 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -399,7 +399,6 @@
     const contextMenuItems = getSliceContextMenuItems(sliceInfo);
     if (contextMenuItems.length > 0) {
       const trigger = m(Button, {
-        minimal: true,
         compact: true,
         label: 'Contextual Options',
         rightIcon: Icons.ContextMenu,
diff --git a/ui/src/frontend/close_track_button.ts b/ui/src/frontend/close_track_button.ts
index e7b667b..1b0fd91 100644
--- a/ui/src/frontend/close_track_button.ts
+++ b/ui/src/frontend/close_track_button.ts
@@ -34,7 +34,6 @@
       },
       icon: Icons.Close,
       title: 'Close',
-      minimal: true,
       compact: true,
     });
   }
diff --git a/ui/src/frontend/drag_handle.ts b/ui/src/frontend/drag_handle.ts
index 70aea03..dd43077 100644
--- a/ui/src/frontend/drag_handle.ts
+++ b/ui/src/frontend/drag_handle.ts
@@ -209,7 +209,6 @@
               onTabClose(key);
               event.preventDefault();
             },
-            minimal: true,
             compact: true,
             icon: 'close',
           }),
@@ -237,7 +236,6 @@
           title: 'Open fullscreen',
           disabled: this.isFullscreen,
           icon: 'vertical_align_top',
-          minimal: true,
           compact: true,
         }),
         m(Button, {
@@ -246,7 +244,6 @@
           },
           title,
           icon,
-          minimal: true,
           compact: true,
         }),
       ),
@@ -258,7 +255,6 @@
       PopupMenu2,
       {
         trigger: m(Button, {
-          minimal: true,
           compact: true,
           icon: 'more_vert',
           disabled: entries.length === 0,
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index 0f4cba2..6e6f550 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -42,6 +42,7 @@
 import {ButtonBar} from '../widgets/button';
 import {DurationWidget} from './widgets/duration';
 import {DetailsShell} from '../widgets/details_shell';
+import {Intent} from '../widgets/common';
 
 const HEADER_HEIGHT = 30;
 
@@ -143,6 +144,7 @@
                 this.profileType === ProfileType.JAVA_HEAP_SAMPLES) &&
                 m(Button, {
                   icon: 'file_download',
+                  intent: Intent.Primary,
                   onclick: () => {
                     this.downloadPprof();
                   },
@@ -416,7 +418,6 @@
     return m(Button, {
       label: text,
       active,
-      minimal: true,
       onclick: () => {
         globals.dispatch(Actions.changeViewFlamegraphState({viewingOption}));
       },
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index 85cbe07..c583e2d 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -108,7 +108,6 @@
             },
             title: allCollapsed ? 'Expand all' : 'Collapse all',
             icon: allCollapsed ? 'unfold_more' : 'unfold_less',
-            minimal: true,
             compact: true,
           }),
           m(Button, {
@@ -118,7 +117,6 @@
             },
             title: 'Clear all pinned tracks',
             icon: 'clear_all',
-            minimal: true,
             compact: true,
           }),
         ),
@@ -394,7 +392,6 @@
         m(Button, {
           label: 'Remove',
           icon: Icons.Delete,
-          minimal: true,
           onclick: () => {
             globals.dispatch(Actions.removeNote({id: this.config.id}));
             raf.scheduleFullRedraw();
diff --git a/ui/src/frontend/omnibox.ts b/ui/src/frontend/omnibox.ts
index b0f0416..16ada06 100644
--- a/ui/src/frontend/omnibox.ts
+++ b/ui/src/frontend/omnibox.ts
@@ -123,6 +123,9 @@
   // Called when the user expresses the intent to "execute" the thing.
   onSubmit?: (value: string, mod: boolean, shift: boolean) => void;
 
+  // Called when the user hits backspace when the field is empty.
+  onGoBack?: () => void;
+
   // When true, disable and grey-out the omnibox's input.
   readonly?: boolean;
 
@@ -160,6 +163,7 @@
       extraClasses,
       onInput = () => {},
       onSubmit = () => {},
+      onGoBack = () => {},
       inputRef = 'omnibox',
       options,
       closeOnSubmit = false,
@@ -190,7 +194,7 @@
             },
             onkeydown: (e: KeyboardEvent) => {
               if (e.key === 'Backspace' && value === '') {
-                this.close(attrs);
+                onGoBack();
               } else if (e.key === 'Escape') {
                 e.preventDefault();
                 this.close(attrs);
diff --git a/ui/src/frontend/plugins_page.ts b/ui/src/frontend/plugins_page.ts
index 19c0739..ed2b2f5 100644
--- a/ui/src/frontend/plugins_page.ts
+++ b/ui/src/frontend/plugins_page.ts
@@ -22,6 +22,7 @@
 import {PluginDescriptor} from '../public';
 import {createPage} from './pages';
 import {defaultPlugins} from '../core/default_plugins';
+import {Intent} from '../widgets/common';
 
 export const PluginsPage = createPage({
   view() {
@@ -31,7 +32,7 @@
       m(
         '.pf-plugins-topbar',
         m(Button, {
-          minimal: false,
+          intent: Intent.Primary,
           label: 'Disable All',
           onclick: async () => {
             for (const plugin of pluginRegistry.values()) {
@@ -41,7 +42,7 @@
           },
         }),
         m(Button, {
-          minimal: false,
+          intent: Intent.Primary,
           label: 'Enable All',
           onclick: async () => {
             for (const plugin of pluginRegistry.values()) {
@@ -51,7 +52,7 @@
           },
         }),
         m(Button, {
-          minimal: false,
+          intent: Intent.Primary,
           label: 'Restore Defaults',
           onclick: async () => {
             await pluginManager.restoreDefaults(true);
@@ -95,6 +96,7 @@
       : m('.pf-tag.pf-inactive', 'Inactive'),
     m(Button, {
       label: isActive ? 'Disable' : 'Enable',
+      intent: Intent.Primary,
       onclick: async () => {
         if (isActive) {
           await pluginManager.disablePlugin(pluginId, true);
diff --git a/ui/src/frontend/query_result_tab.ts b/ui/src/frontend/query_result_tab.ts
index f7fbf62..23b5148 100644
--- a/ui/src/frontend/query_result_tab.ts
+++ b/ui/src/frontend/query_result_tab.ts
@@ -130,7 +130,7 @@
           : m(
               PopupMenu2,
               {
-                trigger: m(Button, {label: 'Show debug track', minimal: true}),
+                trigger: m(Button, {label: 'Show debug track'}),
                 popupPosition: PopupPosition.Top,
               },
               m(AddDebugTrackMenu, {
diff --git a/ui/src/frontend/query_table.ts b/ui/src/frontend/query_table.ts
index 4affa45..e9bbce9 100644
--- a/ui/src/frontend/query_table.ts
+++ b/ui/src/frontend/query_table.ts
@@ -249,7 +249,6 @@
       contextButtons,
       m(Button, {
         label: 'Copy query',
-        minimal: true,
         onclick: () => {
           copyToClipboard(query);
         },
@@ -258,7 +257,6 @@
         resp.error === undefined &&
         m(Button, {
           label: 'Copy result (.tsv)',
-          minimal: true,
           onclick: () => {
             queryResponseToClipboard(resp);
           },
diff --git a/ui/src/frontend/sql_table/tab.ts b/ui/src/frontend/sql_table/tab.ts
index 01c87af..ea173a0 100644
--- a/ui/src/frontend/sql_table/tab.ts
+++ b/ui/src/frontend/sql_table/tab.ts
@@ -94,13 +94,11 @@
         icon: Icons.GoBack,
         disabled: !this.state.canGoBack(),
         onclick: () => this.state.goBack(),
-        minimal: true,
       }),
       m(Button, {
         icon: Icons.GoForward,
         disabled: !this.state.canGoForward(),
         onclick: () => this.state.goForward(),
-        minimal: true,
       }),
     ];
     const {selectStatement, columns} = this.state.buildSqlSelectStatement();
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts
index 26dcb57..fac6b8a 100644
--- a/ui/src/frontend/thread_state_tab.ts
+++ b/ui/src/frontend/thread_state_tab.ts
@@ -24,6 +24,7 @@
 import {Section} from '../widgets/section';
 import {SqlRef} from '../widgets/sql_ref';
 import {Tree, TreeNode} from '../widgets/tree';
+import {Intent} from '../widgets/common';
 
 import {BottomTab, NewBottomTabArgs} from './bottom_tab';
 import {SchedSqlId, ThreadStateSqlId} from './sql_types';
@@ -319,6 +320,7 @@
       ),
       m(Button, {
         label: 'Critical path lite',
+        intent: Intent.Primary,
         onclick: () =>
           runQuery(
             `INCLUDE PERFETTO MODULE sched.thread_executing_span;`,
@@ -355,6 +357,7 @@
       }),
       m(Button, {
         label: 'Critical path',
+        intent: Intent.Primary,
         onclick: () =>
           runQuery(
             `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 1e35329..ec9a43a 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -150,7 +150,6 @@
                 e.stopPropagation();
               },
               icon: checkBox,
-              minimal: true,
               compact: true,
             }),
         ),
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 59c9857..d2cc66b 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -39,6 +39,7 @@
 import {getLegacySelection} from '../common/state';
 import {CloseTrackButton} from './close_track_button';
 import {exists} from '../base/utils';
+import {Intent} from '../widgets/common';
 
 function getTitleSize(title: string): string | undefined {
   const length = title.length;
@@ -99,7 +100,6 @@
       {
         trigger: m(Button, {
           icon: Icons.Crashed,
-          minimal: true,
         }),
       },
       this.renderErrorMessage(attrs.error),
@@ -112,6 +112,7 @@
       'This track has crashed',
       m(Button, {
         label: 'Re-raise exception',
+        intent: Intent.Primary,
         className: Popup.DISMISS_POPUP_GROUP_CLASS,
         onclick: () => {
           throw error;
@@ -190,7 +191,6 @@
             icon: Icons.Pin,
             iconFilled: pinned,
             title: pinned ? 'Unpin' : 'Pin to top',
-            minimal: true,
             compact: true,
           }),
           currentSelection !== null && currentSelection.kind === 'AREA'
@@ -204,7 +204,6 @@
                   );
                   e.stopPropagation();
                 },
-                minimal: true,
                 compact: true,
                 icon: isSelected(attrs.trackKey)
                   ? Icons.Checkbox
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 73decc9..b599dc7 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -293,7 +293,7 @@
           },
         },
         m(
-          '.header',
+          '.pf-timeline-header',
           m(PanelContainer, {
             className: 'header-panel-container',
             panels: [
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index 2792b7b..ea80972 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -49,6 +49,7 @@
 import {PopupMenuButton} from './popup_menu';
 import {TableShowcase} from './tables/table_showcase';
 import {TreeTable, TreeTableAttrs} from './widgets/treetable';
+import {Intent} from '../widgets/common';
 
 const DATA_ENGLISH_LETTER_FREQUENCY = {
   table: [
@@ -290,6 +291,7 @@
       return [
         m(Button, {
           label: 'Toggle Portal',
+          intent: Intent.Primary,
           onclick: () => {
             portalOpen = !portalOpen;
             raf.scheduleFullRedraw();
@@ -586,7 +588,7 @@
           icon: true,
           rightIcon: false,
           disabled: false,
-          minimal: false,
+          intent: new EnumOption(Intent.None, Object.values(Intent)),
           active: false,
           compact: false,
           loading: false,
diff --git a/ui/src/plugins/dev.perfetto.BookmarkletApi/OWNERS b/ui/src/plugins/dev.perfetto.BookmarkletApi/OWNERS
index 888f192..e159207 100644
--- a/ui/src/plugins/dev.perfetto.BookmarkletApi/OWNERS
+++ b/ui/src/plugins/dev.perfetto.BookmarkletApi/OWNERS
@@ -1,2 +1 @@
-hjd@google.com
 stevegolton@google.com
diff --git a/ui/src/plugins/dev.perfetto.Chaos/OWNERS b/ui/src/plugins/dev.perfetto.Chaos/OWNERS
index 9ee9fce..e159207 100644
--- a/ui/src/plugins/dev.perfetto.Chaos/OWNERS
+++ b/ui/src/plugins/dev.perfetto.Chaos/OWNERS
@@ -1 +1 @@
-hjd@google.com
+stevegolton@google.com
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index e51d5d5..a8d2949 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -177,10 +177,15 @@
 
 export interface Track {
   /**
-   * Optional: Called when the track is first materialized on the timeline.
+   * Optional: Called once before onUpdate is first called.
+   *
    * If this function returns a Promise, this promise is awaited before onUpdate
    * or onDestroy is called. Any calls made to these functions in the meantime
    * will be queued up and the hook will be called later once onCreate returns.
+   *
+   * Exactly when this hook is called is left purposely undefined. The only
+   * guarantee is that it will be called once before onUpdate is first called.
+   *
    * @param ctx Our track context object.
    */
   onCreate?(ctx: TrackContext): Promise<void> | void;
diff --git a/ui/src/tracks/android_log/logs_panel.ts b/ui/src/tracks/android_log/logs_panel.ts
index 1a8d233..51e0889 100644
--- a/ui/src/tracks/android_log/logs_panel.ts
+++ b/ui/src/tracks/android_log/logs_panel.ts
@@ -30,6 +30,7 @@
 import {Select} from '../../widgets/select';
 import {Button} from '../../widgets/button';
 import {TextInput} from '../../widgets/text_input';
+import {Intent} from '../../widgets/common';
 
 const ROW_H = 20;
 
@@ -285,6 +286,7 @@
       label: attrs.name,
       rightIcon: 'close',
       onclick: () => attrs.removeTag(attrs.name),
+      intent: Intent.Primary,
     });
   }
 }
@@ -371,7 +373,6 @@
       icon,
       title: tooltip,
       disabled: attrs.disabled,
-      minimal: true,
       onclick: attrs.onClick,
     });
   }
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index a459c8a..5fdf938 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -136,15 +136,21 @@
     const time = visibleTimeScale.pxToHpTime(x).toTime('floor');
 
     const query = `
+      WITH X AS (
+        SELECT
+          id,
+          ts AS leftTs,
+          LEAD(ts) OVER (ORDER BY ts) AS rightTs
+        FROM counter
+        WHERE track_id = ${this.trackId}
+        ORDER BY ts
+      )
       SELECT
         id,
-        ts as leftTs,
-        min(ts) OVER (ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) as rightTs
-      FROM ${this.rootTable}
-      WHERE
-        track_id = ${this.trackId} AND
-        ts < ${time}
-      ORDER BY ts DESC
+        leftTs,
+        rightTs
+      FROM X
+      WHERE rightTs > ${time}
       LIMIT 1
     `;
 
@@ -152,7 +158,7 @@
       const it = result.iter({
         id: NUM,
         leftTs: LONG,
-        rightTs: LONG,
+        rightTs: LONG_NULL,
       });
       if (!it.valid()) {
         return;
@@ -160,7 +166,11 @@
       const trackKey = this.trackKey;
       const id = it.id;
       const leftTs = Time.fromRaw(it.leftTs);
-      const rightTs = Time.fromRaw(it.rightTs);
+
+      // TODO(stevegolton): Don't try to guess times and durations here, make it
+      // obvious to the user that this counter sample has no duration as it's
+      // the last one in the series
+      const rightTs = Time.fromRaw(it.rightTs ?? leftTs);
 
       globals.makeSelection(
         Actions.selectCounter({
diff --git a/ui/src/tracks/ftrace/ftrace_explorer.ts b/ui/src/tracks/ftrace/ftrace_explorer.ts
index 6884d70..9481b86 100644
--- a/ui/src/tracks/ftrace/ftrace_explorer.ts
+++ b/ui/src/tracks/ftrace/ftrace_explorer.ts
@@ -206,7 +206,6 @@
     if (attrs.cache.state !== 'valid') {
       return m(Button, {
         label: 'Filter',
-        minimal: true,
         disabled: true,
         loading: true,
       });
@@ -225,7 +224,6 @@
 
     return m(PopupMultiSelect, {
       label: 'Filter',
-      minimal: true,
       icon: 'filter_list_alt',
       popupPosition: PopupPosition.Top,
       options,
diff --git a/ui/src/tracks/visualised_args/index.ts b/ui/src/tracks/visualised_args/index.ts
index d2ede0c..9e34de3 100644
--- a/ui/src/tracks/visualised_args/index.ts
+++ b/ui/src/tracks/visualised_args/index.ts
@@ -100,7 +100,6 @@
       },
       icon: Icons.Close,
       title: 'Close',
-      minimal: true,
       compact: true,
     });
   }
diff --git a/ui/src/widgets/button.ts b/ui/src/widgets/button.ts
index 276b2f4..6f6a8c0 100644
--- a/ui/src/widgets/button.ts
+++ b/ui/src/widgets/button.ts
@@ -16,7 +16,7 @@
 
 import {classNames} from '../base/classnames';
 
-import {HTMLAttrs, HTMLButtonAttrs} from './common';
+import {HTMLAttrs, HTMLButtonAttrs, Intent, classForIntent} from './common';
 import {Icon} from './icon';
 import {Popup} from './popup';
 import {Spinner} from './spinner';
@@ -31,9 +31,6 @@
   // Use minimal padding, reducing the overall size of the button by a few px.
   // Defaults to false.
   compact?: boolean;
-  // Reduces button decorations.
-  // Defaults to false.
-  minimal?: boolean;
   // Optional right icon.
   rightIcon?: string;
   // List of space separated class names forwarded to the icon.
@@ -47,6 +44,9 @@
   // Whether to use a filled icon
   // Defaults to false;
   iconFilled?: boolean;
+  // Indicate button colouring by intent.
+  // Defaults to undefined aka "None"
+  intent?: Intent;
 }
 
 interface IconButtonAttrs extends CommonAttrs {
@@ -69,11 +69,11 @@
       icon,
       active,
       compact,
-      minimal,
       rightIcon,
       className,
       dismissPopup,
       iconFilled,
+      intent = Intent.None,
       ...htmlAttrs
     } = attrs;
 
@@ -82,7 +82,7 @@
     const classes = classNames(
       active && 'pf-active',
       compact && 'pf-compact',
-      minimal && 'pf-minimal',
+      classForIntent(intent),
       icon && !label && 'pf-icon-only',
       dismissPopup && Popup.DISMISS_POPUP_GROUP_CLASS,
       className,
diff --git a/ui/src/widgets/common.ts b/ui/src/widgets/common.ts
index 2498310..61352fa 100644
--- a/ui/src/widgets/common.ts
+++ b/ui/src/widgets/common.ts
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {assertUnreachable} from '../base/logging';
+
 // This file contains interfaces for attributes for various HTML elements.
 // They are typically used by widgets which pass attributes down to their
 // internal child, to provide a type-safe interface to users of those widgets.
@@ -59,3 +61,19 @@
 export interface HTMLLabelAttrs extends HTMLAttrs {
   for?: string;
 }
+
+export enum Intent {
+  None = 'none',
+  Primary = 'primary',
+}
+
+export function classForIntent(intent: Intent): string | undefined {
+  switch (intent) {
+    case Intent.None:
+      return undefined;
+    case Intent.Primary:
+      return 'pf-intent-primary';
+    default:
+      return assertUnreachable(intent);
+  }
+}
diff --git a/ui/src/widgets/form.ts b/ui/src/widgets/form.ts
index fbfaae6..8105f6d 100644
--- a/ui/src/widgets/form.ts
+++ b/ui/src/widgets/form.ts
@@ -17,6 +17,7 @@
 import {Button} from './button';
 import {HTMLAttrs, HTMLLabelAttrs} from './common';
 import {Popup} from './popup';
+import {Intent} from '../widgets/common';
 
 export interface FormAttrs extends HTMLAttrs {
   // Text to show on the "submit" button.
@@ -73,6 +74,7 @@
           label: submitLabel,
           rightIcon: submitIcon,
           className: Popup.DISMISS_POPUP_GROUP_CLASS,
+          intent: Intent.Primary,
           onclick: (e: Event) => {
             preventDefault && e.preventDefault();
             onSubmit();
@@ -84,13 +86,11 @@
             type: 'button',
             label: cancelLabel,
             className: Popup.DISMISS_POPUP_GROUP_CLASS,
-            minimal: true,
           }),
         // This reset button just clears the form.
         resetLabel &&
           m(Button, {
             label: resetLabel,
-            minimal: true,
             type: 'reset',
           }),
       ),
diff --git a/ui/src/widgets/multiselect.ts b/ui/src/widgets/multiselect.ts
index edff807..9756dc7 100644
--- a/ui/src/widgets/multiselect.ts
+++ b/ui/src/widgets/multiselect.ts
@@ -22,6 +22,7 @@
 import {Popup, PopupPosition} from './popup';
 import {scheduleFullRedraw} from './raf';
 import {TextInput} from './text_input';
+import {Intent} from './common';
 
 export interface Option {
   // The ID is used to indentify this option, and is used in callbacks.
@@ -46,7 +47,7 @@
 }
 
 export type PopupMultiSelectAttrs = MultiSelectAttrs & {
-  minimal?: boolean;
+  intent?: Intent;
   compact?: boolean;
   icon?: string;
   label: string;
@@ -106,7 +107,6 @@
                   label:
                     this.searchText === '' ? 'Clear All' : 'Clear Filtered',
                   icon: Icons.Deselect,
-                  minimal: true,
                   onclick: () => {
                     const diffs = options
                       .filter(({checked}) => checked)
@@ -134,7 +134,6 @@
                 label:
                   this.searchText === '' ? 'Select All' : 'Select Filtered',
                 icon: Icons.SelectAll,
-                minimal: true,
                 compact: true,
                 onclick: () => {
                   const diffs = options
@@ -148,7 +147,6 @@
               m(Button, {
                 label: this.searchText === '' ? 'Clear All' : 'Clear Filtered',
                 icon: Icons.Deselect,
-                minimal: true,
                 compact: true,
                 onclick: () => {
                   const diffs = options
@@ -193,7 +191,6 @@
         },
         label: '',
         icon: 'close',
-        minimal: true,
       });
     } else {
       return null;
@@ -225,7 +222,7 @@
   implements m.ClassComponent<PopupMultiSelectAttrs>
 {
   view({attrs}: m.CVnode<PopupMultiSelectAttrs>) {
-    const {icon, popupPosition = PopupPosition.Auto, minimal, compact} = attrs;
+    const {icon, popupPosition = PopupPosition.Auto, intent, compact} = attrs;
 
     return m(
       Popup,
@@ -233,7 +230,7 @@
         trigger: m(Button, {
           label: this.labelText(attrs),
           icon,
-          minimal,
+          intent,
           compact,
         }),
         position: popupPosition,