Merge "UI: use ccache for wasm when detected" into main
diff --git a/Android.bp b/Android.bp
index bd2375b..8d476ce 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5956,6 +5956,7 @@
         "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",
@@ -6376,6 +6377,7 @@
         "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",
@@ -6458,6 +6460,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.gen.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.gen.cc",
@@ -6540,6 +6543,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.gen.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.gen.h",
@@ -6618,6 +6622,7 @@
         "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",
@@ -6699,6 +6704,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.pb.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pb.cc",
@@ -6780,6 +6786,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.pb.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pb.h",
@@ -6858,6 +6865,7 @@
         "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",
@@ -6940,6 +6948,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pbzero.cc",
@@ -7022,6 +7031,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pbzero.h",
@@ -11731,6 +11741,7 @@
         "src/trace_processor/metrics/sql/android/power_profile_data/raven.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/redfin.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/sargo.sql",
+        "src/trace_processor/metrics/sql/android/power_profile_data/shusky.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/sunfish.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/taimen.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/walleye.sql",
@@ -12056,6 +12067,7 @@
         "src/trace_processor/perfetto_sql/stdlib/graphs/search.sql",
         "src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql",
         "src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql",
+        "src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql",
         "src/trace_processor/perfetto_sql/stdlib/pkvm/hypervisor.sql",
         "src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql",
         "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
@@ -12064,6 +12076,7 @@
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_state_flattened.sql",
+        "src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/utilization/general.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/utilization/process.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/utilization/system.sql",
@@ -12480,6 +12493,7 @@
         "src/trace_redaction/find_package_uid.cc",
         "src/trace_redaction/populate_allow_lists.cc",
         "src/trace_redaction/prune_package_list.cc",
+        "src/trace_redaction/scrub_ftrace_events.cc",
         "src/trace_redaction/scrub_trace_packet.cc",
         "src/trace_redaction/trace_redaction_framework.cc",
         "src/trace_redaction/trace_redactor.cc",
@@ -12492,6 +12506,7 @@
     srcs: [
         "src/trace_redaction/find_package_uid_unittest.cc",
         "src/trace_redaction/prune_package_list_unittest.cc",
+        "src/trace_redaction/scrub_ftrace_events_unittest.cc",
         "src/trace_redaction/scrub_trace_packet_unittest.cc",
     ],
 }
@@ -13465,6 +13480,7 @@
         "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",
diff --git a/BUILD b/BUILD
index 592c1ec..1b3d23f 100644
--- a/BUILD
+++ b/BUILD
@@ -2029,6 +2029,7 @@
         "src/trace_processor/metrics/sql/android/power_profile_data/raven.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/redfin.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/sargo.sql",
+        "src/trace_processor/metrics/sql/android/power_profile_data/shusky.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/sunfish.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/taimen.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/walleye.sql",
@@ -2472,6 +2473,14 @@
     ],
 )
 
+# GN target: //src/trace_processor/perfetto_sql/stdlib/memory:memory
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_memory_memory",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql",
+    ],
+)
+
 # GN target: //src/trace_processor/perfetto_sql/stdlib/pkvm:pkvm
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_stdlib_pkvm_pkvm",
@@ -2509,6 +2518,7 @@
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_state_flattened.sql",
+        "src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql",
     ],
 )
 
@@ -2545,6 +2555,7 @@
         ":src_trace_processor_perfetto_sql_stdlib_graphs_graphs",
         ":src_trace_processor_perfetto_sql_stdlib_intervals_intervals",
         ":src_trace_processor_perfetto_sql_stdlib_linux_linux",
+        ":src_trace_processor_perfetto_sql_stdlib_memory_memory",
         ":src_trace_processor_perfetto_sql_stdlib_pkvm_pkvm",
         ":src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
         ":src_trace_processor_perfetto_sql_stdlib_sched_sched",
@@ -4669,6 +4680,7 @@
         "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",
diff --git a/CHANGELOG b/CHANGELOG
index d586980..4f79dd1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,12 @@
     *
 
 
+v43.2 - 2024-03-07:
+  UI:
+    * Added redirection to pinned UI version when using
+      `trace_processor_shell --httpd` and pinned version is available.
+
+
 v43.1 - 2024-03-05:
   Tracing service and probes:
     * Cherry-pick of https://r.android.com/2988674, which fixes the android x86
diff --git a/buildtools/Android.mk b/buildtools/Android.mk
deleted file mode 100644
index e69de29..0000000
--- a/buildtools/Android.mk
+++ /dev/null
diff --git a/gn/standalone/toolchain/BUILD.gn b/gn/standalone/toolchain/BUILD.gn
index fcf2794..f00325c 100644
--- a/gn/standalone/toolchain/BUILD.gn
+++ b/gn/standalone/toolchain/BUILD.gn
@@ -417,6 +417,11 @@
   lib_switch = ""
   lib_dir_switch = "/LIBPATH:"
   sys_lib_flags = string_join(" ", win_msvc_sys_lib_flags)
+  external_cflags = string_join(" ",
+                                [
+                                  extra_cflags,
+                                  extra_host_cflags,
+                                ])
 
   # Note: /showIncludes below is required for ninja, to build a complete
   # dependency graph for headers. Removing it breaks incremental builds.
@@ -424,7 +429,7 @@
   tool("cc") {
     precompiled_header_type = "msvc"
     pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
-    command = "$cc_wrapper $cc /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\" /guard:cf /ZH:SHA_256"
+    command = "$cc_wrapper $cc /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} ${external_cflags} /c {{source}} /Fo{{output}} /Fd\"$pdbname\" /guard:cf /ZH:SHA_256"
     depsformat = "msvc"
     outputs =
         [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
@@ -434,7 +439,7 @@
   tool("cxx") {
     precompiled_header_type = "msvc"
     pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
-    command = "$cc_wrapper $cxx /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\" /guard:cf /ZH:SHA_256"
+    command = "$cc_wrapper $cxx /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} ${external_cflags} /c {{source}} /Fo{{output}} /Fd\"$pdbname\" /guard:cf /ZH:SHA_256"
     depsformat = "msvc"
     outputs =
         [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
diff --git a/include/perfetto/ext/base/file_utils.h b/include/perfetto/ext/base/file_utils.h
index b3d8978..f877582 100644
--- a/include/perfetto/ext/base/file_utils.h
+++ b/include/perfetto/ext/base/file_utils.h
@@ -102,7 +102,11 @@
                                 const std::string& group_name,
                                 const std::string& mode_bits);
 
-std::optional<size_t> GetFileSize(const std::string& path);
+// Returns the size of the file located at |path|, or nullopt in case of error.
+std::optional<uint64_t> GetFileSize(const std::string& path);
+
+// Returns the size of the open file |fd|, or nullopt in case of error.
+std::optional<uint64_t> GetFileSize(PlatformHandle fd);
 
 }  // namespace base
 }  // namespace perfetto
diff --git a/include/perfetto/trace_processor/basic_types.h b/include/perfetto/trace_processor/basic_types.h
index 0bbfcfa..7ecc934 100644
--- a/include/perfetto/trace_processor/basic_types.h
+++ b/include/perfetto/trace_processor/basic_types.h
@@ -20,14 +20,13 @@
 #include <assert.h>
 #include <math.h>
 #include <stdarg.h>
-#include <stdint.h>
-#include <functional>
+#include <cstdint>
+
 #include <string>
 #include <unordered_map>
 #include <utility>
 #include <vector>
 
-#include "perfetto/base/build_config.h"
 #include "perfetto/base/export.h"
 #include "perfetto/base/logging.h"
 
@@ -126,7 +125,7 @@
   bool ingest_ftrace_in_raw_table = true;
 
   // Indicates the event which should be used as a marker to drop ftrace data in
-  // the trace before that event. See the ennu documenetation for more details.
+  // the trace before that event. See the enum documentation for more details.
   DropFtraceDataBefore drop_ftrace_data_before =
       DropFtraceDataBefore::kTracingStarted;
 
@@ -248,8 +247,8 @@
   //
   // It is encouraged that import key should be the path to the SQL file being
   // run, with slashes replaced by dots and without the SQL extension. For
-  // example, 'android/camera/junk.sql' would be imported by
-  // 'android.camera.junk'.
+  // example, 'android/camera/jank.sql' would be imported by
+  // 'android.camera.jank'.
   std::vector<std::pair<std::string, std::string>> files;
 
   // If true, SqlModule will override registered module with the same name. Can
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index b2a240a..1a5fffd 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -3501,7 +3501,9 @@
     // a 24 hour period.
     // On R-, this override only affected userdebug builds. Since S, it also
     // affects user builds.
-    optional uint64 max_upload_per_day_bytes = 1;
+    // In 24Q3+ (V+), this override is a noop because upload guardrail logic
+    // was removed from Perfetto.
+    optional uint64 max_upload_per_day_bytes = 1 [deprecated = true];
 
     // Overrides the guardrail for maximum trace buffer size.
     // Available on U+
diff --git a/protos/perfetto/config/trace_config.proto b/protos/perfetto/config/trace_config.proto
index 8d17945..29173ec 100644
--- a/protos/perfetto/config/trace_config.proto
+++ b/protos/perfetto/config/trace_config.proto
@@ -234,7 +234,9 @@
     // a 24 hour period.
     // On R-, this override only affected userdebug builds. Since S, it also
     // affects user builds.
-    optional uint64 max_upload_per_day_bytes = 1;
+    // In 24Q3+ (V+), this override is a noop because upload guardrail logic
+    // was removed from Perfetto.
+    optional uint64 max_upload_per_day_bytes = 1 [deprecated = true];
 
     // Overrides the guardrail for maximum trace buffer size.
     // Available on U+
diff --git a/protos/perfetto/trace/android/android_input_event.proto b/protos/perfetto/trace/android/android_input_event.proto
index 7364e14..6a91950 100644
--- a/protos/perfetto/trace/android/android_input_event.proto
+++ b/protos/perfetto/trace/android/android_input_event.proto
@@ -123,12 +123,12 @@
   oneof event {
     // Traces input events received by or generated by InputDispatcher
     AndroidMotionEvent dispatcher_motion_event = 1;
-    AndroidMotionEvent dispatcher_motion_event_sensitive = 2;
+    AndroidMotionEvent dispatcher_motion_event_redacted = 2;
     AndroidKeyEvent dispatcher_key_event = 3;
-    AndroidKeyEvent dispatcher_key_event_sensitive = 4;
+    AndroidKeyEvent dispatcher_key_event_redacted = 4;
 
     // Traces an event being dispatched to a window.
     AndroidWindowInputDispatchEvent dispatcher_window_dispatch_event = 5;
-    AndroidWindowInputDispatchEvent dispatcher_window_dispatch_event_sensitive = 6;
+    AndroidWindowInputDispatchEvent dispatcher_window_dispatch_event_redacted = 6;
   }
 }
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index 2237aef..39e5ba2 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -62,6 +62,7 @@
   "printk.proto",
   "raw_syscalls.proto",
   "regulator.proto",
+  "rpm.proto",
   "samsung.proto",
   "sched.proto",
   "scm.proto",
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index 843b44c..def2a72 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -62,6 +62,7 @@
 import "protos/perfetto/trace/ftrace/printk.proto";
 import "protos/perfetto/trace/ftrace/raw_syscalls.proto";
 import "protos/perfetto/trace/ftrace/regulator.proto";
+import "protos/perfetto/trace/ftrace/rpm.proto";
 import "protos/perfetto/trace/ftrace/samsung.proto";
 import "protos/perfetto/trace/ftrace/sched.proto";
 import "protos/perfetto/trace/ftrace/scm.proto";
@@ -604,5 +605,6 @@
     BinderReturnFtraceEvent binder_return = 486;
     SchedSwitchWithCtrsFtraceEvent sched_switch_with_ctrs = 487;
     GpuWorkPeriodFtraceEvent gpu_work_period = 488;
+    RpmStatusFtraceEvent rpm_status = 489;
   }
 }
diff --git a/protos/perfetto/trace/ftrace/rpm.proto b/protos/perfetto/trace/ftrace/rpm.proto
new file mode 100644
index 0000000..dd542bb
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/rpm.proto
@@ -0,0 +1,11 @@
+// Autogenerated by:
+// ../../src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message RpmStatusFtraceEvent {
+  optional string name = 1;
+  optional int32 status = 2;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index dd4ecff..b8e209d 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -3501,7 +3501,9 @@
     // a 24 hour period.
     // On R-, this override only affected userdebug builds. Since S, it also
     // affects user builds.
-    optional uint64 max_upload_per_day_bytes = 1;
+    // In 24Q3+ (V+), this override is a noop because upload guardrail logic
+    // was removed from Perfetto.
+    optional uint64 max_upload_per_day_bytes = 1 [deprecated = true];
 
     // Overrides the guardrail for maximum trace buffer size.
     // Available on U+
@@ -4308,13 +4310,13 @@
   oneof event {
     // Traces input events received by or generated by InputDispatcher
     AndroidMotionEvent dispatcher_motion_event = 1;
-    AndroidMotionEvent dispatcher_motion_event_sensitive = 2;
+    AndroidMotionEvent dispatcher_motion_event_redacted = 2;
     AndroidKeyEvent dispatcher_key_event = 3;
-    AndroidKeyEvent dispatcher_key_event_sensitive = 4;
+    AndroidKeyEvent dispatcher_key_event_redacted = 4;
 
     // Traces an event being dispatched to a window.
     AndroidWindowInputDispatchEvent dispatcher_window_dispatch_event = 5;
-    AndroidWindowInputDispatchEvent dispatcher_window_dispatch_event_sensitive = 6;
+    AndroidWindowInputDispatchEvent dispatcher_window_dispatch_event_redacted = 6;
   }
 }
 
@@ -9278,6 +9280,15 @@
 
 // End of protos/perfetto/trace/ftrace/regulator.proto
 
+// Begin of protos/perfetto/trace/ftrace/rpm.proto
+
+message RpmStatusFtraceEvent {
+  optional string name = 1;
+  optional int32 status = 2;
+}
+
+// End of protos/perfetto/trace/ftrace/rpm.proto
+
 // Begin of protos/perfetto/trace/ftrace/samsung.proto
 
 message SamsungTracingMarkWriteFtraceEvent {
@@ -10462,6 +10473,7 @@
     BinderReturnFtraceEvent binder_return = 486;
     SchedSwitchWithCtrsFtraceEvent sched_switch_with_ctrs = 487;
     GpuWorkPeriodFtraceEvent gpu_work_period = 488;
+    RpmStatusFtraceEvent rpm_status = 489;
   }
 }
 
@@ -13789,9 +13801,6 @@
   optional Utsname utsname = 1;
   optional string android_build_fingerprint = 2;
 
-  // Ticks per second - sysconf(_SC_CLK_TCK).
-  optional int64 hz = 3;
-
   // The version of traced (the same returned by `traced --version`).
   // This is a human readable string with and its format varies depending on
   // the build system and the repo (standalone vs AOSP).
@@ -13805,9 +13814,18 @@
   // Kernel page size - sysconf(_SC_PAGESIZE).
   optional uint32 page_size = 6;
 
+  // Number of cpus - sysconf(_SC_NPROCESSORS_CONF).
+  // Might be different to the number of online cpus.
+  // Introduced in perfetto v44.
+  optional uint32 num_cpus = 8;
+
   // The timezone offset from UTC, as per strftime("%z"), in minutes.
   // Introduced in v38 / Android V.
   optional int32 timezone_off_mins = 7;
+
+  // Ticks per second - sysconf(_SC_CLK_TCK).
+  // Not serialised as of perfetto v44.
+  optional int64 hz = 3;
 }
 
 // End of protos/perfetto/trace/system_info.proto
diff --git a/protos/perfetto/trace/system_info.proto b/protos/perfetto/trace/system_info.proto
index 9a75773..9f38848 100644
--- a/protos/perfetto/trace/system_info.proto
+++ b/protos/perfetto/trace/system_info.proto
@@ -29,9 +29,6 @@
   optional Utsname utsname = 1;
   optional string android_build_fingerprint = 2;
 
-  // Ticks per second - sysconf(_SC_CLK_TCK).
-  optional int64 hz = 3;
-
   // The version of traced (the same returned by `traced --version`).
   // This is a human readable string with and its format varies depending on
   // the build system and the repo (standalone vs AOSP).
@@ -45,7 +42,16 @@
   // Kernel page size - sysconf(_SC_PAGESIZE).
   optional uint32 page_size = 6;
 
+  // Number of cpus - sysconf(_SC_NPROCESSORS_CONF).
+  // Might be different to the number of online cpus.
+  // Introduced in perfetto v44.
+  optional uint32 num_cpus = 8;
+
   // The timezone offset from UTC, as per strftime("%z"), in minutes.
   // Introduced in v38 / Android V.
   optional int32 timezone_off_mins = 7;
+
+  // Ticks per second - sysconf(_SC_CLK_TCK).
+  // Not serialised as of perfetto v44.
+  optional int64 hz = 3;
 }
diff --git a/python/generators/trace_processor_table/serialize.py b/python/generators/trace_processor_table/serialize.py
index 2096f55..b9d5170 100644
--- a/python/generators/trace_processor_table/serialize.py
+++ b/python/generators/trace_processor_table/serialize.py
@@ -730,7 +730,10 @@
 def serialize_header(ifdef_guard: str, tables: List[ParsedTable],
                      include_paths: List[str]) -> str:
   """Serializes a table header file containing the given set of tables."""
