1 /*
2 * Copyright (C) 2025 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 <stdlib.h>
18 #include <sys/system_properties.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21
22 #include <random>
23 #include <string>
24 #include <string_view>
25
26 #include "perfetto/base/logging.h"
27 #include "perfetto/ext/base/android_utils.h"
28 #include "perfetto/ext/base/string_utils.h"
29 #include "perfetto/tracing/core/data_source_config.h"
30
31 #include "src/base/test/test_task_runner.h"
32 #include "src/base/test/tmp_dir_tree.h"
33 #include "test/android_test_utils.h"
34 #include "test/cts/heapprofd_test_helper.h"
35 #include "test/gtest_and_gmock.h"
36 #include "test/test_helper.h"
37
38 #include "protos/perfetto/config/profiling/heapprofd_config.gen.h"
39 #include "protos/perfetto/trace/profiling/profile_common.gen.h"
40 #include "protos/perfetto/trace/profiling/profile_packet.gen.h"
41 #include "protos/perfetto/trace/trace_packet.gen.h"
42
43 namespace perfetto {
44 namespace {
45
46 // Path in the app external directory where the app writes an interation
47 // counter. It is used to wait for the test apps to actually perform
48 // allocations.
49 constexpr std::string_view kReportCyclePath = "report_cycle.txt";
50
51 // Asks FileContentProvider.java inside the app to read a file.
52 class ContentProviderReader {
53 public:
ContentProviderReader(const std::string & app,const std::string & path)54 explicit ContentProviderReader(const std::string& app,
55 const std::string& path) {
56 tmp_dir_.TrackFile("contents.txt");
57 tempfile_ = tmp_dir_.AbsolutePath("contents.txt");
58
59 std::optional<int32_t> sdk =
60 base::StringToInt32(base::GetAndroidProp("ro.build.version.sdk"));
61 bool multiuser_support = sdk && *sdk >= 34;
62 cmd_ = "content read";
63 if (multiuser_support) {
64 // This is required only starting from android U.
65 cmd_ += " --user `am get-current-user`";
66 }
67 cmd_ += std::string(" --uri content://") + app + std::string("/") + path;
68 cmd_ += " >" + tempfile_;
69 }
70
ReadInt64()71 std::optional<int64_t> ReadInt64() {
72 if (system(cmd_.c_str()) != 0) {
73 return std::nullopt;
74 }
75 return ReadInt64FromFile(tempfile_);
76 }
77
78 private:
ReadInt64FromFile(const std::string & path)79 std::optional<int64_t> ReadInt64FromFile(const std::string& path) {
80 std::string contents;
81 if (!base::ReadFile(path, &contents)) {
82 return std::nullopt;
83 }
84 return base::StringToInt64(contents);
85 }
86
87 base::TmpDirTree tmp_dir_;
88 std::string tempfile_;
89 std::string cmd_;
90 };
91
WaitForAppAllocationCycle(const std::string & app_name,size_t timeout_ms)92 bool WaitForAppAllocationCycle(const std::string& app_name, size_t timeout_ms) {
93 const size_t sleep_per_attempt_us = 100 * 1000;
94 const size_t max_attempts = timeout_ms * 1000 / sleep_per_attempt_us;
95
96 ContentProviderReader app_reader(app_name, std::string(kReportCyclePath));
97
98 for (size_t attempts = 0; attempts < max_attempts;) {
99 int64_t first_value;
100 for (; attempts < max_attempts; attempts++) {
101 std::optional<int64_t> val = app_reader.ReadInt64();
102 if (val) {
103 first_value = *val;
104 break;
105 }
106 base::SleepMicroseconds(sleep_per_attempt_us);
107 }
108
109 for (; attempts < max_attempts; attempts++) {
110 std::optional<int64_t> val = app_reader.ReadInt64();
111 if (!val || *val < first_value) {
112 break;
113 }
114 if (*val >= first_value + 2) {
115 // We've observed the counter being incremented twice. We can be sure
116 // that the app has gone through a full allocation cycle.
117 return true;
118 }
119 base::SleepMicroseconds(sleep_per_attempt_us);
120 }
121 }
122 return false;
123 }
124
125 } // namespace
126
RandomSessionName()127 std::string RandomSessionName() {
128 std::random_device rd;
129 std::default_random_engine generator(rd());
130 std::uniform_int_distribution<> distribution('a', 'z');
131
132 constexpr size_t kSessionNameLen = 20;
133 std::string result(kSessionNameLen, '\0');
134 for (size_t i = 0; i < kSessionNameLen; ++i)
135 result[i] = static_cast<char>(distribution(generator));
136 return result;
137 }
138
139 // Starts the activity `activity` of the app `app_name` and later starts
140 // recording a trace with the allocations in `heap_names`.
141 //
142 // `heap_names` is a list of the heap names whose allocations will be recorded.
143 // An empty list means that only the allocations in the default malloc heap
144 // ("libc.malloc") are recorded.
145 //
146 // Returns the recorded trace.
ProfileRuntime(const std::string & app_name,const std::string & activity,uint64_t sampling_interval,const std::vector<std::string> & heap_names)147 std::vector<protos::gen::TracePacket> ProfileRuntime(
148 const std::string& app_name,
149 const std::string& activity,
150 uint64_t sampling_interval,
151 const std::vector<std::string>& heap_names) {
152 base::TestTaskRunner task_runner;
153
154 // (re)start the target app's main activity
155 if (IsAppRunning(app_name)) {
156 StopApp(app_name, "old.app.stopped", &task_runner);
157 task_runner.RunUntilCheckpoint("old.app.stopped", 10000 /*ms*/);
158 }
159 StartAppActivity(app_name, activity, "target.app.running", &task_runner,
160 /*delay_ms=*/100);
161 task_runner.RunUntilCheckpoint("target.app.running", 10000 /*ms*/);
162
163 // set up tracing
164 TestHelper helper(&task_runner);
165 helper.ConnectConsumer();
166 helper.WaitForConsumerConnect();
167
168 TraceConfig trace_config;
169 trace_config.add_buffers()->set_size_kb(10 * 1024);
170 trace_config.set_unique_session_name(RandomSessionName().c_str());
171
172 auto* ds_config = trace_config.add_data_sources()->mutable_config();
173 ds_config->set_name("android.heapprofd");
174 ds_config->set_target_buffer(0);
175
176 protos::gen::HeapprofdConfig heapprofd_config;
177 heapprofd_config.set_sampling_interval_bytes(sampling_interval);
178 heapprofd_config.add_process_cmdline(app_name.c_str());
179 heapprofd_config.set_block_client(true);
180 heapprofd_config.set_all(false);
181 for (const std::string& heap_name : heap_names) {
182 heapprofd_config.add_heaps(heap_name);
183 }
184 ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
185
186 // start tracing
187 helper.StartTracing(trace_config);
188
189 EXPECT_TRUE(WaitForAppAllocationCycle(app_name, /*timeout_ms=*/10000));
190
191 helper.DisableTracing();
192 helper.WaitForTracingDisabled();
193 helper.ReadData();
194 helper.WaitForReadData();
195
196 return helper.trace();
197 }
198
199 // Starts recording a trace with the allocations in `heap_names` and later
200 // starts the activity `activity` of the app `app_name`
201 //
202 // `heap_names` is a list of the heap names whose allocations will be recorded.
203 // An empty list means that only the allocation in the default malloc heap
204 // ("libc.malloc") are recorded.
205 //
206 // Returns the recorded trace.
ProfileStartup(const std::string & app_name,const std::string & activity,uint64_t sampling_interval,const std::vector<std::string> & heap_names,const bool enable_extra_guardrails)207 std::vector<protos::gen::TracePacket> ProfileStartup(
208 const std::string& app_name,
209 const std::string& activity,
210 uint64_t sampling_interval,
211 const std::vector<std::string>& heap_names,
212 const bool enable_extra_guardrails) {
213 base::TestTaskRunner task_runner;
214
215 if (IsAppRunning(app_name)) {
216 StopApp(app_name, "old.app.stopped", &task_runner);
217 task_runner.RunUntilCheckpoint("old.app.stopped", 10000 /*ms*/);
218 }
219
220 // set up tracing
221 TestHelper helper(&task_runner);
222 helper.ConnectConsumer();
223 helper.WaitForConsumerConnect();
224
225 TraceConfig trace_config;
226 trace_config.add_buffers()->set_size_kb(10 * 1024);
227 trace_config.set_enable_extra_guardrails(enable_extra_guardrails);
228 trace_config.set_unique_session_name(RandomSessionName().c_str());
229
230 auto* ds_config = trace_config.add_data_sources()->mutable_config();
231 ds_config->set_name("android.heapprofd");
232 ds_config->set_target_buffer(0);
233
234 protos::gen::HeapprofdConfig heapprofd_config;
235 heapprofd_config.set_sampling_interval_bytes(sampling_interval);
236 heapprofd_config.add_process_cmdline(app_name.c_str());
237 heapprofd_config.set_block_client(true);
238 heapprofd_config.set_all(false);
239 for (const std::string& heap_name : heap_names) {
240 heapprofd_config.add_heaps(heap_name);
241 }
242 ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
243
244 // start tracing
245 helper.StartTracing(trace_config);
246
247 // start app
248 StartAppActivity(app_name, activity, "target.app.running", &task_runner,
249 /*delay_ms=*/100);
250 task_runner.RunUntilCheckpoint("target.app.running", 10000 /*ms*/);
251
252 EXPECT_TRUE(WaitForAppAllocationCycle(app_name, /*timeout_ms=*/10000));
253
254 helper.DisableTracing();
255 helper.WaitForTracingDisabled();
256 helper.ReadData();
257 helper.WaitForReadData();
258
259 return helper.trace();
260 }
261
AssertExpectedMallocsPresent(uint64_t expected_individual_alloc_sz,const std::vector<protos::gen::TracePacket> & packets)262 void AssertExpectedMallocsPresent(
263 uint64_t expected_individual_alloc_sz,
264 const std::vector<protos::gen::TracePacket>& packets) {
265 ASSERT_GT(packets.size(), 0u);
266
267 // TODO(rsavitski): assert particular stack frames once we clarify the
268 // expected behaviour of unwinding native libs within an apk.
269 // Until then, look for an allocation that is a multiple of the expected
270 // allocation size.
271 bool found_alloc = false;
272 bool found_proc_dump = false;
273 for (const auto& packet : packets) {
274 for (const auto& proc_dump : packet.profile_packet().process_dumps()) {
275 found_proc_dump = true;
276 for (const auto& sample : proc_dump.samples()) {
277 if (sample.self_allocated() > 0 &&
278 sample.self_allocated() % expected_individual_alloc_sz == 0) {
279 found_alloc = true;
280
281 EXPECT_TRUE(sample.self_freed() > 0 &&
282 sample.self_freed() % expected_individual_alloc_sz == 0)
283 << "self_freed: " << sample.self_freed();
284 }
285 }
286 }
287 }
288 ASSERT_TRUE(found_proc_dump);
289 ASSERT_TRUE(found_alloc);
290 }
291
AssertHasSampledAllocs(const std::vector<protos::gen::TracePacket> & packets)292 void AssertHasSampledAllocs(
293 const std::vector<protos::gen::TracePacket>& packets) {
294 ASSERT_GT(packets.size(), 0u);
295
296 bool found_alloc = false;
297 bool found_proc_dump = false;
298 for (const auto& packet : packets) {
299 for (const auto& proc_dump : packet.profile_packet().process_dumps()) {
300 found_proc_dump = true;
301 for (const auto& sample : proc_dump.samples()) {
302 if (sample.self_allocated() > 0) {
303 found_alloc = true;
304 }
305 }
306 }
307 }
308 ASSERT_TRUE(found_proc_dump);
309 ASSERT_TRUE(found_alloc);
310 }
311
AssertNoProfileContents(const std::vector<protos::gen::TracePacket> & packets)312 void AssertNoProfileContents(
313 const std::vector<protos::gen::TracePacket>& packets) {
314 // If profile packets are present, they must be empty.
315 for (const auto& packet : packets) {
316 ASSERT_EQ(packet.profile_packet().process_dumps_size(), 0);
317 }
318 }
319
320 } // namespace perfetto
321