Trace Redaction - Create foundation for trace redaction
In order to set-up the foundation for migrating the trace redaction work
into the Perfetto repo, the following three work items had to be
executed together.
1. Set-up directory and build files for future work
2. Define key data types (context + primitive)
3. Define execution pattern
To demonstrate how everything works, this implements primitives
that find a target package in the package list and removes all
other packages.
Bug: 318576092
Change-Id: I5fca1102abc6bb02e433453817c56270023368e2
diff --git a/Android.bp b/Android.bp
index 73487c9..02b3c6b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2387,6 +2387,8 @@
":perfetto_src_trace_processor_util_stdlib",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_util_zip_reader",
+ ":perfetto_src_trace_redaction_integrationtests",
+ ":perfetto_src_trace_redaction_trace_redaction",
":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
":perfetto_src_traced_probes_android_log_android_log",
":perfetto_src_traced_probes_android_system_property_android_system_property",
@@ -12428,6 +12430,34 @@
],
}
+// GN: //src/trace_redaction:integrationtests
+filegroup {
+ name: "perfetto_src_trace_redaction_integrationtests",
+ srcs: [
+ "src/trace_redaction/trace_redactor_integrationtest.cc",
+ ],
+}
+
+// GN: //src/trace_redaction:trace_redaction
+filegroup {
+ name: "perfetto_src_trace_redaction_trace_redaction",
+ srcs: [
+ "src/trace_redaction/find_package_uid.cc",
+ "src/trace_redaction/prune_package_list.cc",
+ "src/trace_redaction/trace_redaction_framework.cc",
+ "src/trace_redaction/trace_redactor.cc",
+ ],
+}
+
+// GN: //src/trace_redaction:unittests
+filegroup {
+ name: "perfetto_src_trace_redaction_unittests",
+ srcs: [
+ "src/trace_redaction/find_package_uid_unittest.cc",
+ "src/trace_redaction/prune_package_list_unittest.cc",
+ ],
+}
+
// GN: //src/traceconv:gen_cc_trace_descriptor
genrule {
name: "perfetto_src_traceconv_gen_cc_trace_descriptor",
@@ -13901,6 +13931,8 @@
":perfetto_src_trace_processor_util_unittests",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_util_zip_reader",
+ ":perfetto_src_trace_redaction_trace_redaction",
+ ":perfetto_src_trace_redaction_unittests",
":perfetto_src_traceconv_lib",
":perfetto_src_traceconv_pprofbuilder",
":perfetto_src_traceconv_unittests",
diff --git a/gn/perfetto_integrationtests.gni b/gn/perfetto_integrationtests.gni
index 3e65e20..969c977 100644
--- a/gn/perfetto_integrationtests.gni
+++ b/gn/perfetto_integrationtests.gni
@@ -50,6 +50,8 @@
[ "src/trace_processor:integrationtests" ]
}
+perfetto_integrationtests_targets += [ "src/trace_redaction:integrationtests" ]
+
if (enable_perfetto_traced_relay) {
perfetto_integrationtests_targets += [ "src/traced_relay:integrationtests" ]
}
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index 2769a63..f1d46ba 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -85,3 +85,5 @@
if (enable_perfetto_traced_relay) {
perfetto_unittests_targets += [ "src/traced_relay:unittests" ]
}
+
+perfetto_unittests_targets += [ "src/trace_redaction:unittests" ]
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
new file mode 100644
index 0000000..4536331
--- /dev/null
+++ b/src/trace_redaction/BUILD.gn
@@ -0,0 +1,72 @@
+# 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/test.gni")
+
+source_set("trace_redaction") {
+ sources = [
+ "find_package_uid.cc",
+ "find_package_uid.h",
+ "prune_package_list.cc",
+ "prune_package_list.h",
+ "trace_redaction_framework.cc",
+ "trace_redaction_framework.h",
+ "trace_redactor.cc",
+ "trace_redactor.h",
+ ]
+ deps = [
+ "../../gn:default_deps",
+ "../../include/perfetto/base",
+ "../../include/perfetto/ext/base",
+ "../../include/perfetto/protozero:protozero",
+ "../../include/perfetto/trace_processor:storage",
+ "../../protos/perfetto/trace:non_minimal_cpp",
+ "../../protos/perfetto/trace:non_minimal_zero",
+ "../../protos/perfetto/trace/android:cpp",
+ "../../protos/perfetto/trace/android:zero",
+ "../trace_processor:storage_minimal",
+ ]
+}
+
+source_set("integrationtests") {
+ testonly = true
+ sources = [ "trace_redactor_integrationtest.cc" ]
+ deps = [
+ ":trace_redaction",
+ "../../gn:default_deps",
+ "../../gn:gtest_and_gmock",
+ "../../include/perfetto/ext/base",
+ "../../protos/perfetto/trace:non_minimal_zero",
+ "../../protos/perfetto/trace/android:zero",
+ "../base:test_support",
+ ]
+}
+
+perfetto_unittest_source_set("unittests") {
+ testonly = true
+ sources = [
+ "find_package_uid_unittest.cc",
+ "prune_package_list_unittest.cc",
+ ]
+ deps = [
+ ":trace_redaction",
+ "../../gn:default_deps",
+ "../../gn:gtest_and_gmock",
+ "../../protos/perfetto/trace:non_minimal_cpp",
+ "../../protos/perfetto/trace/android:cpp",
+ "../../protos/perfetto/trace/ps:cpp",
+ "../../protos/perfetto/trace/ps:zero",
+ "../base:test_support",
+ ]
+}
diff --git a/src/trace_redaction/find_package_uid.cc b/src/trace_redaction/find_package_uid.cc
new file mode 100644
index 0000000..d069fe2
--- /dev/null
+++ b/src/trace_redaction/find_package_uid.cc
@@ -0,0 +1,67 @@
+/*
+ * 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/find_package_uid.h"
+
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+
+using PackagesList_PackageInfo = protos::pbzero::PackagesList_PackageInfo;
+using PackagesList = protos::pbzero::PackagesList;
+
+} // namespace
+
+FindPackageUid::FindPackageUid() = default;
+
+FindPackageUid::~FindPackageUid() = default;
+
+base::StatusOr<CollectPrimitive::ContinueCollection> FindPackageUid::Collect(
+ const protos::pbzero::TracePacket_Decoder& packet,
+ Context* context) const {
+ if (context->package_name.empty()) {
+ return base::ErrStatus("FindPackageUid: missing package name.");
+ }
+
+ // Skip package and move onto the next packet.
+ if (!packet.has_packages_list()) {
+ return ContinueCollection::kNextPacket;
+ }
+
+ const PackagesList::Decoder pkg_list_decoder(packet.packages_list());
+
+ for (auto pkg_it = pkg_list_decoder.packages(); pkg_it; ++pkg_it) {
+ PackagesList_PackageInfo::Decoder pkg_info(*pkg_it);
+
+ if (pkg_info.has_name() && pkg_info.has_uid()) {
+ // Package names should be lowercase, but this check is meant to be more
+ // forgiving.
+ if (base::StringView(context->package_name)
+ .CaseInsensitiveEq(pkg_info.name())) {
+ context->package_uid = NormalizeUid(pkg_info.uid());
+ return ContinueCollection::kRetire;
+ }
+ }
+ }
+
+ return ContinueCollection::kNextPacket;
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/find_package_uid.h b/src/trace_redaction/find_package_uid.h
new file mode 100644
index 0000000..4ae34d4
--- /dev/null
+++ b/src/trace_redaction/find_package_uid.h
@@ -0,0 +1,42 @@
+/*
+ * 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_FIND_PACKAGE_UID_H_
+#define SRC_TRACE_REDACTION_FIND_PACKAGE_UID_H_
+
+#include "perfetto/ext/base/status_or.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// Writes the uid for the package matching `Context.package_name`. Returns
+// `kStop` with a package is found, otherwrise `kContinue`. If a package is not
+// found, `Context.package_uid` will remain unset and a later primitive will
+// need to report the failure.
+class FindPackageUid final : public CollectPrimitive {
+ public:
+ FindPackageUid();
+ ~FindPackageUid() override;
+
+ base::StatusOr<ContinueCollection> Collect(
+ const protos::pbzero::TracePacket::Decoder& packet,
+ Context* context) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_FIND_PACKAGE_UID_H_
diff --git a/src/trace_redaction/find_package_uid_unittest.cc b/src/trace_redaction/find_package_uid_unittest.cc
new file mode 100644
index 0000000..44f0a45
--- /dev/null
+++ b/src/trace_redaction/find_package_uid_unittest.cc
@@ -0,0 +1,239 @@
+
+/*
+ * 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 "find_package_uid.h"
+
+#include <cstdint>
+#include <string>
+
+#include "protos/perfetto/trace/android/packages_list.gen.h"
+#include "protos/perfetto/trace/ps/process_tree.gen.h"
+#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+#include "src/base/test/status_matchers.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+
+// packet {
+// trusted_uid: 9999
+// trusted_packet_sequence_id: 2
+// previous_packet_dropped: true
+// packages_list {
+// packages {
+// name: "com.shannon.qualifiednetworksservice"
+// uid: 10201
+// debuggable: false
+// profileable_from_shell: false
+// version_code: 131
+// }
+// packages {
+// name: "com.google.android.uvexposurereporter"
+// uid: 10205
+// debuggable: false
+// profileable_from_shell: false
+// version_code: 34
+// }
+// packages {
+// name: "com.android.internal.display.cutout.emulation.noCutout"
+// uid: 10007
+// debuggable: false
+// profileable_from_shell: false
+// version_code: 1
+// }
+// packages {
+// name: "com.google.android.networkstack.tethering"
+// uid: 1073
+// debuggable: false
+// profileable_from_shell: false
+// version_code: 34
+// }
+// packages {
+// name: "com.amazon.mShop.android.shopping"
+// uid: 10303
+// debuggable: false
+// profileable_from_shell: false
+// version_code: 1241261011
+// }
+// }
+// trusted_pid: 1085
+// first_packet_on_sequence: true
+// }
+//
+// packet {
+// process_tree {
+// processes {
+// pid: 23022
+// ppid: 1
+// cmdline: "/vendor/bin/hw/wpa_supplicant"
+// cmdline: "-O/data/vendor/wifi/wpa/sockets"
+// cmdline: "-dd"
+// cmdline: "-g@android:wpa_wlan0"
+// uid: 1010
+// }
+// threads {
+// tid: 6382
+// tgid: 18176
+// }
+// threads {
+// tid: 18419
+// tgid: 18176
+// }
+// threads {
+// tid: 18434
+// tgid: 18176
+// }
+// collection_end_timestamp: 333724398314653
+// }
+// trusted_uid: 9999
+// timestamp: 333724396714922
+// trusted_packet_sequence_id: 3
+// trusted_pid: 1085
+// }
+
+void AddPackage(const std::string& name,
+ uint64_t uid,
+ int version_code,
+ protos::gen::PackagesList* list) {
+ auto* pkg = list->add_packages();
+ pkg->set_name(name);
+ pkg->set_uid(uid);
+ pkg->set_version_code(version_code);
+}
+
+void AddProcess(protos::gen::ProcessTree* process_tree) {
+ auto* p0 = process_tree->add_processes();
+ p0->set_pid(23022);
+ p0->set_ppid(1);
+ p0->add_cmdline("/vendor/bin/hw/wpa_supplicant");
+ p0->add_cmdline("-O/data/vendor/wifi/wpa/sockets");
+ p0->add_cmdline("-dd");
+ p0->add_cmdline("-g@android:wpa_wlan0");
+ p0->set_uid(1010);
+}
+
+void AddThread(int32_t tid,
+ int32_t ttid,
+ protos::gen::ProcessTree* process_tree) {
+ auto* thread = process_tree->add_threads();
+ thread->set_tid(tid);
+ thread->set_tgid(ttid);
+}
+
+std::string CreatePackageListPacket() {
+ protos::gen::TracePacket packet;
+ packet.set_trusted_uid(9999);
+ packet.set_trusted_packet_sequence_id(2);
+ packet.set_previous_packet_dropped(true);
+ packet.set_trusted_pid(1085);
+ packet.set_first_packet_on_sequence(true);
+
+ auto* package_list = packet.mutable_packages_list();
+
+ AddPackage("com.shannon.qualifiednetworksservice", 10201, 131, package_list);
+ AddPackage("com.google.android.uvexposurereporter", 10205, 34, package_list);
+ AddPackage("com.android.internal.display.cutout.emulation.noCutout", 10007, 1,
+ package_list);
+ AddPackage("com.google.android.networkstack.tethering", 1073, 34,
+ package_list);
+ AddPackage("com.amazon.mShop.android.shopping", 10303, 1241261011,
+ package_list);
+
+ return packet.SerializeAsString();
+}
+
+std::string CreateProcessTreePacket() {
+ protos::gen::TracePacket packet;
+ packet.set_trusted_uid(9999);
+ packet.set_timestamp(333724396714922);
+ packet.set_trusted_packet_sequence_id(3);
+ packet.set_trusted_pid(1085);
+
+ auto* tree = packet.mutable_process_tree();
+ tree->set_collection_end_timestamp(333724398314653);
+
+ AddProcess(tree);
+ AddThread(6382, 18176, tree);
+ AddThread(18419, 18176, tree);
+ AddThread(18434, 18176, tree);
+
+ return packet.SerializeAsString();
+}
+} // namespace
+
+TEST(FindPackageUidTest, FindsUidInPackageList) {
+ const auto packet = CreatePackageListPacket();
+
+ Context context;
+ context.package_name = "com.google.android.uvexposurereporter";
+
+ const FindPackageUid find;
+
+ const auto decoder = protos::pbzero::TracePacket::Decoder(packet);
+
+ ASSERT_OK_AND_ASSIGN(auto status, find.Collect(decoder, &context));
+ ASSERT_EQ(status, CollectPrimitive::ContinueCollection::kRetire);
+
+ ASSERT_TRUE(context.package_uid.has_value());
+ ASSERT_EQ(NormalizeUid(context.package_uid.value()), NormalizeUid(10205));
+}
+
+TEST(FindPackageUidTest, ContinuesOverNonPackageList) {
+ const auto packet = CreateProcessTreePacket();
+
+ Context context;
+ context.package_name = "com.google.android.uvexposurereporter";
+
+ const FindPackageUid find;
+
+ const auto decoder = protos::pbzero::TracePacket::Decoder(packet);
+
+ ASSERT_OK_AND_ASSIGN(auto status, find.Collect(decoder, &context));
+ ASSERT_EQ(status, CollectPrimitive::ContinueCollection::kNextPacket);
+
+ ASSERT_FALSE(context.package_uid.has_value());
+}
+
+TEST(FindPackageUidTest, ContinuesOverPackageListWithOutPackageName) {
+ const auto packet = CreatePackageListPacket();
+
+ Context context;
+ context.package_name = "com.not.a.packagename";
+
+ const FindPackageUid find;
+
+ const auto decoder = protos::pbzero::TracePacket::Decoder(packet);
+
+ ASSERT_OK_AND_ASSIGN(auto status, find.Collect(decoder, &context));
+ ASSERT_EQ(status, CollectPrimitive::ContinueCollection::kNextPacket);
+
+ ASSERT_FALSE(context.package_uid.has_value());
+}
+
+TEST(FindPackageUidTest, MissingPackageNameReturnsError) {
+ const auto packet = CreatePackageListPacket();
+
+ Context context;
+ const FindPackageUid find;
+
+ const auto decoder = protos::pbzero::TracePacket::Decoder(packet);
+ ASSERT_FALSE(find.Collect(decoder, &context).ok());
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/prune_package_list.cc b/src/trace_redaction/prune_package_list.cc
new file mode 100644
index 0000000..52c1394
--- /dev/null
+++ b/src/trace_redaction/prune_package_list.cc
@@ -0,0 +1,59 @@
+/*
+ * 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/prune_package_list.h"
+
+#include "protos/perfetto/trace/android/packages_list.gen.h"
+
+namespace perfetto::trace_redaction {
+
+PrunePackageList::PrunePackageList() = default;
+PrunePackageList::~PrunePackageList() = default;
+
+base::Status PrunePackageList::Transform(const Context& context,
+ std::string* packet) const {
+ if (!context.package_uid.has_value()) {
+ return base::ErrStatus("PrunePackageList: missing package uid.");
+ }
+
+ if (protos::pbzero::TracePacket::Decoder trace_packet_decoder(*packet);
+ !trace_packet_decoder.has_packages_list()) {
+ return base::OkStatus();
+ }
+
+ auto normalized_uid = NormalizeUid(context.package_uid.value());
+
+ protos::gen::TracePacket mutable_packet;
+ mutable_packet.ParseFromString(*packet);
+
+ auto* packages = mutable_packet.mutable_packages_list()->mutable_packages();
+
+ // Remove all entries that don't match the uid. After this, one or more
+ // packages will be left in the list (multiple packages can share a uid).
+ packages->erase(
+ std::remove_if(
+ packages->begin(), packages->end(),
+ [normalized_uid](const protos::gen::PackagesList::PackageInfo& info) {
+ return NormalizeUid(info.uid()) != normalized_uid;
+ }),
+ packages->end());
+
+ packet->assign(mutable_packet.SerializeAsString());
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/prune_package_list.h b/src/trace_redaction/prune_package_list.h
new file mode 100644
index 0000000..cf1298f
--- /dev/null
+++ b/src/trace_redaction/prune_package_list.h
@@ -0,0 +1,39 @@
+/*
+ * 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_PRUNE_PACKAGE_LIST_H_
+#define SRC_TRACE_REDACTION_PRUNE_PACKAGE_LIST_H_
+
+#include <string>
+
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// Removes all package list entries that don't match `Context.package_uid`.
+// Returns `base::ErrStatus()` if `Context.package_uid` was not set.
+class PrunePackageList final : public TransformPrimitive {
+ public:
+ PrunePackageList();
+ ~PrunePackageList() override;
+
+ base::Status Transform(const Context& context,
+ std::string* packet) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_PRUNE_PACKAGE_LIST_H_
diff --git a/src/trace_redaction/prune_package_list_unittest.cc b/src/trace_redaction/prune_package_list_unittest.cc
new file mode 100644
index 0000000..6a9af6c
--- /dev/null
+++ b/src/trace_redaction/prune_package_list_unittest.cc
@@ -0,0 +1,182 @@
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdint>
+#include <string>
+
+#include "protos/perfetto/trace/android/packages_list.gen.h"
+#include "protos/perfetto/trace/ps/process_tree.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+#include "src/trace_redaction/prune_package_list.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+using PackageList = protos::gen::PackagesList;
+using TracePacket = protos::gen::TracePacket;
+using ProcessTree = protos::gen::ProcessTree;
+
+constexpr uint64_t kPackageUid = 1037;
+constexpr std::string_view kPackageName =
+ "com.google.android.networkstack.tethering";
+
+void AddPackage(uint64_t uid, std::string_view name, PackageList* list) {
+ auto* package = list->add_packages();
+ package->set_uid(uid);
+ package->set_name(std::string(name));
+}
+
+std::string CreateTestPacket() {
+ auto packet = std::make_unique<TracePacket>();
+
+ packet->set_trusted_uid(9999);
+ packet->set_trusted_packet_sequence_id(2);
+ packet->set_previous_packet_dropped(true);
+
+ auto* packages = packet->mutable_packages_list();
+ AddPackage(10205, "com.google.android.uvexposurereporter", packages);
+ AddPackage(10007, "com.android.internal.display.cutout.emulation.noCutout",
+ packages);
+ AddPackage(kPackageUid, kPackageName, packages);
+ AddPackage(10367, "com.android.systemui.clocks.metro", packages);
+
+ return packet->SerializeAsString();
+}
+
+// packet {
+// process_tree {
+// processes {
+// pid: 23022
+// ppid: 1
+// cmdline: "/vendor/bin/hw/wpa_supplicant"
+// cmdline: "-O/data/vendor/wifi/wpa/sockets"
+// cmdline: "-dd"
+// cmdline: "-g@android:wpa_wlan0"
+// uid: 1010
+// }
+// threads {
+// tid: 6382
+// tgid: 18176
+// }
+// threads {
+// tid: 18419
+// tgid: 18176
+// }
+// threads {
+// tid: 18434
+// tgid: 18176
+// }
+// collection_end_timestamp: 333724398314653
+// }
+// trusted_uid: 9999
+// timestamp: 333724396714922
+// trusted_packet_sequence_id: 3
+// trusted_pid: 1085
+// }
+std::string CreateNoPackageListPacket() {
+ auto packet = std::make_unique<TracePacket>();
+
+ packet->set_trusted_uid(9999);
+ packet->set_timestamp(333724396714922);
+ packet->set_trusted_packet_sequence_id(3);
+ packet->set_trusted_pid(1085);
+
+ ProcessTree* tree = packet->mutable_process_tree();
+
+ auto* p0 = tree->add_processes();
+ p0->set_pid(23022);
+ p0->set_ppid(1);
+ p0->add_cmdline("/vendor/bin/hw/wpa_supplicant");
+ p0->add_cmdline("-O/data/vendor/wifi/wpa/sockets");
+ p0->add_cmdline("-dd");
+ p0->add_cmdline("-g@android:wpa_wlan0");
+ p0->set_uid(1010);
+
+ auto* t0 = tree->add_threads();
+ t0->set_tid(6382);
+ t0->set_tgid(18176);
+
+ auto* t1 = tree->add_threads();
+ t1->set_tid(18419);
+ t1->set_tgid(18176);
+
+ auto* t2 = tree->add_threads();
+ t2->set_tid(18434);
+ t2->set_tgid(18176);
+
+ tree->set_collection_end_timestamp(333724398314653);
+
+ return packet->SerializeAsString();
+}
+} // namespace
+
+TEST(PrunePackageListTest, ReturnsErrorWhenPackageUidIsMissing) {
+ auto before = CreateTestPacket();
+
+ const Context context;
+ const PrunePackageList prune;
+ ASSERT_FALSE(prune.Transform(context, &before).ok());
+}
+
+TEST(PrunePackageListTest, NoopWhenThereIsNoPackageList) {
+ Context context;
+ context.package_uid.emplace(1037);
+
+ const auto before = CreateNoPackageListPacket();
+ auto after = CreateNoPackageListPacket();
+
+ ASSERT_EQ(before, after);
+
+ const PrunePackageList prune;
+ ASSERT_TRUE(prune.Transform(context, &after).ok());
+
+ // The buffer should have changed.
+ ASSERT_EQ(before, after);
+}
+
+// PrunePackageList should not drop packets, instead it should drop individual
+// PackageInfo entries.
+TEST(PrunePackageListTest, RemovesPackagesInfoFromPackageList) {
+ Context context;
+ context.package_uid.emplace(1037);
+
+ const auto before = CreateTestPacket();
+ auto after = CreateTestPacket();
+
+ ASSERT_EQ(before, after);
+
+ const PrunePackageList prune;
+ ASSERT_TRUE(prune.Transform(context, &after).ok());
+
+ // The buffer should have changed.
+ ASSERT_NE(before, after);
+
+ protos::gen::TracePacket after_packet;
+ after_packet.ParseFromString(after);
+
+ ASSERT_TRUE(after_packet.has_packages_list());
+ ASSERT_EQ(1, after_packet.packages_list().packages_size());
+
+ ASSERT_TRUE(after_packet.packages_list().packages().at(0).has_uid());
+ ASSERT_EQ(kPackageUid, after_packet.packages_list().packages().at(0).uid());
+
+ ASSERT_TRUE(after_packet.packages_list().packages().at(0).has_name());
+ ASSERT_EQ(kPackageName, after_packet.packages_list().packages().at(0).name());
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/trace_redaction_framework.cc b/src/trace_redaction/trace_redaction_framework.cc
new file mode 100644
index 0000000..e3b729a
--- /dev/null
+++ b/src/trace_redaction/trace_redaction_framework.cc
@@ -0,0 +1,27 @@
+/*
+ * 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/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+CollectPrimitive::~CollectPrimitive() = default;
+
+BuildPrimitive::~BuildPrimitive() = default;
+
+TransformPrimitive::~TransformPrimitive() = default;
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/trace_redaction_framework.h b/src/trace_redaction/trace_redaction_framework.h
new file mode 100644
index 0000000..c9e7fb4
--- /dev/null
+++ b/src/trace_redaction/trace_redaction_framework.h
@@ -0,0 +1,138 @@
+/*
+ * 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_TRACE_REDACTION_FRAMEWORK_H_
+#define SRC_TRACE_REDACTION_TRACE_REDACTION_FRAMEWORK_H_
+
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/status_or.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+// Multiple packages can share the same name. This is common when a device has
+// multiple users. When this happens, each instance shares the 5 least
+// significant digits.
+constexpr uint64_t NormalizeUid(uint64_t uid) {
+ return uid % 1000000;
+}
+
+// Primitives should be stateless. All state should be stored in the context.
+// Primitives should depend on data in the context, not the origin of the data.
+// This allows primitives to be swapped out or work together to populate data
+// needed by another primitive.
+//
+// For this to work, primitives are divided into three types:
+//
+// `CollectPrimitive` : Reads data from trace packets and saves low-level data
+// in the context.
+//
+// `BuildPrimitive` : Reads low-level data from the context and builds
+// high-level (read-optimized) data structures.
+//
+// `TransformPrimitive`: Reads high-level data from the context and modifies
+// trace packets.
+class Context {
+ public:
+ // The package that should not be redacted. This must be populated before
+ // running any primitives.
+ std::string package_name;
+
+ // The package list maps a package name to a uid. It is possible for multiple
+ // package names to map to the same uid, for example:
+ //
+ // packages {
+ // name: "com.google.android.gms"
+ // uid: 10113
+ // debuggable: false
+ // profileable_from_shell: false
+ // version_code: 235013038
+ // }
+ // packages {
+ // name: "com.google.android.gsf"
+ // uid: 10113
+ // debuggable: false
+ // profileable_from_shell: false
+ // version_code: 34
+ // }
+ //
+ // The process tree maps processes to packages via the uid value. However
+ // multiple processes can map to the same uid, only differed by some multiple
+ // of 100000, for example:
+ //
+ // processes {
+ // pid: 18176
+ // ppid: 904
+ // cmdline: "com.google.android.gms.persistent"
+ // uid: 10113
+ // }
+ // processes {
+ // pid: 21388
+ // ppid: 904
+ // cmdline: "com.google.android.gms.persistent"
+ // uid: 1010113
+ // }
+ std::optional<uint64_t> package_uid;
+};
+
+// Responsible for extracting low-level data from the trace and storing it in
+// the context.
+class CollectPrimitive {
+ public:
+ // When a collect primitive has collected all necessary information, it can
+ // stop processing packets by returning kRetire. If the primitives wants to
+ // continue processing packets, it will return kNextPacket.
+ //
+ // If a collector encounters an unrecoverable error, base::ErrStatus() is
+ // returned.
+ enum class ContinueCollection : bool { kRetire = false, kNextPacket = true };
+
+ virtual ~CollectPrimitive();
+
+ // Processes a packet and writes low-level data to the context. Returns
+ // kContinue if the primitive wants more data (i.e. next packet).
+ virtual base::StatusOr<ContinueCollection> Collect(
+ const protos::pbzero::TracePacket::Decoder& packet,
+ Context* context) const = 0;
+};
+
+// Responsible for converting low-level data from the context and storing it in
+// the context (high-level data).
+class BuildPrimitive {
+ public:
+ virtual ~BuildPrimitive();
+
+ // Reads low-level data from the context and writes high-level data to the
+ // context.
+ virtual base::Status Build(Context* context) const = 0;
+};
+
+// Responsible for modifying trace packets using data from the context.
+class TransformPrimitive {
+ public:
+ virtual ~TransformPrimitive();
+
+ // Modifies a packet using data from the context.
+ virtual base::Status Transform(const Context& context,
+ std::string* packet) const = 0;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_TRACE_REDACTION_FRAMEWORK_H_
diff --git a/src/trace_redaction/trace_redactor.cc b/src/trace_redaction/trace_redactor.cc
new file mode 100644
index 0000000..eb20c27
--- /dev/null
+++ b/src/trace_redaction/trace_redactor.cc
@@ -0,0 +1,215 @@
+/*
+ * 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/trace_redactor.h"
+
+#include <sys/mman.h>
+#include <unistd.h>
+#include <cstddef>
+#include <cstdio>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/trace.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+
+using Trace = protos::pbzero::Trace;
+using TracePacket = protos::pbzero::TracePacket;
+
+// Basic scoped objects (similar to base::ScopedResource) to make sure page
+// resources are cleaned-up.
+struct MappedFile {
+ void* start_address;
+ size_t size;
+
+ MappedFile() : start_address(MAP_FAILED), size(0) {}
+
+ ~MappedFile() {
+ if (start_address != MAP_FAILED) {
+ munmap(start_address, size);
+ }
+ }
+
+ MappedFile(const MappedFile&) = delete;
+ MappedFile& operator=(const MappedFile&) = delete;
+ MappedFile(MappedFile&&) = delete;
+ MappedFile& operator=(MappedFile&&) = delete;
+};
+
+} // namespace
+
+TraceRedactor::TraceRedactor() = default;
+
+TraceRedactor::~TraceRedactor() = default;
+
+base::Status TraceRedactor::Redact(std::string_view source_filename,
+ std::string_view dest_filename,
+ Context* context) const {
+ const std::string source_filename_str(source_filename);
+
+ base::ScopedFile file_handle(base::OpenFile(source_filename_str, O_RDONLY));
+
+ if (!file_handle) {
+ return base::ErrStatus("Failed to read trace from disk: %s",
+ source_filename_str.c_str());
+ }
+
+ auto file_size_offset = lseek(*file_handle, 0, SEEK_END);
+
+ if (file_size_offset <= 0) {
+ return base::ErrStatus("Could not determine trace size (%s)",
+ source_filename_str.c_str());
+ }
+
+ lseek(*file_handle, 0, SEEK_SET);
+
+ MappedFile page;
+ page.size = static_cast<size_t>(file_size_offset);
+ page.start_address =
+ mmap(nullptr, page.size, PROT_READ, MAP_PRIVATE, *file_handle, 0);
+
+ if (page.start_address == MAP_FAILED) {
+ return base::ErrStatus("Failed to map pages for trace (%zu bytes)",
+ page.size);
+ }
+
+ trace_processor::TraceBlobView whole_view(
+ trace_processor::TraceBlob::FromMmap(page.start_address, page.size));
+
+ if (auto status = Collect(context, whole_view); !status.ok()) {
+ return status;
+ }
+
+ if (auto status = Build(context); !status.ok()) {
+ return status;
+ }
+
+ if (auto status = Transform(*context, whole_view, std::string(dest_filename));
+ !status.ok()) {
+ return status;
+ }
+
+ return base::OkStatus();
+}
+
+base::Status TraceRedactor::Collect(
+ Context* context,
+ const trace_processor::TraceBlobView& view) const {
+ // Mask, marking which collectors should be ran. When a collector no longer
+ // needs to run, the value will be null.
+ std::vector<const CollectPrimitive*> collectors;
+ collectors.reserve(collectors_.size());
+
+ for (const auto& collector : collectors_) {
+ collectors.push_back(collector.get());
+ }
+
+ const Trace::Decoder trace_decoder(view.data(), view.length());
+
+ for (auto packet_it = trace_decoder.packet(); packet_it; ++packet_it) {
+ const TracePacket::Decoder packet(packet_it->as_bytes());
+
+ for (auto cit = collectors.begin(); cit != collectors.end();) {
+ auto status = (*cit)->Collect(packet, context);
+
+ if (!status.ok()) {
+ return status.status();
+ }
+
+ // If this collector has returned `kStop`, it means that it (and it alone)
+ // no longer needs to run. The driver (TraceRedactor) should not invoke it
+ // on any future packets.
+ if (status.value() == CollectPrimitive::ContinueCollection::kRetire) {
+ cit = collectors.erase(cit);
+ } else {
+ ++cit;
+ }
+ }
+
+ // If all the collectors have found what they were looking for, then there
+ // is no reason to continue through the trace.
+ if (collectors.empty()) {
+ break;
+ }
+ }
+
+ return base::OkStatus();
+}
+
+base::Status TraceRedactor::Build(Context* context) const {
+ for (const auto& builder : builders_) {
+ if (auto status = builder->Build(context); !status.ok()) {
+ return status;
+ }
+ }
+
+ return base::OkStatus();
+}
+
+base::Status TraceRedactor::Transform(
+ const Context& context,
+ const trace_processor::TraceBlobView& view,
+ const std::string& dest_file) const {
+ std::ignore = context;
+ const auto dest_fd = base::OpenFile(dest_file, O_RDWR | O_CREAT, 0666);
+
+ if (dest_fd.get() == -1) {
+ return base::ErrStatus(
+ "Failed to open destination file; can't write redacted trace.");
+ }
+
+ const Trace::Decoder trace_decoder(view.data(), view.length());
+ for (auto packet_it = trace_decoder.packet(); packet_it; ++packet_it) {
+ auto packet = packet_it->as_std_string();
+
+ for (const auto& transformer : transformers_) {
+ if (auto status = transformer->Transform(context, &packet);
+ !status.ok()) {
+ return status;
+ }
+ }
+
+ protozero::HeapBuffered<> serializer;
+ auto* packet_message =
+ serializer->BeginNestedMessage<TracePacket>(Trace::kPacketFieldNumber);
+ packet_message->AppendRawProtoBytes(packet.data(), packet.size());
+ packet_message->Finalize();
+ serializer->Finalize();
+
+ auto encoded_packet = serializer.SerializeAsString();
+
+ if (const auto exported_data = base::WriteAll(
+ dest_fd.get(), encoded_packet.data(), encoded_packet.size());
+ exported_data <= 0) {
+ return base::ErrStatus("Failed to write redacted trace to disk");
+ }
+ }
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/trace_redactor.h b/src/trace_redaction/trace_redactor.h
new file mode 100644
index 0000000..99a8832
--- /dev/null
+++ b/src/trace_redaction/trace_redactor.h
@@ -0,0 +1,96 @@
+/*
+ * 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_TRACE_REDACTOR_H_
+#define SRC_TRACE_REDACTION_TRACE_REDACTOR_H_
+
+#include <string_view>
+
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// Removes sensitive information from Perfetto traces by executing collect,
+// build, and transforms primtives in the correct order.
+//
+// The caller is responsible for adding all neccessary primitives. Primitives
+// are not directly dependent on each other, but rather dependent on the
+// information inside of the context.
+class TraceRedactor {
+ public:
+ TraceRedactor();
+ virtual ~TraceRedactor();
+
+ // Entry point for redacting a trace. Regardless of success/failure, `context`
+ // will contain the current state.
+ base::Status Redact(std::string_view source_filename,
+ std::string_view dest_filename,
+ Context* context) const;
+
+ std::vector<std::unique_ptr<CollectPrimitive>>* collectors() {
+ return &collectors_;
+ }
+
+ std::vector<std::unique_ptr<BuildPrimitive>>* builders() {
+ return &builders_;
+ }
+
+ std::vector<std::unique_ptr<TransformPrimitive>>* transformers() {
+ return &transformers_;
+ }
+
+ private:
+ // Run all collectors on a packet because moving to the next package.
+ //
+ // ```
+ // with context:
+ // for packet in packets:
+ // for collector in collectors:
+ // collector(context, packet)
+ // ```
+ base::Status Collect(Context* context,
+ const trace_processor::TraceBlobView& view) const;
+
+ // Runs builders once.
+ //
+ // ```
+ // with context:
+ // for builder in builders:
+ // builder(context)
+ // ```
+ base::Status Build(Context* context) const;
+
+ // Runs all transformers on a packet before moving to the next package.
+ //
+ // ```
+ // with context:
+ // for packet in packets:
+ // for transform in transformers:
+ // transform(context, packet)
+ // ```
+ base::Status Transform(const Context& context,
+ const trace_processor::TraceBlobView& view,
+ const std::string& dest_file) const;
+
+ std::vector<std::unique_ptr<CollectPrimitive>> collectors_;
+ std::vector<std::unique_ptr<BuildPrimitive>> builders_;
+ std::vector<std::unique_ptr<TransformPrimitive>> transformers_;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_TRACE_REDACTOR_H_
diff --git a/src/trace_redaction/trace_redactor_integrationtest.cc b/src/trace_redaction/trace_redactor_integrationtest.cc
new file mode 100644
index 0000000..c30f2f8
--- /dev/null
+++ b/src/trace_redaction/trace_redactor_integrationtest.cc
@@ -0,0 +1,114 @@
+/*
+ * 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 "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/temp_file.h"
+#include "src/base/test/utils.h"
+#include "src/trace_redaction/find_package_uid.h"
+#include "src/trace_redaction/prune_package_list.h"
+#include "src/trace_redaction/trace_redactor.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+using PackagesList = protos::pbzero::PackagesList;
+using PackageInfo = protos::pbzero::PackagesList::PackageInfo;
+using Trace = protos::pbzero::Trace;
+using TracePacket = protos::pbzero::TracePacket;
+
+constexpr std::string_view kTracePath =
+ "test/data/trace_redaction_jank_high_cpu.pftrace";
+
+// "com.google.android.settings.intelligence" will have one package, but two
+// processes will reference it. When doing so, they will use two different
+// uids (multiples of 1,000,000).
+constexpr std::string_view kPackageName =
+ "com.google.android.settings.intelligence";
+constexpr uint64_t kPackageUid = 10118;
+
+class TraceRedactorIntegrationTest : public testing::Test {
+ public:
+ TraceRedactorIntegrationTest() = default;
+ ~TraceRedactorIntegrationTest() override = default;
+
+ protected:
+ void SetUp() override {
+ src_trace_ = base::GetTestDataPath(std::string(kTracePath));
+ dest_trace_ = std::make_unique<base::TempFile>(base::TempFile::Create());
+ }
+
+ const std::string& src_trace() const { return src_trace_; }
+
+ const std::string& dest_trace() const { return dest_trace_->path(); }
+
+ private:
+ std::string src_trace_;
+ std::unique_ptr<base::TempFile> dest_trace_;
+};
+
+TEST_F(TraceRedactorIntegrationTest, FindsPackageAndFiltersPackageList) {
+ TraceRedactor redaction;
+ redaction.collectors()->emplace_back(new FindPackageUid());
+ redaction.transformers()->emplace_back(new PrunePackageList());
+
+ Context context;
+ context.package_name = kPackageName;
+
+ auto result = redaction.Redact(src_trace(), dest_trace(), &context);
+
+ ASSERT_TRUE(result.ok()) << result.message();
+
+ std::string redacted_buffer;
+ ASSERT_TRUE(base::ReadFile(dest_trace(), &redacted_buffer));
+
+ // Collect package info from the trace.
+ std::vector<protozero::ConstBytes> infos;
+
+ Trace::Decoder trace_decoder(redacted_buffer);
+
+ for (auto packet_it = trace_decoder.packet(); packet_it; ++packet_it) {
+ TracePacket::Decoder packet_decoder(*packet_it);
+
+ if (packet_decoder.has_packages_list()) {
+ PackagesList::Decoder list_it(packet_decoder.packages_list());
+
+ for (auto info_it = list_it.packages(); info_it; ++info_it) {
+ infos.push_back(*info_it);
+ }
+ }
+ }
+
+ ASSERT_EQ(infos.size(), 1u);
+
+ PackageInfo::Decoder info(infos[0]);
+
+ ASSERT_TRUE(info.has_name());
+ ASSERT_EQ(info.name().ToStdString(), kPackageName);
+
+ ASSERT_TRUE(info.has_uid());
+ ASSERT_EQ(NormalizeUid(info.uid()), NormalizeUid(kPackageUid));
+
+ ASSERT_TRUE(context.package_uid.has_value());
+ ASSERT_EQ(NormalizeUid(context.package_uid.value()),
+ NormalizeUid(kPackageUid));
+}
+
+} // namespace
+} // namespace perfetto::trace_redaction
diff --git a/test/data/trace_redaction_jank_high_cpu.pftrace.sha256 b/test/data/trace_redaction_jank_high_cpu.pftrace.sha256
new file mode 100644
index 0000000..93dd891
--- /dev/null
+++ b/test/data/trace_redaction_jank_high_cpu.pftrace.sha256
@@ -0,0 +1 @@
+5ea2ff312b5061bf23c2da43380295ab1b207902ffa01462a35d665c56bb4e97
\ No newline at end of file