-  include_paths_str = '\n'.join([f'#include "{i}"' for i in include_paths])
+  # Replace the backslash with forward slash when building on Windows.
+  # Caused b/327985369 without the replace.
+  include_paths_str = '\n'.join(
+      [f'#include "{i}"' for i in include_paths]).replace("\\", "/")
   tables_str = '\n\n'.join([TableSerializer(t).serialize() for t in tables])
   return f'''
 #ifndef {ifdef_guard}
diff --git a/python/perfetto/prebuilts/manifests/trace_processor_shell.py b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
index 28489e9..d71c679 100755
--- a/python/perfetto/prebuilts/manifests/trace_processor_shell.py
+++ b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v41.0
+# This file has been generated by: tools/roll-prebuilts v43.2
 TRACE_PROCESSOR_SHELL_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        10175112,
+        8583624,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-amd64/trace_processor_shell',
     'sha256':
-        '62b14997b0c21755b80e82de03b487615a4abc9a99702acd9382b5bdd67ce28f',
+        'a1c16a74725cefb62406b39538b5d22f56a94e390a0394816d2945793f91f8cf',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8692328,
+        7980232,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/mac-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-arm64/trace_processor_shell',
     'sha256':
-        '04e37680992b6b52ad3abdae34792858e70bf7a5523b2b2d9e76248b5bc6cbe4',
+        '3651654cd462df8a2ec8cb3f7375cee01ccc11861a675b9da0d00aa697efe7b2',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        10025552,
+        8770200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-amd64/trace_processor_shell',
     'sha256':
-        '3ffce07c28144eb9da315b548b14f6304e7bd62fca485b4c4a625b80489ba2de',
+        '0796a01af496a6b62623fea89b2d34063ade9d156783e1f88949d8b7ab1f76d0',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7417332,
+        6371036,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm/trace_processor_shell',
     'sha256':
-        '767056bd8f1d7415552d1ca109918d35fb457673bf754b9641149a9b9980ba35',
+        'f4bbef5008de376913c3a95410802d94d8d5715439c3f797be0e4ca8c9bccb1a',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9434600,
+        8425776,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm64/trace_processor_shell',
     'sha256':
-        'b17cfb3e78a1aa2c8b83e60d5f716bbf23c8d613053b268a3489295218006540',
+        'f1ff5585a06ad8b9fc1d13dbf8d02f39d2804019ea7e70b740872e0f6826695f',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,55 +75,55 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7051720,
+        6382140,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/android-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm/trace_processor_shell',
     'sha256':
-        '1f62f0957208d7043d4eba5f1acadb57c14542790a95b6be46db12f63434ad02'
+        '989b209a108c7d44e2531bb15a5d57f667c717cf774bb8a4a810f99fda0b958d'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8612232,
+        8340616,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/android-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm64/trace_processor_shell',
     'sha256':
-        '132cea4fe3208ae4c4b8d1aadc95c1d5f1dddd3ec77f64aa1d7c9230e9d6259f'
+        'ade7e72990cb97fd74766cd0df50a24cbd547d2f54c26b49d66236c809922645'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9525644,
+        9170488,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/android-x86/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x86/trace_processor_shell',
     'sha256':
-        '4cbea3a6e9158d4ac0a68040755520511143254653ab1ed553ab0b4e0b16c30b'
+        '6bd1f74616fd8f620fbf3228f83301844adae08a772b8ac2a64703724a79b516'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9775560,
+        8591040,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/android-x64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x64/trace_processor_shell',
     'sha256':
-        '469467f9f7d4f521d48f3938d0ec8b964b7552c4747b94aed1dd9c7e40ba82a7'
+        '1ccbcb8b2928615cf512cf97eaba395de6f1fc5d70313e884ea3975867f365ea'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        9444864,
+        8676352,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        '695174fd2791096db233e52673bffa2c5cb92275d191e760ab5d9fe33261da6e',
+        '9125418fedd96eb0e6f1ddeaf46069a05bdcb910592b059669fc982b4fff3f1b',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/python/perfetto/prebuilts/manifests/tracebox.py b/python/perfetto/prebuilts/manifests/tracebox.py
index 44e8aed..159aa28 100755
--- a/python/perfetto/prebuilts/manifests/tracebox.py
+++ b/python/perfetto/prebuilts/manifests/tracebox.py
@@ -1,4 +1,4 @@
-# This file has been generated by: tools/roll-prebuilts v43.1
+# This file has been generated by: tools/roll-prebuilts v43.2
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
@@ -7,9 +7,9 @@
     'file_size':
         1564728,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-amd64/tracebox',
     'sha256':
-        'dde1f657b10376f3fd684d1ce4302fd12c0479b567689f5dace8647375edd08c',
+        '239736808cbfba5085892e15c145381ea37ddba5df7c8fad97b68d9c04a4d860',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -21,9 +21,9 @@
     'file_size':
         1459160,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-arm64/tracebox',
     'sha256':
-        '349fc531090e134d708bfe2c44330c2f08280aa424f4e9f6d139897c1ad14da3',
+        '4af1449dc90e5505bd5f3d638f11b8bf7e5dc82c0290f0085dc0b335ababd143',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -35,9 +35,9 @@
     'file_size':
         2314424,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-amd64/tracebox',
     'sha256':
-        'e35fd880f483ab26d57d292a7c4d1c9df6393bff7f1e7694e7d3642472c8fff9',
+        'a97a5efdaf475f13f4f5947c03289029253f89d0f44caa64765b00b269551297',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -49,9 +49,9 @@
     'file_size':
         1418968,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm/tracebox',
     'sha256':
-        '7e550ab781f79fcf548f37a7cc3aaa50dbab235b53c445829815d987eb162843',
+        '818390305d15730fadcbd87dc3c8d87a439e040a02b5098f51af15dfff3f0ca0',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -63,9 +63,9 @@
     'file_size':
         2221176,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm64/tracebox',
     'sha256':
-        '355f2c6e66467a9e81855aa34a16fbe8cd68f01089ec0f5e3074f2011328a97f',
+        '5a3cf8c755e08b7a558083a70ad28293baa389e544ebd09806b6a883a5f17952',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -77,9 +77,9 @@
     'file_size':
         1304280,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm/tracebox',
     'sha256':
-        'b1c31ea2c07b519c40732416ecf91d8dbe0c04355150598c5ca2434249669a92'
+        '87bb07c7ac4c58d306975cabad3ed5d4b6fe11a8d617dad30fe7dd25bfdc6736'
 }, {
     'arch':
         'android-arm64',
@@ -88,9 +88,9 @@
     'file_size':
         2076144,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm64/tracebox',
     'sha256':
-        '28d7476c048123b6d73e1af4f5054dffdc87b67163980454761433bf49626848'
+        '501b2bb0cba0ecb770e2b568698f89f6b42d083fcca111c872f7a0e95c0cacc5'
 }, {
     'arch':
         'android-x86',
@@ -99,9 +99,9 @@
     'file_size':
         2253568,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x86/tracebox',
     'sha256':
-        '22e61978317ac4ef2934768d9e65bee2b1c7a332bdada5b4c1525d6b0339d4ac'
+        '28be4f88a9b8f950ebc45a20d4844002f9b3f81ef0230d0a5d9b1627cf89c9a5'
 }, {
     'arch':
         'android-x64',
@@ -110,7 +110,7 @@
     'file_size':
         2101752,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x64/tracebox',
     'sha256':
-        '6c74f75555dc7bb31e54debe9fc27fc4db960d2382862d1a8a9d0cf03f7d8300'
+        '6c08b743b9cb6073a75e2b3dd34098e09e3c8bcace89096dc5b9d0f071b2831a'
 }]
diff --git a/python/perfetto/prebuilts/manifests/traceconv.py b/python/perfetto/prebuilts/manifests/traceconv.py
index 00cae29..85bdfe6 100755
--- a/python/perfetto/prebuilts/manifests/traceconv.py
+++ b/python/perfetto/prebuilts/manifests/traceconv.py
@@ -1,4 +1,4 @@
-# This file has been generated by: tools/roll-prebuilts v43.1
+# This file has been generated by: tools/roll-prebuilts v43.2
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
@@ -7,9 +7,9 @@
     'file_size':
         7790424,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-amd64/traceconv',
     'sha256':
-        '88007b64828e835e0326c11f66f0bba7d8ab117562963086a4f19d8cb060204d',
+        'c1d9c50c89545b41af88525dc6f3ce508156ed3787ccecae0ff7c8e736c39318',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -21,9 +21,9 @@
     'file_size':
         7264824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-arm64/traceconv',
     'sha256':
-        'be5769279ef8442e80130e4bdb6a0a6aa11305442207ea18ff2cf38b21a71a57',
+        'df5349ae462dbd7c1ca9a1b8a0f09c044a47026d6ad8dc24e6945701d7c61a84',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -35,9 +35,9 @@
     'file_size':
         7885952,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-amd64/traceconv',
     'sha256':
-        '51cfdf5060bcd87d08402620d88d0243f7bb39f2878906614d53fa3ddd78dd92',
+        'b2c19364c1fb68e9f5cde610e5d71dd59b9fdf2bada8f7e1eefc319f828f7cb1',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -49,9 +49,9 @@
     'file_size':
         5919372,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm/traceconv',
     'sha256':
-        '04300b1c4dcec1e01bc23017dab3b406f9f0ffd7dd9ea3723784aa8730762bc9',
+        'b669be326b4b6a024e557e0927f1014fd1ea5d5427e194dc0653f21acac273ee',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -63,9 +63,9 @@
     'file_size':
         7588200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm64/traceconv',
     'sha256':
-        'd3edc1cd7b216e18955135e0e9e767cdd7b1b8b7efa64793aa6b923a6c278d68',
+        'aff1c4751e721733ce85f58048c17971399fe605a81ac300d306c200d6957818',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -77,9 +77,9 @@
     'file_size':
         5931120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm/traceconv',
     'sha256':
-        '8c3cb3dc96aa6ca296876b8ed56f8eed8c33e12e756b178360cc145263130e7e'
+        '826212f658fef744fbaeea66331b6fe7ca0152f69cf63ff2ea218a376d5d41d9'
 }, {
     'arch':
         'android-arm64',
@@ -88,9 +88,9 @@
     'file_size':
         7546224,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm64/traceconv',
     'sha256':
-        '180cfd2184d601c8f202b6bcd899cc7f63a8bb384505c1a2c3e889dfbe8bdb6d'
+        '29dd7e93e9182c4413a9f9c1c6a6f643f64e1fe0b9657ab1ea3cec8b0bb360c9'
 }, {
     'arch':
         'android-x86',
@@ -99,9 +99,9 @@
     'file_size':
         8176528,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x86/traceconv',
     'sha256':
-        'a4e8ff19daa58726138aa66f5adae74b609fceee403c8cddbaaf46d6d07e4cc8'
+        '63c2ebe7ed51f9667bcf69d7b9679f6077db5fd8ee9e1be7b786037e2a649fcb'
 }, {
     'arch':
         'android-x64',
@@ -110,9 +110,9 @@
     'file_size':
         7767560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x64/traceconv',
     'sha256':
-        '19626b87f8c8d956d3807d24faf5764c6bca289f55732cae2f6753dbec33e7f7'
+        'bb9350230c2fac5adf9e6fe21937865b6eaafaefc555ae26e68cae9419ad5ee8'
 }, {
     'arch':
         'windows-amd64',
@@ -121,9 +121,9 @@
     'file_size':
         7645696,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/windows-amd64/traceconv.exe',
     'sha256':
-        '24eb5322f22c0219694789fa04aaa5ad09b0746f8b993fd1713e6b3f7943708a',
+        '0c84b712941e4f63f74e66731745f94aec3cd30d94469e52cdf1143262f063a4',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/src/android_stats/perfetto_atoms.h b/src/android_stats/perfetto_atoms.h
index edc6382..1981a77 100644
--- a/src/android_stats/perfetto_atoms.h
+++ b/src/android_stats/perfetto_atoms.h
@@ -32,9 +32,6 @@
   // Guardrails inside perfetto_cmd before tracing is finished.
   kOnTimeout = 16,
   kCmdUserBuildTracingNotAllowed = 43,
-  kCmdFailedToInitGuardrailState = 44,
-  kCmdInvalidGuardrailState = 45,
-  kCmdHitUploadLimit = 46,
 
   // Checkpoints inside traced.
   kTracedEnableTracing = 37,
@@ -107,6 +104,10 @@
   // Contained status of Dropbox uploads. Removed as Perfetto no
   // longer supports uploading traces using Dropbox.
   // reserved 5, 6, 7;
+
+  // Contained status of guardrail state initalization and upload limit in
+  // perfetto_cmd. Removed as perfetto no longer manages stateful guardrails
+  // reserved 44, 45, 46;
 };
 
 // This must match the values of the PerfettoTrigger::TriggerType enum in:
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc
index db0105c..fba722d 100644
--- a/src/base/file_utils.cc
+++ b/src/base/file_utils.cc
@@ -408,33 +408,37 @@
 #endif
 }
 
-std::optional<size_t> GetFileSize(const std::string& file_path) {
+std::optional<uint64_t> GetFileSize(const std::string& file_path) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
   // This does not use base::OpenFile to avoid getting an exclusive lock.
-  HANDLE file =
+  base::ScopedPlatformHandle fd(
       CreateFileA(file_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
-                  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
-  if (file == INVALID_HANDLE_VALUE) {
-    return std::nullopt;
-  }
-  LARGE_INTEGER file_size;
-  file_size.QuadPart = 0;
-  std::optional<size_t> res;
-  if (GetFileSizeEx(file, &file_size)) {
-    res = static_cast<size_t>(file_size.QuadPart);
-  }
-  CloseHandle(file);
-  return res;
+                  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
 #else
   base::ScopedFile fd(base::OpenFile(file_path, O_RDONLY | O_CLOEXEC));
+#endif
   if (!fd) {
     return std::nullopt;
   }
-  struct stat buf;
-  if (fstat(*fd, &buf) == -1) {
+  return GetFileSize(*fd);
+}
+
+std::optional<uint64_t> GetFileSize(PlatformHandle fd) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  LARGE_INTEGER file_size;
+  file_size.QuadPart = 0;
+  if (!GetFileSizeEx(fd, &file_size)) {
     return std::nullopt;
   }
-  return static_cast<size_t>(buf.st_size);
+  static_assert(sizeof(decltype(file_size.QuadPart)) <= sizeof(uint64_t));
+  return static_cast<uint64_t>(file_size.QuadPart);
+#else
+  struct stat buf;
+  if (fstat(fd, &buf) == -1) {
+    return std::nullopt;
+  }
+  static_assert(sizeof(decltype(buf.st_size)) <= sizeof(uint64_t));
+  return static_cast<uint64_t>(buf.st_size);
 #endif
 }
 
diff --git a/src/base/scoped_mmap.cc b/src/base/scoped_mmap.cc
index 4bc97b8..6fe8494 100644
--- a/src/base/scoped_mmap.cc
+++ b/src/base/scoped_mmap.cc
@@ -50,41 +50,6 @@
 #endif
 }
 
-std::optional<size_t> GetPlatformHandleFileSize(PlatformHandle file) {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
-  off_t file_size_offset = lseek(file, 0, SEEK_END);
-  if (file_size_offset <= 0) {
-    return std::nullopt;
-  }
-
-  lseek(file, 0, SEEK_SET);
-
-  size_t file_size = static_cast<size_t>(file_size_offset);
-  if (static_cast<off_t>(file_size) != file_size_offset) {
-    return std::nullopt;
-  }
-  return file_size;
-#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-  LARGE_INTEGER fs;
-  fs.QuadPart = 0;
-  if (!GetFileSizeEx(file, &fs)) {
-    return std::nullopt;
-  }
-
-  size_t file_size = static_cast<size_t>(fs.QuadPart);
-  if (static_cast<decltype(fs.QuadPart)>(file_size) != fs.QuadPart) {
-    return std::nullopt;
-  }
-  return file_size;
-#else
-  // mmap is not supported. This does not matter.
-  base::ignore_result(file);
-  return std::nullopt;
-#endif
-}
-
 }  // namespace
 
 ScopedMmap::ScopedMmap(ScopedMmap&& other) noexcept {
@@ -185,11 +150,15 @@
   if (!file) {
     return ScopedMmap();
   }
-  std::optional<size_t> file_size = GetPlatformHandleFileSize(file.get());
+  std::optional<uint64_t> file_size = GetFileSize(file.get());
   if (!file_size.has_value()) {
     return ScopedMmap();
   }
-  return ScopedMmap::FromHandle(std::move(file), *file_size);
+  size_t size = static_cast<size_t>(*file_size);
+  if (static_cast<uint64_t>(size) != *file_size) {
+    return ScopedMmap();
+  }
+  return ScopedMmap::FromHandle(std::move(file), size);
 }
 
 }  // namespace perfetto::base
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index b1840e9..2ccd24e 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -165,12 +165,6 @@
   switch (resp) {
     case RateLimiter::kNotAllowedOnUserBuild:
       return PerfettoStatsdAtom::kCmdUserBuildTracingNotAllowed;
-    case RateLimiter::kFailedToInitState:
-      return PerfettoStatsdAtom::kCmdFailedToInitGuardrailState;
-    case RateLimiter::kInvalidState:
-      return PerfettoStatsdAtom::kCmdInvalidGuardrailState;
-    case RateLimiter::kHitUploadLimit:
-      return PerfettoStatsdAtom::kCmdHitUploadLimit;
     case RateLimiter::kOkToTrace:
       return std::nullopt;
   }
@@ -493,8 +487,9 @@
     }
 
     if (option == OPT_RESET_GUARDRAILS) {
-      PERFETTO_CHECK(limiter_->ClearState());
-      PERFETTO_ILOG("Guardrail state cleared");
+      PERFETTO_ILOG(
+          "Guardrails no longer exist in perfetto_cmd; this option only exists "
+          "for backwards compatability.");
       return 0;
     }
 
@@ -994,15 +989,10 @@
   RateLimiter::Args args{};
   args.is_user_build = IsUserBuild();
   args.is_uploading = save_to_incidentd_ || report_to_android_framework_;
-  args.current_time = base::GetWallTimeS();
-  args.ignore_guardrails = ignore_guardrails_;
   args.allow_user_build_tracing = trace_config_->allow_user_build_tracing();
-  args.unique_session_name = trace_config_->unique_session_name();
-  args.max_upload_bytes_override =
-      trace_config_->guardrail_overrides().max_upload_per_day_bytes();
 
-  if (!args.unique_session_name.empty())
-    base::MaybeSetThreadName("p-" + args.unique_session_name);
+  if (!trace_config_->unique_session_name().empty())
+    base::MaybeSetThreadName("p-" + trace_config_->unique_session_name());
 
   expected_duration_ms_ = trace_config_->duration_ms();
   if (!expected_duration_ms_) {
@@ -1053,9 +1043,7 @@
   SetupCtrlCSignalHandler();
   task_runner_.Run();
 
-  return limiter_->OnTraceDone(args, update_guardrail_state_, bytes_written_)
-             ? 0
-             : 1;
+  return tracing_succeeded_ ? 0 : 1;
 }
 
 void PerfettoCmd::OnConnect() {
@@ -1221,13 +1209,13 @@
       remove(trace_out_path_.c_str());
     }
 
-    // Update guardrail state even if we failed. This is for two
-    // reasons:
-    // 1. Keeps compatibility with pre-stats code which used to
-    // ignore errors from the service and always update state.
-    // 2. We want to prevent failure storms and the guardrails help
-    // by preventing tracing too frequently with the same session.
-    update_guardrail_state_ = true;
+    // Even though there was a failure, we mark this as success for legacy
+    // reasons: when guardrails used to exist in perfetto_cmd, this codepath
+    // would still cause guardrails to be written and the exit code to be 0.
+    //
+    // We want to preserve that semantic and the easiest way to do that would
+    // be to set |tracing_succeeded_| to true.
+    tracing_succeeded_ = true;
     task_runner_.Quit();
     return;
   }
@@ -1287,7 +1275,7 @@
   ReportFinalizeTraceUuidToAtrace(base::Uuid(uuid_));
 #endif
 
-  update_guardrail_state_ = true;
+  tracing_succeeded_ = true;
   task_runner_.Quit();
 }
 
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index d526b6f..844afaf 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -154,7 +154,7 @@
   bool save_to_incidentd_ = false;
   bool report_to_android_framework_ = false;
   bool statsd_logging_ = false;
-  bool update_guardrail_state_ = false;
+  bool tracing_succeeded_ = false;
   uint64_t bytes_written_ = 0;
   std::string detach_key_;
   std::string attach_key_;
diff --git a/src/perfetto_cmd/rate_limiter.cc b/src/perfetto_cmd/rate_limiter.cc
index 75e36cb..8b80718 100644
--- a/src/perfetto_cmd/rate_limiter.cc
+++ b/src/perfetto_cmd/rate_limiter.cc
@@ -16,41 +16,15 @@
 
 #include "src/perfetto_cmd/rate_limiter.h"
 
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <algorithm>
-#include <cinttypes>
-
 #include "perfetto/base/logging.h"
-#include "perfetto/ext/base/file_utils.h"
-#include "perfetto/ext/base/scoped_file.h"
-#include "perfetto/ext/base/utils.h"
 #include "src/perfetto_cmd/perfetto_cmd.h"
 
 namespace perfetto {
-namespace {
-
-// Every 24 hours we reset how much we've uploaded.
-const uint64_t kMaxUploadResetPeriodInSeconds = 60 * 60 * 24;
-
-// Maximum of 10mb every 24h.
-const uint64_t kMaxUploadInBytes = 1024 * 1024 * 10;
-
-// Keep track of last 10 observed sessions.
-const uint64_t kMaxSessionsInHistory = 10;
-
-}  // namespace
-
-using PerSessionState = gen::PerfettoCmdState::PerSessionState;
 
 RateLimiter::RateLimiter() = default;
 RateLimiter::~RateLimiter() = default;
 
 RateLimiter::ShouldTraceResponse RateLimiter::ShouldTrace(const Args& args) {
-  uint64_t now_in_s = static_cast<uint64_t>(args.current_time.count());
-
   // Not uploading?
   // -> We can just trace.
   if (!args.is_uploading)
@@ -64,181 +38,7 @@
         "builds");
     return ShouldTraceResponse::kNotAllowedOnUserBuild;
   }
-
-  // The state file is gone.
-  // Maybe we're tracing for the first time or maybe something went wrong the
-  // last time we tried to save the state. Either way reinitialize the state
-  // file.
-  if (!StateFileExists()) {
-    // We can't write the empty state file?
-    // -> Give up.
-    if (!ClearState()) {
-      PERFETTO_ELOG("Guardrail: failed to initialize guardrail state.");
-      return ShouldTraceResponse::kFailedToInitState;
-    }
-  }
-
-  bool loaded_state = LoadState(&state_);
-
-  // Failed to load the state?
-  // Current time is before either saved times?
-  // Last saved trace time is before first saved trace time?
-  // -> Try to save a clean state but don't trace.
-  if (!loaded_state || now_in_s < state_.first_trace_timestamp() ||
-      now_in_s < state_.last_trace_timestamp() ||
-      state_.last_trace_timestamp() < state_.first_trace_timestamp()) {
-    ClearState();
-    PERFETTO_ELOG("Guardrail: state invalid, clearing it.");
-    if (!args.ignore_guardrails)
-      return ShouldTraceResponse::kInvalidState;
-  }
-
-  // First trace was more than 24h ago? Reset state.
-  if ((now_in_s - state_.first_trace_timestamp()) >
-      kMaxUploadResetPeriodInSeconds) {
-    state_.set_first_trace_timestamp(0);
-    state_.set_last_trace_timestamp(0);
-    state_.set_total_bytes_uploaded(0);
-    return ShouldTraceResponse::kOkToTrace;
-  }
-
-  uint64_t max_upload_guardrail = kMaxUploadInBytes;
-  if (args.max_upload_bytes_override > 0) {
-    if (args.unique_session_name.empty()) {
-      PERFETTO_ELOG(
-          "Ignoring max_upload_per_day_bytes override as unique_session_name "
-          "not set");
-    } else {
-      max_upload_guardrail = args.max_upload_bytes_override;
-    }
-  }
-
-  uint64_t uploaded_so_far = state_.total_bytes_uploaded();
-  if (!args.unique_session_name.empty()) {
-    uploaded_so_far = 0;
-    for (const auto& session_state : state_.session_state()) {
-      if (session_state.session_name() == args.unique_session_name) {
-        uploaded_so_far = session_state.total_bytes_uploaded();
-        break;
-      }
-    }
-  }
-
-  if (uploaded_so_far > max_upload_guardrail) {
-    PERFETTO_ELOG("Guardrail: Uploaded %" PRIu64
-                  " in the last 24h. Limit is %" PRIu64 ".",
-                  uploaded_so_far, max_upload_guardrail);
-    if (!args.ignore_guardrails)
-      return ShouldTraceResponse::kHitUploadLimit;
-  }
-
   return ShouldTraceResponse::kOkToTrace;
 }
 
-bool RateLimiter::OnTraceDone(const Args& args, bool success, uint64_t bytes) {
-  uint64_t now_in_s = static_cast<uint64_t>(args.current_time.count());
-
-  // Failed to upload? Don't update the state.
-  if (!success)
-    return false;
-
-  if (!args.is_uploading)
-    return true;
-
-  // If the first trace timestamp is 0 (either because this is the
-  // first time or because it was reset for being more than 24h ago).
-  // -> We update it to the time of this trace.
-  if (state_.first_trace_timestamp() == 0)
-    state_.set_first_trace_timestamp(now_in_s);
-  // Always updated the last trace timestamp.
-  state_.set_last_trace_timestamp(now_in_s);
-
-  if (args.unique_session_name.empty()) {
-    // Add the amount we uploaded to the running total.
-    state_.set_total_bytes_uploaded(state_.total_bytes_uploaded() + bytes);
-  } else {
-    PerSessionState* target_session = nullptr;
-    for (PerSessionState& session : *state_.mutable_session_state()) {
-      if (session.session_name() == args.unique_session_name) {
-        target_session = &session;
-        break;
-      }
-    }
-    if (!target_session) {
-      target_session = state_.add_session_state();
-      target_session->set_session_name(args.unique_session_name);
-    }
-    target_session->set_total_bytes_uploaded(
-        target_session->total_bytes_uploaded() + bytes);
-    target_session->set_last_trace_timestamp(now_in_s);
-  }
-
-  if (!SaveState(state_)) {
-    PERFETTO_ELOG("Failed to save state.");
-    return false;
-  }
-
-  return true;
-}
-
-std::string RateLimiter::GetStateFilePath() const {
-  return std::string(kStateDir) + "/.guardraildata";
-}
-
-bool RateLimiter::StateFileExists() {
-  struct stat out;
-  return stat(GetStateFilePath().c_str(), &out) != -1;
-}
-
-bool RateLimiter::ClearState() {
-  gen::PerfettoCmdState zero{};
-  zero.set_total_bytes_uploaded(0);
-  zero.set_last_trace_timestamp(0);
-  zero.set_first_trace_timestamp(0);
-  bool success = SaveState(zero);
-  if (!success && StateFileExists())
-    remove(GetStateFilePath().c_str());
-  return success;
-}
-
-bool RateLimiter::LoadState(gen::PerfettoCmdState* state) {
-  base::ScopedFile in_fd(base::OpenFile(GetStateFilePath(), O_RDONLY));
-  if (!in_fd)
-    return false;
-  std::string s;
-  base::ReadFileDescriptor(in_fd.get(), &s);
-  if (s.empty())
-    return false;
-  return state->ParseFromString(s);
-}
-
-bool RateLimiter::SaveState(const gen::PerfettoCmdState& input_state) {
-  gen::PerfettoCmdState state = input_state;
-
-  // Keep only the N most recent per session states so the file doesn't
-  // grow indefinitely:
-  std::vector<PerSessionState>* sessions = state.mutable_session_state();
-  std::sort(sessions->begin(), sessions->end(),
-            [](const PerSessionState& a, const PerSessionState& b) {
-              return a.last_trace_timestamp() > b.last_trace_timestamp();
-            });
-  if (sessions->size() > kMaxSessionsInHistory) {
-    sessions->resize(kMaxSessionsInHistory);
-  }
-
-  // Rationale for 0666: the cmdline client can be executed under two
-  // different Unix UIDs: shell and statsd. If we run one after the
-  // other and the file has 0600 permissions, then the 2nd run won't
-  // be able to read the file and will clear it, aborting the trace.
-  // SELinux still prevents that anything other than the perfetto
-  // executable can change the guardrail file.
-  std::vector<uint8_t> buf = state.SerializeAsArray();
-  base::ScopedFile out_fd(
-      base::OpenFile(GetStateFilePath(), O_WRONLY | O_CREAT | O_TRUNC, 0666));
-  if (!out_fd)
-    return false;
-  ssize_t written = base::WriteAll(out_fd.get(), buf.data(), buf.size());
-  return written >= 0 && static_cast<size_t>(written) == buf.size();
-}
-
 }  // namespace perfetto
diff --git a/src/perfetto_cmd/rate_limiter.h b/src/perfetto_cmd/rate_limiter.h
index 7543090..f82211e 100644
--- a/src/perfetto_cmd/rate_limiter.h
+++ b/src/perfetto_cmd/rate_limiter.h
@@ -17,9 +17,6 @@
 #ifndef SRC_PERFETTO_CMD_RATE_LIMITER_H_
 #define SRC_PERFETTO_CMD_RATE_LIMITER_H_
 
-#include "perfetto/base/time.h"
-#include "src/perfetto_cmd/perfetto_cmd_state.gen.h"
-
 namespace perfetto {
 
 class RateLimiter {
@@ -27,39 +24,17 @@
   struct Args {
     bool is_user_build = false;
     bool is_uploading = false;
-    bool ignore_guardrails = false;
     bool allow_user_build_tracing = false;
-    base::TimeSeconds current_time = base::TimeSeconds(0);
-    uint64_t max_upload_bytes_override = 0;
-    std::string unique_session_name = "";
   };
   enum ShouldTraceResponse {
     kOkToTrace,
     kNotAllowedOnUserBuild,
-    kFailedToInitState,
-    kInvalidState,
-    kHitUploadLimit,
   };
 
   RateLimiter();
   virtual ~RateLimiter();
 
   ShouldTraceResponse ShouldTrace(const Args& args);
-  bool OnTraceDone(const Args& args, bool success, uint64_t bytes);
-
-  bool ClearState();
-
-  // virtual for testing.
-  virtual bool LoadState(gen::PerfettoCmdState* state);
-
-  // virtual for testing.
-  virtual bool SaveState(const gen::PerfettoCmdState& state);
-
-  bool StateFileExists();
-  virtual std::string GetStateFilePath() const;
-
- private:
-  gen::PerfettoCmdState state_{};
 };
 
 }  // namespace perfetto
diff --git a/src/perfetto_cmd/rate_limiter_unittest.cc b/src/perfetto_cmd/rate_limiter_unittest.cc
index 38b2822..59045b0 100644
--- a/src/perfetto_cmd/rate_limiter_unittest.cc
+++ b/src/perfetto_cmd/rate_limiter_unittest.cc
@@ -16,13 +16,6 @@
 
 #include "src/perfetto_cmd/rate_limiter.h"
 
-#include <stdio.h>
-
-#include "perfetto/ext/base/file_utils.h"
-#include "perfetto/ext/base/scoped_file.h"
-#include "perfetto/ext/base/temp_file.h"
-#include "perfetto/ext/base/utils.h"
-
 #include "test/gtest_and_gmock.h"
 
 using testing::_;
@@ -33,475 +26,27 @@
 using testing::StrictMock;
 
 namespace perfetto {
-
 namespace {
 
-class MockRateLimiter : public RateLimiter {
- public:
-  MockRateLimiter() : dir_(base::TempDir::Create()) {
-    ON_CALL(*this, LoadState(_))
-        .WillByDefault(Invoke(this, &MockRateLimiter::LoadStateConcrete));
-    ON_CALL(*this, SaveState(_))
-        .WillByDefault(Invoke(this, &MockRateLimiter::SaveStateConcrete));
-  }
-
-  virtual std::string GetStateFilePath() const {
-    return std::string(dir_.path()) + "/.guardraildata";
-  }
-
-  virtual ~MockRateLimiter() override {
-    if (StateFileExists())
-      remove(GetStateFilePath().c_str());
-  }
-
-  bool LoadStateConcrete(gen::PerfettoCmdState* state) {
-    return RateLimiter::LoadState(state);
-  }
-
-  bool SaveStateConcrete(const gen::PerfettoCmdState& state) {
-    return RateLimiter::SaveState(state);
-  }
-
-  MOCK_METHOD(bool, LoadState, (gen::PerfettoCmdState*), (override));
-  MOCK_METHOD(bool, SaveState, (const gen::PerfettoCmdState&), (override));
-
- private:
-  base::TempDir dir_;
-};
-
-void WriteGarbageToFile(const std::string& path) {
-  base::ScopedFile fd(base::OpenFile(path, O_WRONLY | O_CREAT, 0600));
-  constexpr char data[] = "Some random bytes.";
-  if (base::WriteAll(fd.get(), data, sizeof(data)) != sizeof(data))
-    ADD_FAILURE() << "Could not write garbage";
-}
-
-TEST(RateLimiterTest, RoundTripState) {
-  NiceMock<MockRateLimiter> limiter;
-
-  gen::PerfettoCmdState input{};
-  gen::PerfettoCmdState output{};
-
-  input.set_total_bytes_uploaded(42);
-  ASSERT_TRUE(limiter.SaveState(input));
-  ASSERT_TRUE(limiter.LoadState(&output));
-  ASSERT_EQ(output.total_bytes_uploaded(), 42u);
-  ASSERT_EQ(output.session_state_size(), 0);
-}
-
-TEST(RateLimiterTest, FileIsSensiblyTruncated) {
-  NiceMock<MockRateLimiter> limiter;
-
-  gen::PerfettoCmdState input{};
-  gen::PerfettoCmdState output{};
-
-  input.set_total_bytes_uploaded(42);
-  input.set_first_trace_timestamp(1);
-  input.set_last_trace_timestamp(2);
-
-  for (size_t i = 0; i < 100; ++i) {
-    auto* session = input.add_session_state();
-    session->set_session_name("session_" + std::to_string(i));
-    session->set_total_bytes_uploaded(i * 100);
-    session->set_last_trace_timestamp(i);
-  }
-
-  ASSERT_TRUE(limiter.SaveState(input));
-  ASSERT_TRUE(limiter.LoadState(&output));
-
-  ASSERT_EQ(output.total_bytes_uploaded(), 42u);
-  ASSERT_EQ(output.first_trace_timestamp(), 1u);
-  ASSERT_EQ(output.last_trace_timestamp(), 2u);
-  ASSERT_LE(output.session_state_size(), 50);
-  ASSERT_GE(output.session_state_size(), 5);
-
-  {
-    gen::PerfettoCmdState::PerSessionState session;
-    session.set_session_name("session_99");
-    session.set_total_bytes_uploaded(99 * 100);
-    session.set_last_trace_timestamp(99);
-    ASSERT_THAT(output.session_state(), Contains(session));
-  }
-}
-
-TEST(RateLimiterTest, LoadFromEmpty) {
-  NiceMock<MockRateLimiter> limiter;
-
-  gen::PerfettoCmdState input{};
-  input.set_total_bytes_uploaded(0);
-  input.set_last_trace_timestamp(0);
-  input.set_first_trace_timestamp(0);
-  gen::PerfettoCmdState output{};
-
-  ASSERT_TRUE(limiter.SaveState(input));
-  ASSERT_TRUE(limiter.LoadState(&output));
-  ASSERT_EQ(output.total_bytes_uploaded(), 0u);
-}
-
-TEST(RateLimiterTest, LoadFromNoFileFails) {
-  NiceMock<MockRateLimiter> limiter;
-  gen::PerfettoCmdState output{};
-  ASSERT_FALSE(limiter.LoadState(&output));
-  ASSERT_EQ(output.total_bytes_uploaded(), 0u);
-}
-
-TEST(RateLimiterTest, LoadFromGarbageFails) {
-  NiceMock<MockRateLimiter> limiter;
-
-  WriteGarbageToFile(limiter.GetStateFilePath().c_str());
-
-  gen::PerfettoCmdState output{};
-  ASSERT_FALSE(limiter.LoadState(&output));
-  ASSERT_EQ(output.total_bytes_uploaded(), 0u);
-}
-
-TEST(RateLimiterTest, NotDropBox) {
-  StrictMock<MockRateLimiter> limiter;
-
-  ASSERT_EQ(limiter.ShouldTrace({}), RateLimiter::kOkToTrace);
-  ASSERT_TRUE(limiter.OnTraceDone({}, true, 10000));
-  ASSERT_FALSE(limiter.StateFileExists());
-}
-
-TEST(RateLimiterTest, NotDropBox_FailedToTrace) {
-  StrictMock<MockRateLimiter> limiter;
-
-  ASSERT_FALSE(limiter.OnTraceDone({}, false, 0));
-  ASSERT_FALSE(limiter.StateFileExists());
-}
-
-TEST(RateLimiterTest, DropBox_IgnoreGuardrails) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-  args.ignore_guardrails = true;
-  args.current_time = base::TimeSeconds(41);
-
-  EXPECT_CALL(limiter, SaveState(_));
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
-
-  EXPECT_CALL(limiter, SaveState(_));
-  ASSERT_TRUE(limiter.OnTraceDone(args, true, 42u));
-
-  gen::PerfettoCmdState output{};
-  ASSERT_TRUE(limiter.LoadStateConcrete(&output));
-  ASSERT_EQ(output.first_trace_timestamp(), 41u);
-  ASSERT_EQ(output.last_trace_timestamp(), 41u);
-  ASSERT_EQ(output.total_bytes_uploaded(), 42u);
-}
-
-TEST(RateLimiterTest, DropBox_EmptyState) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-  args.current_time = base::TimeSeconds(10000);
-
-  EXPECT_CALL(limiter, SaveState(_));
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
-
-  EXPECT_CALL(limiter, SaveState(_));
-  ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024));
-
-  gen::PerfettoCmdState output{};
-  ASSERT_TRUE(limiter.LoadStateConcrete(&output));
-  EXPECT_EQ(output.total_bytes_uploaded(), 1024u * 1024u);
-  EXPECT_EQ(output.first_trace_timestamp(), 10000u);
-  EXPECT_EQ(output.last_trace_timestamp(), 10000u);
-}
-
-TEST(RateLimiterTest, DropBox_NormalUpload) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  gen::PerfettoCmdState input{};
-  input.set_first_trace_timestamp(10000);
-  input.set_last_trace_timestamp(10000 + 60 * 10);
-  input.set_total_bytes_uploaded(1024 * 1024 * 2);
-  ASSERT_TRUE(limiter.SaveStateConcrete(input));
-
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-  args.current_time = base::TimeSeconds(input.last_trace_timestamp() + 60 * 10);
-
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
-
-  EXPECT_CALL(limiter, SaveState(_));
-  ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024));
-
-  gen::PerfettoCmdState output{};
-  ASSERT_TRUE(limiter.LoadStateConcrete(&output));
-  EXPECT_EQ(output.total_bytes_uploaded(), 1024u * 1024u * 3);
-  EXPECT_EQ(output.first_trace_timestamp(), input.first_trace_timestamp());
-  EXPECT_EQ(output.last_trace_timestamp(),
-            static_cast<uint64_t>(args.current_time.count()));
-}
-
-TEST(RateLimiterTest, DropBox_NormalUploadWithSessionName) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  gen::PerfettoCmdState input{};
-  input.set_first_trace_timestamp(10000);
-  input.set_last_trace_timestamp(10000 + 60 * 10);
-  input.set_total_bytes_uploaded(1024 * 1024 * 2);
-  ASSERT_TRUE(limiter.SaveStateConcrete(input));
-
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-  args.unique_session_name = "foo";
-  args.current_time = base::TimeSeconds(input.last_trace_timestamp() + 60 * 10);
-
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
-
-  EXPECT_CALL(limiter, SaveState(_));
-  ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024));
-
-  gen::PerfettoCmdState output{};
-  ASSERT_TRUE(limiter.LoadStateConcrete(&output));
-  EXPECT_EQ(output.total_bytes_uploaded(), 1024u * 1024u * 2);
-  EXPECT_EQ(output.first_trace_timestamp(), input.first_trace_timestamp());
-  EXPECT_EQ(output.last_trace_timestamp(),
-            static_cast<uint64_t>(args.current_time.count()));
-  ASSERT_GE(output.session_state_size(), 1);
-
-  {
-    gen::PerfettoCmdState::PerSessionState session;
-    session.set_session_name("foo");
-    session.set_total_bytes_uploaded(1024 * 1024);
-    session.set_last_trace_timestamp(
-        static_cast<uint64_t>(args.current_time.count()));
-    ASSERT_THAT(output.session_state(), Contains(session));
-  }
-}
-
-TEST(RateLimiterTest, DropBox_FailedToLoadState) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-
-  WriteGarbageToFile(limiter.GetStateFilePath().c_str());
-
-  EXPECT_CALL(limiter, LoadState(_));
-  EXPECT_CALL(limiter, SaveState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kInvalidState);
-
-  gen::PerfettoCmdState output{};
-  ASSERT_TRUE(limiter.LoadStateConcrete(&output));
-  EXPECT_EQ(output.total_bytes_uploaded(), 0u);
-  EXPECT_EQ(output.first_trace_timestamp(), 0u);
-  EXPECT_EQ(output.last_trace_timestamp(), 0u);
-}
-
-TEST(RateLimiterTest, DropBox_NoTimeTravel) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  gen::PerfettoCmdState input{};
-  input.set_first_trace_timestamp(100);
-  input.set_last_trace_timestamp(100);
-  ASSERT_TRUE(limiter.SaveStateConcrete(input));
-
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-  args.current_time = base::TimeSeconds(99);
-
-  EXPECT_CALL(limiter, LoadState(_));
-  EXPECT_CALL(limiter, SaveState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kInvalidState);
-
-  gen::PerfettoCmdState output{};
-  ASSERT_TRUE(limiter.LoadStateConcrete(&output));
-  EXPECT_EQ(output.total_bytes_uploaded(), 0u);
-  EXPECT_EQ(output.first_trace_timestamp(), 0u);
-  EXPECT_EQ(output.last_trace_timestamp(), 0u);
-}
-
-TEST(RateLimiterTest, DropBox_TooMuch_OtherSession) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  gen::PerfettoCmdState input{};
-  auto* session = input.add_session_state();
-  session->set_session_name("foo");
-  session->set_total_bytes_uploaded(100 * 1024 * 1024);
-
-  ASSERT_TRUE(limiter.SaveStateConcrete(input));
-
-  args.is_user_build = true;
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-  args.unique_session_name = "bar";
-  args.current_time = base::TimeSeconds(60 * 60);
-
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
-}
-
-TEST(RateLimiterTest, DropBox_TooMuch_Session) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  gen::PerfettoCmdState input{};
-  auto* session = input.add_session_state();
-  session->set_session_name("foo");
-  session->set_total_bytes_uploaded(100 * 1024 * 1024);
-
-  ASSERT_TRUE(limiter.SaveStateConcrete(input));
-
-  args.is_user_build = true;
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-  args.unique_session_name = "foo";
-  args.current_time = base::TimeSeconds(60 * 60);
-
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kHitUploadLimit);
-}
-
-TEST(RateLimiterTest, DropBox_TooMuch_User) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  gen::PerfettoCmdState input{};
-  input.set_total_bytes_uploaded(10 * 1024 * 1024 + 1);
-  ASSERT_TRUE(limiter.SaveStateConcrete(input));
-
-  args.is_user_build = true;
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-  args.current_time = base::TimeSeconds(60 * 60);
-
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kHitUploadLimit);
-}
-
-TEST(RateLimiterTest, DropBox_TooMuch_Override) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  gen::PerfettoCmdState input{};
-  auto* session = input.add_session_state();
-  session->set_session_name("foo");
-  session->set_total_bytes_uploaded(10 * 1024 * 1024 + 1);
-  ASSERT_TRUE(limiter.SaveStateConcrete(input));
-
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-  args.current_time = base::TimeSeconds(60 * 60);
-  args.max_upload_bytes_override = 10 * 1024 * 1024 + 2;
-  args.unique_session_name = "foo";
-
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
-}
-
-// Override doesn't apply to traces without session name.
-TEST(RateLimiterTest, DropBox_OverrideOnEmptySesssionName) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  gen::PerfettoCmdState input{};
-  input.set_total_bytes_uploaded(10 * 1024 * 1024 + 1);
-  ASSERT_TRUE(limiter.SaveStateConcrete(input));
-
-  args.allow_user_build_tracing = true;
-  args.is_uploading = true;
-  args.current_time = base::TimeSeconds(60 * 60);
-  args.max_upload_bytes_override = 10 * 1024 * 1024 + 2;
-
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kHitUploadLimit);
-}
-
-TEST(RateLimiterTest, DropBox_TooMuchWasUploaded) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  gen::PerfettoCmdState input{};
-  input.set_first_trace_timestamp(1);
-  input.set_last_trace_timestamp(1);
-  input.set_total_bytes_uploaded(10 * 1024 * 1024 + 1);
-  ASSERT_TRUE(limiter.SaveStateConcrete(input));
-
-  args.is_uploading = true;
-  args.current_time = base::TimeSeconds(60 * 60 * 24 + 2);
-
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
-
-  EXPECT_CALL(limiter, SaveState(_));
-  ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024));
-
-  gen::PerfettoCmdState output{};
-  ASSERT_TRUE(limiter.LoadStateConcrete(&output));
-  EXPECT_EQ(output.total_bytes_uploaded(), 1024u * 1024u);
-  EXPECT_EQ(output.first_trace_timestamp(),
-            static_cast<uint64_t>(args.current_time.count()));
-  EXPECT_EQ(output.last_trace_timestamp(),
-            static_cast<uint64_t>(args.current_time.count()));
-}
-
-TEST(RateLimiterTest, DropBox_FailedToUpload) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  args.is_uploading = true;
-  args.current_time = base::TimeSeconds(10000);
-
-  EXPECT_CALL(limiter, SaveState(_));
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
-  ASSERT_FALSE(limiter.OnTraceDone(args, false, 1024 * 1024));
-}
-
-TEST(RateLimiterTest, DropBox_FailedToSave) {
-  StrictMock<MockRateLimiter> limiter;
-  RateLimiter::Args args;
-
-  args.is_uploading = true;
-  args.current_time = base::TimeSeconds(10000);
-
-  EXPECT_CALL(limiter, SaveState(_));
-  EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
-
-  EXPECT_CALL(limiter, SaveState(_)).WillOnce(Return(false));
-  ASSERT_FALSE(limiter.OnTraceDone(args, true, 1024 * 1024));
-}
-
-TEST(RateLimiterTest, DropBox_CantTraceOnUser) {
-  StrictMock<MockRateLimiter> limiter;
+TEST(RateLimiterTest, CantTraceOnUser) {
+  RateLimiter limiter;
   RateLimiter::Args args;
 
   args.is_user_build = true;
   args.allow_user_build_tracing = false;
   args.is_uploading = true;
-  args.current_time = base::TimeSeconds(10000);
 
   ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kNotAllowedOnUserBuild);
 }
 
-TEST(RateLimiterTest, DropBox_CanTraceOnUser) {
-  StrictMock<MockRateLimiter> limiter;
+TEST(RateLimiterTest, CanTraceOnUser) {
+  RateLimiter limiter;
   RateLimiter::Args args;
 
   args.is_user_build = false;
   args.allow_user_build_tracing = false;
   args.is_uploading = true;
-  args.current_time = base::TimeSeconds(10000);
 
-  EXPECT_CALL(limiter, SaveState(_));
-  EXPECT_CALL(limiter, LoadState(_));
   ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
 }
 
diff --git a/src/profiling/symbolizer/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
index eee5076..5efb347 100644
--- a/src/profiling/symbolizer/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -19,6 +19,7 @@
 #include <fcntl.h>
 
 #include <cinttypes>
+#include <limits>
 #include <memory>
 #include <optional>
 #include <sstream>
@@ -352,18 +353,22 @@
     return std::nullopt;
   }
   // Openfile opens the file with an exclusive lock on windows.
-  std::optional<size_t> size = base::GetFileSize(symbol_file);
-  if (!size.has_value()) {
+  std::optional<uint64_t> file_size = base::GetFileSize(symbol_file);
+  if (!file_size.has_value()) {
     PERFETTO_PLOG("Failed to get file size %s", symbol_file.c_str());
     return std::nullopt;
   }
 
-  if (*size == 0) {
+  static_assert(sizeof(size_t) <= sizeof(uint64_t));
+  size_t size = static_cast<size_t>(
+      std::min<uint64_t>(std::numeric_limits<size_t>::max(), *file_size));
+
+  if (size == 0) {
     return std::nullopt;
   }
 
   std::optional<BuildIdAndLoadBias> build_id_and_load_bias =
-      GetBuildIdAndLoadBias(symbol_file.c_str(), *size);
+      GetBuildIdAndLoadBias(symbol_file.c_str(), size);
   if (!build_id_and_load_bias)
     return std::nullopt;
   if (build_id_and_load_bias->build_id != build_id) {
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index 269b7b9..b4c80bc 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -483,3 +483,4 @@
 binder/binder_return
 perf_trace_counters/sched_switch_with_ctrs
 power/gpu_work_period
+rpm/rpm_status
diff --git a/src/tools/proto_filter/proto_filter.cc b/src/tools/proto_filter/proto_filter.cc
index 0ccdecc..b3a9572 100644
--- a/src/tools/proto_filter/proto_filter.cc
+++ b/src/tools/proto_filter/proto_filter.cc
@@ -56,7 +56,7 @@
   proto_filter -r perfetto.protos.Trace -s protos/perfetto/trace/trace.proto \
                -F /tmp/bytecode [--dedupe] \
                [-x protos.Message:message_field_to_pass] \
-               [-r protos.Message:string_field_to_filter]
+               [-g protos.Message:string_field_to_filter]
 
 # List the used/filtered fields from a trace file
 
@@ -77,6 +77,7 @@
 # Show which fields are allowed by a filter bytecode
 
   proto_filter -r perfetto.protos.Trace -s protos/perfetto/trace/trace.proto \
+               [-g protos.Message:string_field_to_filter] \
                -f /tmp/bytecode
 )";
 
diff --git a/src/trace_processor/containers/bit_vector.cc b/src/trace_processor/containers/bit_vector.cc
index 7a3dfd1..2c3d218 100644
--- a/src/trace_processor/containers/bit_vector.cc
+++ b/src/trace_processor/containers/bit_vector.cc
@@ -390,12 +390,15 @@
     // previous word), add them into the new out_word. Important: we *must* not
     // change out_word if there was no spillover as |out_word| could be pointing
     // to |data + 1| which needs to be preserved for the next loop iteration.
-    *out_word = spillover ? ext >> (popcount - out_word_bits) : *out_word;
+    if (spillover) {
+      *out_word = ext >> (popcount - out_word_bits);
+    }
   }
 
   // Loop post-condition: we must have written as many words as is required
   // to store |set_bits_in_mask|.
-  PERFETTO_DCHECK(out_word - words_.data() <= WordCount(set_bits_in_mask));
+  PERFETTO_DCHECK(static_cast<uint32_t>(out_word - words_.data()) <=
+                  WordCount(set_bits_in_mask));
 
   // Resize the BitVector to equal to the number of elements in the  mask we
   // calculated at the start of the loop.
diff --git a/src/trace_processor/containers/bit_vector_unittest.cc b/src/trace_processor/containers/bit_vector_unittest.cc
index 6c1a01e..d6487bf 100644
--- a/src/trace_processor/containers/bit_vector_unittest.cc
+++ b/src/trace_processor/containers/bit_vector_unittest.cc
@@ -410,6 +410,23 @@
   ASSERT_EQ(expected.CountSetBits(), bv.CountSetBits());
 }
 
+TEST(BitVectorUnittest, SelectBitsOob) {
+  BitVector bv = BitVector::RangeForTesting(
+      0, 512, [](uint32_t idx) { return idx % 7 == 0; });
+  BitVector mask = BitVector(512, true);
+  bv.SelectBits(mask);
+
+  BitVector expected = BitVector::RangeForTesting(
+      0, 512, [](uint32_t idx) { return idx % 7 == 0; });
+
+  ASSERT_EQ(bv.size(), 512u);
+  for (uint32_t i = 0; i < expected.size(); ++i) {
+    ASSERT_EQ(expected.IsSet(i), bv.IsSet(i)) << "Index " << i;
+    ASSERT_EQ(expected.CountSetBits(i), bv.CountSetBits(i)) << "Index " << i;
+  }
+  ASSERT_EQ(expected.CountSetBits(), bv.CountSetBits());
+}
+
 TEST(BitVectorUnittest, IntersectRange) {
   BitVector bv =
       BitVector::RangeForTesting(1, 20, [](uint32_t t) { return t % 2 == 0; });
diff --git a/src/trace_processor/db/column/string_storage.cc b/src/trace_processor/db/column/string_storage.cc
index 02c8696..9ab8c1f 100644
--- a/src/trace_processor/db/column/string_storage.cc
+++ b/src/trace_processor/db/column/string_storage.cc
@@ -145,12 +145,8 @@
                              const StringPool::Id* data,
                              NullTermStringView val,
                              Range search_range) {
-  Less comp{pool};
-  const auto* lower =
-      std::lower_bound(data + search_range.start, data + search_range.end, val,
-                       [comp](StringPool::Id id, NullTermStringView val) {
-                         return comp(id, val);
-                       });
+  const auto* lower = std::lower_bound(
+      data + search_range.start, data + search_range.end, val, Less{pool});
   return static_cast<uint32_t>(std::distance(data, lower));
 }
 
@@ -171,10 +167,11 @@
                              const StringPool::Id* data,
                              NullTermStringView val,
                              const uint32_t* indices,
-                             uint32_t indices_count) {
+                             uint32_t indices_count,
+                             uint32_t offset) {
   Less comp{pool};
   const auto* lower =
-      std::lower_bound(indices, indices + indices_count, val,
+      std::lower_bound(indices + offset, indices + indices_count, val,
                        [comp, data](uint32_t index, NullTermStringView val) {
                          return comp(data[index], val);
                        });
@@ -185,10 +182,11 @@
                              const StringPool::Id* data,
                              NullTermStringView val,
                              const uint32_t* indices,
-                             uint32_t indices_count) {
+                             uint32_t indices_count,
+                             uint32_t offset) {
   Greater comp{pool};
   const auto* upper =
-      std::upper_bound(indices, indices + indices_count, val,
+      std::upper_bound(indices + offset, indices + indices_count, val,
                        [comp, data](NullTermStringView val, uint32_t index) {
                          return comp(data[index], val);
                        });
@@ -320,16 +318,35 @@
       case FilterOp::kGe:
       case FilterOp::kGt:
       case FilterOp::kLe:
-      case FilterOp::kLt:
-        return RangeOrBitVector(
-            BinarySearchIntrinsic(op, sql_val, search_range));
+      case FilterOp::kLt: {
+        auto first_non_null = static_cast<uint32_t>(std::distance(
+            data_->begin(),
+            std::partition_point(data_->begin() + search_range.start,
+                                 data_->begin() + search_range.end,
+                                 [](StringPool::Id id) {
+                                   return id == StringPool::Id::Null();
+                                 })));
+        return RangeOrBitVector(BinarySearchIntrinsic(
+            op, sql_val,
+            {std::max(search_range.start, first_non_null), search_range.end}));
+      }
       case FilterOp::kNe: {
         // Not equal is a special operation on binary search, as it doesn't
         // define a range, and rather just `not` range returned with `equal`
-        // operation.
-        Range r = BinarySearchIntrinsic(FilterOp::kEq, sql_val, search_range);
-        BitVector bv(r.start, true);
-        bv.Resize(r.end);
+        // operation on non null values.
+        auto first_non_null = static_cast<uint32_t>(std::distance(
+            data_->begin(),
+            std::partition_point(data_->begin() + search_range.start,
+                                 data_->begin() + search_range.end,
+                                 [](StringPool::Id id) {
+                                   return id == StringPool::Id::Null();
+                                 })));
+        Range ret = BinarySearchIntrinsic(
+            FilterOp::kEq, sql_val,
+            {std::max(search_range.start, first_non_null), search_range.end});
+        BitVector bv(first_non_null, false);
+        bv.Resize(ret.start, true);
+        bv.Resize(ret.end, false);
         bv.Resize(search_range.end, true);
         return RangeOrBitVector(std::move(bv));
       }
@@ -511,46 +528,42 @@
           : string_pool_->InternString(base::StringView(sql_val.AsString()));
   NullTermStringView val_str = string_pool_->Get(val);
 
+  auto first_non_null = static_cast<uint32_t>(std::distance(
+      indices.data,
+      std::partition_point(indices.data, indices.data + indices.size,
+                           [this](uint32_t i) {
+                             return (*data_)[i] == StringPool::Id::Null();
+                           })));
+
   switch (op) {
     case FilterOp::kEq:
       return {LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size),
+                                  indices.data, indices.size, first_non_null),
               UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size)};
+                                  indices.data, indices.size, first_non_null)};
     case FilterOp::kLe:
-      return {0, UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                     indices.data, indices.size)};
+      return {first_non_null,
+              UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
+                                  indices.data, indices.size, first_non_null)};
     case FilterOp::kLt:
-      return {0, LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                     indices.data, indices.size)};
+      return {first_non_null,
+              LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
+                                  indices.data, indices.size, first_non_null)};
     case FilterOp::kGe:
       return {LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size),
+                                  indices.data, indices.size, first_non_null),
               indices.size};
     case FilterOp::kGt:
       return {UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size),
+                                  indices.data, indices.size, first_non_null),
               indices.size};
     case FilterOp::kIsNull: {
       // Assuming nulls are at the front.
-      IsNull comp;
-      const auto* first_non_null = std::partition_point(
-          indices.data, indices.data + indices.size, [comp, this](uint32_t i) {
-            return comp((*data_)[i], StringPool::Id::Null());
-          });
-      return Range(0, static_cast<uint32_t>(
-                          std::distance(indices.data, first_non_null)));
+      return Range(0, first_non_null);
     }
     case FilterOp::kIsNotNull: {
       // Assuming nulls are at the front.
-      IsNull comp;
-      const auto* first_non_null = std::partition_point(
-          indices.data, indices.data + indices.size, [comp, this](uint32_t i) {
-            return comp((*data_)[i], StringPool::Id::Null());
-          });
-      return Range(
-          static_cast<uint32_t>(std::distance(indices.data, first_non_null)),
-          indices.size);
+      return Range(first_non_null, indices.size);
     }
 
     case FilterOp::kNe:
@@ -607,20 +620,51 @@
                                           SortToken* end,
                                           SortDirection direction) const {
   switch (direction) {
-    case SortDirection::kAscending:
+    case SortDirection::kAscending: {
       std::stable_sort(start, end,
-                       [this](const SortToken& a, const SortToken& b) {
-                         return string_pool_->Get((*data_)[a.index]) <
-                                string_pool_->Get((*data_)[b.index]);
+                       [this](const SortToken& lhs, const SortToken& rhs) {
+                         // If RHS is NULL, we know that LHS is not less than
+                         // NULL, as nothing is less then null. This check is
+                         // only required to keep the stability of the sort.
+                         if ((*data_)[rhs.index] == StringPool::Id::Null()) {
+                           return false;
+                         }
+
+                         // If LHS is NULL, it will always be smaller than any
+                         // RHS value.
+                         if ((*data_)[lhs.index] == StringPool::Id::Null()) {
+                           return true;
+                         }
+
+                         // If neither LHS or RHS are NULL, we have to simply
+                         // check which string is smaller.
+                         return string_pool_->Get((*data_)[lhs.index]) <
+                                string_pool_->Get((*data_)[rhs.index]);
                        });
       return;
-    case SortDirection::kDescending:
+    }
+    case SortDirection::kDescending: {
       std::stable_sort(start, end,
-                       [this](const SortToken& a, const SortToken& b) {
-                         return string_pool_->Get((*data_)[a.index]) >
-                                string_pool_->Get((*data_)[b.index]);
+                       [this](const SortToken& lhs, const SortToken& rhs) {
+                         // If LHS is NULL, we know that it's not greater than
+                         // any RHS. This check is only required to keep the
+                         // stability of the sort.
+                         if ((*data_)[lhs.index] == StringPool::Id::Null()) {
+                           return false;
+                         }
+
+                         // If RHS is NULL, everything will be greater from it.
+                         if ((*data_)[rhs.index] == StringPool::Id::Null()) {
+                           return true;
+                         }
+
+                         // If neither LHS or RHS are NULL, we have to simply
+                         // check which string is smaller.
+                         return string_pool_->Get((*data_)[lhs.index]) >
+                                string_pool_->Get((*data_)[rhs.index]);
                        });
       return;
+    }
   }
   PERFETTO_FATAL("For GCC");
 }
diff --git a/src/trace_processor/db/column/string_storage_unittest.cc b/src/trace_processor/db/column/string_storage_unittest.cc
index 5312404..4e06185 100644
--- a/src/trace_processor/db/column/string_storage_unittest.cc
+++ b/src/trace_processor/db/column/string_storage_unittest.cc
@@ -248,10 +248,38 @@
 }
 #endif
 
+TEST(StringStorage, SearchEmptyString) {
+  std::vector<std::string> strings{"", "apple"};
+  std::vector<StringPool::Id> ids(3, StringPool::Id::Null());
+  StringPool pool;
+  for (const auto& string : strings) {
+    ids.push_back(pool.InternString(base::StringView(string)));
+  }
+  StringStorage storage(&pool, &ids, true);
+  auto chain = storage.MakeChain();
+
+  auto res = chain->Search(FilterOp::kEq, SqlValue::String(""), {0, 5});
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3));
+}
+
+TEST(StringStorage, SearchEmptyStringIsNull) {
+  std::vector<std::string> strings{"", "apple"};
+  std::vector<StringPool::Id> ids(3, StringPool::Id::Null());
+  StringPool pool;
+  for (const auto& string : strings) {
+    ids.push_back(pool.InternString(base::StringView(string)));
+  }
+  StringStorage storage(&pool, &ids, true);
+  auto chain = storage.MakeChain();
+
+  auto res = chain->Search(FilterOp::kIsNull, SqlValue(), {0, 5});
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(0, 1, 2));
+}
+
 TEST(StringStorage, SearchSorted) {
   std::vector<std::string> strings{"apple",    "burger",   "cheese",
                                    "doughnut", "eggplant", "fries"};
-  std::vector<StringPool::Id> ids;
+  std::vector<StringPool::Id> ids(3, StringPool::Id::Null());
   StringPool pool;
   for (const auto& string : strings) {
     ids.push_back(pool.InternString(base::StringView(string)));
@@ -259,35 +287,36 @@
   StringStorage storage(&pool, &ids, true);
   auto chain = storage.MakeChain();
   SqlValue val = SqlValue::String("cheese");
-  Range filter_range(0, 6);
+  // 1:NULL, 2:NULL, 3:apple, 4:burger, 5:cheese, 6:doughnut, 7:eggplant,
+  Range filter_range(1, 8);
 
   FilterOp op = FilterOp::kEq;
   auto res = chain->Search(op, val, filter_range);
-  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(2));
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(5));
 
   op = FilterOp::kNe;
   res = chain->Search(op, val, filter_range);
-  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(0, 1, 3, 4, 5));
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4, 6, 7));
 
   op = FilterOp::kLt;
   res = chain->Search(op, val, filter_range);
-  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(0, 1));
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4));
 
   op = FilterOp::kLe;
   res = chain->Search(op, val, filter_range);
-  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(0, 1, 2));
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4, 5));
 
   op = FilterOp::kGt;
   res = chain->Search(op, val, filter_range);
-  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4, 5));
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(6, 7));
 
   op = FilterOp::kGe;
   res = chain->Search(op, val, filter_range);
-  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(2, 3, 4, 5));
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(5, 6, 7));
 
   op = FilterOp::kGlob;
   res = chain->Search(op, SqlValue::String("*e"), filter_range);
-  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(0, 2));
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 5));
 }
 
 TEST(StringStorage, IndexSearchSorted) {
@@ -336,7 +365,7 @@
 TEST(StringStorage, OrderedIndexSearch) {
   std::vector<std::string> strings{"cheese",  "pasta", "pizza",
                                    "pierogi", "onion", "fries"};
-  std::vector<StringPool::Id> ids;
+  std::vector<StringPool::Id> ids(1, StringPool::Id::Null());
   StringPool pool;
   for (const auto& string : strings) {
     ids.push_back(pool.InternString(base::StringView(string)));
@@ -344,8 +373,7 @@
   StringStorage storage(&pool, &ids);
   auto chain = storage.MakeChain();
   SqlValue val = SqlValue::String("pierogi");
-  // cheese, fries, onion, pasta, pierogi, pizza
-  std::vector<uint32_t> indices_vec{0, 5, 4, 1, 3, 2};
+  std::vector<uint32_t> indices_vec{0, 6, 5, 2, 4, 3};
   OrderedIndices indices{indices_vec.data(), 6, Indices::State::kNonmonotonic};
 
   FilterOp op = FilterOp::kEq;
@@ -355,12 +383,12 @@
 
   op = FilterOp::kLt;
   res = chain->OrderedIndexSearch(op, val, indices);
-  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.start, 1u);
   ASSERT_EQ(res.end, 4u);
 
   op = FilterOp::kLe;
   res = chain->OrderedIndexSearch(op, val, indices);
-  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.start, 1u);
   ASSERT_EQ(res.end, 5u);
 
   op = FilterOp::kGt;
@@ -372,6 +400,31 @@
   res = chain->OrderedIndexSearch(op, val, indices);
   ASSERT_EQ(res.start, 4u);
   ASSERT_EQ(res.end, 6u);
+
+  op = FilterOp::kIsNull;
+  res = chain->OrderedIndexSearch(op, val, indices);
+  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.end, 1u);
+}
+
+TEST(StringStorage, OrderedIndexSearchLowerBoundWithNulls) {
+  std::vector<std::string> strings{"cheese",  "pasta", "pizza",
+                                   "pierogi", "onion", "fries"};
+  std::vector<StringPool::Id> ids(3, StringPool::Id::Null());
+  StringPool pool;
+  for (const auto& string : strings) {
+    ids.push_back(pool.InternString(base::StringView(string)));
+  }
+  StringStorage storage(&pool, &ids);
+  auto chain = storage.MakeChain();
+
+  // NULL, NULL, cheese, pizza
+  std::vector<uint32_t> indices_vec{0, 2, 3, 7};
+  OrderedIndices indices{indices_vec.data(), 4, Indices::State::kNonmonotonic};
+  auto res = chain->OrderedIndexSearch(FilterOp::kEq,
+                                       SqlValue::String("cheese"), indices);
+  ASSERT_EQ(res.start, 2u);
+  ASSERT_EQ(res.end, 3u);
 }
 
 TEST(StringStorage, OrderedIndexSearchIsNull) {
diff --git a/src/trace_processor/db/runtime_table.cc b/src/trace_processor/db/runtime_table.cc
index 0de15c3..321cff3 100644
--- a/src/trace_processor/db/runtime_table.cc
+++ b/src/trace_processor/db/runtime_table.cc
@@ -94,13 +94,20 @@
   // The special treatement for Id columns makes no sense for empty or
   // single element indices. Those should be treated as standard int
   // column.
-  // We are checking if the first value is not too big - we will later
-  // create a BitVector which will skip all of the bits until the front
-  // of the index vector, so we are creating a cutoff to prevent
-  // unreasonable memory usage.
-  bool is_id = is_monotonic && values.size() > 1 && values.front() < 1 << 20 &&
-               values.front() >= std::numeric_limits<uint32_t>::min() &&
-               values.back() < std::numeric_limits<uint32_t>::max();
+
+  // We expect id column to:
+  // - be strictly monotonic.
+  bool is_id = is_monotonic;
+  // - have more than 1 element.
+  is_id = is_id && values.size() > 1;
+  // - have first elements smaller then 2^20, mostly to prevent timestamps
+  // columns from becoming Id columns.
+  is_id = is_id && values.front() < 1 << 20;
+  // - have `uint32_t` values.
+  is_id = is_id && values.front() >= std::numeric_limits<uint32_t>::min() &&
+          values.back() < std::numeric_limits<uint32_t>::max();
+  // - have on average more than 1 set bit per int64_t (over 1/64 density)
+  is_id = is_id && static_cast<uint32_t>(values.back()) < 64 * values.size();
 
   if (is_id) {
     // The column is an Id column.
diff --git a/src/trace_processor/importers/common/system_info_tracker.cc b/src/trace_processor/importers/common/system_info_tracker.cc
index 89f0baf..3bbed5b 100644
--- a/src/trace_processor/importers/common/system_info_tracker.cc
+++ b/src/trace_processor/importers/common/system_info_tracker.cc
@@ -20,7 +20,7 @@
 namespace perfetto {
 namespace trace_processor {
 
-SystemInfoTracker::SystemInfoTracker() {}
+SystemInfoTracker::SystemInfoTracker() = default;
 SystemInfoTracker::~SystemInfoTracker() = default;
 
 void SystemInfoTracker::SetKernelVersion(base::StringView name,
diff --git a/src/trace_processor/importers/common/system_info_tracker.h b/src/trace_processor/importers/common/system_info_tracker.h
index d9544d1..ec46e80 100644
--- a/src/trace_processor/importers/common/system_info_tracker.h
+++ b/src/trace_processor/importers/common/system_info_tracker.h
@@ -42,13 +42,16 @@
   }
 
   void SetKernelVersion(base::StringView name, base::StringView release);
+  void SetNumCpus(uint32_t num_cpus) { num_cpus_ = num_cpus; }
 
-  std::optional<VersionNumber> GetKernelVersion() { return version_; }
+  std::optional<VersionNumber> GetKernelVersion() const { return version_; }
+  std::optional<uint32_t> GetNumCpus() const { return num_cpus_; }
 
  private:
   explicit SystemInfoTracker();
 
   std::optional<VersionNumber> version_;
+  std::optional<uint32_t> num_cpus_;
 };
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 9db1fa7..640647b 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -390,6 +390,19 @@
   return track;
 }
 
+TrackId TrackTracker::InternLinuxDeviceTrack(StringId name) {
+  if (auto it = linux_device_tracks_.find(name);
+      it != linux_device_tracks_.end()) {
+    return it->second;
+  }
+
+  tables::LinuxDeviceTrackTable::Row row(name);
+  TrackId track =
+      context_->storage->mutable_linux_device_track_table()->Insert(row).id;
+  linux_device_tracks_[name] = track;
+  return track;
+}
+
 TrackId TrackTracker::CreateGpuCounterTrack(StringId name,
                                             uint32_t gpu_id,
                                             StringId description,
diff --git a/src/trace_processor/importers/common/track_tracker.h b/src/trace_processor/importers/common/track_tracker.h
index 0783081..a7d481a 100644
--- a/src/trace_processor/importers/common/track_tracker.h
+++ b/src/trace_processor/importers/common/track_tracker.h
@@ -129,6 +129,10 @@
                                          int32_t consumer_id,
                                          int32_t uid);
 
+  // Interns a track associated with a Linux device (where a Linux device
+  // implies a kernel-level device managed by a Linux driver).
+  TrackId InternLinuxDeviceTrack(StringId name);
+
   // Creates a counter track associated with a GPU into the storage.
   TrackId CreateGpuCounterTrack(StringId name,
                                 uint32_t gpu_id,
@@ -216,6 +220,7 @@
   std::map<std::pair<StringId, int32_t>, TrackId> uid_counter_tracks_;
   std::map<std::pair<StringId, int32_t>, TrackId>
       energy_per_uid_counter_tracks_;
+  std::map<StringId, TrackId> linux_device_tracks_;
 
   std::optional<TrackId> chrome_global_instant_track_id_;
   std::optional<TrackId> trigger_track_id_;
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index acef866..a88cc26 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
 namespace trace_processor {
 namespace {
 
-std::array<FtraceMessageDescriptor, 489> descriptors{{
+std::array<FtraceMessageDescriptor, 490> descriptors{{
     {nullptr, 0, {}},
     {nullptr, 0, {}},
     {nullptr, 0, {}},
@@ -5387,6 +5387,15 @@
             {"total_active_duration_ns", ProtoSchemaType::kUint64},
         },
     },
+    {
+        "rpm_status",
+        2,
+        {
+            {},
+            {"name", ProtoSchemaType::kString},
+            {"status", ProtoSchemaType::kInt32},
+        },
+    },
 }};
 
 }  // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 6948fc3..c853c44 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -68,6 +68,7 @@
 #include "protos/perfetto/trace/ftrace/oom.pbzero.h"
 #include "protos/perfetto/trace/ftrace/power.pbzero.h"
 #include "protos/perfetto/trace/ftrace/raw_syscalls.pbzero.h"
+#include "protos/perfetto/trace/ftrace/rpm.pbzero.h"
 #include "protos/perfetto/trace/ftrace/samsung.pbzero.h"
 #include "protos/perfetto/trace/ftrace/sched.pbzero.h"
 #include "protos/perfetto/trace/ftrace/scm.pbzero.h"
@@ -220,6 +221,14 @@
   }
   return buffer;
 }
+
+enum RpmStatus {
+  RPM_INVALID = -1,
+  RPM_ACTIVE = 0,
+  RPM_RESUMING,
+  RPM_SUSPENDED,
+  RPM_SUSPENDING,
+};
 }  // namespace
 FtraceParser::FtraceParser(TraceProcessorContext* context)
     : context_(context),
@@ -326,7 +335,13 @@
       bytes_read_id_end_(context_->storage->InternString("bytes_read_end")),
       android_fs_category_id_(context_->storage->InternString("android_fs")),
       android_fs_data_read_id_(
-          context_->storage->InternString("android_fs_data_read")) {
+          context_->storage->InternString("android_fs_data_read")),
+      runtime_status_invalid_id_(
+          context->storage->InternString("Invalid State")),
+      runtime_status_active_id_(context->storage->InternString("Active")),
+      runtime_status_suspending_id_(
+          context->storage->InternString("Suspending")),
+      runtime_status_resuming_id_(context->storage->InternString("Resuming")) {
   // Build the lookup table for the strings inside ftrace events (e.g. the
   // name of ftrace event fields and the names of their args).
   for (size_t i = 0; i < GetDescriptorsSize(); i++) {
@@ -1099,6 +1114,10 @@
         gpu_work_period_tracker_.ParseGpuWorkPeriodEvent(ts, fld_bytes);
         break;
       }
+      case FtraceEvent::kRpmStatusFieldNumber: {
+        ParseRpmStatus(ts, fld_bytes);
+        break;
+      }
       default:
         break;
     }
@@ -1274,8 +1293,6 @@
       case ProtoSchemaType::kInt64:
       case ProtoSchemaType::kSfixed32:
       case ProtoSchemaType::kSfixed64:
-      case ProtoSchemaType::kSint32:
-      case ProtoSchemaType::kSint64:
       case ProtoSchemaType::kBool:
       case ProtoSchemaType::kEnum: {
         inserter.AddArg(name_id, Variadic::Integer(fld.as_int64()));
@@ -1291,6 +1308,11 @@
         inserter.AddArg(name_id, Variadic::UnsignedInteger(fld.as_uint64()));
         break;
       }
+      case ProtoSchemaType::kSint32:
+      case ProtoSchemaType::kSint64: {
+        inserter.AddArg(name_id, Variadic::Integer(fld.as_sint64()));
+        break;
+      }
       case ProtoSchemaType::kString:
       case ProtoSchemaType::kBytes: {
         StringId value = context_->storage->InternString(fld.as_string());
@@ -3202,6 +3224,56 @@
   inode_offset_thread_map_.Erase(key);
 }
 
+StringId FtraceParser::GetRpmStatusStringId(int32_t rpm_status_val) {
+  // `RPM_SUSPENDED` is omitted from this list as it would never be used as a
+  // slice label.
+  switch (rpm_status_val) {
+    case RPM_INVALID:
+      return runtime_status_invalid_id_;
+    case RPM_SUSPENDING:
+      return runtime_status_suspending_id_;
+    case RPM_RESUMING:
+      return runtime_status_resuming_id_;
+    case RPM_ACTIVE:
+      return runtime_status_active_id_;
+  }
+
+  PERFETTO_DLOG(
+      "Invalid runtime status value obtained from rpm_status ftrace event");
+  return runtime_status_invalid_id_;
+}
+
+void FtraceParser::ParseRpmStatus(int64_t ts, protozero::ConstBytes blob) {
+  protos::pbzero::RpmStatusFtraceEvent::Decoder rpm_event(blob.data, blob.size);
+
+  // Device here refers to anything managed by a Linux kernel driver.
+  std::string device_name = rpm_event.name().ToStdString();
+  int32_t rpm_status = rpm_event.status();
+  StringId device_name_string_id =
+      context_->storage->InternString(device_name.c_str());
+  TrackId track_id =
+      context_->track_tracker->InternLinuxDeviceTrack(device_name_string_id);
+
+  // A `runtime_status` event implies a potential change in state. Hence, if an
+  // active slice exists for this device, end that slice.
+  if (devices_with_active_rpm_slice_.find(device_name) !=
+      devices_with_active_rpm_slice_.end()) {
+    context_->slice_tracker->End(ts, track_id);
+  }
+
+  // To reduce visual clutter, the "SUSPENDED" state will be omitted from the
+  // visualization, as devices typically spend the majority of their time in
+  // this state.
+  if (rpm_status == RPM_SUSPENDED) {
+    devices_with_active_rpm_slice_.erase(device_name);
+    return;
+  }
+
+  context_->slice_tracker->Begin(ts, track_id, /*category=*/kNullStringId,
+                                 /*raw_name=*/GetRpmStatusStringId(rpm_status));
+  devices_with_active_rpm_slice_.insert(device_name);
+}
+
 StringId FtraceParser::InternedKernelSymbolOrFallback(
     uint64_t key,
     PacketSequenceStateGeneration* seq_state) {
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index b3764f1..de0b3bb 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -293,6 +293,8 @@
   void ParseAndroidFsDatareadStart(int64_t ts,
                                    uint32_t pid,
                                    protozero::ConstBytes);
+  StringId GetRpmStatusStringId(int32_t rpm_status_val);
+  void ParseRpmStatus(int64_t ts, protozero::ConstBytes);
 
   TraceProcessorContext* context_;
   RssStatTracker rss_stat_tracker_;
@@ -382,6 +384,10 @@
   const StringId bytes_read_id_end_;
   const StringId android_fs_category_id_;
   const StringId android_fs_data_read_id_;
+  const StringId runtime_status_invalid_id_;
+  const StringId runtime_status_active_id_;
+  const StringId runtime_status_suspending_id_;
+  const StringId runtime_status_resuming_id_;
   std::vector<StringId> syscall_arg_name_ids_;
 
   struct FtraceMessageStrings {
@@ -445,6 +451,10 @@
   // re-emits begin stats on every flush).
   std::unordered_set<uint32_t> seen_errors_for_sequence_id_;
 
+  // Tracks Linux devices with active runtime power management (RPM) status
+  // slices.
+  std::unordered_set<std::string> devices_with_active_rpm_slice_;
+
   struct PairHash {
     std::size_t operator()(const std::pair<uint64_t, int64_t>& p) const {
       base::Hasher hasher;
diff --git a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc
index 0f5943d..6b929c9 100644
--- a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc
@@ -126,11 +126,12 @@
                                   mali_KCPU_CQS_SET_id_, 0);
 }
 
-PERFETTO_NORETURN void MaliGpuEventTracker::ParseMaliKcpuCqsWaitStart(
-    int64_t timestamp,
-    TrackId track_id) {
+void MaliGpuEventTracker::ParseMaliKcpuCqsWaitStart(int64_t timestamp,
+                                                    TrackId track_id) {
   // TODO(b/294866695): Remove
-  PERFETTO_FATAL("This causes incorrectly nested slices at present.");
+  if (base::GetSysPageSize()) {
+    PERFETTO_FATAL("This causes incorrectly nested slices at present.");
+  }
   context_->slice_tracker->Begin(timestamp, track_id, kNullStringId,
                                  mali_KCPU_CQS_WAIT_id_);
 }
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index dd94ffb..72c0f33 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -16,17 +16,30 @@
 
 #include "src/trace_processor/importers/proto/heap_graph_tracker.h"
 
+#include <algorithm>
+#include <array>
+#include <cinttypes>
+#include <cstdint>
+#include <cstring>
+#include <deque>
+#include <map>
+#include <memory>
 #include <optional>
+#include <set>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
 
-#include "perfetto/base/flat_set.h"
-#include "perfetto/ext/base/string_splitter.h"
-#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_view.h"
 #include "protos/perfetto/trace/profiling/heap_graph.pbzero.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
 #include "src/trace_processor/util/profiler_util.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 namespace {
 
@@ -247,7 +260,7 @@
 ObjectTable::RowReference HeapGraphTracker::GetOrInsertObject(
     SequenceState* sequence_state,
     uint64_t object_id) {
-  auto object_table = storage_->mutable_heap_graph_object_table();
+  auto* object_table = storage_->mutable_heap_graph_object_table();
   auto* ptr = sequence_state->object_id_to_db_row.Find(object_id);
   if (!ptr) {
     auto id_and_row = object_table->Insert({sequence_state->current_upid,
@@ -269,7 +282,7 @@
 ClassTable::RowReference HeapGraphTracker::GetOrInsertType(
     SequenceState* sequence_state,
     uint64_t type_id) {
-  auto class_table = storage_->mutable_heap_graph_class_table();
+  auto* class_table = storage_->mutable_heap_graph_class_table();
   auto* ptr = sequence_state->type_id_to_db_row.Find(type_id);
   if (!ptr) {
     auto id_and_row =
@@ -407,7 +420,7 @@
 
   auto it = sequence_state.references_for_field_name_id.find(intern_id);
   if (it != sequence_state.references_for_field_name_id.end()) {
-    auto hgr = storage_->mutable_heap_graph_reference_table();
+    auto* hgr = storage_->mutable_heap_graph_reference_table();
     for (ReferenceTable::RowNumber reference_row_num : it->second) {
       auto row_ref = reference_row_num.ToRowReference(hgr);
       row_ref.set_field_name(field_name);
@@ -754,8 +767,10 @@
   }
 }
 
-base::FlatSet<ObjectTable::Id> HeapGraphTracker::GetChildren(
-    ObjectTable::RowReference object) {
+void HeapGraphTracker::GetChildren(ObjectTable::RowReference object,
+                                   std::vector<ObjectTable::Id>& children) {
+  children.clear();
+
   auto cls_row_ref =
       *storage_->heap_graph_class_table().FindById(object.type_id());
 
@@ -771,7 +786,6 @@
       kind == InternTypeKindString(
                   protos::pbzero::HeapGraphType::KIND_PHANTOM_REFERENCE);
 
-  base::FlatSet<ObjectTable::Id> children;
   ForReferenceSet(
       storage_, object,
       [object, &children, is_ignored_reference,
@@ -786,10 +800,14 @@
           // "java.lang.ref.Reference.referent" field should be ignored.
           return true;
         }
-        children.insert(*opt_owned);
+        children.push_back(*opt_owned);
         return true;
       });
-  return children;
+  std::sort(children.begin(), children.end(),
+            [](const ObjectTable::Id& a, const ObjectTable::Id& b) {
+              return a.value < b.value;
+            });
+  children.erase(std::unique(children.begin(), children.end()), children.end());
 }
 
 size_t HeapGraphTracker::RankRoot(StringId type) {
@@ -813,6 +831,8 @@
   }
   row_ref.set_root_type(type);
 
+  std::vector<ObjectTable::Id> children;
+
   // DFS to mark reachability for all children
   std::vector<ObjectTable::RowReference> stack({row_ref});
   while (!stack.empty()) {
@@ -823,7 +843,8 @@
       continue;
     cur_node.set_reachable(true);
 
-    for (ObjectTable::Id child_node : GetChildren(cur_node)) {
+    GetChildren(cur_node, children);
+    for (ObjectTable::Id child_node : children) {
       auto child_ref =
           *storage_->mutable_heap_graph_object_table()->FindById(child_node);
       stack.push_back(child_ref);
@@ -835,6 +856,8 @@
   // Calculate shortest distance to a GC root.
   std::deque<std::pair<int32_t, ObjectTable::RowReference>> reachable_nodes{
       {0, row_ref}};
+
+  std::vector<ObjectTable::Id> children;
   while (!reachable_nodes.empty()) {
     auto pair = reachable_nodes.front();
 
@@ -846,7 +869,8 @@
     if (cur_distance == -1 || cur_distance > distance) {
       cur_row_ref.set_root_distance(distance);
 
-      for (ObjectTable::Id child_node : GetChildren(cur_row_ref)) {
+      GetChildren(cur_row_ref, children);
+      for (ObjectTable::Id child_node : children) {
         auto child_row_ref =
             *storage_->mutable_heap_graph_object_table()->FindById(child_node);
         int32_t child_distance = child_row_ref.root_distance();
@@ -872,7 +896,6 @@
   };
 
   std::vector<StackElem> stack{{row_ref, PathFromRoot::kRoot, 0, 0, {}}};
-
   while (!stack.empty()) {
     ObjectTable::RowReference object_row_ref = stack.back().node;
 
@@ -915,9 +938,7 @@
       // size to the relevant node in the resulting tree.
       output_tree_node->size += object_row_ref.self_size();
       output_tree_node->count++;
-      base::FlatSet<ObjectTable::Id> children_set = GetChildren(object_row_ref);
-      children.assign(children_set.begin(), children_set.end());
-      PERFETTO_CHECK(children.size() == children_set.size());
+      GetChildren(object_row_ref, children);
 
       if (object_row_ref.native_size()) {
         StringId native_class_name_id = storage_->InternString(
@@ -1106,5 +1127,4 @@
 
 HeapGraphTracker::~HeapGraphTracker() = default;
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.h b/src/trace_processor/importers/proto/heap_graph_tracker.h
index c25d0a1..27bf93e 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.h
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.h
@@ -226,8 +226,8 @@
   // all the other tables have been fully populated.
   void PopulateNativeSize(const SequenceState& seq);
 
-  base::FlatSet<tables::HeapGraphObjectTable::Id> GetChildren(
-      tables::HeapGraphObjectTable::RowReference);
+  void GetChildren(tables::HeapGraphObjectTable::RowReference,
+                   std::vector<tables::HeapGraphObjectTable::Id>&);
   void MarkRoot(tables::HeapGraphObjectTable::RowReference, StringId type);
   size_t RankRoot(StringId type);
   void UpdateShortestPaths(tables::HeapGraphObjectTable::RowReference row_ref);
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 09adb4a..46e6166 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -652,6 +652,8 @@
 
 void SystemProbesParser::ParseSystemInfo(ConstBytes blob) {
   protos::pbzero::SystemInfo::Decoder packet(blob.data, blob.size);
+  SystemInfoTracker* system_info_tracker =
+      SystemInfoTracker::GetOrCreate(context_);
   if (packet.has_utsname()) {
     ConstBytes utsname_blob = packet.utsname();
     protos::pbzero::Utsname::Decoder utsname(utsname_blob.data,
@@ -666,8 +668,6 @@
                     machine.ToStdString().c_str());
     }
 
-    SystemInfoTracker* system_info_tracker =
-        SystemInfoTracker::GetOrCreate(context_);
     system_info_tracker->SetKernelVersion(utsname.sysname(), utsname.release());
 
     StringPool::Id sysname_id =
@@ -717,13 +717,14 @@
         metadata::android_sdk_version, Variadic::Integer(*opt_sdk_version));
   }
 
-  int64_t hz = packet.hz();
-  if (hz > 0)
-    ms_per_tick_ = 1000u / static_cast<uint64_t>(hz);
-
   page_size_ = packet.page_size();
-  if (!page_size_)
+  if (!page_size_) {
     page_size_ = 4096;
+  }
+
+  if (packet.has_num_cpus()) {
+    system_info_tracker->SetNumCpus(packet.num_cpus());
+  }
 }
 
 void SystemProbesParser::ParseCpuInfo(ConstBytes blob) {
diff --git a/src/trace_processor/importers/proto/system_probes_parser.h b/src/trace_processor/importers/proto/system_probes_parser.h
index 0378128..776b2c6 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.h
+++ b/src/trace_processor/importers/proto/system_probes_parser.h
@@ -18,7 +18,6 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_SYSTEM_PROBES_PARSER_H_
 
 #include <array>
-#include <set>
 #include <vector>
 
 #include "perfetto/protozero/field.h"
@@ -79,7 +78,6 @@
   std::array<StringId, protos::pbzero::SysStats_PsiSample_PsiResource_MAX + 1>
       sys_stats_psi_resource_names_{};
 
-  uint64_t ms_per_tick_ = 0;
   uint32_t page_size_ = 0;
 
   int64_t prev_read_amount = -1;
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index 0aa6e64..c3d3da0 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -109,6 +109,7 @@
     "power_profile_data/raven.sql",
     "power_profile_data/redfin.sql",
     "power_profile_data/sargo.sql",
+    "power_profile_data/shusky.sql",
     "power_profile_data/sunfish.sql",
     "power_profile_data/taimen.sql",
     "power_profile_data/walleye.sql",
diff --git a/src/trace_processor/metrics/sql/android/power_profile_data.sql b/src/trace_processor/metrics/sql/android/power_profile_data.sql
index 707c301..a79905c 100644
--- a/src/trace_processor/metrics/sql/android/power_profile_data.sql
+++ b/src/trace_processor/metrics/sql/android/power_profile_data.sql
@@ -29,3 +29,5 @@
 SELECT RUN_METRIC('android/power_profile_data/oriole.sql');
 SELECT RUN_METRIC('android/power_profile_data/raven.sql');
 SELECT RUN_METRIC('android/power_profile_data/bluejay.sql');
+SELECT RUN_METRIC('android/power_profile_data/shusky.sql');
+
diff --git a/src/trace_processor/metrics/sql/android/power_profile_data/shusky.sql b/src/trace_processor/metrics/sql/android/power_profile_data/shusky.sql
new file mode 100644
index 0000000..71f6753
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/power_profile_data/shusky.sql
@@ -0,0 +1,306 @@
+--
+-- 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
+--
+--     https://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.
+
+INSERT OR REPLACE INTO power_profile VALUES
+("shiba", 0, 0, 324000, 1.89),
+("shiba", 0, 0, 615000, 6.15),
+("shiba", 0, 0, 820000, 9.34),
+("shiba", 0, 0, 975000, 14.22),
+("shiba", 0, 0, 1098000, 18.94),
+("shiba", 0, 0, 1197000, 21.98),
+("shiba", 0, 0, 1328000, 26.83),
+("shiba", 0, 0, 1475000, 30.17),
+("shiba", 0, 0, 1548000, 41.55),
+("shiba", 0, 0, 1704000, 48.36),
+("shiba", 0, 0, 1844000, 58.45),
+("shiba", 0, 0, 1950000, 68.0),
+("shiba", 0, 0, 2024000, 78.0),
+("shiba", 0, 0, 2098000, 88.0),
+("shiba", 0, 0, 2147000, 98.0),
+("shiba", 1, 0, 324000, 1.89),
+("shiba", 1, 0, 615000, 6.15),
+("shiba", 1, 0, 820000, 9.34),
+("shiba", 1, 0, 975000, 14.22),
+("shiba", 1, 0, 1098000, 18.94),
+("shiba", 1, 0, 1197000, 21.98),
+("shiba", 1, 0, 1328000, 26.83),
+("shiba", 1, 0, 1475000, 30.17),
+("shiba", 1, 0, 1548000, 41.55),
+("shiba", 1, 0, 1704000, 48.36),
+("shiba", 1, 0, 1844000, 58.45),
+("shiba", 1, 0, 1950000, 68.0),
+("shiba", 1, 0, 2024000, 78.0),
+("shiba", 1, 0, 2098000, 88.0),
+("shiba", 1, 0, 2147000, 98.0),
+("shiba", 2, 0, 324000, 1.89),
+("shiba", 2, 0, 615000, 6.15),
+("shiba", 2, 0, 820000, 9.34),
+("shiba", 2, 0, 975000, 14.22),
+("shiba", 2, 0, 1098000, 18.94),
+("shiba", 2, 0, 1197000, 21.98),
+("shiba", 2, 0, 1328000, 26.83),
+("shiba", 2, 0, 1475000, 30.17),
+("shiba", 2, 0, 1548000, 41.55),
+("shiba", 2, 0, 1704000, 48.36),
+("shiba", 2, 0, 1844000, 58.45),
+("shiba", 2, 0, 1950000, 68.0),
+("shiba", 2, 0, 2024000, 78.0),
+("shiba", 2, 0, 2098000, 88.0),
+("shiba", 2, 0, 2147000, 98.0),
+("shiba", 3, 0, 324000, 1.89),
+("shiba", 3, 0, 615000, 6.15),
+("shiba", 3, 0, 820000, 9.34),
+("shiba", 3, 0, 975000, 14.22),
+("shiba", 3, 0, 1098000, 18.94),
+("shiba", 3, 0, 1197000, 21.98),
+("shiba", 3, 0, 1328000, 26.83),
+("shiba", 3, 0, 1475000, 30.17),
+("shiba", 3, 0, 1548000, 41.55),
+("shiba", 3, 0, 1704000, 48.36),
+("shiba", 3, 0, 1844000, 58.45),
+("shiba", 3, 0, 1950000, 68.0),
+("shiba", 3, 0, 2024000, 78.0),
+("shiba", 3, 0, 2098000, 88.0),
+("shiba", 3, 0, 2147000, 98.0),
+("shiba", 4, 1, 402000, 3.71),
+("shiba", 4, 1, 578000, 6.16),
+("shiba", 4, 1, 697000, 8.0),
+("shiba", 4, 1, 721000, 10.94),
+("shiba", 4, 1, 910000, 12.73),
+("shiba", 4, 1, 1082000, 14.4),
+("shiba", 4, 1, 1221000, 21.39),
+("shiba", 4, 1, 1328000, 24.1),
+("shiba", 4, 1, 1418000, 30.42),
+("shiba", 4, 1, 1549000, 42.49),
+("shiba", 4, 1, 1622000, 49.37),
+("shiba", 4, 1, 1836000, 58.09),
+("shiba", 4, 1, 1999000, 67.54),
+("shiba", 4, 1, 2130000, 79.04),
+("shiba", 4, 1, 2245000, 92.0),
+("shiba", 4, 1, 2352000, 104.0),
+("shiba", 4, 1, 2450000, 116.0),
+("shiba", 5, 1, 402000, 3.71),
+("shiba", 5, 1, 578000, 6.16),
+("shiba", 5, 1, 697000, 8.0),
+("shiba", 5, 1, 721000, 10.94),
+("shiba", 5, 1, 910000, 12.73),
+("shiba", 5, 1, 1082000, 14.4),
+("shiba", 5, 1, 1221000, 21.39),
+("shiba", 5, 1, 1328000, 24.1),
+("shiba", 5, 1, 1418000, 30.42),
+("shiba", 5, 1, 1549000, 42.49),
+("shiba", 5, 1, 1622000, 49.37),
+("shiba", 5, 1, 1836000, 58.09),
+("shiba", 5, 1, 1999000, 67.54),
+("shiba", 5, 1, 2130000, 79.04),
+("shiba", 5, 1, 2245000, 92.0),
+("shiba", 5, 1, 2352000, 104.0),
+("shiba", 5, 1, 2450000, 116.0),
+("shiba", 6, 1, 402000, 3.71),
+("shiba", 6, 1, 578000, 6.16),
+("shiba", 6, 1, 697000, 8.0),
+("shiba", 6, 1, 721000, 10.94),
+("shiba", 6, 1, 910000, 12.73),
+("shiba", 6, 1, 1082000, 14.4),
+("shiba", 6, 1, 1221000, 21.39),
+("shiba", 6, 1, 1328000, 24.1),
+("shiba", 6, 1, 1418000, 30.42),
+("shiba", 6, 1, 1549000, 42.49),
+("shiba", 6, 1, 1622000, 49.37),
+("shiba", 6, 1, 1836000, 58.09),
+("shiba", 6, 1, 1999000, 67.54),
+("shiba", 6, 1, 2130000, 79.04),
+("shiba", 6, 1, 2245000, 92.0),
+("shiba", 6, 1, 2352000, 104.0),
+("shiba", 6, 1, 2450000, 116.0),
+("shiba", 7, 1, 402000, 3.71),
+("shiba", 7, 1, 578000, 6.16),
+("shiba", 7, 1, 697000, 8.0),
+("shiba", 7, 1, 721000, 10.94),
+("shiba", 7, 1, 910000, 12.73),
+("shiba", 7, 1, 1082000, 14.4),
+("shiba", 7, 1, 1221000, 21.39),
+("shiba", 7, 1, 1328000, 24.1),
+("shiba", 7, 1, 1418000, 30.42),
+("shiba", 7, 1, 1549000, 42.49),
+("shiba", 7, 1, 1622000, 49.37),
+("shiba", 7, 1, 1836000, 58.09),
+("shiba", 7, 1, 1999000, 67.54),
+("shiba", 7, 1, 2130000, 79.04),
+("shiba", 7, 1, 2245000, 92.0),
+("shiba", 7, 1, 2352000, 104.0),
+("shiba", 7, 1, 2450000, 116.0),
+("shiba", 8, 2, 500000, 8.36),
+("shiba", 8, 2, 893000, 16.33),
+("shiba", 8, 2, 1164000, 19.44),
+("shiba", 8, 2, 1328000, 36.71),
+("shiba", 8, 2, 1557000, 41.42),
+("shiba", 8, 2, 1745000, 48.24),
+("shiba", 8, 2, 1852000, 54.77),
+("shiba", 8, 2, 1901000, 65.32),
+("shiba", 8, 2, 2049000, 69.58),
+("shiba", 8, 2, 2147000, 128.49),
+("shiba", 8, 2, 2294000, 142.15),
+("shiba", 8, 2, 2409000, 149.74),
+("shiba", 8, 2, 2556000, 164.78),
+("shiba", 8, 2, 2687000, 188.68),
+("shiba", 8, 2, 2802000, 193.15),
+("shiba", 8, 2, 2914000, 227.98),
+("shiba", 8, 2, 3015000, 254.25),
+("husky", 0, 0, 324000, 1.89),
+("husky", 0, 0, 615000, 6.15),
+("husky", 0, 0, 820000, 9.34),
+("husky", 0, 0, 975000, 14.22),
+("husky", 0, 0, 1098000, 18.94),
+("husky", 0, 0, 1197000, 21.98),
+("husky", 0, 0, 1328000, 26.83),
+("husky", 0, 0, 1475000, 30.17),
+("husky", 0, 0, 1548000, 41.55),
+("husky", 0, 0, 1704000, 48.36),
+("husky", 0, 0, 1844000, 58.45),
+("husky", 0, 0, 1950000, 68.0),
+("husky", 0, 0, 2024000, 78.0),
+("husky", 0, 0, 2098000, 88.0),
+("husky", 0, 0, 2147000, 98.0),
+("husky", 1, 0, 324000, 1.89),
+("husky", 1, 0, 615000, 6.15),
+("husky", 1, 0, 820000, 9.34),
+("husky", 1, 0, 975000, 14.22),
+("husky", 1, 0, 1098000, 18.94),
+("husky", 1, 0, 1197000, 21.98),
+("husky", 1, 0, 1328000, 26.83),
+("husky", 1, 0, 1475000, 30.17),
+("husky", 1, 0, 1548000, 41.55),
+("husky", 1, 0, 1704000, 48.36),
+("husky", 1, 0, 1844000, 58.45),
+("husky", 1, 0, 1950000, 68.0),
+("husky", 1, 0, 2024000, 78.0),
+("husky", 1, 0, 2098000, 88.0),
+("husky", 1, 0, 2147000, 98.0),
+("husky", 2, 0, 324000, 1.89),
+("husky", 2, 0, 615000, 6.15),
+("husky", 2, 0, 820000, 9.34),
+("husky", 2, 0, 975000, 14.22),
+("husky", 2, 0, 1098000, 18.94),
+("husky", 2, 0, 1197000, 21.98),
+("husky", 2, 0, 1328000, 26.83),
+("husky", 2, 0, 1475000, 30.17),
+("husky", 2, 0, 1548000, 41.55),
+("husky", 2, 0, 1704000, 48.36),
+("husky", 2, 0, 1844000, 58.45),
+("husky", 2, 0, 1950000, 68.0),
+("husky", 2, 0, 2024000, 78.0),
+("husky", 2, 0, 2098000, 88.0),
+("husky", 2, 0, 2147000, 98.0),
+("husky", 3, 0, 324000, 1.89),
+("husky", 3, 0, 615000, 6.15),
+("husky", 3, 0, 820000, 9.34),
+("husky", 3, 0, 975000, 14.22),
+("husky", 3, 0, 1098000, 18.94),
+("husky", 3, 0, 1197000, 21.98),
+("husky", 3, 0, 1328000, 26.83),
+("husky", 3, 0, 1475000, 30.17),
+("husky", 3, 0, 1548000, 41.55),
+("husky", 3, 0, 1704000, 48.36),
+("husky", 3, 0, 1844000, 58.45),
+("husky", 3, 0, 1950000, 68.0),
+("husky", 3, 0, 2024000, 78.0),
+("husky", 3, 0, 2098000, 88.0),
+("husky", 3, 0, 2147000, 98.0),
+("husky", 4, 1, 402000, 3.71),
+("husky", 4, 1, 578000, 6.16),
+("husky", 4, 1, 697000, 8.0),
+("husky", 4, 1, 721000, 10.94),
+("husky", 4, 1, 910000, 12.73),
+("husky", 4, 1, 1082000, 14.4),
+("husky", 4, 1, 1221000, 21.39),
+("husky", 4, 1, 1328000, 24.1),
+("husky", 4, 1, 1418000, 30.42),
+("husky", 4, 1, 1549000, 42.49),
+("husky", 4, 1, 1622000, 49.37),
+("husky", 4, 1, 1836000, 58.09),
+("husky", 4, 1, 1999000, 67.54),
+("husky", 4, 1, 2130000, 79.04),
+("husky", 4, 1, 2245000, 92.0),
+("husky", 4, 1, 2352000, 104.0),
+("husky", 4, 1, 2450000, 116.0),
+("husky", 5, 1, 402000, 3.71),
+("husky", 5, 1, 578000, 6.16),
+("husky", 5, 1, 697000, 8.0),
+("husky", 5, 1, 721000, 10.94),
+("husky", 5, 1, 910000, 12.73),
+("husky", 5, 1, 1082000, 14.4),
+("husky", 5, 1, 1221000, 21.39),
+("husky", 5, 1, 1328000, 24.1),
+("husky", 5, 1, 1418000, 30.42),
+("husky", 5, 1, 1549000, 42.49),
+("husky", 5, 1, 1622000, 49.37),
+("husky", 5, 1, 1836000, 58.09),
+("husky", 5, 1, 1999000, 67.54),
+("husky", 5, 1, 2130000, 79.04),
+("husky", 5, 1, 2245000, 92.0),
+("husky", 5, 1, 2352000, 104.0),
+("husky", 5, 1, 2450000, 116.0),
+("husky", 6, 1, 402000, 3.71),
+("husky", 6, 1, 578000, 6.16),
+("husky", 6, 1, 697000, 8.0),
+("husky", 6, 1, 721000, 10.94),
+("husky", 6, 1, 910000, 12.73),
+("husky", 6, 1, 1082000, 14.4),
+("husky", 6, 1, 1221000, 21.39),
+("husky", 6, 1, 1328000, 24.1),
+("husky", 6, 1, 1418000, 30.42),
+("husky", 6, 1, 1549000, 42.49),
+("husky", 6, 1, 1622000, 49.37),
+("husky", 6, 1, 1836000, 58.09),
+("husky", 6, 1, 1999000, 67.54),
+("husky", 6, 1, 2130000, 79.04),
+("husky", 6, 1, 2245000, 92.0),
+("husky", 6, 1, 2352000, 104.0),
+("husky", 6, 1, 2450000, 116.0),
+("husky", 7, 1, 402000, 3.71),
+("husky", 7, 1, 578000, 6.16),
+("husky", 7, 1, 697000, 8.0),
+("husky", 7, 1, 721000, 10.94),
+("husky", 7, 1, 910000, 12.73),
+("husky", 7, 1, 1082000, 14.4),
+("husky", 7, 1, 1221000, 21.39),
+("husky", 7, 1, 1328000, 24.1),
+("husky", 7, 1, 1418000, 30.42),
+("husky", 7, 1, 1549000, 42.49),
+("husky", 7, 1, 1622000, 49.37),
+("husky", 7, 1, 1836000, 58.09),
+("husky", 7, 1, 1999000, 67.54),
+("husky", 7, 1, 2130000, 79.04),
+("husky", 7, 1, 2245000, 92.0),
+("husky", 7, 1, 2352000, 104.0),
+("husky", 7, 1, 2450000, 116.0),
+("husky", 8, 2, 500000, 8.36),
+("husky", 8, 2, 893000, 16.33),
+("husky", 8, 2, 1164000, 19.44),
+("husky", 8, 2, 1328000, 36.71),
+("husky", 8, 2, 1557000, 41.42),
+("husky", 8, 2, 1745000, 48.24),
+("husky", 8, 2, 1852000, 54.77),
+("husky", 8, 2, 1901000, 65.32),
+("husky", 8, 2, 2049000, 69.58),
+("husky", 8, 2, 2147000, 128.49),
+("husky", 8, 2, 2294000, 142.15),
+("husky", 8, 2, 2409000, 149.74),
+("husky", 8, 2, 2556000, 164.78),
+("husky", 8, 2, 2687000, 188.68),
+("husky", 8, 2, 2802000, 193.15),
+("husky", 8, 2, 2914000, 227.98),
+("husky", 8, 2, 3015000, 254.25);
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index 4ad989b..c277966 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -28,6 +28,7 @@
     "graphs",
     "intervals",
     "linux",
+    "memory",
     "pkvm",
     "prelude",
     "sched",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/statsd.sql b/src/trace_processor/perfetto_sql/stdlib/android/statsd.sql
index dfae727..353454f 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/statsd.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/statsd.sql
@@ -73,4 +73,20 @@
 WHERE
   track.name = 'Statsd Atoms';
 
-
+-- Information about Perfetto triggers, extracted from statsd atoms, which
+-- happened during the trace.
+--
+-- This requires the `android.statsd` data-source to be enabled and the
+-- `ATOM_PERFETTO_TRIGGER` push atom to be configured.
+CREATE PERFETTO TABLE _android_statsd_perfetto_triggers(
+  -- Timestamp of the trigger.
+  ts INT,
+  -- The name of the trigger.
+  trigger_name STRING
+)
+AS
+SELECT
+  ts,
+  extract_arg(arg_set_id, 'perfetto_trigger.trigger_name') AS trigger_name
+FROM android_statsd_atoms
+WHERE name = 'perfetto_trigger';
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/thread.sql b/src/trace_processor/perfetto_sql/stdlib/android/thread.sql
index d8c0b74..d7a8d44 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/thread.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/thread.sql
@@ -18,7 +18,7 @@
 SELECT STR_SPLIT(STR_SPLIT(STR_SPLIT(STR_SPLIT($thread_name, "-", 0), "[", 0), ":", 0), " ", 0);
 
 -- Per process stats of threads created in a process
-CREATE PERFETTO FUNCTION android_thread_creation_spam(
+CREATE PERFETTO FUNCTION _android_thread_creation_spam(
   -- Minimum duration between creating and destroying a thread before their the
   -- thread creation event is considered. If NULL, considers all thread creations.
   min_thread_dur FLOAT,
diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
index 6d9bf00..4bba25a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
@@ -23,7 +23,7 @@
 -- level functions/macros in the standard library can be built.
 --
 -- Example usage on traces containing heap graphs:
---
+-- ```
 -- -- Compute the reachable nodes from the first heap root.
 -- SELECT *
 -- FROM graph_reachable_dfs!(
@@ -73,7 +73,7 @@
 -- The order of the next sibling is undefined if the |sort_key| is not unique.
 --
 -- Example usage:
---
+-- ```
 -- -- Compute the next sibling:
 -- SELECT *
 -- FROM graph_next_sibling!(
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn
new file mode 100644
index 0000000..ae3ad17
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("memory") {
+  sources = [ "heap_graph_dominator_tree.sql" ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql b/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql
new file mode 100644
index 0000000..2326aab
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql
@@ -0,0 +1,163 @@
+--
+-- 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
+--
+--     https://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 PERFETTO MODULE graphs.dominator_tree;
+
+-- Excluding following types from the graph as they share objects' ownership
+-- with their real (more interesting) owners and will mask their idom to be the
+-- "super root".
+CREATE PERFETTO TABLE _excluded_type_ids AS
+WITH RECURSIVE class_visitor(type_id) AS (
+  SELECT id AS type_id
+  FROM heap_graph_class
+  WHERE name IN (
+    'java.lang.ref.PhantomReference',
+    'java.lang.ref.FinalizerReference'
+  )
+  UNION ALL
+  SELECT child.id AS type_id
+  FROM heap_graph_class child
+  JOIN class_visitor parent ON parent.type_id = child.superclass_id
+)
+SELECT * FROM class_visitor;
+
+-- The assigned id of the "super root".
+-- Since a Java heap graph is a "forest" structure, we need to add a imaginary
+-- "super root" node which connects all the roots of the forest into a single
+-- connected component, so that the dominator tree algorithm can be performed.
+CREATE PERFETTO FUNCTION memory_heap_graph_super_root_fn()
+-- The assigned id of the "super root".
+RETURNS INT AS
+SELECT max(id) + 1 FROM heap_graph_object;
+
+CREATE PERFETTO VIEW _dominator_compatible_heap_graph AS
+SELECT
+  ref.owner_id AS source_node_id,
+  ref.owned_id AS dest_node_id
+FROM heap_graph_reference ref
+JOIN heap_graph_object source_node ON ref.owner_id = source_node.id
+WHERE source_node.reachable AND source_node.type_id NOT IN _excluded_type_ids
+  AND ref.owned_id IS NOT NULL
+UNION ALL
+SELECT
+  (SELECT memory_heap_graph_super_root_fn()) as source_node_id,
+  id AS dest_node_id
+FROM heap_graph_object
+WHERE root_type IS NOT NULL;
+
+CREATE PERFETTO TABLE _heap_graph_dominator_tree AS
+SELECT
+  node_id AS id,
+  dominator_node_id AS idom_id
+FROM graph_dominator_tree!(
+  _dominator_compatible_heap_graph,
+  (SELECT memory_heap_graph_super_root_fn())
+)
+-- Excluding the imaginary root.
+WHERE dominator_node_id IS NOT NULL
+-- Ordering by idom_id so queries below are faster when joining on idom_id.
+-- TODO(lalitm): support create index for Perfetto tables.
+ORDER BY idom_id;
+
+CREATE PERFETTO TABLE _heap_graph_dominator_tree_depth AS
+WITH RECURSIVE _tree_visitor(id, depth) AS (
+  -- Let the super root have depth 0.
+  SELECT id, 1 AS depth
+  FROM _heap_graph_dominator_tree
+  WHERE idom_id IN (SELECT memory_heap_graph_super_root_fn())
+  UNION ALL
+  SELECT child.id, parent.depth + 1
+  FROM _heap_graph_dominator_tree child
+  JOIN _tree_visitor parent ON child.idom_id = parent.id
+)
+SELECT * FROM _tree_visitor
+ORDER BY id;
+
+-- A performance note: we need 3 memoize functions because EXPERIMENTAL_MEMOIZE
+-- limits the function to return only 1 int.
+-- This means the exact same "memoized dfs pass" on the tree is done 3 times, so
+-- it takes 3x the time taken by only doing 1 pass. Doing only 1 pass would be
+-- possible if EXPERIMENTAL_MEMOIZE could return more than 1 int.
+
+CREATE PERFETTO FUNCTION _subtree_obj_count(id INT)
+RETURNS INT AS
+SELECT 1 + IFNULL((
+  SELECT
+    SUM(_subtree_obj_count(child.id))
+  FROM _heap_graph_dominator_tree child
+  WHERE child.idom_id = $id
+), 0);
+SELECT EXPERIMENTAL_MEMOIZE('_subtree_obj_count');
+
+CREATE PERFETTO FUNCTION _subtree_size_bytes(id INT)
+RETURNS INT AS
+SELECT (
+  SELECT self_size
+  FROM heap_graph_object
+  WHERE heap_graph_object.id = $id
+) +
+IFNULL((
+  SELECT
+    SUM(_subtree_size_bytes(child.id))
+  FROM _heap_graph_dominator_tree child
+  WHERE child.idom_id = $id
+), 0);
+SELECT EXPERIMENTAL_MEMOIZE('_subtree_size_bytes');
+
+CREATE PERFETTO FUNCTION _subtree_native_size_bytes(id INT)
+RETURNS INT AS
+SELECT (
+  SELECT native_size
+  FROM heap_graph_object
+  WHERE heap_graph_object.id = $id
+) +
+IFNULL((
+  SELECT
+    SUM(_subtree_native_size_bytes(child.id))
+  FROM _heap_graph_dominator_tree child
+  WHERE child.idom_id = $id
+), 0);
+SELECT EXPERIMENTAL_MEMOIZE('_subtree_native_size_bytes');
+
+-- All reachable heap graph objects, their immediate dominators and summary of
+-- their dominated sets.
+-- The heap graph dominator tree is calculated by stdlib graphs.dominator_tree.
+-- Each reachable object is a node in the dominator tree, their immediate
+-- dominator is their parent node in the tree, and their dominated set is all
+-- their descendants in the tree. All size information come from the
+-- heap_graph_object prelude table.
+CREATE PERFETTO TABLE memory_heap_graph_dominator_tree (
+  -- Heap graph object id.
+  id INT,
+  -- Immediate dominator object id of the object.
+  idom_id INT,
+  -- Count of all objects dominated by this object, self inclusive.
+  dominated_obj_count INT,
+  -- Total self_size of all objects dominated by this object, self inclusive.
+  dominated_size_bytes INT,
+  -- Total native_size of all objects dominated by this object, self inclusive.
+  dominated_native_size_bytes INT,
+  -- Depth of the object in the dominator tree. Depth of root objects are 1.
+  depth INT
+) AS
+SELECT
+  t.id,
+  t.idom_id,
+  _subtree_obj_count(t.id) AS dominated_obj_count,
+  _subtree_size_bytes(t.id) AS dominated_size_bytes,
+  _subtree_native_size_bytes(t.id) AS dominated_native_size_bytes,
+  d.depth
+FROM _heap_graph_dominator_tree t
+JOIN _heap_graph_dominator_tree_depth d USING(id);
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
index 642b822..bd2dc94 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
@@ -21,5 +21,6 @@
     "thread_executing_span.sql",
     "thread_level_parallelism.sql",
     "thread_state_flattened.sql",
+    "time_in_state.sql",
   ]
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/states.sql b/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
index b991692..37f759a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
@@ -69,4 +69,4 @@
     WHEN 0 THEN ' (non-IO)'
     ELSE ''
   END
-);
+);
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql b/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql
new file mode 100644
index 0000000..6f9b3a3
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql
@@ -0,0 +1,91 @@
+--
+-- 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
+--
+--     https://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.
+
+-- The time a thread spent in each scheduling state during it's lifetime.
+CREATE PERFETTO TABLE sched_thread_time_in_state(
+  -- Utid of the thread.
+  utid INT,
+  -- Total runtime of thread.
+  total_runtime INT,
+  -- One of the scheduling states of kernel thread.
+  state STRING,
+  -- Total time spent in the scheduling state.
+  time_in_state INT,
+  -- Percentage of time thread spent in scheduling state in [0-100] range.
+  percentage_in_state INT
+) AS
+WITH total_dur AS (
+  SELECT
+    utid,
+    sum(dur) AS sum_dur
+  FROM thread_state
+  GROUP BY 1
+),
+summed AS (
+  SELECT
+    utid,
+    state,
+    sum(dur) AS time_in_state
+  FROM thread_state group by 1, 2
+)
+SELECT
+  utid,
+  sum_dur AS total_runtime,
+  state,
+  time_in_state,
+  (time_in_state*100)/(sum_dur) AS percentage_in_state
+FROM summed JOIN total_dur USING (utid);
+
+CREATE PERFETTO MACRO _case_for_state(state Expr)
+RETURNS Expr AS
+MAX(CASE WHEN state = $state THEN percentage_in_state END);
+
+-- Summary of time spent by thread in each scheduling state, in percentage ([0, 100]
+-- ranges). Sum of all states might be smaller than 100, as those values
+-- are rounded down.
+CREATE PERFETTO TABLE sched_percentage_of_time_in_state(
+  -- Utid of the thread.
+  utid INT,
+  -- Percentage of time thread spent in running ('Running') state in [0, 100]
+  -- range.
+  running INT,
+  -- Percentage of time thread spent in runnable ('R') state in [0, 100]
+  -- range.
+  runnable INT,
+  -- Percentage of time thread spent in preempted runnable ('R+') state in
+  -- [0, 100] range.
+  runnable_preempted INT,
+  -- Percentage of time thread spent in sleeping ('S') state in [0, 100] range.
+  sleeping INT,
+  -- Percentage of time thread spent in uninterruptible sleep ('D') state in
+  -- [0, 100] range.
+  uninterruptible_sleep INT,
+  -- Percentage of time thread spent in other ('T', 't', 'X', 'Z', 'x', 'I',
+  -- 'K', 'W', 'P', 'N') states in [0, 100] range.
+  other INT
+) AS
+SELECT
+  utid,
+  _case_for_state!('Running') AS running,
+  _case_for_state!('R') AS runnable,
+  _case_for_state!('R+') AS runnable_preempted,
+  _case_for_state!('S') AS sleeping,
+  _case_for_state!('D') AS uninterruptible_sleep,
+  SUM(
+    CASE WHEN state IN ('T', 't', 'X', 'Z', 'x', 'I', 'K', 'W', 'P', 'N')
+    THEN time_in_state END
+  ) * 100/total_runtime AS other
+FROM sched_thread_time_in_state
+GROUP BY utid;
\ No newline at end of file
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 3c22af9..d6e86cf 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -381,6 +381,13 @@
     return &energy_counter_track_table_;
   }
 
+  const tables::LinuxDeviceTrackTable& linux_device_track_table() const {
+    return linux_device_track_table_;
+  }
+  tables::LinuxDeviceTrackTable* mutable_linux_device_track_table() {
+    return &linux_device_track_table_;
+  }
+
   const tables::UidCounterTrackTable& uid_counter_track_table() const {
     return uid_counter_track_table_;
   }
@@ -943,6 +950,8 @@
       &string_pool_, &uid_track_table_};
   tables::ProcessTrackTable process_track_table_{&string_pool_, &track_table_};
   tables::ThreadTrackTable thread_track_table_{&string_pool_, &track_table_};
+  tables::LinuxDeviceTrackTable linux_device_track_table_{
+      &string_pool_, &track_table_};
 
   // Track tables for counter events.
   tables::CounterTrackTable counter_track_table_{&string_pool_, &track_table_};
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index c34e4d3..fd420b3 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -107,6 +107,7 @@
 EnergyCounterTrackTable::~EnergyCounterTrackTable() = default;
 UidCounterTrackTable::~UidCounterTrackTable() = default;
 EnergyPerUidCounterTrackTable::~EnergyPerUidCounterTrackTable() = default;
+LinuxDeviceTrackTable::~LinuxDeviceTrackTable() = default;
 
 // trace_proto_tables_py.h
 ExperimentalProtoPathTable::~ExperimentalProtoPathTable() = default;
diff --git a/src/trace_processor/tables/track_tables.py b/src/trace_processor/tables/track_tables.py
index 7a3bd9e..cfdc59d 100644
--- a/src/trace_processor/tables/track_tables.py
+++ b/src/trace_processor/tables/track_tables.py
@@ -329,6 +329,24 @@
             'ordinal': 'ordinal of energy consumer'
         }))
 
+LINUX_DEVICE_TRACK_TABLE = Table(
+    python_module=__file__,
+    class_name='LinuxDeviceTrackTable',
+    sql_name='linux_device_track',
+    columns=[],
+    parent=TRACK_TABLE,
+    tabledoc=TableDoc(
+        doc='''
+          Slice data corresponding to runtime power state transitions
+          associated with Linux devices (where a Linux device is anything
+          managed by a Linux driver). The name of each track corresponds to the
+          device name as recognized by the linux kernel running on the system.
+        ''',
+        group='Tracks',
+        # No additional columns are needed because the track name implicitly
+        # serves as the device name, providing all required information.
+        columns={}))
+
 UID_COUNTER_TRACK_TABLE = Table(
     python_module=__file__,
     class_name='UidCounterTrackTable',
@@ -366,6 +384,7 @@
     GPU_TRACK_TABLE,
     GPU_WORK_PERIOD_TRACK_TABLE,
     IRQ_COUNTER_TRACK_TABLE,
+    LINUX_DEVICE_TRACK_TABLE,
     PERF_COUNTER_TRACK_TABLE,
     PROCESS_COUNTER_TRACK_TABLE,
     PROCESS_TRACK_TABLE,
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 8894f47..02bc3ae 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -785,6 +785,7 @@
   RegisterStaticTable(storage->gpu_counter_group_table());
   RegisterStaticTable(storage->perf_counter_track_table());
   RegisterStaticTable(storage->energy_counter_track_table());
+  RegisterStaticTable(storage->linux_device_track_table());
   RegisterStaticTable(storage->uid_counter_track_table());
   RegisterStaticTable(storage->energy_per_uid_counter_track_table());
 
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index f65c588..7b2f4c5 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -34,6 +34,8 @@
     "populate_allow_lists.h",
     "prune_package_list.cc",
     "prune_package_list.h",
+    "scrub_ftrace_events.cc",
+    "scrub_ftrace_events.h",
     "scrub_trace_packet.cc",
     "scrub_trace_packet.h",
     "trace_redaction_framework.cc",
@@ -51,6 +53,7 @@
     "../../protos/perfetto/trace:non_minimal_zero",
     "../../protos/perfetto/trace/android:cpp",
     "../../protos/perfetto/trace/android:zero",
+    "../../protos/perfetto/trace/ftrace:zero",
     "../trace_processor:storage_minimal",
   ]
 }
@@ -65,6 +68,7 @@
     "../../include/perfetto/ext/base",
     "../../protos/perfetto/trace:non_minimal_zero",
     "../../protos/perfetto/trace/android:zero",
+    "../../protos/perfetto/trace/ftrace:zero",
     "../base:test_support",
   ]
 }
@@ -74,15 +78,19 @@
   sources = [
     "find_package_uid_unittest.cc",
     "prune_package_list_unittest.cc",
+    "scrub_ftrace_events_unittest.cc",
     "scrub_trace_packet_unittest.cc",
   ]
   deps = [
     ":trace_redaction",
     "../../gn:default_deps",
     "../../gn:gtest_and_gmock",
+    "../../include/perfetto/protozero:protozero",
     "../../protos/perfetto/trace:non_minimal_cpp",
     "../../protos/perfetto/trace:zero",
     "../../protos/perfetto/trace/android:cpp",
+    "../../protos/perfetto/trace/ftrace:cpp",
+    "../../protos/perfetto/trace/ftrace:zero",
     "../../protos/perfetto/trace/ps:cpp",
     "../../protos/perfetto/trace/ps:zero",
     "../base:test_support",
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 24ad116..153e437 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -18,6 +18,8 @@
 #include "perfetto/base/status.h"
 #include "src/trace_redaction/find_package_uid.h"
 #include "src/trace_redaction/prune_package_list.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/scrub_trace_packet.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 #include "src/trace_redaction/trace_redactor.h"
 
@@ -36,6 +38,8 @@
 
   // Add all transforms.
   redactor.transformers()->emplace_back(new PrunePackageList());
+  redactor.transformers()->emplace_back(new ScrubTracePacket());
+  redactor.transformers()->emplace_back(new ScrubFtraceEvents());
 
   Context context;
   context.package_name = package_name;
diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc
index b14b1f6..fbfe279 100644
--- a/src/trace_redaction/populate_allow_lists.cc
+++ b/src/trace_redaction/populate_allow_lists.cc
@@ -19,6 +19,7 @@
 #include "perfetto/base/status.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto::trace_redaction {
@@ -44,8 +45,47 @@
       protos::pbzero::TracePacket::kAndroidSystemPropertyFieldNumber,
       protos::pbzero::TracePacket::kSynchronizationMarkerFieldNumber,
       protos::pbzero::TracePacket::kFtraceEventsFieldNumber,
+
+      // Keep the package list. There are some metrics and stdlib queries that
+      // depend on the package list.
+      protos::pbzero::TracePacket::kPackagesListFieldNumber,
   };
 
+  context->ftrace_packet_allow_list = {
+      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,
+      protos::pbzero::FtraceEvent::kRssStatFieldNumber,
+      protos::pbzero::FtraceEvent::kIonHeapShrinkFieldNumber,
+      protos::pbzero::FtraceEvent::kIonHeapGrowFieldNumber,
+      protos::pbzero::FtraceEvent::kIonStatFieldNumber,
+      protos::pbzero::FtraceEvent::kIonBufferCreateFieldNumber,
+      protos::pbzero::FtraceEvent::kIonBufferDestroyFieldNumber,
+      protos::pbzero::FtraceEvent::kDmaHeapStatFieldNumber,
+      protos::pbzero::FtraceEvent::kRssStatThrottledFieldNumber,
+  };
+
+  // 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.
+  //
+  // 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::kPrintFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderTransactionFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderTransactionReceivedFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderSetPriorityFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderLockedFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderUnlockFieldNumber,
+
   return base::OkStatus();
 }
 
diff --git a/src/trace_redaction/scrub_ftrace_events.cc b/src/trace_redaction/scrub_ftrace_events.cc
new file mode 100644
index 0000000..67585d1
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events.cc
@@ -0,0 +1,187 @@
+/*
+ * 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/scrub_ftrace_events.h"
+
+#include <string>
+
+#include "perfetto/protozero/message.h"
+#include "perfetto/protozero/message_arena.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+
+constexpr auto kFtraceEventsFieldNumber =
+    protos::pbzero::TracePacket::kFtraceEventsFieldNumber;
+
+constexpr auto kEventFieldNumber =
+    protos::pbzero::FtraceEventBundle::kEventFieldNumber;
+
+enum class Redact : uint8_t {
+  // Some resources in the target need to be redacted.
+  kSomething = 0,
+
+  // No resources in the target need to be redacted.
+  kNothing = 1,
+};
+
+// Return kSomething if an event will change after redaction . If a packet
+// will not change, then the packet should skip redaction and be appended
+// to the output.
+//
+// Event packets have few packets (e.g. timestamp, pid, the event payload).
+// because of this, it is relatively cheap to test a packet.
+//
+//  event {
+//    timestamp: 6702095044306682
+//    pid: 0
+//    sched_switch {
+//      prev_comm: "swapper/2"
+//      prev_pid: 0
+//      prev_prio: 120
+//      prev_state: 0
+//      next_comm: "surfaceflinger"
+//      next_pid: 819
+//      next_prio: 120
+//    }
+//  }
+Redact ProbeEvent(const Context& context, const protozero::Field& event) {
+  if (event.id() != kEventFieldNumber) {
+    PERFETTO_FATAL("Invalid proto field. Expected kEventFieldNumber.");
+  }
+
+  protozero::ProtoDecoder decoder(event.data(), event.size());
+
+  for (auto field = decoder.ReadField(); field.valid();
+       field = decoder.ReadField()) {
+    if (context.ftrace_packet_allow_list.count(field.id()) != 0) {
+      return Redact::kNothing;
+    }
+  }
+
+  return Redact::kSomething;
+}
+
+}  // namespace
+
+//  packet {
+//    ftrace_events {
+//      event {                   <-- This is where we test the allow-list
+//        timestamp: 6702095044299807
+//        pid: 0
+//        cpu_idle {              <-- This is the event data (allow-list)
+//          state: 4294967295
+//          cpu_id: 2
+//        }
+//      }
+//    }
+//  }
+base::Status ScrubFtraceEvents::Transform(const Context& context,
+                                          std::string* packet) const {
+  if (packet == nullptr || packet->empty()) {
+    return base::ErrStatus("Cannot scrub null or empty trace packet.");
+  }
+
+  if (context.ftrace_packet_allow_list.empty()) {
+    return base::ErrStatus("Cannot scrub ftrace packets, missing allow-list.");
+  }
+
+  // If the packet has no ftrace events, skip it, leaving it unmodified.
+  protos::pbzero::TracePacket::Decoder query(*packet);
+  if (!query.has_ftrace_events()) {
+    return base::OkStatus();
+  }
+
+  protozero::HeapBuffered<protos::pbzero::TracePacket> packet_msg;
+
+  // packet.foreach_child.foreach( ... )
+  protozero::ProtoDecoder d_packet(*packet);
+  for (auto packet_child_it = d_packet.ReadField(); packet_child_it.valid();
+       packet_child_it = d_packet.ReadField()) {
+    // packet.child_not<ftrace_events>( ).do ( ... )
+    if (packet_child_it.id() != kFtraceEventsFieldNumber) {
+      AppendField(packet_child_it, packet_msg.get());
+      continue;
+    }
+
+    // To clarify, "ftrace_events" is the field name and "FtraceEventBundle" is
+    // the field type. The terms are often used interchangeably.
+    auto* ftrace_events_msg = packet_msg->set_ftrace_events();
+
+    // packet.child<ftrace_events>( ).foreach_child( ... )
+    protozero::ProtoDecoder ftrace_events(packet_child_it.as_bytes());
+    for (auto ftrace_events_it = ftrace_events.ReadField();
+         ftrace_events_it.valid();
+         ftrace_events_it = ftrace_events.ReadField()) {
+      // packet.child<ftrace_events>( ).child_not<event>( ).do ( ... )
+      if (ftrace_events_it.id() != kEventFieldNumber) {
+        AppendField(ftrace_events_it, ftrace_events_msg);
+        continue;
+      }
+
+      // packet.child<ftrace_events>( ).child_is<event>( ).do ( ... )
+      if (ProbeEvent(context, ftrace_events_it) == Redact::kNothing) {
+        AppendField(ftrace_events_it, ftrace_events_msg);
+        continue;
+      }
+
+      // Dropping packet = "is event" and "is redacted"
+    }
+  }
+
+  packet->assign(packet_msg.SerializeAsString());
+  return base::OkStatus();
+}
+
+// This is copied from "src/protozero/field.cc", but was modified to use the
+// serialization methods provided in "perfetto/protozero/message.h".
+void ScrubFtraceEvents::AppendField(const protozero::Field& field,
+                                    protozero::Message* message) {
+  auto id = field.id();
+  auto type = field.type();
+
+  switch (type) {
+    case protozero::proto_utils::ProtoWireType::kVarInt: {
+      message->AppendVarInt(id, field.raw_int_value());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kFixed32: {
+      message->AppendFixed(id, field.as_uint32());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kFixed64: {
+      message->AppendFixed(id, field.as_uint64());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kLengthDelimited: {
+      message->AppendBytes(id, field.data(), field.size());
+      return;
+    }
+  }
+
+  // A switch-statement would be preferred, but when using a switch statement,
+  // it complains that about case coverage.
+  PERFETTO_FATAL("Unknown field type %u", static_cast<uint8_t>(type));
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_ftrace_events.h b/src/trace_redaction/scrub_ftrace_events.h
new file mode 100644
index 0000000..9ae6a98
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events.h
@@ -0,0 +1,62 @@
+/*
+ * 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_SCRUB_FTRACE_EVENTS_H_
+#define SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_
+
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/message.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+//  Assumptions:
+//    1. This is a hot path (a lot of ftrace packets)
+//    2. Allocations are slower than CPU cycles.
+//
+//  Overview:
+//    To limit allocations pbzero protos are used to build a new packet. These
+//    protos are append-only, so data is not removed from the packet. Instead,
+//    data is optionally added to a new packet.
+//
+//    To limit allocations, the goal is to add data as large chucks rather than
+//    small fragments. To do this, a reactive strategy is used. All operations
+//    follow a probe-than-act pattern. Before any action can be taken, the
+//    input data must be queries to determine the scope. For example:
+//
+//        [------A------][---B---][------C------]
+//                                [---][-D-][---]
+//
+//        Assume that A and B don't need any work, they can be appended to the
+//        output as two large blocks.
+//
+//        Block C is different, there is a block D that falls within block C.
+//        Block D contains sensitive information and should be dropped. When C
+//        is probed, it will come back saying that C needs additional redaction.
+
+class ScrubFtraceEvents final : public TransformPrimitive {
+ public:
+  base::Status Transform(const Context& context,
+                         std::string* packet) const override;
+
+  // Used by `Transform()`. Only exposed for conformance testing.
+  static void AppendField(const protozero::Field& field,
+                          protozero::Message* message);
+};
+
+}  // namespace perfetto::trace_redaction
+
+#endif  // SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_
diff --git a/src/trace_redaction/scrub_ftrace_events_unittest.cc b/src/trace_redaction/scrub_ftrace_events_unittest.cc
new file mode 100644
index 0000000..179c504
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events_unittest.cc
@@ -0,0 +1,329 @@
+/*
+ * 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/scrub_ftrace_events.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ftrace/power.pbzero.h"
+#include "src/base/test/status_matchers.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/power.gen.h"
+#include "protos/perfetto/trace/ftrace/task.gen.h"
+#include "protos/perfetto/trace/ps/process_tree.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+// task_rename should be in the allow-list.
+void AddTaskRename(protos::gen::FtraceEventBundle* bundle,
+                   int32_t pid,
+                   const std::string& old_comm,
+                   const std::string& new_comm) {
+  auto* e = bundle->add_event();
+  e->mutable_task_rename()->set_pid(pid);
+  e->mutable_task_rename()->set_oldcomm(old_comm);
+  e->mutable_task_rename()->set_newcomm(new_comm);
+}
+
+void AddClockSetRate(protos::gen::FtraceEventBundle* bundle,
+                     uint64_t cpu,
+                     const std::string& name,
+                     uint64_t state) {
+  auto* e = bundle->add_event();
+  e->mutable_clock_set_rate()->set_cpu_id(cpu);
+  e->mutable_clock_set_rate()->set_name(name);
+  e->mutable_clock_set_rate()->set_state(state);
+}
+
+}  // namespace
+
+class ScrubFtraceEventsSerializationTest : public testing::Test {
+ public:
+  ScrubFtraceEventsSerializationTest() = default;
+  ~ScrubFtraceEventsSerializationTest() override = default;
+
+ protected:
+  void SetUp() override {
+    std::array bytes = {
+        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+        0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+    };
+
+    protozero::HeapBuffered<protozero::Message> msg;
+    msg->AppendFixed<uint32_t>(field_id_fixed_32, 7);
+    msg->AppendFixed<uint64_t>(field_id_fixed_64, 9);
+    msg->AppendVarInt(field_id_var_int, 723);
+    msg->AppendBytes(field_id_bytes, bytes.data(), bytes.size());
+
+    message_.assign(msg.SerializeAsString());
+  }
+
+  std::string message_;
+
+  static constexpr uint32_t field_id_fixed_32 = 0;
+  static constexpr uint32_t field_id_fixed_64 = 1;
+  static constexpr uint32_t field_id_var_int = 2;
+  static constexpr uint32_t field_id_bytes = 3;
+};
+
+TEST_F(ScrubFtraceEventsSerializationTest, AppendFixedUint32) {
+  protozero::ProtoDecoder decoder(message_);
+
+  const auto& field = decoder.FindField(field_id_fixed_32);
+
+  std::string expected = "";
+  field.SerializeAndAppendTo(&expected);
+
+  protozero::HeapBuffered<protozero::Message> actual;
+  ScrubFtraceEvents::AppendField(field, actual.get());
+
+  ASSERT_EQ(actual.SerializeAsString(), expected);
+}
+
+TEST_F(ScrubFtraceEventsSerializationTest, AppendFixedUint64) {
+  protozero::ProtoDecoder decoder(message_);
+
+  const auto& field = decoder.FindField(field_id_fixed_64);
+
+  std::string expected = "";
+  field.SerializeAndAppendTo(&expected);
+
+  protozero::HeapBuffered<protozero::Message> actual;
+  ScrubFtraceEvents::AppendField(field, actual.get());
+
+  ASSERT_EQ(actual.SerializeAsString(), expected);
+}
+
+TEST_F(ScrubFtraceEventsSerializationTest, AppendBytes) {
+  protozero::ProtoDecoder decoder(message_);
+
+  const auto& field = decoder.FindField(field_id_bytes);
+
+  std::string expected = "";
+  field.SerializeAndAppendTo(&expected);
+
+  protozero::HeapBuffered<protozero::Message> actual;
+  ScrubFtraceEvents::AppendField(field, actual.get());
+
+  ASSERT_EQ(actual.SerializeAsString(), expected);
+}
+
+TEST_F(ScrubFtraceEventsSerializationTest, AppendVarInt) {
+  protozero::ProtoDecoder decoder(message_);
+
+  const auto& field = decoder.FindField(field_id_var_int);
+
+  std::string expected = "";
+  field.SerializeAndAppendTo(&expected);
+
+  protozero::HeapBuffered<protozero::Message> actual;
+  ScrubFtraceEvents::AppendField(field, actual.get());
+
+  ASSERT_EQ(actual.SerializeAsString(), expected);
+}
+
+TEST(ScrubFtraceEventsTest, ReturnErrorForNullPacket) {
+  // Have something in the allow-list to avoid that error.
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents scrub;
+  ASSERT_FALSE(scrub.Transform(context, nullptr).ok());
+}
+
+TEST(ScrubFtraceEventsTest, ReturnErrorForEmptyPacket) {
+  // Have something in the allow-list to avoid that error.
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  std::string packet_str = "";
+
+  ScrubFtraceEvents scrub;
+  ASSERT_FALSE(scrub.Transform(context, &packet_str).ok());
+}
+
+TEST(ScrubFtraceEventsTest, ReturnErrorForEmptyAllowList) {
+  // The context will have no allow-list entries. ScrubFtraceEvents should fail.
+  Context context;
+
+  protos::gen::TracePacket packet;
+  std::string packet_str = packet.SerializeAsString();
+
+  ScrubFtraceEvents scrub;
+  ASSERT_FALSE(scrub.Transform(context, &packet_str).ok());
+}
+
+TEST(ScrubFtraceEventsTest, IgnorePacketWithNoFtraceEvents) {
+  protos::gen::TracePacket trace_packet;
+  auto* tree = trace_packet.mutable_process_tree();
+
+  auto& process = tree->mutable_processes()->emplace_back();
+  process.set_pid(1);
+  process.set_ppid(2);
+  process.set_uid(3);
+
+  auto& thread = tree->mutable_threads()->emplace_back();
+  thread.set_name("hello world");
+  thread.set_tgid(1);
+  thread.set_tid(135);
+
+  auto original_packet = trace_packet.SerializeAsString();
+  auto packet = original_packet;
+
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &packet));
+
+  // The packet doesn't have any ftrace events. It should not be affected by
+  // this transform.
+  ASSERT_EQ(original_packet, packet);
+}
+
+// There are some values in a ftrace event that sits behind the ftrace bundle.
+// These values should be retained.
+TEST(ScrubFtraceEventsTest, KeepsFtraceBundleSiblingValues) {
+  protos::gen::TracePacket trace_packet;
+  auto* ftrace_events = trace_packet.mutable_ftrace_events();
+
+  ftrace_events->set_cpu(7);
+  AddTaskRename(ftrace_events, 7, "old_comm", "new_comm_7");
+  AddClockSetRate(ftrace_events, 7, "cool cpu name", 1);
+
+  auto original_packet = trace_packet.SerializeAsString();
+  auto packet = original_packet;
+
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &packet));
+
+  protos::gen::TracePacket gen_packet;
+  gen_packet.ParseFromString(packet);
+
+  ASSERT_TRUE(gen_packet.has_ftrace_events());
+  const auto& gen_events = gen_packet.ftrace_events();
+
+  // Because the CPU sits beside the event list, and not inside the event list,
+  // the CPU value should be retained.
+  ASSERT_TRUE(gen_events.has_cpu());
+  ASSERT_EQ(gen_events.cpu(), 7u);
+
+  // ClockSetRate should be dropped. Only TaskRename should remain.
+  ASSERT_EQ(gen_events.event_size(), 1);
+  ASSERT_FALSE(gen_events.event().front().has_clock_set_rate());
+  ASSERT_TRUE(gen_events.event().front().has_task_rename());
+}
+
+TEST(ScrubFtraceEventsTest, KeepsAllowedEvents) {
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber,
+  };
+
+  protos::gen::TracePacket before;
+  AddTaskRename(before.mutable_ftrace_events(), 7, "old_comm", "new_comm_7");
+  AddTaskRename(before.mutable_ftrace_events(), 8, "old_comm", "new_comm_8");
+  AddTaskRename(before.mutable_ftrace_events(), 9, "old_comm", "new_comm_9");
+
+  auto before_str = before.SerializeAsString();
+  auto after_str = before_str;
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &after_str));
+
+  protos::gen::TracePacket after;
+  after.ParseFromString(after_str);
+
+  // Implementation detail: ScrubFtraceEvents may change entry order. The diff
+  // must be order independent. Sort the events by pid, this will make it easier
+  // to assert values.
+  auto events = after.ftrace_events().event();
+  std::sort(events.begin(), events.end(),
+            [](const auto& l, const auto& r) { return l.pid() < r.pid(); });
+
+  ASSERT_EQ(events.size(), 3u);
+
+  ASSERT_TRUE(events[0].has_task_rename());
+  ASSERT_EQ(events[0].task_rename().pid(), 7);
+  ASSERT_EQ(events[0].task_rename().oldcomm(), "old_comm");
+  ASSERT_EQ(events[0].task_rename().newcomm(), "new_comm_7");
+
+  ASSERT_TRUE(events[1].has_task_rename());
+  ASSERT_EQ(events[1].task_rename().pid(), 8);
+  ASSERT_EQ(events[1].task_rename().oldcomm(), "old_comm");
+  ASSERT_EQ(events[1].task_rename().newcomm(), "new_comm_8");
+
+  ASSERT_TRUE(events[2].has_task_rename());
+  ASSERT_EQ(events[2].task_rename().pid(), 9);
+  ASSERT_EQ(events[2].task_rename().oldcomm(), "old_comm");
+  ASSERT_EQ(events[2].task_rename().newcomm(), "new_comm_9");
+}
+
+// Only the specific non-allowed events should be removed from the event list.
+TEST(ScrubFtraceEventsTest, OnlyDropsNotAllowedEvents) {
+  // AddTaskRename >> Keep
+  // AddClockSetRate >> Drop
+  protos::gen::TracePacket original_packet;
+  AddTaskRename(original_packet.mutable_ftrace_events(), 7, "old_comm",
+                "new_comm_7");
+  AddClockSetRate(original_packet.mutable_ftrace_events(), 0, "cool cpu name",
+                  1);
+  AddTaskRename(original_packet.mutable_ftrace_events(), 8, "old_comm",
+                "new_comm_8");
+  AddTaskRename(original_packet.mutable_ftrace_events(), 9, "old_comm",
+                "new_comm_9");
+  auto packet = original_packet.SerializeAsString();
+
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &packet));
+
+  protos::gen::TracePacket modified_packet;
+  ASSERT_TRUE(modified_packet.ParseFromString(packet));
+
+  // Only the clock set rate event should have been removed (drop 1 of the 4
+  // events).
+  ASSERT_TRUE(modified_packet.has_ftrace_events());
+  ASSERT_EQ(modified_packet.ftrace_events().event_size(), 3);
+
+  // All ftrace events should be rename events.
+  const auto& events = modified_packet.ftrace_events().event();
+
+  ASSERT_TRUE(events.at(0).has_task_rename());
+  ASSERT_EQ(events.at(0).task_rename().pid(), 7);
+
+  ASSERT_TRUE(events.at(1).has_task_rename());
+  ASSERT_EQ(events.at(1).task_rename().pid(), 8);
+
+  ASSERT_TRUE(events.at(2).has_task_rename());
+  ASSERT_EQ(events.at(2).task_rename().pid(), 9);
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_trace_packet.h b/src/trace_redaction/scrub_trace_packet.h
index fbf89ca..75c5722 100644
--- a/src/trace_redaction/scrub_trace_packet.h
+++ b/src/trace_redaction/scrub_trace_packet.h
@@ -21,8 +21,17 @@
 
 namespace perfetto::trace_redaction {
 
-// Drops whole trace packets based on an allow-list (e.g. retain ProcessTree
-// packets).
+// TODO(vaage): This primitive DOES NOT handle redacting sched_switch and
+// sched_waking ftrace events. These events contain a large amount of "to be
+// redacted" information AND there are a high quantity of them AND they are
+// large packets. As such, this primitive is not enough and an ADDITIONAL
+// primitive is required.
+
+// Goes through individual ftrace packs and drops the ftrace packets from the
+// trace packet without modifying the surround fields.
+//
+// ScrubTracePacket does not respect field order - i.e. the field order going
+// may not match the field order going out.
 class ScrubTracePacket final : public TransformPrimitive {
  public:
   base::Status Transform(const Context& context,
diff --git a/src/trace_redaction/trace_redaction_framework.h b/src/trace_redaction/trace_redaction_framework.h
index 76b8594..d22ecd2 100644
--- a/src/trace_redaction/trace_redaction_framework.h
+++ b/src/trace_redaction/trace_redaction_framework.h
@@ -117,6 +117,45 @@
   // Because "data" is a "one of", if no field in "trace_packet_allow_list" can
   // be found, it packet should be removed.
   base::FlatSet<uint32_t> trace_packet_allow_list;
+
+  // Ftrace packets contain a "one of" entry called "event". Within the scope of
+  // a ftrace event, the event can be considered the payload and other other
+  // values can be considered metadata (e.g. timestamp and pid).
+  //
+  // A ftrace event should be removed if:
+  //
+  //  ... we know it contains too much sensitive information
+  //
+  //  ... we know it contains sensitive information and we have some ideas on
+  //      to remove it, but don't have the resources to do it right now (e.g.
+  //      print).
+  //
+  //  ... we don't see value in including it
+  //
+  // "ftrace_packet_allow_list" contains field ids of ftrace packets that we
+  // want to pass onto later transformations. An example would be:
+  //
+  //  ... kSchedWakingFieldNumber because it contains cpu activity information
+  //
+  // Compared against track days, the rules around removing ftrace packets are
+  // complicated because...
+  //
+  //  packet {
+  //    ftrace_packets {  <-- ONE-OF    (1)
+  //      event {         <-- REPEATED  (2)
+  //        cpu_idle { }  <-- ONE-OF    (3)
+  //      }
+  //      event { ... }
+  //    }
+  //  }
+  //
+  //  1.  A ftrace packet will populate the one-of slot in the trace packet.
+  //
+  //  2.  A ftrace packet can have multiple events
+  //
+  //  3.  In this example, a cpu_idle event populates the one-of slot in the
+  //      ftrace event
+  base::FlatSet<uint32_t> ftrace_packet_allow_list;
 };
 
 // Responsible for extracting low-level data from the trace and storing it in
diff --git a/src/trace_redaction/trace_redactor.cc b/src/trace_redaction/trace_redactor.cc
index 94168e6..49fcf8f 100644
--- a/src/trace_redaction/trace_redactor.cc
+++ b/src/trace_redaction/trace_redactor.cc
@@ -161,17 +161,12 @@
       continue;
     }
 
-    protozero::HeapBuffered<> serializer;
-    auto* packet_message =
-        serializer->BeginNestedMessage<TracePacket>(Trace::kPacketFieldNumber);
-    packet_message->AppendRawProtoBytes(packet.data(), packet.size());
-    packet_message->Finalize();
-    serializer->Finalize();
+    protozero::HeapBuffered<protos::pbzero::Trace> serializer;
+    serializer->add_packet()->AppendRawProtoBytes(packet.data(), packet.size());
+    packet.assign(serializer.SerializeAsString());
 
-    auto encoded_packet = serializer.SerializeAsString();
-
-    if (const auto exported_data = base::WriteAll(
-            dest_fd.get(), encoded_packet.data(), encoded_packet.size());
+    if (const auto exported_data =
+            base::WriteAll(dest_fd.get(), packet.data(), packet.size());
         exported_data <= 0) {
       return base::ErrStatus("Failed to write redacted trace to disk");
     }
diff --git a/src/trace_redaction/trace_redactor_integrationtest.cc b/src/trace_redaction/trace_redactor_integrationtest.cc
index 50074b1..bd7847d 100644
--- a/src/trace_redaction/trace_redactor_integrationtest.cc
+++ b/src/trace_redaction/trace_redactor_integrationtest.cc
@@ -15,28 +15,34 @@
  */
 
 #include <cstdint>
-#include <memory>
 #include <string>
 #include <string_view>
 #include <vector>
 
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/file_utils.h"
-#include "perfetto/ext/base/temp_file.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/populate_allow_lists.h"
 #include "src/trace_redaction/prune_package_list.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/scrub_trace_packet.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 {
 
 namespace {
+using FtraceEvent = protos::pbzero::FtraceEvent;
 using PackagesList = protos::pbzero::PackagesList;
 using PackageInfo = protos::pbzero::PackagesList::PackageInfo;
 using Trace = protos::pbzero::Trace;
@@ -55,6 +61,19 @@
  protected:
   void SetUp() override {
     src_trace_ = base::GetTestDataPath(std::string(kTracePath));
+
+    // Add every primitive to the redactor. This should mirror the production
+    // configuration. This configuration may differ to help with verifying the
+    // results.
+    redactor_.collectors()->emplace_back(new FindPackageUid());
+    redactor_.builders()->emplace_back(new PopulateAllowlists());
+    redactor_.transformers()->emplace_back(new PrunePackageList());
+    redactor_.transformers()->emplace_back(new ScrubTracePacket());
+    redactor_.transformers()->emplace_back(new ScrubFtraceEvents());
+
+    // Set the package name to "just some package name". If a specific package
+    // name is needed, it should overwrite this value.
+    context_.package_name = "com.google.omadm.trigger";
   }
 
   const std::string& src_trace() const { return src_trace_; }
@@ -77,33 +96,80 @@
     return infos;
   }
 
+  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());
+  }
+
+  // NOTE - this will include fields like "timestamp" and "pid".
+  static void GetEventFields(const Trace::Decoder& trace,
+                             base::FlatSet<uint32_t>* set) {
+    for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+      TracePacket::Decoder packet(*packet_it);
+
+      if (!packet.has_ftrace_events()) {
+        continue;
+      }
+
+      protos::pbzero::FtraceEventBundle::Decoder bundle(packet.ftrace_events());
+
+      if (!bundle.has_event()) {
+        continue;
+      }
+
+      for (auto events_it = bundle.event(); events_it; ++events_it) {
+        protozero::ProtoDecoder event(*events_it);
+
+        for (auto event_it = event.ReadField(); event_it.valid();
+             event_it = event.ReadField()) {
+          set->insert(event_it.id());
+        }
+      }
+    }
+  }
+
+  static base::StatusOr<protozero::ConstBytes> FindFirstFtraceEvents(
+      const Trace::Decoder& trace) {
+    for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+      TracePacket::Decoder packet(*packet_it);
+
+      if (packet.has_ftrace_events()) {
+        return packet.ftrace_events();
+      }
+    }
+
+    return base::ErrStatus("Failed to find ftrace events");
+  }
+
   std::string src_trace_;
   base::TmpDirTree tmp_dir_;
+
+  Context context_;
+  TraceRedactor redactor_;
 };
 
 TEST_F(TraceRedactorIntegrationTest, FindsPackageAndFiltersPackageList) {
-  TraceRedactor redaction;
-  redaction.collectors()->emplace_back(new FindPackageUid());
-  redaction.transformers()->emplace_back(new PrunePackageList());
+  context_.package_name = "com.Unity.com.unity.multiplayer.samples.coop";
 
-  Context context;
-  context.package_name = "com.Unity.com.unity.multiplayer.samples.coop";
-
-  auto result = redaction.Redact(
-      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context);
+  auto result = redactor_.Redact(
+      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
   tmp_dir_.TrackFile("dst.pftrace");
 
-  ASSERT_TRUE(result.ok()) << result.message();
+  ASSERT_OK(result);
 
-  std::string redacted_buffer;
-  ASSERT_TRUE(
-      base::ReadFile(tmp_dir_.AbsolutePath("dst.pftrace"), &redacted_buffer));
+  ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
+                       ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
 
   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()),
+  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
@@ -132,21 +198,15 @@
 // 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(TraceRedactorIntegrationTest, RetainsAllInstancesOfUid) {
-  TraceRedactor redaction;
-  redaction.collectors()->emplace_back(new FindPackageUid());
-  redaction.transformers()->emplace_back(new PrunePackageList());
+  context_.package_name = "com.google.android.networkstack.tethering";
 
-  Context context;
-  context.package_name = "com.google.android.networkstack.tethering";
-
-  auto result = redaction.Redact(
-      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context);
+  auto result = redactor_.Redact(
+      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
   tmp_dir_.TrackFile("dst.pftrace");
-  ASSERT_TRUE(result.ok()) << result.message();
+  ASSERT_OK(result);
 
-  std::string redacted_buffer;
-  ASSERT_TRUE(
-      base::ReadFile(tmp_dir_.AbsolutePath("dst.pftrace"), &redacted_buffer));
+  ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
+                       ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
 
   Trace::Decoder redacted_trace(redacted_buffer);
   std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
@@ -174,5 +234,110 @@
   ASSERT_EQ(package_names[7], "com.google.android.networkstack.tethering");
 }
 
+// Makes sure all not-allowed ftrace event is removed from a trace.
+TEST_F(TraceRedactorIntegrationTest, RemovesFtraceEvents) {
+  auto pre_redaction_file = src_trace();
+  auto post_redaction_file = tmp_dir_.AbsolutePath("dst.pftrace");
+
+  // We know that there are two oom score updates in the test trace. These
+  // events are not in the allowlist and should be dropped.
+  auto pre_redaction_buffer = ReadRawTrace(pre_redaction_file);
+  ASSERT_OK(pre_redaction_buffer) << pre_redaction_buffer.status().message();
+  Trace::Decoder pre_redaction_trace(*pre_redaction_buffer);
+
+  base::FlatSet<uint32_t> pre_redaction_event_types;
+  GetEventFields(pre_redaction_trace, &pre_redaction_event_types);
+  ASSERT_GT(pre_redaction_event_types.count(
+                FtraceEvent::kOomScoreAdjUpdateFieldNumber),
+            0u);
+
+  auto result =
+      redactor_.Redact(pre_redaction_file, post_redaction_file, &context_);
+  tmp_dir_.TrackFile("dst.pftrace");
+  ASSERT_OK(result) << result.message();
+
+  auto post_redaction_buffer = ReadRawTrace(post_redaction_file);
+  ASSERT_OK(post_redaction_buffer) << post_redaction_buffer.status().message();
+  Trace::Decoder post_redaction_trace(*post_redaction_buffer);
+
+  base::FlatSet<uint32_t> post_redaction_event_types;
+  GetEventFields(post_redaction_trace, &post_redaction_event_types);
+  ASSERT_EQ(post_redaction_event_types.count(
+                FtraceEvent::kOomScoreAdjUpdateFieldNumber),
+            0u);
+}
+
+// When a event is dropped from ftrace_events, only that event should be droped,
+// the other events in the ftrace_events should be retained.
+TEST_F(TraceRedactorIntegrationTest,
+       RetainsFtraceEventsWhenRemovingFtraceEvent) {
+  auto pre_redaction_file = src_trace();
+  auto post_redaction_file = tmp_dir_.AbsolutePath("dst.pftrace");
+
+  auto pre_redaction_buffer = ReadRawTrace(pre_redaction_file);
+  ASSERT_OK(pre_redaction_buffer) << pre_redaction_buffer.status().message();
+
+  Trace::Decoder pre_redaction_trace(*pre_redaction_buffer);
+
+  auto pre_redaction_first_events = FindFirstFtraceEvents(pre_redaction_trace);
+  ASSERT_OK(pre_redaction_first_events)
+      << pre_redaction_first_events.status().message();
+
+  auto result =
+      redactor_.Redact(pre_redaction_file, post_redaction_file, &context_);
+  tmp_dir_.TrackFile("dst.pftrace");
+  ASSERT_OK(result) << result.message();
+
+  auto post_redaction_buffer = ReadRawTrace(post_redaction_file);
+  ASSERT_OK(post_redaction_buffer) << post_redaction_buffer.status().message();
+
+  Trace::Decoder post_redaction_trace(*post_redaction_buffer);
+
+  auto post_redaction_ftrace_events =
+      FindFirstFtraceEvents(post_redaction_trace);
+  ASSERT_OK(post_redaction_ftrace_events)
+      << post_redaction_ftrace_events.status().message();
+
+  base::FlatSet<uint32_t> events_before;
+  GetEventFields(pre_redaction_trace, &events_before);
+  ASSERT_EQ(events_before.size(), 14u);
+  ASSERT_TRUE(events_before.count(FtraceEvent::kTimestampFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kPidFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kPrintFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedSwitchFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kCpuFrequencyFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kCpuIdleFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedWakeupFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedWakingFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedWakeupNewFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kTaskNewtaskFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kTaskRenameFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedProcessExitFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedProcessFreeFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kOomScoreAdjUpdateFieldNumber));
+
+  base::FlatSet<uint32_t> events_after;
+  GetEventFields(post_redaction_trace, &events_after);
+  ASSERT_EQ(events_after.size(), 9u);
+
+  // Retained.
+  ASSERT_TRUE(events_after.count(FtraceEvent::kTimestampFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kPidFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kSchedSwitchFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kCpuFrequencyFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kCpuIdleFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kSchedWakingFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kTaskNewtaskFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kTaskRenameFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kSchedProcessFreeFieldNumber));
+
+  // Dropped.
+  ASSERT_FALSE(events_after.count(FtraceEvent::kPrintFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kSchedWakeupFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kSchedWakeupNewFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kSchedProcessExitFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kOomScoreAdjUpdateFieldNumber));
+}
+
 }  // namespace
 }  // namespace perfetto::trace_redaction
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 4c028d6..46b7d24 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -7701,6 +7701,19 @@
        kUnsetFtraceId,
        66,
        kUnsetSize},
+      {"rpm_status",
+       "rpm",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "name", 1, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "status", 2, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       489,
+       kUnsetSize},
       {"tracing_mark_write",
        "samsung",
        {
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/rpm/rpm_status/format b/src/traced/probes/ftrace/test/data/synthetic/events/rpm/rpm_status/format
new file mode 100644
index 0000000..0d210de
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/rpm/rpm_status/format
@@ -0,0 +1,12 @@
+name: rpm_status
+ID: 218
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:__data_loc char[] name;	offset:8;	size:4;	signed:0;
+	field:int status;	offset:12;	size:4;	signed:1;
+
+print fmt: "%s status=%s", __get_str(name), __print_symbolic(REC->status, { -1, "RPM_INVALID" }, { 0, "RPM_ACTIVE" }, { 1, "RPM_RESUMING" }, { 2, "RPM_SUSPENDED" }, { 3, "RPM_SUSPENDING" })
diff --git a/src/traced/probes/kmem_activity_trigger.cc b/src/traced/probes/kmem_activity_trigger.cc
index 7e29a96..833e3ac 100644
--- a/src/traced/probes/kmem_activity_trigger.cc
+++ b/src/traced/probes/kmem_activity_trigger.cc
@@ -66,7 +66,7 @@
       FtraceProcfs::CreateGuessingMountPoint("instances/mm_events/");
   if (!ftrace_procfs_) {
 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
-    PERFETTO_LOG(
+    PERFETTO_DLOG(
         "mm_events ftrace instance not found. Triggering of traces on memory "
         "pressure will not be available on this device.");
 #endif
diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc
index a72754c..b57f135 100644
--- a/src/tracing/service/tracing_service_impl.cc
+++ b/src/tracing/service/tracing_service_impl.cc
@@ -16,12 +16,11 @@
 
 #include "src/tracing/service/tracing_service_impl.h"
 
-#include <errno.h>
 #include <limits.h>
-#include <stdint.h>
 #include <string.h>
 
 #include <cinttypes>
+#include <cstdint>
 #include <limits>
 #include <optional>
 #include <regex>
@@ -59,7 +58,7 @@
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/metatrace.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/temp_file.h"
+#include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/uuid.h"
 #include "perfetto/ext/base/version.h"
@@ -156,8 +155,8 @@
 // Format (by bit range):
 // [   31 ][         30 ][             29:20 ][            19:10 ][        9:0]
 // [unused][has flush id][num chunks to patch][num chunks to move][producer id]
-static int32_t EncodeCommitDataRequest(ProducerID producer_id,
-                                       const CommitDataRequest& req_untrusted) {
+int32_t EncodeCommitDataRequest(ProducerID producer_id,
+                                const CommitDataRequest& req_untrusted) {
   uint32_t cmov = static_cast<uint32_t>(req_untrusted.chunks_to_move_size());
   uint32_t cpatch = static_cast<uint32_t>(req_untrusted.chunks_to_patch_size());
   uint32_t has_flush_id = req_untrusted.flush_request_id() != 0;
@@ -1015,7 +1014,7 @@
     auto range = data_sources_.equal_range(cfg_data_source.config().name());
     for (auto it = range.first; it != range.second; it++) {
       TraceConfig::ProducerConfig producer_config;
-      for (auto& config : cfg.producers()) {
+      for (const auto& config : cfg.producers()) {
         if (GetProducer(it->second.producer_id)->name_ ==
             config.producer_name()) {
           producer_config = config;
@@ -1184,7 +1183,7 @@
       // If it wasn't previously setup, set it up now.
       // (The per-producer config is optional).
       TraceConfig::ProducerConfig producer_config;
-      for (auto& config : tracing_session->config.producers()) {
+      for (const auto& config : tracing_session->config.producers()) {
         if (producer->name_ == config.producer_name()) {
           producer_config = config;
           break;
@@ -1796,13 +1795,13 @@
     data_source_instances[producer_id].push_back(ds_inst.instance_id);
   }
   FlushDataSourceInstances(tracing_session, timeout_ms, data_source_instances,
-                           callback, flush_flags);
+                           std::move(callback), flush_flags);
 }
 
 void TracingServiceImpl::FlushDataSourceInstances(
     TracingSession* tracing_session,
     uint32_t timeout_ms,
-    std::map<ProducerID, std::vector<DataSourceInstanceID>>
+    const std::map<ProducerID, std::vector<DataSourceInstanceID>>&
         data_source_instances,
     ConsumerEndpoint::FlushCallback callback,
     FlushFlags flush_flags) {
@@ -2706,7 +2705,7 @@
     }
 
     TraceConfig::ProducerConfig producer_config;
-    for (auto& config : tracing_session.config.producers()) {
+    for (const auto& config : tracing_session.config.producers()) {
       if (producer->name_ == config.producer_name()) {
         producer_config = config;
         break;
@@ -3519,6 +3518,8 @@
     utsname_info->set_machine(uname_info.machine);
     utsname_info->set_release(uname_info.release);
   }
+  info->set_page_size(static_cast<uint32_t>(sysconf(_SC_PAGESIZE)));
+  info->set_num_cpus(static_cast<uint32_t>(sysconf(_SC_NPROCESSORS_CONF)));
 #endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   std::string fingerprint_value = base::GetAndroidProp("ro.build.fingerprint");
@@ -3535,8 +3536,6 @@
   } else {
     PERFETTO_ELOG("Unable to read ro.build.version.sdk");
   }
-  info->set_hz(sysconf(_SC_CLK_TCK));
-  info->set_page_size(static_cast<uint32_t>(sysconf(_SC_PAGESIZE)));
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   packet->set_trusted_uid(static_cast<int32_t>(uid_));
   packet->set_trusted_packet_sequence_id(kServicePacketSequenceID);
@@ -3573,7 +3572,7 @@
               return a.first < b.first;
             });
 
-  for (const auto& pair : timestamped_packets)
+  for (auto& pair : timestamped_packets)
     SerializeAndAppendPacket(packets, std::move(pair.second));
 }
 
@@ -3608,14 +3607,14 @@
       auto* sync_exchange_msg = remote_clock_sync->add_synced_clocks();
 
       auto* client_snapshots = sync_exchange_msg->set_client_clocks();
-      for (auto& client_clock : sync_exchange.client_clocks) {
+      for (const auto& client_clock : sync_exchange.client_clocks) {
         auto* clock = client_snapshots->add_clocks();
         clock->set_clock_id(client_clock.clock_id);
         clock->set_timestamp(client_clock.timestamp);
       }
 
       auto* host_snapshots = sync_exchange_msg->set_host_clocks();
-      for (auto& host_clock : sync_exchange.host_clocks) {
+      for (const auto& host_clock : sync_exchange.host_clocks) {
         auto* clock = host_snapshots->add_clocks();
         clock->set_clock_id(host_clock.clock_id);
         clock->set_timestamp(host_clock.timestamp);
@@ -3805,7 +3804,7 @@
 std::map<ProducerID, std::vector<DataSourceInstanceID>>
 TracingServiceImpl::GetFlushableDataSourceInstancesForBuffers(
     TracingSession* session,
-    std::set<BufferID> bufs) {
+    const std::set<BufferID>& bufs) {
   std::map<ProducerID, std::vector<DataSourceInstanceID>> data_source_instances;
 
   for (const auto& [producer_id, ds_inst] : session->data_source_instances) {
@@ -3825,7 +3824,7 @@
 
 void TracingServiceImpl::OnFlushDoneForClone(TracingSessionID tsid,
                                              PendingCloneID clone_id,
-                                             std::set<BufferID> buf_ids,
+                                             const std::set<BufferID>& buf_ids,
                                              bool final_flush_outcome) {
   TracingSession* src = GetTracingSession(tsid);
   // The session might be gone by the time we try to clone it.
@@ -3881,7 +3880,7 @@
 
 bool TracingServiceImpl::DoCloneBuffers(
     TracingSession* src,
-    std::set<BufferID> buf_ids,
+    const std::set<BufferID>& buf_ids,
     std::vector<std::unique_ptr<TraceBuffer>>* buf_snaps) {
   PERFETTO_DCHECK(src->num_buffers() == src->config.buffers().size());
   buf_snaps->resize(src->buffers_index.size());
@@ -4273,7 +4272,7 @@
   int num_started = 0;
   for (const auto& kv : sessions)
     num_started += kv.second.state == TracingSession::State::STARTED ? 1 : 0;
-  svc_state.set_num_sessions_started(static_cast<int>(num_started));
+  svc_state.set_num_sessions_started(num_started);
 
   for (const auto& kv : service_->producers_) {
     if (args.sessions_only)
diff --git a/src/tracing/service/tracing_service_impl.h b/src/tracing/service/tracing_service_impl.h
index 5a583ba..a20fa68 100644
--- a/src/tracing/service/tracing_service_impl.h
+++ b/src/tracing/service/tracing_service_impl.h
@@ -21,7 +21,6 @@
 #include <functional>
 #include <map>
 #include <memory>
-#include <mutex>
 #include <optional>
 #include <random>
 #include <set>
@@ -332,8 +331,8 @@
   void ApplyChunkPatches(ProducerID,
                          const std::vector<CommitDataRequest::ChunkToPatch>&);
   void NotifyFlushDoneForProducer(ProducerID, FlushRequestID);
-  void NotifyDataSourceStarted(ProducerID, const DataSourceInstanceID);
-  void NotifyDataSourceStopped(ProducerID, const DataSourceInstanceID);
+  void NotifyDataSourceStarted(ProducerID, DataSourceInstanceID);
+  void NotifyDataSourceStopped(ProducerID, DataSourceInstanceID);
   void ActivateTriggers(ProducerID, const std::vector<std::string>& triggers);
 
   // Called by ConsumerEndpointImpl.
@@ -804,14 +803,14 @@
   void FlushDataSourceInstances(
       TracingSession*,
       uint32_t timeout_ms,
-      std::map<ProducerID, std::vector<DataSourceInstanceID>>,
+      const std::map<ProducerID, std::vector<DataSourceInstanceID>>&,
       ConsumerEndpoint::FlushCallback,
       FlushFlags);
   std::map<ProducerID, std::vector<DataSourceInstanceID>>
   GetFlushableDataSourceInstancesForBuffers(TracingSession*,
-                                            std::set<BufferID>);
+                                            const std::set<BufferID>&);
   bool DoCloneBuffers(TracingSession*,
-                      std::set<BufferID>,
+                      const std::set<BufferID>&,
                       std::vector<std::unique_ptr<TraceBuffer>>*);
   base::Status FinishCloneSession(ConsumerEndpointImpl*,
                                   TracingSessionID,
@@ -821,7 +820,7 @@
                                   base::Uuid*);
   void OnFlushDoneForClone(TracingSessionID src_tsid,
                            PendingCloneID clone_id,
-                           std::set<BufferID> buf_ids,
+                           const std::set<BufferID>& buf_ids,
                            bool final_flush_outcome);
 
   // Returns true if `*tracing_session` is waiting for a trigger that hasn't
diff --git a/test/cts/heapprofd_test_cts.cc b/test/cts/heapprofd_test_cts.cc
index fb5e78f..6c87cf4 100644
--- a/test/cts/heapprofd_test_cts.cc
+++ b/test/cts/heapprofd_test_cts.cc
@@ -22,6 +22,7 @@
 #include <random>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/tracing/core/data_source_config.h"
 #include "src/base/test/test_task_runner.h"
 #include "test/android_test_utils.h"
@@ -46,6 +47,11 @@
 static_assert(kExpectedIndividualAllocSz > kTestSamplingInterval,
               "kTestSamplingInterval invalid");
 
+// Path in the app external directory where the app writes an interation
+// counter. It is used to wait for the test apps to actually perform
+// allocations.
+constexpr std::string_view kReportCyclePath = "report_cycle.txt";
+
 // Activity that runs a JNI thread that repeatedly calls
 // malloc(kExpectedIndividualAllocSz).
 static char kMallocActivity[] = "MainActivity";
@@ -65,6 +71,48 @@
   return result;
 }
 
+std::optional<int64_t> ReadInt64FromFile(const std::string& path) {
+  std::string contents;
+  if (!base::ReadFile(path, &contents)) {
+    return std::nullopt;
+  }
+  return base::StringToInt64(contents);
+}
+
+bool WaitForAppAllocationCycle(const std::string& app_name, size_t timeout_ms) {
+  const size_t sleep_per_attempt_us = 100 * 1000;
+  const size_t max_attempts = timeout_ms * 1000 / sleep_per_attempt_us;
+
+  std::string path = std::string("/sdcard/Android/data/") + app_name +
+                     std::string("/files/") + std::string(kReportCyclePath);
+
+  for (size_t attempts = 0; attempts < max_attempts;) {
+    int64_t first_value;
+    for (; attempts < max_attempts; attempts++) {
+      std::optional<int64_t> val = ReadInt64FromFile(path);
+      if (val) {
+        first_value = *val;
+        break;
+      }
+      base::SleepMicroseconds(sleep_per_attempt_us);
+    }
+
+    for (; attempts < max_attempts; attempts++) {
+      std::optional<int64_t> val = ReadInt64FromFile(path);
+      if (!val || *val < first_value) {
+        break;
+      }
+      if (*val >= first_value + 2) {
+        // We've observed the counter being incremented twice. We can be sure
+        // that the app has gone through a full allocation cycle.
+        return true;
+      }
+      base::SleepMicroseconds(sleep_per_attempt_us);
+    }
+  }
+  return false;
+}
+
 // Starts the activity `activity` of the app `app_name` and later starts
 // recording a trace with the allocations in `heap_names`.
 //
@@ -95,7 +143,6 @@
 
   TraceConfig trace_config;
   trace_config.add_buffers()->set_size_kb(10 * 1024);
-  trace_config.set_duration_ms(4000);
   trace_config.set_unique_session_name(RandomSessionName().c_str());
 
   auto* ds_config = trace_config.add_data_sources()->mutable_config();
@@ -114,6 +161,10 @@
 
   // start tracing
   helper.StartTracing(trace_config);
+
+  EXPECT_TRUE(WaitForAppAllocationCycle(app_name, /*timeout_ms=*/10000));
+
+  helper.DisableTracing();
   helper.WaitForTracingDisabled();
   helper.ReadData();
   helper.WaitForReadData();
@@ -148,7 +199,6 @@
 
   TraceConfig trace_config;
   trace_config.add_buffers()->set_size_kb(10 * 1024);
-  trace_config.set_duration_ms(4000);
   trace_config.set_enable_extra_guardrails(enable_extra_guardrails);
   trace_config.set_unique_session_name(RandomSessionName().c_str());
 
@@ -174,6 +224,9 @@
                    /*delay_ms=*/100);
   task_runner.RunUntilCheckpoint("target.app.running", 10000 /*ms*/);
 
+  EXPECT_TRUE(WaitForAppAllocationCycle(app_name, /*timeout_ms=*/10000));
+
+  helper.DisableTracing();
   helper.WaitForTracingDisabled();
   helper.ReadData();
   helper.WaitForReadData();
diff --git a/test/cts/test_apps/jni/target.cc b/test/cts/test_apps/jni/target.cc
index 8f847c4..9b843a2 100644
--- a/test/cts/test_apps/jni/target.cc
+++ b/test/cts/test_apps/jni/target.cc
@@ -15,8 +15,13 @@
  */
 
 #include <jni.h>
-#include <stdlib.h>
 #include <unistd.h>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+
+#include <fstream>
+#include <limits>
 
 namespace {
 
@@ -24,15 +29,47 @@
 constexpr int kIndividualAllocSz = 4153;
 constexpr int kAllocationIntervalUs = 10 * 1000;
 
-__attribute__((noreturn)) void perfetto_test_allocations() {
-  for (;;) {
-    // volatile & use avoids builtin malloc optimizations
-    volatile char* x = static_cast<char*>(malloc(kIndividualAllocSz));
-    if (x) {
-      x[0] = '\0';
-      free(const_cast<char*>(x));
+// Increments a value in the text file `path`. The file is read by the CTS test
+// to observe the app progress.
+void ReportCycle(const char* path) {
+  int64_t value = 0;
+  {
+    // Read the previous value from the file (it might be from a separate
+    // execution of this app).
+    std::ifstream ifs(path);
+    if (ifs) {
+      ifs >> value;
     }
-    usleep(kAllocationIntervalUs);
+  }
+
+  std::string tmppath = std::string(path) + std::string(".tmp");
+  std::ofstream ofs(tmppath, std::ios::trunc);
+  if (value == std::numeric_limits<int64_t>::max()) {
+    value = std::numeric_limits<int64_t>::min();
+  } else {
+    value++;
+  }
+  ofs << value;
+  ofs.close();
+  if (!ofs) {
+    abort();
+  }
+  rename(tmppath.c_str(), path);
+}
+
+__attribute__((noreturn)) void perfetto_test_allocations(
+    const char* report_cycle_path) {
+  for (;;) {
+    for (size_t j = 0; j < 20; j++) {
+      // volatile & use avoids builtin malloc optimizations
+      volatile char* x = static_cast<char*>(malloc(kIndividualAllocSz));
+      if (x) {
+        x[0] = '\0';
+        free(const_cast<char*>(x));
+      }
+      usleep(kAllocationIntervalUs);
+    }
+    ReportCycle(report_cycle_path);
   }
 }
 
@@ -45,8 +82,13 @@
 }  // namespace
 
 extern "C" JNIEXPORT void JNICALL
-Java_android_perfetto_cts_app_MainActivity_runNative(JNIEnv*, jclass) {
-  perfetto_test_allocations();
+Java_android_perfetto_cts_app_MainActivity_runNative(
+    JNIEnv* env,
+    jclass,
+    jstring jreport_cycle_path) {
+  const char* path = env->GetStringUTFChars(jreport_cycle_path, NULL);
+  perfetto_test_allocations(path);
+  env->ReleaseStringUTFChars(jreport_cycle_path, NULL);
 }
 
 extern "C" JNIEXPORT void JNICALL
diff --git a/test/cts/test_apps/src/android/perfetto/cts/app/JavaAllocActivity.java b/test/cts/test_apps/src/android/perfetto/cts/app/JavaAllocActivity.java
index 077d32f..cffe3e4 100644
--- a/test/cts/test_apps/src/android/perfetto/cts/app/JavaAllocActivity.java
+++ b/test/cts/test_apps/src/android/perfetto/cts/app/JavaAllocActivity.java
@@ -19,9 +19,19 @@
 import android.app.Activity;
 import android.os.Bundle;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Scanner;
 import java.util.TreeMap;
 
 public class JavaAllocActivity extends Activity {
+    // Keep in sync with heapprofd_test_cts.cc
+    private static final String CYCLE_REPORT_PATH = "report_cycle.txt";
+
     @Override
     public void onCreate(Bundle state) {
         super.onCreate(state);
@@ -29,7 +39,7 @@
         new Thread(new Runnable() {
             public void run() {
                 try {
-                    runAllocationLoop();
+                    runAllocationLoop(getExternalFilesDir(null));
                 } catch (Exception ex) {
                     ex.printStackTrace();
                 }
@@ -40,12 +50,13 @@
     private static TreeMap treeMap = new TreeMap();
     private static long index = 0;
 
-    private static void runAllocationLoop() {
+    private static void runAllocationLoop(File external) throws IOException {
         for (;;) {
-            for (int i = 0; i < 1000; i++) {
+            for (int i = 0; i < 2000; i++) {
                 Object o = new Object();
                 treeMap.put(++index, o);
             }
+            reportCycle(external);
             try {
                 Thread.sleep(10);
             } catch (InterruptedException ignored) {
@@ -53,4 +64,27 @@
             treeMap.clear();
         }
     }
+
+    // Increments a value in a file in the app `external` directory. The file is read by the CTS
+    // test to observe the app progress.
+    private static void reportCycle(File external) throws IOException {
+        File f = new File(external, CYCLE_REPORT_PATH);
+        File tmp = new File(external, CYCLE_REPORT_PATH + ".tmp");
+        long val = 0;
+        // Read the previous value from the file (it might be from a separate execution of this
+        // app).
+        try (Scanner scanner = new Scanner(f)) {
+            if (scanner.hasNextLong()) {
+                val = scanner.nextLong();
+            }
+        } catch (FileNotFoundException ignored) {
+        }
+
+        try (FileWriter wr = new FileWriter(tmp)) {
+            wr.write(Long.toString(val + 1));
+        }
+
+        Files.move(tmp.toPath(), f.toPath(), StandardCopyOption.REPLACE_EXISTING,
+                StandardCopyOption.ATOMIC_MOVE);
+    }
 }
diff --git a/test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java b/test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java
index 0cabd16..efddd67 100644
--- a/test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java
+++ b/test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java
@@ -19,7 +19,12 @@
 import android.app.Activity;
 import android.os.Bundle;
 
+import java.io.File;
+
 public class MainActivity extends Activity {
+    // Keep in sync with heapprofd_test_cts.cc
+    private static final String CYCLE_REPORT_PATH = "report_cycle.txt";
+
     static {
         System.loadLibrary("perfettocts_native");
     }
@@ -31,7 +36,8 @@
         new Thread(new Runnable() {
             public void run() {
                 try {
-                    runNative();
+                    File running = new File(getExternalFilesDir(null), CYCLE_REPORT_PATH);
+                    runNative(running.getPath());
                 } catch (Exception ex) {
                     ex.printStackTrace();
                 }
@@ -39,5 +45,5 @@
         }).start();
     }
 
-    private static native void runNative();
+    private static native void runNative(String running);
 }
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index fb9e893..a5347cf 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -102,6 +102,7 @@
 from diff_tests.stdlib.graphs.dominator_tree_tests import DominatorTree
 from diff_tests.stdlib.graphs.search_tests import GraphSearchTests
 from diff_tests.stdlib.linux.tests import LinuxStdlib
+from diff_tests.stdlib.memory.heap_graph_dominator_tree_tests import HeapGraphDominatorTree
 from diff_tests.stdlib.pkvm.tests import Pkvm
 from diff_tests.stdlib.prelude.math_functions_tests import PreludeMathFunctions
 from diff_tests.stdlib.prelude.pprof_functions_tests import PreludePprofFunctions
@@ -247,12 +248,14 @@
       *GraphSearchTests(index_path, 'stdlib/graphs',
                         'GraphSearchTests').fetch(),
       *StdlibCounterIntervals(index_path, 'stdlib/counters',
-                       'StdlibCounterIntervals').fetch(),
+                              'StdlibCounterIntervals').fetch(),
       *DynamicTables(index_path, 'stdlib/dynamic_tables',
                      'DynamicTables').fetch(),
       *LinuxStdlib(index_path, 'stdlib/linux', 'LinuxStdlib').fetch(),
       *PreludeMathFunctions(index_path, 'stdlib/prelude',
                             'PreludeMathFunctions').fetch(),
+      *HeapGraphDominatorTree(index_path, 'stdlib/memory',
+                              'HeapGraphDominatorTree').fetch(),
       *PreludePprofFunctions(index_path, 'stdlib/prelude',
                              'PreludePprofFunctions').fetch(),
       *PreludeWindowFunctions(index_path, 'stdlib/prelude',
diff --git a/test/trace_processor/diff_tests/stdlib/android/tests.py b/test/trace_processor/diff_tests/stdlib/android/tests.py
index 956ac87..07801ad 100644
--- a/test/trace_processor/diff_tests/stdlib/android/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/tests.py
@@ -357,7 +357,7 @@
         trace=DataPath('android_monitor_contention_trace.atr'),
         query="""
       INCLUDE PERFETTO MODULE android.thread;
-      SELECT * FROM ANDROID_THREAD_CREATION_SPAM(1e9, 1e9);
+      SELECT * FROM _android_thread_creation_spam(1e9, 1e9);
       """,
         out=Csv("""
       "process_name","pid","thread_name_prefix","max_count_per_sec"
diff --git a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py
new file mode 100644
index 0000000..52bb465
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Path
+from python.generators.diff_tests.testing import Csv
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class HeapGraphDominatorTree(TestSuite):
+
+  def test_heap_graph_dominator_tree(self):
+    return DiffTestBlueprint(
+        trace=Path('heap_graph_for_dominator_tree.textproto'),
+        query="""
+          INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree;
+
+          SELECT
+            node.id,
+            node.idom_id,
+            node.dominated_obj_count,
+            node.dominated_size_bytes,
+            node.depth,
+            cls.name AS type_name
+          FROM memory_heap_graph_dominator_tree node
+          JOIN heap_graph_object obj USING(id)
+          JOIN heap_graph_class cls ON obj.type_id = cls.id
+          ORDER BY type_name;
+        """,
+        out=Csv("""
+          "id","idom_id","dominated_obj_count","dominated_size_bytes",\
+"depth","type_name"
+          0,12,1,3,2,"A"
+          2,12,1,3,2,"B"
+          4,12,4,12,2,"C"
+          1,12,2,6,2,"D"
+          3,12,1,3,2,"E"
+          5,4,1,3,3,"F"
+          6,4,2,6,3,"G"
+          8,12,1,3,2,"H"
+          9,12,1,3,2,"I"
+          10,6,1,3,4,"J"
+          11,12,1,3,2,"K"
+          7,1,1,3,3,"L"
+          13,22,6,922,2,"M"
+          16,22,3,100,2,"N"
+          14,13,4,904,3,"O"
+          15,13,1,16,3,"P"
+          17,16,1,32,3,"Q"
+          12,25,13,39,1,"R"
+          22,25,10,1023,1,"S"
+          18,16,1,64,3,"T"
+          19,14,1,128,4,"U"
+          20,14,1,256,4,"V"
+          21,14,1,512,4,"W"
+          23,25,1,1024,1,"java.lang.ref.FinalizerReference"
+        """))
+
+  def test_heap_graph_super_root_fn(self):
+    return DiffTestBlueprint(
+        trace=Path('heap_graph_for_dominator_tree.textproto'),
+        query="""
+          INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree;
+
+          SELECT memory_heap_graph_super_root_fn();
+        """,
+        out=Csv("""
+          "memory_heap_graph_super_root_fn()"
+          25
+        """))
diff --git a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto
new file mode 100644
index 0000000..b0de072
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto
@@ -0,0 +1,344 @@
+packet {
+  process_tree {
+    processes {
+      pid: 1
+      ppid: 0
+      cmdline: "init"
+      uid: 0
+    }
+    processes {
+      pid: 2
+      ppid: 1
+      cmdline: "system_server"
+      uid: 1000
+    }
+  }
+}
+packet {
+  trusted_packet_sequence_id: 999
+  timestamp: 10
+
+  # The heap graph contains two separate parts.
+  # Each object has a unique class name for easy demo and debug.
+  # The two parts of the graph are:
+  # 1. the Lengauer-Tarjan example graph, with a root object of type "R". All
+  #    objects in this part are 3 bytes in size.
+  # 2. a synthetic tree whose dominator tree is itself. It's drawn below with
+  #    each object represented by it's class name. Number in the bracket is the
+  #    size of each node in bytes.
+  #                 S[1]     java.lang.ref.FinalizerReference[1024]
+  #                /    \    /
+  #            M[2]      N[4]
+  #           /   \      /   \
+  #        O[8]  P[16] Q[32] T[64]
+  #      /  |  \
+  #     U   V   W
+  # [128] [256] [512]
+
+  heap_graph {
+    pid: 2
+    roots {
+      root_type: ROOT_JNI_GLOBAL
+      object_ids: 0x12  # root of the Lengauer-Tarjan example graph
+      object_ids: 0x13  # root of the synthetic tree
+      object_ids: 0x18  # just to test type exclusion
+    }
+    objects {
+      id: 0x01
+      type_id: 1
+      self_size: 3
+      reference_object_id: 0x04
+    }
+    objects {
+      id: 0x02
+      type_id: 2
+      self_size: 3
+      reference_object_id: 0x01
+      reference_object_id: 0x04
+      reference_object_id: 0x05
+    }
+    objects {
+      id: 0x03
+      type_id: 3
+      self_size: 3
+      reference_object_id: 0x06
+      reference_object_id: 0x07
+    }
+    objects {
+      id: 0x04
+      type_id: 4
+      self_size: 3
+      reference_object_id: 0x0c
+    }
+    objects {
+      id: 0x05
+      type_id: 5
+      self_size: 3
+      reference_field_id: 2
+      reference_object_id: 0x08
+    }
+    objects {
+      id: 0x06
+      type_id: 6
+      self_size: 3
+      reference_object_id: 0x09
+    }
+    objects {
+      id: 0x07
+      type_id: 7
+      self_size: 3
+      reference_object_id: 0x09
+      reference_object_id: 0x0a
+    }
+    objects {
+      id: 0x08
+      type_id: 8
+      self_size: 3
+      reference_object_id: 0x05
+      reference_object_id: 0x0b
+    }
+    objects {
+      id: 0x09
+      type_id: 9
+      self_size: 3
+      reference_object_id: 0x0b
+    }
+    objects {
+      id: 0x0a
+      type_id: 10
+      self_size: 3
+      reference_object_id: 0x09
+    }
+    objects {
+      id: 0x0b
+      type_id: 11
+      self_size: 3
+      reference_object_id: 0x09
+      reference_object_id: 0x12
+    }
+    objects {
+      id: 0x0c
+      type_id: 12
+      self_size: 3
+      reference_object_id: 0x08
+    }
+    objects {
+      id: 0x0d
+      type_id: 13
+      self_size: 2
+      reference_object_id: 0x0f
+      reference_object_id: 0x10
+    }
+    objects {
+      id: 0x0e
+      type_id: 14
+      self_size: 4
+      reference_object_id: 0x11
+      reference_object_id: 0x14
+    }
+    objects {
+      id: 0x0f
+      type_id: 15
+      self_size: 8
+      reference_object_id: 0x15
+      reference_object_id: 0x16
+      reference_object_id: 0x17
+    }
+    objects {
+      id: 0x10
+      type_id: 16
+      self_size: 16
+    }
+    objects {
+      id: 0x11
+      type_id: 17
+      self_size: 32
+    }
+    objects {
+      id: 0x12
+      type_id: 18
+      self_size: 3
+      reference_object_id: 0x01
+      reference_object_id: 0x02
+      reference_object_id: 0x03
+    }
+    objects {
+      id: 0x13
+      type_id: 19
+      self_size: 1
+      reference_object_id: 0x0d
+      reference_object_id: 0x0e
+    }
+    objects {
+      id: 0x14
+      type_id: 20
+      self_size: 64
+    }
+    objects {
+      id: 0x15
+      type_id: 21
+      self_size: 128
+    }
+    objects {
+      id: 0x16
+      type_id: 22
+      self_size: 256
+    }
+    objects {
+      id: 0x17
+      type_id: 23
+      self_size: 512
+    }
+    objects {
+      id: 0x18
+      type_id: 24 # "java.lang.ref.FinalizerReference"
+      self_size: 1024
+      reference_object_id: 0x0e
+    }
+    objects {
+      id: 0x19
+      type_id: 1 # unreachable, should be excluded
+      self_size: 1024
+    }
+    continued: true
+    index: 0
+  }
+}
+packet {
+  trusted_packet_sequence_id: 999
+  timestamp: 10
+  heap_graph {
+    pid: 2
+    location_names {
+      iid: 1
+      str: "/data/app/~~ASDFG==/invalid.test.android-SDASD/test.apk"
+    }
+    types {
+      id: 1
+      class_name: "A"
+      location_id: 1
+      object_size: 64
+    }
+    types {
+      id: 2
+      class_name: "B"
+      location_id: 1
+      object_size: 64
+    }
+    types {
+      id: 3
+      class_name: "C"
+      location_id: 1
+      object_size: 64
+    }
+    types {
+      id: 4
+      class_name: "D"
+      location_id: 1
+      object_size: 128
+    }
+    types {
+      id: 5
+      class_name: "E"
+      location_id: 1
+      object_size: 1024
+    }
+    types {
+      id: 6
+      class_name: "F"
+      location_id: 1
+    }
+    types {
+      id: 7
+      class_name: "G"
+      location_id: 1
+    }
+    types {
+      id: 8
+      class_name: "H"
+      location_id: 1
+    }
+    types {
+      id: 9
+      class_name: "I"
+      location_id: 1
+    }
+    types {
+      id: 10
+      class_name: "J"
+      location_id: 1
+    }
+    types {
+      id: 11
+      class_name: "K"
+      location_id: 1
+    }
+    types {
+      id: 12
+      class_name: "L"
+      location_id: 1
+    }
+    types {
+      id: 13
+      class_name: "M"
+      location_id: 1
+    }
+    types {
+      id: 14
+      class_name: "N"
+      location_id: 1
+    }
+    types {
+      id: 15
+      class_name: "O"
+      location_id: 1
+    }
+    types {
+      id: 16
+      class_name: "P"
+      location_id: 1
+    }
+    types {
+      id: 17
+      class_name: "Q"
+      location_id: 1
+    }
+    types {
+      id: 18
+      class_name: "R"
+      location_id: 1
+    }
+    types {
+      id: 19
+      class_name: "S"
+      location_id: 1
+    }
+    types {
+      id: 20
+      class_name: "T"
+      location_id: 1
+    }
+    types {
+      id: 21
+      class_name: "U"
+      location_id: 1
+    }
+    types {
+      id: 22
+      class_name: "V"
+      location_id: 1
+    }
+    types {
+      id: 23
+      class_name: "W"
+      location_id: 1
+    }
+    types {
+      id: 24
+      class_name: "java.lang.ref.FinalizerReference"
+      location_id: 1
+    }
+    continued: false
+    index: 1
+  }
+}
diff --git a/test/trace_processor/diff_tests/stdlib/sched/tests.py b/test/trace_processor/diff_tests/stdlib/sched/tests.py
index 443d049..2438dea 100644
--- a/test/trace_processor/diff_tests/stdlib/sched/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/sched/tests.py
@@ -45,7 +45,7 @@
         trace=Path('../../common/synth_1.py'),
         query="""
       INCLUDE PERFETTO MODULE sched.thread_level_parallelism;
-      
+
       SELECT * FROM sched_active_cpu_count;
       """,
         out=Csv("""
@@ -109,7 +109,7 @@
         query="""
         INCLUDE PERFETTO MODULE sched.utilization.process;
 
-        SELECT * 
+        SELECT *
         FROM sched_process_utilization_per_second(10);
         """,
         out=Csv("""
@@ -133,7 +133,7 @@
         query="""
         INCLUDE PERFETTO MODULE sched.utilization.thread;
 
-        SELECT * 
+        SELECT *
         FROM sched_thread_utilization_per_second(10);
         """,
         out=Csv("""
@@ -161,3 +161,53 @@
         91000000000,0.000025,0.000201
         92000000000,0.000009,0.000071
         """))
+
+  def test_sched_thread_time_in_state(self):
+    return DiffTestBlueprint(
+        trace=DataPath('example_android_trace_30s.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE sched.time_in_state;
+
+        SELECT *
+        FROM sched_thread_time_in_state
+        ORDER BY utid, state
+        LIMIT 10;
+        """,
+        out=Csv("""
+        "utid","total_runtime","state","time_in_state","percentage_in_state"
+        1,27540674878,"D",596720,0
+        1,27540674878,"R",1988438,0
+        1,27540674878,"R+",2435415,0
+        1,27540674878,"Running",23098223,0
+        1,27540674878,"S",27512556082,99
+        2,27761417087,"D",833039830,3
+        2,27761417087,"R+",2931096,0
+        2,27761417087,"Running",92350845,0
+        2,27761417087,"S",26833095316,96
+        3,29374171050,"R",140800325,0
+        """))
+
+  def test_sched_percentage_of_time_in_state(self):
+    return DiffTestBlueprint(
+        trace=DataPath('example_android_trace_30s.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE sched.time_in_state;
+
+        SELECT *
+        FROM sched_percentage_of_time_in_state
+        ORDER BY utid
+        LIMIT 10;
+        """,
+        out=Csv("""
+        "utid","running","runnable","runnable_preempted","sleeping","uninterruptible_sleep","other"
+        1,0,0,0,99,0,"[NULL]"
+        2,0,"[NULL]",0,96,3,"[NULL]"
+        3,5,0,0,93,"[NULL]","[NULL]"
+        4,100,"[NULL]","[NULL]","[NULL]","[NULL]",0
+        5,0,0,0,99,0,"[NULL]"
+        6,0,"[NULL]",0,99,"[NULL]","[NULL]"
+        7,0,0,0,99,"[NULL]","[NULL]"
+        8,0,0,0,98,0,"[NULL]"
+        9,0,"[NULL]","[NULL]",99,"[NULL]","[NULL]"
+        10,0,"[NULL]",0,99,"[NULL]","[NULL]"
+        """))
diff --git a/test/trace_processor/diff_tests/syntax/filtering_tests.py b/test/trace_processor/diff_tests/syntax/filtering_tests.py
index adf8c3e..22b4111 100644
--- a/test/trace_processor/diff_tests/syntax/filtering_tests.py
+++ b/test/trace_processor/diff_tests/syntax/filtering_tests.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from python.generators.diff_tests.testing import DataPath
+from python.generators.diff_tests.testing import DataPath, TextProto
 from python.generators.diff_tests.testing import Csv
 from python.generators.diff_tests.testing import DiffTestBlueprint
 from python.generators.diff_tests.testing import TestSuite
@@ -189,3 +189,49 @@
         "cnt"
         0
         """))
+
+  def test_string_null_vs_empty(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO TABLE foo AS
+        SELECT 0 as id, NULL AS strings
+        UNION ALL
+        SELECT 1, 'cheese'
+        UNION ALL
+        SELECT 2, NULL
+        UNION ALL
+        SELECT 3, '';
+
+        SELECT * FROM foo ORDER BY strings ASC;
+        """,
+        out=Csv("""
+        "id","strings"
+        0,"[NULL]"
+        2,"[NULL]"
+        3,""
+        1,"cheese"
+        """))
+
+  def test_string_null_vs_empty_desc(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO TABLE foo AS
+        SELECT 0 as id, NULL AS strings
+        UNION ALL
+        SELECT 1, 'cheese'
+        UNION ALL
+        SELECT 2, NULL
+        UNION ALL
+        SELECT 3, '';
+
+        SELECT * FROM foo ORDER BY strings DESC;
+        """,
+        out=Csv("""
+        "id","strings"
+        1,"cheese"
+        3,""
+        0,"[NULL]"
+        2,"[NULL]"
+        """))
diff --git a/test/trace_processor/diff_tests/syntax/table_tests.py b/test/trace_processor/diff_tests/syntax/table_tests.py
index 10ec9ab..0125c72 100644
--- a/test/trace_processor/diff_tests/syntax/table_tests.py
+++ b/test/trace_processor/diff_tests/syntax/table_tests.py
@@ -136,11 +136,14 @@
         trace=DataPath('android_boot.pftrace'),
         query="""
         CREATE PERFETTO TABLE foo AS
-        SELECT dur FROM slice WHERE dur != -1
-        GROUP BY 1 ORDER BY 1;
+        SELECT 2 AS c
+        UNION
+        SELECT 4
+        UNION
+        SELECT 6;
 
         SELECT col_type FROM perfetto_table_info('foo')
-        WHERE name = 'dur';
+        WHERE name = 'c';
         """,
         out=Csv("""
         "col_type"
diff --git a/tools/cpu_profile b/tools/cpu_profile
index 3403e38..9d84d12 100755
--- a/tools/cpu_profile
+++ b/tools/cpu_profile
@@ -37,7 +37,7 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v43.1
+# This file has been generated by: tools/roll-prebuilts v43.2
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
@@ -46,9 +46,9 @@
     'file_size':
         7790424,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-amd64/traceconv',
     'sha256':
-        '88007b64828e835e0326c11f66f0bba7d8ab117562963086a4f19d8cb060204d',
+        'c1d9c50c89545b41af88525dc6f3ce508156ed3787ccecae0ff7c8e736c39318',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -60,9 +60,9 @@
     'file_size':
         7264824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-arm64/traceconv',
     'sha256':
-        'be5769279ef8442e80130e4bdb6a0a6aa11305442207ea18ff2cf38b21a71a57',
+        'df5349ae462dbd7c1ca9a1b8a0f09c044a47026d6ad8dc24e6945701d7c61a84',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -74,9 +74,9 @@
     'file_size':
         7885952,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-amd64/traceconv',
     'sha256':
-        '51cfdf5060bcd87d08402620d88d0243f7bb39f2878906614d53fa3ddd78dd92',
+        'b2c19364c1fb68e9f5cde610e5d71dd59b9fdf2bada8f7e1eefc319f828f7cb1',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -88,9 +88,9 @@
     'file_size':
         5919372,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm/traceconv',
     'sha256':
-        '04300b1c4dcec1e01bc23017dab3b406f9f0ffd7dd9ea3723784aa8730762bc9',
+        'b669be326b4b6a024e557e0927f1014fd1ea5d5427e194dc0653f21acac273ee',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -102,9 +102,9 @@
     'file_size':
         7588200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm64/traceconv',
     'sha256':
-        'd3edc1cd7b216e18955135e0e9e767cdd7b1b8b7efa64793aa6b923a6c278d68',
+        'aff1c4751e721733ce85f58048c17971399fe605a81ac300d306c200d6957818',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -116,9 +116,9 @@
     'file_size':
         5931120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm/traceconv',
     'sha256':
-        '8c3cb3dc96aa6ca296876b8ed56f8eed8c33e12e756b178360cc145263130e7e'
+        '826212f658fef744fbaeea66331b6fe7ca0152f69cf63ff2ea218a376d5d41d9'
 }, {
     'arch':
         'android-arm64',
@@ -127,9 +127,9 @@
     'file_size':
         7546224,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm64/traceconv',
     'sha256':
-        '180cfd2184d601c8f202b6bcd899cc7f63a8bb384505c1a2c3e889dfbe8bdb6d'
+        '29dd7e93e9182c4413a9f9c1c6a6f643f64e1fe0b9657ab1ea3cec8b0bb360c9'
 }, {
     'arch':
         'android-x86',
@@ -138,9 +138,9 @@
     'file_size':
         8176528,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x86/traceconv',
     'sha256':
-        'a4e8ff19daa58726138aa66f5adae74b609fceee403c8cddbaaf46d6d07e4cc8'
+        '63c2ebe7ed51f9667bcf69d7b9679f6077db5fd8ee9e1be7b786037e2a649fcb'
 }, {
     'arch':
         'android-x64',
@@ -149,9 +149,9 @@
     'file_size':
         7767560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x64/traceconv',
     'sha256':
-        '19626b87f8c8d956d3807d24faf5764c6bca289f55732cae2f6753dbec33e7f7'
+        'bb9350230c2fac5adf9e6fe21937865b6eaafaefc555ae26e68cae9419ad5ee8'
 }, {
     'arch':
         'windows-amd64',
@@ -160,9 +160,9 @@
     'file_size':
         7645696,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/windows-amd64/traceconv.exe',
     'sha256':
-        '24eb5322f22c0219694789fa04aaa5ad09b0746f8b993fd1713e6b3f7943708a',
+        '0c84b712941e4f63f74e66731745f94aec3cd30d94469e52cdf1143262f063a4',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/heap_profile b/tools/heap_profile
index 0ab79a6..39249bb 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -34,7 +34,7 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v43.1
+# This file has been generated by: tools/roll-prebuilts v43.2
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
@@ -43,9 +43,9 @@
     'file_size':
         7790424,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-amd64/traceconv',
     'sha256':
-        '88007b64828e835e0326c11f66f0bba7d8ab117562963086a4f19d8cb060204d',
+        'c1d9c50c89545b41af88525dc6f3ce508156ed3787ccecae0ff7c8e736c39318',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -57,9 +57,9 @@
     'file_size':
         7264824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-arm64/traceconv',
     'sha256':
-        'be5769279ef8442e80130e4bdb6a0a6aa11305442207ea18ff2cf38b21a71a57',
+        'df5349ae462dbd7c1ca9a1b8a0f09c044a47026d6ad8dc24e6945701d7c61a84',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -71,9 +71,9 @@
     'file_size':
         7885952,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-amd64/traceconv',
     'sha256':
-        '51cfdf5060bcd87d08402620d88d0243f7bb39f2878906614d53fa3ddd78dd92',
+        'b2c19364c1fb68e9f5cde610e5d71dd59b9fdf2bada8f7e1eefc319f828f7cb1',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -85,9 +85,9 @@
     'file_size':
         5919372,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm/traceconv',
     'sha256':
-        '04300b1c4dcec1e01bc23017dab3b406f9f0ffd7dd9ea3723784aa8730762bc9',
+        'b669be326b4b6a024e557e0927f1014fd1ea5d5427e194dc0653f21acac273ee',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -99,9 +99,9 @@
     'file_size':
         7588200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm64/traceconv',
     'sha256':
-        'd3edc1cd7b216e18955135e0e9e767cdd7b1b8b7efa64793aa6b923a6c278d68',
+        'aff1c4751e721733ce85f58048c17971399fe605a81ac300d306c200d6957818',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -113,9 +113,9 @@
     'file_size':
         5931120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm/traceconv',
     'sha256':
-        '8c3cb3dc96aa6ca296876b8ed56f8eed8c33e12e756b178360cc145263130e7e'
+        '826212f658fef744fbaeea66331b6fe7ca0152f69cf63ff2ea218a376d5d41d9'
 }, {
     'arch':
         'android-arm64',
@@ -124,9 +124,9 @@
     'file_size':
         7546224,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm64/traceconv',
     'sha256':
-        '180cfd2184d601c8f202b6bcd899cc7f63a8bb384505c1a2c3e889dfbe8bdb6d'
+        '29dd7e93e9182c4413a9f9c1c6a6f643f64e1fe0b9657ab1ea3cec8b0bb360c9'
 }, {
     'arch':
         'android-x86',
@@ -135,9 +135,9 @@
     'file_size':
         8176528,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x86/traceconv',
     'sha256':
-        'a4e8ff19daa58726138aa66f5adae74b609fceee403c8cddbaaf46d6d07e4cc8'
+        '63c2ebe7ed51f9667bcf69d7b9679f6077db5fd8ee9e1be7b786037e2a649fcb'
 }, {
     'arch':
         'android-x64',
@@ -146,9 +146,9 @@
     'file_size':
         7767560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x64/traceconv',
     'sha256':
-        '19626b87f8c8d956d3807d24faf5764c6bca289f55732cae2f6753dbec33e7f7'
+        'bb9350230c2fac5adf9e6fe21937865b6eaafaefc555ae26e68cae9419ad5ee8'
 }, {
     'arch':
         'windows-amd64',
@@ -157,9 +157,9 @@
     'file_size':
         7645696,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/windows-amd64/traceconv.exe',
     'sha256':
-        '24eb5322f22c0219694789fa04aaa5ad09b0746f8b993fd1713e6b3f7943708a',
+        '0c84b712941e4f63f74e66731745f94aec3cd30d94469e52cdf1143262f063a4',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/record_android_trace b/tools/record_android_trace
index 7199429..f77222b 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -33,7 +33,7 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts v43.1
+# This file has been generated by: tools/roll-prebuilts v43.2
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
@@ -42,9 +42,9 @@
     'file_size':
         1564728,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-amd64/tracebox',
     'sha256':
-        'dde1f657b10376f3fd684d1ce4302fd12c0479b567689f5dace8647375edd08c',
+        '239736808cbfba5085892e15c145381ea37ddba5df7c8fad97b68d9c04a4d860',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -56,9 +56,9 @@
     'file_size':
         1459160,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-arm64/tracebox',
     'sha256':
-        '349fc531090e134d708bfe2c44330c2f08280aa424f4e9f6d139897c1ad14da3',
+        '4af1449dc90e5505bd5f3d638f11b8bf7e5dc82c0290f0085dc0b335ababd143',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -70,9 +70,9 @@
     'file_size':
         2314424,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-amd64/tracebox',
     'sha256':
-        'e35fd880f483ab26d57d292a7c4d1c9df6393bff7f1e7694e7d3642472c8fff9',
+        'a97a5efdaf475f13f4f5947c03289029253f89d0f44caa64765b00b269551297',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -84,9 +84,9 @@
     'file_size':
         1418968,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm/tracebox',
     'sha256':
-        '7e550ab781f79fcf548f37a7cc3aaa50dbab235b53c445829815d987eb162843',
+        '818390305d15730fadcbd87dc3c8d87a439e040a02b5098f51af15dfff3f0ca0',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -98,9 +98,9 @@
     'file_size':
         2221176,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm64/tracebox',
     'sha256':
-        '355f2c6e66467a9e81855aa34a16fbe8cd68f01089ec0f5e3074f2011328a97f',
+        '5a3cf8c755e08b7a558083a70ad28293baa389e544ebd09806b6a883a5f17952',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -112,9 +112,9 @@
     'file_size':
         1304280,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm/tracebox',
     'sha256':
-        'b1c31ea2c07b519c40732416ecf91d8dbe0c04355150598c5ca2434249669a92'
+        '87bb07c7ac4c58d306975cabad3ed5d4b6fe11a8d617dad30fe7dd25bfdc6736'
 }, {
     'arch':
         'android-arm64',
@@ -123,9 +123,9 @@
     'file_size':
         2076144,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm64/tracebox',
     'sha256':
-        '28d7476c048123b6d73e1af4f5054dffdc87b67163980454761433bf49626848'
+        '501b2bb0cba0ecb770e2b568698f89f6b42d083fcca111c872f7a0e95c0cacc5'
 }, {
     'arch':
         'android-x86',
@@ -134,9 +134,9 @@
     'file_size':
         2253568,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x86/tracebox',
     'sha256':
-        '22e61978317ac4ef2934768d9e65bee2b1c7a332bdada5b4c1525d6b0339d4ac'
+        '28be4f88a9b8f950ebc45a20d4844002f9b3f81ef0230d0a5d9b1627cf89c9a5'
 }, {
     'arch':
         'android-x64',
@@ -145,9 +145,9 @@
     'file_size':
         2101752,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x64/tracebox',
     'sha256':
-        '6c74f75555dc7bb31e54debe9fc27fc4db960d2382862d1a8a9d0cf03f7d8300'
+        '6c08b743b9cb6073a75e2b3dd34098e09e3c8bcace89096dc5b9d0f071b2831a'
 }]
 
 # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/trace_processor b/tools/trace_processor
index 378aeb3..ba2a38b 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -30,18 +30,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/trace_processor_shell.py
-# This file has been generated by: tools/roll-prebuilts v41.0
+# This file has been generated by: tools/roll-prebuilts v43.2
 TRACE_PROCESSOR_SHELL_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        10175112,
+        8583624,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-amd64/trace_processor_shell',
     'sha256':
-        '62b14997b0c21755b80e82de03b487615a4abc9a99702acd9382b5bdd67ce28f',
+        'a1c16a74725cefb62406b39538b5d22f56a94e390a0394816d2945793f91f8cf',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8692328,
+        7980232,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/mac-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-arm64/trace_processor_shell',
     'sha256':
-        '04e37680992b6b52ad3abdae34792858e70bf7a5523b2b2d9e76248b5bc6cbe4',
+        '3651654cd462df8a2ec8cb3f7375cee01ccc11861a675b9da0d00aa697efe7b2',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        10025552,
+        8770200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-amd64/trace_processor_shell',
     'sha256':
-        '3ffce07c28144eb9da315b548b14f6304e7bd62fca485b4c4a625b80489ba2de',
+        '0796a01af496a6b62623fea89b2d34063ade9d156783e1f88949d8b7ab1f76d0',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7417332,
+        6371036,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm/trace_processor_shell',
     'sha256':
-        '767056bd8f1d7415552d1ca109918d35fb457673bf754b9641149a9b9980ba35',
+        'f4bbef5008de376913c3a95410802d94d8d5715439c3f797be0e4ca8c9bccb1a',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9434600,
+        8425776,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm64/trace_processor_shell',
     'sha256':
-        'b17cfb3e78a1aa2c8b83e60d5f716bbf23c8d613053b268a3489295218006540',
+        'f1ff5585a06ad8b9fc1d13dbf8d02f39d2804019ea7e70b740872e0f6826695f',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,55 +107,55 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7051720,
+        6382140,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/android-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm/trace_processor_shell',
     'sha256':
-        '1f62f0957208d7043d4eba5f1acadb57c14542790a95b6be46db12f63434ad02'
+        '989b209a108c7d44e2531bb15a5d57f667c717cf774bb8a4a810f99fda0b958d'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8612232,
+        8340616,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/android-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm64/trace_processor_shell',
     'sha256':
-        '132cea4fe3208ae4c4b8d1aadc95c1d5f1dddd3ec77f64aa1d7c9230e9d6259f'
+        'ade7e72990cb97fd74766cd0df50a24cbd547d2f54c26b49d66236c809922645'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9525644,
+        9170488,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/android-x86/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x86/trace_processor_shell',
     'sha256':
-        '4cbea3a6e9158d4ac0a68040755520511143254653ab1ed553ab0b4e0b16c30b'
+        '6bd1f74616fd8f620fbf3228f83301844adae08a772b8ac2a64703724a79b516'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9775560,
+        8591040,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/android-x64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x64/trace_processor_shell',
     'sha256':
-        '469467f9f7d4f521d48f3938d0ec8b964b7552c4747b94aed1dd9c7e40ba82a7'
+        '1ccbcb8b2928615cf512cf97eaba395de6f1fc5d70313e884ea3975867f365ea'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        9444864,
+        8676352,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v41.0/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        '695174fd2791096db233e52673bffa2c5cb92275d191e760ab5d9fe33261da6e',
+        '9125418fedd96eb0e6f1ddeaf46069a05bdcb910592b059669fc982b4fff3f1b',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/tracebox b/tools/tracebox
index 5cb2e1b..496fe43 100755
--- a/tools/tracebox
+++ b/tools/tracebox
@@ -30,7 +30,7 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts v43.1
+# This file has been generated by: tools/roll-prebuilts v43.2
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
@@ -39,9 +39,9 @@
     'file_size':
         1564728,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-amd64/tracebox',
     'sha256':
-        'dde1f657b10376f3fd684d1ce4302fd12c0479b567689f5dace8647375edd08c',
+        '239736808cbfba5085892e15c145381ea37ddba5df7c8fad97b68d9c04a4d860',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -53,9 +53,9 @@
     'file_size':
         1459160,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-arm64/tracebox',
     'sha256':
-        '349fc531090e134d708bfe2c44330c2f08280aa424f4e9f6d139897c1ad14da3',
+        '4af1449dc90e5505bd5f3d638f11b8bf7e5dc82c0290f0085dc0b335ababd143',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -67,9 +67,9 @@
     'file_size':
         2314424,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-amd64/tracebox',
     'sha256':
-        'e35fd880f483ab26d57d292a7c4d1c9df6393bff7f1e7694e7d3642472c8fff9',
+        'a97a5efdaf475f13f4f5947c03289029253f89d0f44caa64765b00b269551297',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -81,9 +81,9 @@
     'file_size':
         1418968,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm/tracebox',
     'sha256':
-        '7e550ab781f79fcf548f37a7cc3aaa50dbab235b53c445829815d987eb162843',
+        '818390305d15730fadcbd87dc3c8d87a439e040a02b5098f51af15dfff3f0ca0',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -95,9 +95,9 @@
     'file_size':
         2221176,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm64/tracebox',
     'sha256':
-        '355f2c6e66467a9e81855aa34a16fbe8cd68f01089ec0f5e3074f2011328a97f',
+        '5a3cf8c755e08b7a558083a70ad28293baa389e544ebd09806b6a883a5f17952',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -109,9 +109,9 @@
     'file_size':
         1304280,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm/tracebox',
     'sha256':
-        'b1c31ea2c07b519c40732416ecf91d8dbe0c04355150598c5ca2434249669a92'
+        '87bb07c7ac4c58d306975cabad3ed5d4b6fe11a8d617dad30fe7dd25bfdc6736'
 }, {
     'arch':
         'android-arm64',
@@ -120,9 +120,9 @@
     'file_size':
         2076144,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm64/tracebox',
     'sha256':
-        '28d7476c048123b6d73e1af4f5054dffdc87b67163980454761433bf49626848'
+        '501b2bb0cba0ecb770e2b568698f89f6b42d083fcca111c872f7a0e95c0cacc5'
 }, {
     'arch':
         'android-x86',
@@ -131,9 +131,9 @@
     'file_size':
         2253568,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x86/tracebox',
     'sha256':
-        '22e61978317ac4ef2934768d9e65bee2b1c7a332bdada5b4c1525d6b0339d4ac'
+        '28be4f88a9b8f950ebc45a20d4844002f9b3f81ef0230d0a5d9b1627cf89c9a5'
 }, {
     'arch':
         'android-x64',
@@ -142,9 +142,9 @@
     'file_size':
         2101752,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x64/tracebox',
     'sha256':
-        '6c74f75555dc7bb31e54debe9fc27fc4db960d2382862d1a8a9d0cf03f7d8300'
+        '6c08b743b9cb6073a75e2b3dd34098e09e3c8bcace89096dc5b9d0f071b2831a'
 }]
 
 # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/traceconv b/tools/traceconv
index 6ad6a9c..dba29fa 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -30,7 +30,7 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v43.1
+# This file has been generated by: tools/roll-prebuilts v43.2
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
@@ -39,9 +39,9 @@
     'file_size':
         7790424,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-amd64/traceconv',
     'sha256':
-        '88007b64828e835e0326c11f66f0bba7d8ab117562963086a4f19d8cb060204d',
+        'c1d9c50c89545b41af88525dc6f3ce508156ed3787ccecae0ff7c8e736c39318',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -53,9 +53,9 @@
     'file_size':
         7264824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/mac-arm64/traceconv',
     'sha256':
-        'be5769279ef8442e80130e4bdb6a0a6aa11305442207ea18ff2cf38b21a71a57',
+        'df5349ae462dbd7c1ca9a1b8a0f09c044a47026d6ad8dc24e6945701d7c61a84',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -67,9 +67,9 @@
     'file_size':
         7885952,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-amd64/traceconv',
     'sha256':
-        '51cfdf5060bcd87d08402620d88d0243f7bb39f2878906614d53fa3ddd78dd92',
+        'b2c19364c1fb68e9f5cde610e5d71dd59b9fdf2bada8f7e1eefc319f828f7cb1',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -81,9 +81,9 @@
     'file_size':
         5919372,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm/traceconv',
     'sha256':
-        '04300b1c4dcec1e01bc23017dab3b406f9f0ffd7dd9ea3723784aa8730762bc9',
+        'b669be326b4b6a024e557e0927f1014fd1ea5d5427e194dc0653f21acac273ee',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -95,9 +95,9 @@
     'file_size':
         7588200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/linux-arm64/traceconv',
     'sha256':
-        'd3edc1cd7b216e18955135e0e9e767cdd7b1b8b7efa64793aa6b923a6c278d68',
+        'aff1c4751e721733ce85f58048c17971399fe605a81ac300d306c200d6957818',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -109,9 +109,9 @@
     'file_size':
         5931120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm/traceconv',
     'sha256':
-        '8c3cb3dc96aa6ca296876b8ed56f8eed8c33e12e756b178360cc145263130e7e'
+        '826212f658fef744fbaeea66331b6fe7ca0152f69cf63ff2ea218a376d5d41d9'
 }, {
     'arch':
         'android-arm64',
@@ -120,9 +120,9 @@
     'file_size':
         7546224,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-arm64/traceconv',
     'sha256':
-        '180cfd2184d601c8f202b6bcd899cc7f63a8bb384505c1a2c3e889dfbe8bdb6d'
+        '29dd7e93e9182c4413a9f9c1c6a6f643f64e1fe0b9657ab1ea3cec8b0bb360c9'
 }, {
     'arch':
         'android-x86',
@@ -131,9 +131,9 @@
     'file_size':
         8176528,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x86/traceconv',
     'sha256':
-        'a4e8ff19daa58726138aa66f5adae74b609fceee403c8cddbaaf46d6d07e4cc8'
+        '63c2ebe7ed51f9667bcf69d7b9679f6077db5fd8ee9e1be7b786037e2a649fcb'
 }, {
     'arch':
         'android-x64',
@@ -142,9 +142,9 @@
     'file_size':
         7767560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/android-x64/traceconv',
     'sha256':
-        '19626b87f8c8d956d3807d24faf5764c6bca289f55732cae2f6753dbec33e7f7'
+        'bb9350230c2fac5adf9e6fe21937865b6eaafaefc555ae26e68cae9419ad5ee8'
 }, {
     'arch':
         'windows-amd64',
@@ -153,9 +153,9 @@
     'file_size':
         7645696,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.1/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v43.2/windows-amd64/traceconv.exe',
     'sha256':
-        '24eb5322f22c0219694789fa04aaa5ad09b0746f8b993fd1713e6b3f7943708a',
+        '0c84b712941e4f63f74e66731745f94aec3cd30d94469e52cdf1143262f063a4',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 2e0ec2d..5d29857 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -2,11 +2,11 @@
   "channels": [
     {
       "name": "stable",
-      "rev": "c2c4bf18776fa73068f26dd60a7af630e1dc4d27"
+      "rev": "b88536ad5a5569af9559fe0a3f295f12cff37751"
     },
     {
       "name": "canary",
-      "rev": "dc270ece0d4ff6106505f6340c5440666e785c0e"
+      "rev": "5457e2afae21f167ef490c7e76a5e3fc6c53e04a"
     },
     {
       "name": "autopush",
diff --git a/ui/src/common/default_plugins.ts b/ui/src/common/default_plugins.ts
index 1a48e28..ea6f263 100644
--- a/ui/src/common/default_plugins.ts
+++ b/ui/src/common/default_plugins.ts
@@ -55,4 +55,5 @@
   'perfetto.Screenshots',
   'perfetto.ThreadState',
   'perfetto.VisualisedArgs',
+  'linuxDeviceTracks',
 ];
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/common/recordingV2/recording_config_utils.ts
index a14415b..530aa05 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/common/recordingV2/recording_config_utils.ts
@@ -265,6 +265,10 @@
     ftraceEvents.add('task/task_rename');
   }
 
+  if (uiCfg.linuxDeviceRpm) {
+    ftraceEvents.add('rpm/rpm_status');
+  }
+
   if (uiCfg.meminfo) {
     if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig();
     sysStatsCfg.meminfoPeriodMs = uiCfg.meminfoPeriodMs;
diff --git a/ui/src/controller/record_config_types.ts b/ui/src/controller/record_config_types.ts
index 06e4ec8..05bb92c 100644
--- a/ui/src/controller/record_config_types.ts
+++ b/ui/src/controller/record_config_types.ts
@@ -113,6 +113,8 @@
   tracePerf: bool(),
   timebaseFrequency: num(100),
   targetCmdLine: arrayOf(str()),
+
+  linuxDeviceRpm: bool(),
 });
 export const namedRecordConfigValidator = record(
   {title: requiredStr, key: requiredStr, config: recordConfigValidator});
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 40a8f09..b911a45 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -1159,9 +1159,11 @@
     visibleStart = Time.max(visibleStart, reliableRangeStart);
   }
 
+  // Move start of visible window to the first ftrace event
   const ftraceBounds = await computeFtraceBounds(engine);
   if (ftraceBounds !== null) {
-    visibleStart = ftraceBounds.start;
+    // Avoid moving start of visible window past its end!
+    visibleStart = Time.min(ftraceBounds.start, visibleEnd);
   }
   return HighPrecisionTimeSpan.fromTime(visibleStart, visibleEnd);
 }
diff --git a/ui/src/plugins/linuxDeviceTracks/OWNERS b/ui/src/plugins/linuxDeviceTracks/OWNERS
new file mode 100644
index 0000000..3757ab5
--- /dev/null
+++ b/ui/src/plugins/linuxDeviceTracks/OWNERS
@@ -0,0 +1,3 @@
+isaacmanjarres@google.com
+saravanak@google.com
+vilasbhat@google.com
diff --git a/ui/src/plugins/linuxDeviceTracks/index.ts b/ui/src/plugins/linuxDeviceTracks/index.ts
new file mode 100644
index 0000000..9b2d157
--- /dev/null
+++ b/ui/src/plugins/linuxDeviceTracks/index.ts
@@ -0,0 +1,87 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {
+  NUM,
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+  STR_NULL,
+} from '../../public';
+import {ASYNC_SLICE_TRACK_KIND} from '../../tracks/async_slices';
+import {AsyncSliceTrackV2} from '../../tracks/async_slices/async_slice_track_v2';
+
+// This plugin renders visualizations of runtime power state transitions for
+// Linux kernel devices (devices managed by Linux drivers).
+class linuxDevices implements Plugin {
+  onActivate(_: PluginContext): void {
+  }
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    const result = await ctx.engine.query(`
+      with
+        slices_tracks as materialized (
+          select distinct track_id
+          from slice
+        ),
+        tracks as (
+          select
+            linux_device_track.id as track_id,
+            linux_device_track.name
+          from linux_device_track
+          join slices_tracks on
+	    slices_tracks.track_id = linux_device_track.id
+        )
+      select
+        t.name,
+        t.track_id as trackId
+      from tracks as t
+      order by t.name;
+    `);
+
+    const it = result.iter({
+      name: STR_NULL,
+      trackId: NUM,
+    });
+
+    for (; it.valid(); it.next()) {
+      const trackId = it.trackId;
+      const name = it.name ?? `${trackId}`;
+
+      ctx.registerStaticTrack({
+        uri: `linuxDeviceTracks#${name}`,
+        displayName: name,
+        trackIds: [trackId],
+        kind: ASYNC_SLICE_TRACK_KIND,
+        trackFactory: ({trackKey}) => {
+          return new AsyncSliceTrackV2(
+            {
+              engine: ctx.engine,
+              trackKey,
+            },
+            0,
+            [trackId],
+          );
+        },
+        groupName: `Linux Kernel Devices`,
+      });
+    }
+  }
+}
+
+export const plugin: PluginDescriptor = {
+  pluginId: 'linuxDeviceTracks',
+  plugin: linuxDevices,
+};