1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <string>
18 #include <string_view>
19 #include <vector>
20
21 #include "src/base/test/status_matchers.h"
22 #include "src/trace_redaction/collect_system_info.h"
23 #include "src/trace_redaction/collect_timeline_events.h"
24 #include "src/trace_redaction/find_package_uid.h"
25 #include "src/trace_redaction/redact_process_trees.h"
26 #include "src/trace_redaction/trace_redaction_framework.h"
27 #include "src/trace_redaction/trace_redaction_integration_fixture.h"
28 #include "src/trace_redaction/trace_redactor.h"
29 #include "test/gtest_and_gmock.h"
30
31 #include "protos/perfetto/trace/ps/process_tree.pbzero.h"
32 #include "protos/perfetto/trace/trace.pbzero.h"
33
34 namespace perfetto::trace_redaction {
35
36 namespace {
37
38 constexpr std::string_view kProcessName =
39 "com.Unity.com.unity.multiplayer.samples.coop";
40
41 } // namespace
42
43 class RedactProcessTreesIntegrationTest
44 : public testing::Test,
45 protected TraceRedactionIntegrationFixure {
46 protected:
SetUp()47 void SetUp() override {
48 trace_redactor_.emplace_collect<CollectSystemInfo>();
49 trace_redactor_.emplace_build<BuildSyntheticThreads>();
50
51 trace_redactor_.emplace_collect<FindPackageUid>();
52 trace_redactor_.emplace_collect<CollectTimelineEvents>();
53
54 // Filter the process tree based on whether or not a process is part of the
55 // target package.
56 auto* process_tree =
57 trace_redactor_.emplace_transform<RedactProcessTrees>();
58 process_tree->emplace_modifier<ProcessTreeDoNothing>();
59 process_tree->emplace_filter<ConnectedToPackage>();
60
61 // In this case, the process and package have the same name.
62 context_.package_name = kProcessName;
63 }
64
GetPids(const std::string & bytes) const65 std::unordered_set<int32_t> GetPids(const std::string& bytes) const {
66 std::unordered_set<int32_t> pids;
67
68 protos::pbzero::Trace::Decoder decoder(bytes);
69
70 for (auto it = decoder.packet(); it; ++it) {
71 protos::pbzero::TracePacket::Decoder packet(*it);
72
73 if (packet.has_process_tree()) {
74 GetPids(packet.process_tree(), &pids);
75 }
76 }
77
78 return pids;
79 }
80
GetTids(const std::string & bytes) const81 std::unordered_set<int32_t> GetTids(const std::string& bytes) const {
82 std::unordered_set<int32_t> tids;
83
84 protos::pbzero::Trace::Decoder decoder(bytes);
85
86 for (auto it = decoder.packet(); it; ++it) {
87 protos::pbzero::TracePacket::Decoder packet(*it);
88
89 if (packet.has_process_tree()) {
90 GetTids(packet.process_tree(), &tids);
91 }
92 }
93
94 return tids;
95 }
96
97 Context context_;
98 TraceRedactor trace_redactor_;
99
100 private:
GetPids(protozero::ConstBytes bytes,std::unordered_set<int32_t> * pids) const101 void GetPids(protozero::ConstBytes bytes,
102 std::unordered_set<int32_t>* pids) const {
103 protos::pbzero::ProcessTree::Decoder process_tree(bytes);
104
105 for (auto it = process_tree.processes(); it; ++it) {
106 protos::pbzero::ProcessTree::Process::Decoder process(*it);
107 pids->insert(process.ppid());
108 pids->insert(process.pid());
109 }
110 }
111
GetTids(protozero::ConstBytes bytes,std::unordered_set<int32_t> * tids) const112 void GetTids(protozero::ConstBytes bytes,
113 std::unordered_set<int32_t>* tids) const {
114 protos::pbzero::ProcessTree::Decoder process_tree(bytes);
115
116 for (auto it = process_tree.threads(); it; ++it) {
117 protos::pbzero::ProcessTree::Thread::Decoder thread(*it);
118 tids->insert(thread.tgid());
119 tids->insert(thread.tid());
120 }
121 }
122 };
123
TEST_F(RedactProcessTreesIntegrationTest,FilterProcesses)124 TEST_F(RedactProcessTreesIntegrationTest, FilterProcesses) {
125 ASSERT_OK(Redact(trace_redactor_, &context_));
126
127 auto original_trace_str = LoadOriginal();
128 ASSERT_OK(original_trace_str);
129
130 auto redacted_trace_str = LoadRedacted();
131 ASSERT_OK(redacted_trace_str);
132
133 auto original_pids = GetPids(*original_trace_str);
134 auto redacted_pids = GetPids(*redacted_trace_str);
135
136 // There are 902 unique pids across all process trees:
137 // grep 'processes {' -A 1 src.pftrace.txt | grep 'pid: ' | grep -Po "\d+"
138 // | sort | uniq | wc -l
139 //
140 // But if ppids are included, there are 903 pids in the process tree:
141 // grep 'processes {' -A 2 src.pftrace.txt | grep 'pid: ' | grep -Po "\d+"
142 // | sort | uniq | wc -l
143 //
144 // The above grep statements use a stringified version of the trace. Using "-A
145 // 1" will return the pid line. Using "-A 2" will include both pid and ppid.
146 //
147 // The original process count aligns with trace processor. However, the
148 // redacted count does not. The final tree has one process but trace processor
149 // reports 4 processes.
150 ASSERT_EQ(original_pids.size(), 903u);
151 ASSERT_EQ(redacted_pids.size(), 2u);
152
153 ASSERT_TRUE(redacted_pids.count(7105));
154 }
155
TEST_F(RedactProcessTreesIntegrationTest,FilterThreads)156 TEST_F(RedactProcessTreesIntegrationTest, FilterThreads) {
157 ASSERT_OK(Redact(trace_redactor_, &context_));
158
159 auto original_trace_str = LoadOriginal();
160 ASSERT_OK(original_trace_str);
161
162 auto redacted_trace_str = LoadRedacted();
163 ASSERT_OK(redacted_trace_str);
164
165 auto original_tids = GetTids(*original_trace_str);
166 auto redacted_tids = GetTids(*redacted_trace_str);
167
168 // There are 2761 unique tids across all process trees:
169 // grep 'threads {' -A 1 src.pftrace.txt | grep 'tid: ' | grep -Po "\d+" |
170 // sort | uniq | wc -l
171 //
172 // There are 2896 unique tids/tgis across all process trees:
173 // grep 'threads {' -A 2 src.pftrace.txt | grep -P '(tid|tgid): ' | grep
174 // -Po '\d+' | sort | uniq | wc -l
175 //
176 // The original tid count does NOT align with what trace processor returns.
177 // Trace processor reports 3666 threads. The assumption is trace processor is
178 // fulling thread information from additional.
179 //
180 // The redacted tid+tgid count does NOT align with what trace processor
181 // returns. Trace processor reports 199 tids where are there are only 63 tids
182 // found in process tree. This suggests that trace processor is pulling tid
183 // data from other locations.
184 ASSERT_EQ(original_tids.size(), 2896u);
185 ASSERT_EQ(redacted_tids.size(), 64u);
186 }
187
TEST_F(RedactProcessTreesIntegrationTest,AddSynthProcess)188 TEST_F(RedactProcessTreesIntegrationTest, AddSynthProcess) {
189 // Append another primitive that won't filter, but will add new threads. This
190 // will be compatible with the other instanced in SetUp().
191 auto* process_tree = trace_redactor_.emplace_transform<RedactProcessTrees>();
192 process_tree->emplace_modifier<ProcessTreeCreateSynthThreads>();
193 process_tree->emplace_filter<AllowAll>();
194
195 ASSERT_OK(Redact(trace_redactor_, &context_));
196
197 auto redacted_trace_str = LoadRedacted();
198 ASSERT_OK(redacted_trace_str);
199
200 auto redacted_pids = GetPids(*redacted_trace_str);
201
202 const auto* synth_process = context_.synthetic_process.get();
203 ASSERT_TRUE(synth_process);
204
205 ASSERT_NE(std::find(redacted_pids.begin(), redacted_pids.end(),
206 synth_process->tgid()),
207 redacted_pids.end());
208 }
209
TEST_F(RedactProcessTreesIntegrationTest,AddSynthThreads)210 TEST_F(RedactProcessTreesIntegrationTest, AddSynthThreads) {
211 // Append another primitive that won't filter, but will add new threads. This
212 // will be compatible with the other instanced in SetUp().
213 auto* process_tree = trace_redactor_.emplace_transform<RedactProcessTrees>();
214 process_tree->emplace_modifier<ProcessTreeCreateSynthThreads>();
215 process_tree->emplace_filter<AllowAll>();
216
217 ASSERT_OK(Redact(trace_redactor_, &context_));
218
219 const auto* synth_process = context_.synthetic_process.get();
220 ASSERT_TRUE(synth_process);
221
222 ASSERT_FALSE(synth_process->tids().empty());
223
224 auto original_trace_str = LoadOriginal();
225 ASSERT_OK(original_trace_str);
226
227 auto original_tids = GetTids(*original_trace_str);
228
229 // The synth threads should not be found in the original trace.
230 for (auto tid : synth_process->tids()) {
231 ASSERT_FALSE(original_tids.count(tid));
232 }
233
234 auto redacted_trace_str = LoadRedacted();
235 ASSERT_OK(redacted_trace_str);
236
237 auto redacted_tids = GetTids(*redacted_trace_str);
238
239 // The synth threads should be found in the redacted trace.
240 for (auto tid : synth_process->tids()) {
241 ASSERT_TRUE(redacted_tids.count(tid));
242 }
243 }
244
245 } // namespace perfetto::trace_redaction
246