Merge "tp: add depth column and super root fn to stdlib memory.heap_graph_dominator_tree" into main
diff --git a/Android.bp b/Android.bp
index 8f7474a..8d476ce 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12493,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",
@@ -12505,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",
     ],
 }
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/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/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 8180b44..b8e209d 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -4310,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;
   }
 }
 
@@ -13801,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).
@@ -13817,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/src/trace_processor/db/column/string_storage.cc b/src/trace_processor/db/column/string_storage.cc
index 07dfa76..9ab8c1f 100644
--- a/src/trace_processor/db/column/string_storage.cc
+++ b/src/trace_processor/db/column/string_storage.cc
@@ -620,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/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/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/perfetto_sql/stdlib/graphs/search.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
index 4d2eea8..4bba25a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
@@ -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_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 1ccaa00..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 {
@@ -50,6 +51,41 @@
       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/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/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]"
+        """))