Trace Redaction - Create "Modify Process Tree" Primitive
There was a single primitive that modified the process tree - removes
process names (cmd line).
When creating fake threads (synth threads), there'll be two primitives
traversing and modifying the process threads. Since some operations
will drop fields, while others will drop entries, this creates a base
class that supports both.
Bug: 336807771
Change-Id: Iad442d97de303158939ddeb2e6b6c8bef00c2057
diff --git a/Android.bp b/Android.bp
index 94b84ff..fdcfede 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12913,6 +12913,7 @@
"src/trace_redaction/filter_sched_waking_events.cc",
"src/trace_redaction/filter_task_rename.cc",
"src/trace_redaction/find_package_uid.cc",
+ "src/trace_redaction/modify_process_trees.cc",
"src/trace_redaction/optimize_timeline.cc",
"src/trace_redaction/populate_allow_lists.cc",
"src/trace_redaction/process_thread_timeline.cc",
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index 4f2fb5f..76089c1 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -45,6 +45,8 @@
"find_package_uid.cc",
"find_package_uid.h",
"frame_cookie.h",
+ "modify_process_trees.cc",
+ "modify_process_trees.h",
"optimize_timeline.cc",
"optimize_timeline.h",
"populate_allow_lists.cc",
diff --git a/src/trace_redaction/modify_process_trees.cc b/src/trace_redaction/modify_process_trees.cc
new file mode 100644
index 0000000..099890b
--- /dev/null
+++ b/src/trace_redaction/modify_process_trees.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 "src/trace_redaction/modify_process_trees.h"
+
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "src/trace_redaction/proto_util.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status ModifyProcessTree::VerifyContext(const Context&) const {
+ return base::OkStatus();
+}
+
+base::Status ModifyProcessTree::Transform(const Context& context,
+ std::string* packet) const {
+ protozero::ProtoDecoder decoder(*packet);
+
+ auto process_tree =
+ decoder.FindField(protos::pbzero::TracePacket::kProcessTreeFieldNumber);
+
+ if (!process_tree.valid()) {
+ return base::OkStatus();
+ }
+
+ auto timestamp =
+ decoder.FindField(protos::pbzero::TracePacket::kTimestampFieldNumber);
+
+ protozero::HeapBuffered<protos::pbzero::TracePacket> packet_message;
+
+ for (auto field = decoder.ReadField(); field.valid();
+ field = decoder.ReadField()) {
+ if (field.id() == protos::pbzero::TracePacket::kProcessTreeFieldNumber) {
+ TransformProcessTree(context, timestamp, field,
+ packet_message->set_process_tree());
+ } else {
+ proto_util::AppendField(field, packet_message.get());
+ }
+ }
+
+ packet->assign(packet_message.SerializeAsString());
+
+ return base::OkStatus();
+}
+
+void ModifyProcessTree::TransformProcess(
+ const Context&,
+ const protozero::Field&,
+ const protozero::Field& process,
+ protos::pbzero::ProcessTree* process_tree) const {
+ PERFETTO_DCHECK(process.id() ==
+ protos::pbzero::ProcessTree::kProcessesFieldNumber);
+ proto_util::AppendField(process, process_tree);
+}
+
+void ModifyProcessTree::TransformThread(
+ const Context&,
+ const protozero::Field&,
+ const protozero::Field& thread,
+ protos::pbzero::ProcessTree* process_tree) const {
+ PERFETTO_DCHECK(thread.id() ==
+ protos::pbzero::ProcessTree::kThreadsFieldNumber);
+ proto_util::AppendField(thread, process_tree);
+}
+
+void ModifyProcessTree::TransformProcessTree(
+ const Context& context,
+ const protozero::Field& timestamp,
+ const protozero::Field& process_tree,
+ protos::pbzero::ProcessTree* message) const {
+ protozero::ProtoDecoder decoder(process_tree.as_bytes());
+
+ for (auto field = decoder.ReadField(); field.valid();
+ field = decoder.ReadField()) {
+ switch (field.id()) {
+ case protos::pbzero::ProcessTree::kProcessesFieldNumber:
+ TransformProcess(context, timestamp, field, message);
+ break;
+
+ case protos::pbzero::ProcessTree::kThreadsFieldNumber:
+ TransformThread(context, timestamp, field, message);
+ break;
+
+ default:
+ proto_util::AppendField(field, message);
+ break;
+ }
+ }
+
+ // TODO(vaage): Call the handler to add extra fields to the process tree.
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/modify_process_trees.h b/src/trace_redaction/modify_process_trees.h
new file mode 100644
index 0000000..e36223e
--- /dev/null
+++ b/src/trace_redaction/modify_process_trees.h
@@ -0,0 +1,70 @@
+/*
+ * 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_MODIFY_PROCESS_TREES_H_
+#define SRC_TRACE_REDACTION_MODIFY_PROCESS_TREES_H_
+
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+// Walk through process trees, calling process and thread handlers to add new
+// process and threads messages to the process tree. If the default handler is
+// not replaced, the thread/process will be added to the parent.
+class ModifyProcessTree : public TransformPrimitive {
+ public:
+ base::Status Transform(const Context& context,
+ std::string* packet) const override;
+
+ protected:
+ // Verifies that the context contains required values. No-op by default.
+ virtual base::Status VerifyContext(const Context& context) const;
+
+ // Modifies a process before adding it back to the process tree. Appends the
+ // field to the process tree without modification by default.
+ virtual void TransformProcess(
+ const Context& context,
+ const protozero::Field& timestamp,
+ const protozero::Field& process,
+ protos::pbzero::ProcessTree* process_tree) const;
+
+ // Modifies a thread before adding it back to the process tree. Appends the
+ // field to the process tree without modification by default.
+ virtual void TransformThread(
+ const Context& context,
+ const protozero::Field& timestamp,
+ const protozero::Field& thread,
+ protos::pbzero::ProcessTree* process_trees) const;
+
+ // TODO(vaage): Add a handler that is called the process tree is populated so
+ // that fields can be added to process tree (e.g. creating new threads -
+ // needed for thread merging).
+
+ private:
+ void TransformProcessTree(const Context& context,
+ const protozero::Field& timestamp,
+ const protozero::Field& process_tree,
+ protos::pbzero::ProcessTree* message) const;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_MODIFY_PROCESS_TREES_H_
diff --git a/src/trace_redaction/scrub_process_trees.cc b/src/trace_redaction/scrub_process_trees.cc
index d8baf99..989d094 100644
--- a/src/trace_redaction/scrub_process_trees.cc
+++ b/src/trace_redaction/scrub_process_trees.cc
@@ -16,9 +16,6 @@
#include "src/trace_redaction/scrub_process_trees.h"
-#include <cstdint>
-#include <string>
-
#include "perfetto/base/status.h"
#include "perfetto/protozero/field.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
@@ -29,149 +26,90 @@
#include "protos/perfetto/trace/trace_packet.pbzero.h"
namespace perfetto::trace_redaction {
+
namespace {
-constexpr auto kThreadsFieldNumber =
- protos::pbzero::ProcessTree::kThreadsFieldNumber;
-constexpr auto kTimestampFieldNumber =
- protos::pbzero::TracePacket::kTimestampFieldNumber;
-constexpr auto kProcessTreeFieldNumber =
- protos::pbzero::TracePacket::kProcessTreeFieldNumber;
-constexpr auto kProcessesFieldNumber =
- protos::pbzero::ProcessTree::kProcessesFieldNumber;
-
-// Skips the cmdline fields.
-void ClearProcessName(protozero::ConstBytes bytes,
- protos::pbzero::ProcessTree::Process* message) {
- protozero::ProtoDecoder decoder(bytes);
-
- for (auto field = decoder.ReadField(); field; field = decoder.ReadField()) {
- if (field.id() !=
- protos::pbzero::ProcessTree::Process::kCmdlineFieldNumber) {
- proto_util::AppendField(field, message);
- }
- }
-}
-
-void ScrubProcess(protozero::Field field,
- const ProcessThreadTimeline& timeline,
- uint64_t now,
- uint64_t uid,
- protos::pbzero::ProcessTree* message) {
- if (field.id() != kProcessesFieldNumber) {
- PERFETTO_FATAL(
- "ScrubProcess() should only be called with a ProcessTree::Processes");
+// Appends a value to the message if (and only if) the pid belongs to the target
+// package.
+void TryAppendPid(const Context& context,
+ const protozero::Field& timestamp,
+ const protozero::Field& pid,
+ const protozero::Field& value,
+ protozero::Message* message) {
+ // All valid processes with have a time and pid/tid values. However, if
+ // they're missing values, the trace is corrupt. To avoid making this work by
+ // dropping too much data, drop the cmdline for all processes.
+ if (!timestamp.valid() || !pid.valid()) {
+ return;
}
- protos::pbzero::ProcessTree::Process::Decoder decoder(field.as_bytes());
- auto slice = timeline.Search(now, decoder.pid());
+ auto slice = context.timeline->Search(timestamp.as_uint64(), pid.as_int32());
- if (NormalizeUid(slice.uid) == NormalizeUid(uid)) {
- proto_util::AppendField(field, message);
- } else {
- ClearProcessName(field.as_bytes(), message->add_processes());
- }
-}
-
-// The thread name is unused, but it's safer to remove it.
-void ClearThreadName(protozero::ConstBytes bytes,
- protos::pbzero::ProcessTree::Thread* message) {
- protozero::ProtoDecoder decoder(bytes);
-
- for (auto field = decoder.ReadField(); field; field = decoder.ReadField()) {
- if (field.id() != protos::pbzero::ProcessTree::Thread::kNameFieldNumber) {
- proto_util::AppendField(field, message);
- }
- }
-}
-
-void ScrubThread(protozero::Field field,
- const ProcessThreadTimeline& timeline,
- uint64_t now,
- uint64_t uid,
- protos::pbzero::ProcessTree* message) {
- if (field.id() != kThreadsFieldNumber) {
- PERFETTO_FATAL(
- "ScrubThread() should only be called with a ProcessTree::Threads");
+ // Only keep the target process cmdline.
+ if (NormalizeUid(slice.uid) != NormalizeUid(context.package_uid.value())) {
+ return;
}
- protos::pbzero::ProcessTree::Thread::Decoder thread_decoder(field.as_bytes());
- auto slice = timeline.Search(now, thread_decoder.tid());
-
- if (NormalizeUid(slice.uid) == NormalizeUid(uid)) {
- proto_util::AppendField(field, message);
- } else {
- ClearThreadName(field.as_bytes(), message->add_threads());
- }
+ proto_util::AppendField(value, message);
}
} // namespace
-base::Status ScrubProcessTrees::Transform(const Context& context,
- std::string* packet) const {
+base::Status ScrubProcessTrees::VerifyContext(const Context& context) const {
if (!context.package_uid.has_value()) {
return base::ErrStatus("ScrubProcessTrees: missing package uid.");
}
- if (context.timeline == nullptr) {
+ if (!context.timeline) {
return base::ErrStatus("ScrubProcessTrees: missing timeline.");
}
- protozero::ProtoDecoder decoder(*packet);
-
- if (!decoder.FindField(kProcessTreeFieldNumber).valid()) {
- return base::OkStatus();
- }
-
- auto timestamp_field = decoder.FindField(kTimestampFieldNumber);
-
- if (!timestamp_field.valid()) {
- return base::ErrStatus("ScrubProcessTrees: trace packet missing timestamp");
- }
-
- auto timestamp = timestamp_field.as_uint64();
-
- auto uid = context.package_uid.value();
-
- const auto& timeline = *context.timeline.get();
-
- protozero::HeapBuffered<protos::pbzero::TracePacket> message;
-
- for (auto packet_field = decoder.ReadField(); packet_field.valid();
- packet_field = decoder.ReadField()) {
- if (packet_field.id() != kProcessTreeFieldNumber) {
- proto_util::AppendField(packet_field, message.get());
- continue;
- }
-
- auto* process_tree_message = message->set_process_tree();
-
- protozero::ProtoDecoder process_tree_decoder(packet_field.as_bytes());
-
- for (auto process_tree_field = process_tree_decoder.ReadField();
- process_tree_field.valid();
- process_tree_field = process_tree_decoder.ReadField()) {
- switch (process_tree_field.id()) {
- case kProcessesFieldNumber:
- ScrubProcess(process_tree_field, timeline, timestamp, uid,
- process_tree_message);
- break;
-
- case kThreadsFieldNumber:
- ScrubThread(process_tree_field, timeline, timestamp, uid,
- process_tree_message);
- break;
-
- default:
- proto_util::AppendField(process_tree_field, process_tree_message);
- break;
- }
- }
- }
-
- packet->assign(message.SerializeAsString());
-
return base::OkStatus();
}
+void ScrubProcessTrees::TransformProcess(
+ const Context& context,
+ const protozero::Field& timestamp,
+ const protozero::Field& process,
+ protos::pbzero::ProcessTree* process_tree) const {
+ protozero::ProtoDecoder decoder(process.as_bytes());
+
+ auto pid =
+ decoder.FindField(protos::pbzero::ProcessTree::Process::kPidFieldNumber);
+
+ auto* process_message = process_tree->add_processes();
+
+ for (auto field = decoder.ReadField(); field.valid();
+ field = decoder.ReadField()) {
+ if (field.id() ==
+ protos::pbzero::ProcessTree::Process::kCmdlineFieldNumber) {
+ TryAppendPid(context, timestamp, pid, field, process_message);
+ } else {
+ proto_util::AppendField(field, process_message);
+ }
+ }
+}
+
+void ScrubProcessTrees::TransformThread(
+ const Context& context,
+ const protozero::Field& timestamp,
+ const protozero::Field& thread,
+ protos::pbzero::ProcessTree* process_tree) const {
+ protozero::ProtoDecoder decoder(thread.as_bytes());
+
+ auto tid =
+ decoder.FindField(protos::pbzero::ProcessTree::Thread::kTidFieldNumber);
+
+ auto* thread_message = process_tree->add_threads();
+
+ for (auto field = decoder.ReadField(); field.valid();
+ field = decoder.ReadField()) {
+ if (field.id() == protos::pbzero::ProcessTree::Thread::kNameFieldNumber) {
+ TryAppendPid(context, timestamp, tid, field, thread_message);
+ } else {
+ proto_util::AppendField(field, thread_message);
+ }
+ }
+}
+
} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_process_trees.h b/src/trace_redaction/scrub_process_trees.h
index 61cb85a..7c2b07a 100644
--- a/src/trace_redaction/scrub_process_trees.h
+++ b/src/trace_redaction/scrub_process_trees.h
@@ -17,19 +17,32 @@
#ifndef SRC_TRACE_REDACTION_SCRUB_PROCESS_TREES_H_
#define SRC_TRACE_REDACTION_SCRUB_PROCESS_TREES_H_
-#include <string>
-
#include "perfetto/base/status.h"
+#include "perfetto/protozero/field.h"
+#include "src/trace_redaction/modify_process_trees.h"
#include "src/trace_redaction/trace_redaction_framework.h"
+#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
+
namespace perfetto::trace_redaction {
// Removes process names and thread names from process_trees if their pids/tids
// are not connected to the target package.
-class ScrubProcessTrees final : public TransformPrimitive {
- public:
- base::Status Transform(const Context& context,
- std::string* packet) const override;
+class ScrubProcessTrees : public ModifyProcessTree {
+ protected:
+ base::Status VerifyContext(const Context& context) const override;
+
+ void TransformProcess(
+ const Context& context,
+ const protozero::Field& timestamp,
+ const protozero::Field& process,
+ protos::pbzero::ProcessTree* process_trees) const override;
+
+ void TransformThread(
+ const Context& context,
+ const protozero::Field& timestamp,
+ const protozero::Field& thread,
+ protos::pbzero::ProcessTree* process_tree) const override;
};
} // namespace perfetto::trace_redaction