1 /*
2 * Copyright (C) 2019 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 #include "src/base/test/test_task_runner.h"
31 #include "src/base/test/tmp_dir_tree.h"
32 #include "test/android_test_utils.h"
33 #include "test/gtest_and_gmock.h"
34 #include "test/test_helper.h"
35
36 #include "protos/perfetto/config/process_stats/process_stats_config.gen.h"
37 #include "protos/perfetto/config/profiling/heapprofd_config.gen.h"
38 #include "protos/perfetto/trace/profiling/profile_common.gen.h"
39 #include "protos/perfetto/trace/profiling/profile_packet.gen.h"
40 #include "protos/perfetto/trace/trace_packet.gen.h"
41
42 namespace perfetto {
43 namespace {
44
45 // Size of individual (repeated) allocations done by the test apps (must be kept
46 // in sync with their sources).
47 constexpr uint64_t kTestSamplingInterval = 4096;
48 constexpr uint64_t kExpectedIndividualAllocSz = 4153;
49 // Tests rely on the sampling behaviour where allocations larger than the
50 // sampling interval are recorded at their actual size.
51 static_assert(kExpectedIndividualAllocSz > kTestSamplingInterval,
52 "kTestSamplingInterval invalid");
53
54 // Path in the app external directory where the app writes an interation
55 // counter. It is used to wait for the test apps to actually perform
56 // allocations.
57 constexpr std::string_view kReportCyclePath = "report_cycle.txt";
58
59 // Activity that runs a JNI thread that repeatedly calls
60 // malloc(kExpectedIndividualAllocSz).
61 static char kMallocActivity[] = "MainActivity";
62 // Activity that runs a java thread that repeatedly constructs small java
63 // objects.
64 static char kJavaAllocActivity[] = "JavaAllocActivity";
65
RandomSessionName()66 std::string RandomSessionName() {
67 std::random_device rd;
68 std::default_random_engine generator(rd());
69 std::uniform_int_distribution<> distribution('a', 'z');
70
71 constexpr size_t kSessionNameLen = 20;
72 std::string result(kSessionNameLen, '\0');
73 for (size_t i = 0; i < kSessionNameLen; ++i)
74 result[i] = static_cast<char>(distribution(generator));
75 return result;
76 }
77
78 // Asks FileContentProvider.java inside the app to read a file.
79 class ContentProviderReader {
80 public:
ContentProviderReader(const std::string & app,const std::string & path)81 explicit ContentProviderReader(const std::string& app,
82 const std::string& path) {
83 tmp_dir_.TrackFile("contents.txt");
84 tempfile_ = tmp_dir_.AbsolutePath("contents.txt");
85
86 std::optional<int32_t> sdk =
87 base::StringToInt32(base::GetAndroidProp("ro.build.version.sdk"));
88 bool multiuser_support = sdk && *sdk >= 34;
89 cmd_ = "content read";
90 if (multiuser_support) {
91 // This is required only starting from android U.
92 cmd_ += " --user `am get-current-user`";
93 }
94 cmd_ += std::string(" --uri content://") + app + std::string("/") + path;
95 cmd_ += " >" + tempfile_;
96 }
97
ReadInt64()98 std::optional<int64_t> ReadInt64() {
99 if (system(cmd_.c_str()) != 0) {
100 return std::nullopt;
101 }
102 return ReadInt64FromFile(tempfile_);
103 }
104
105 private:
ReadInt64FromFile(const std::string & path)106 std::optional<int64_t> ReadInt64FromFile(const std::string& path) {
107 std::string contents;
108 if (!base::ReadFile(path, &contents)) {
109 return std::nullopt;
110 }
111 return base::StringToInt64(contents);
112 }
113
114 base::TmpDirTree tmp_dir_;
115 std::string tempfile_;
116 std::string cmd_;
117 };
118
WaitForAppAllocationCycle(const std::string & app_name,size_t timeout_ms)119 bool WaitForAppAllocationCycle(const std::string& app_name, size_t timeout_ms) {
120 const size_t sleep_per_attempt_us = 100 * 1000;
121 const size_t max_attempts = timeout_ms * 1000 / sleep_per_attempt_us;
122
123 ContentProviderReader app_reader(app_name, std::string(kReportCyclePath));
124
125 for (size_t attempts = 0; attempts < max_attempts;) {
126 int64_t first_value;
127 for (; attempts < max_attempts; attempts++) {
128 std::optional<int64_t> val = app_reader.ReadInt64();
129 if (val) {
130 first_value = *val;
131 break;
132 }
133 base::SleepMicroseconds(sleep_per_attempt_us);
134 }
135
136 for (; attempts < max_attempts; attempts++) {
137 std::optional<int64_t> val = app_reader.ReadInt64();
138 if (!val || *val < first_value) {
139 break;
140 }
141 if (*val >= first_value + 2) {
142 // We've observed the counter being incremented twice. We can be sure
143 // that the app has gone through a full allocation cycle.
144 return true;
145 }
146 base::SleepMicroseconds(sleep_per_attempt_us);
147 }
148 }
149 return false;
150 }
151
152 // Starts the activity `activity` of the app `app_name` and later starts
153 // recording a trace with the allocations in `heap_names`.
154 //
155 // `heap_names` is a list of the heap names whose allocations will be recorded.
156 // An empty list means that only the allocations in the default malloc heap
157 // ("libc.malloc") are recorded.
158 //
159 // Returns the recorded trace.
ProfileRuntime(const std::string & app_name,const std::string & activity,const std::vector<std::string> & heap_names)160 std::vector<protos::gen::TracePacket> ProfileRuntime(
161 const std::string& app_name,
162 const std::string& activity,
163 const std::vector<std::string>& heap_names) {
164 base::TestTaskRunner task_runner;
165
166 // (re)start the target app's main activity
167 if (IsAppRunning(app_name)) {
168 StopApp(app_name, "old.app.stopped", &task_runner);
169 task_runner.RunUntilCheckpoint("old.app.stopped", 10000 /*ms*/);
170 }
171 StartAppActivity(app_name, activity, "target.app.running", &task_runner,
172 /*delay_ms=*/100);
173 task_runner.RunUntilCheckpoint("target.app.running", 10000 /*ms*/);
174
175 // set up tracing
176 TestHelper helper(&task_runner);
177 helper.ConnectConsumer();
178 helper.WaitForConsumerConnect();
179
180 TraceConfig trace_config;
181 trace_config.add_buffers()->set_size_kb(10 * 1024);
182 trace_config.set_unique_session_name(RandomSessionName().c_str());
183
184 auto* ds_config = trace_config.add_data_sources()->mutable_config();
185 ds_config->set_name("android.heapprofd");
186 ds_config->set_target_buffer(0);
187
188 protos::gen::HeapprofdConfig heapprofd_config;
189 heapprofd_config.set_sampling_interval_bytes(kTestSamplingInterval);
190 heapprofd_config.add_process_cmdline(app_name.c_str());
191 heapprofd_config.set_block_client(true);
192 heapprofd_config.set_all(false);
193 for (const std::string& heap_name : heap_names) {
194 heapprofd_config.add_heaps(heap_name);
195 }
196 ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
197
198 // start tracing
199 helper.StartTracing(trace_config);
200
201 EXPECT_TRUE(WaitForAppAllocationCycle(app_name, /*timeout_ms=*/10000));
202
203 helper.DisableTracing();
204 helper.WaitForTracingDisabled();
205 helper.ReadData();
206 helper.WaitForReadData();
207
208 return helper.trace();
209 }
210
211 // Starts recording a trace with the allocations in `heap_names` and later
212 // starts the activity `activity` of the app `app_name`
213 //
214 // `heap_names` is a list of the heap names whose allocations will be recorded.
215 // An empty list means that only the allocation in the default malloc heap
216 // ("libc.malloc") are recorded.
217 //
218 // Returns the recorded trace.
ProfileStartup(const std::string & app_name,const std::string & activity,const std::vector<std::string> & heap_names,const bool enable_extra_guardrails=false)219 std::vector<protos::gen::TracePacket> ProfileStartup(
220 const std::string& app_name,
221 const std::string& activity,
222 const std::vector<std::string>& heap_names,
223 const bool enable_extra_guardrails = false) {
224 base::TestTaskRunner task_runner;
225
226 if (IsAppRunning(app_name)) {
227 StopApp(app_name, "old.app.stopped", &task_runner);
228 task_runner.RunUntilCheckpoint("old.app.stopped", 10000 /*ms*/);
229 }
230
231 // set up tracing
232 TestHelper helper(&task_runner);
233 helper.ConnectConsumer();
234 helper.WaitForConsumerConnect();
235
236 TraceConfig trace_config;
237 trace_config.add_buffers()->set_size_kb(10 * 1024);
238 trace_config.set_enable_extra_guardrails(enable_extra_guardrails);
239 trace_config.set_unique_session_name(RandomSessionName().c_str());
240
241 auto* ds_config = trace_config.add_data_sources()->mutable_config();
242 ds_config->set_name("android.heapprofd");
243 ds_config->set_target_buffer(0);
244
245 protos::gen::HeapprofdConfig heapprofd_config;
246 heapprofd_config.set_sampling_interval_bytes(kTestSamplingInterval);
247 heapprofd_config.add_process_cmdline(app_name.c_str());
248 heapprofd_config.set_block_client(true);
249 heapprofd_config.set_all(false);
250 for (const std::string& heap_name : heap_names) {
251 heapprofd_config.add_heaps(heap_name);
252 }
253 ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
254
255 // start tracing
256 helper.StartTracing(trace_config);
257
258 // start app
259 StartAppActivity(app_name, activity, "target.app.running", &task_runner,
260 /*delay_ms=*/100);
261 task_runner.RunUntilCheckpoint("target.app.running", 10000 /*ms*/);
262
263 EXPECT_TRUE(WaitForAppAllocationCycle(app_name, /*timeout_ms=*/10000));
264
265 helper.DisableTracing();
266 helper.WaitForTracingDisabled();
267 helper.ReadData();
268 helper.WaitForReadData();
269
270 return helper.trace();
271 }
272
273 // Check that `packets` contain some allocations performed by kMallocActivity.
AssertExpectedMallocsPresent(const std::vector<protos::gen::TracePacket> & packets)274 void AssertExpectedMallocsPresent(
275 const std::vector<protos::gen::TracePacket>& packets) {
276 ASSERT_GT(packets.size(), 0u);
277
278 // TODO(rsavitski): assert particular stack frames once we clarify the
279 // expected behaviour of unwinding native libs within an apk.
280 // Until then, look for an allocation that is a multiple of the expected
281 // allocation size.
282 bool found_alloc = false;
283 bool found_proc_dump = false;
284 for (const auto& packet : packets) {
285 for (const auto& proc_dump : packet.profile_packet().process_dumps()) {
286 found_proc_dump = true;
287 for (const auto& sample : proc_dump.samples()) {
288 if (sample.self_allocated() > 0 &&
289 sample.self_allocated() % kExpectedIndividualAllocSz == 0) {
290 found_alloc = true;
291
292 EXPECT_TRUE(sample.self_freed() > 0 &&
293 sample.self_freed() % kExpectedIndividualAllocSz == 0)
294 << "self_freed: " << sample.self_freed();
295 }
296 }
297 }
298 }
299 ASSERT_TRUE(found_proc_dump);
300 ASSERT_TRUE(found_alloc);
301 }
302
AssertHasSampledAllocs(const std::vector<protos::gen::TracePacket> & packets)303 void AssertHasSampledAllocs(
304 const std::vector<protos::gen::TracePacket>& packets) {
305 ASSERT_GT(packets.size(), 0u);
306
307 bool found_alloc = false;
308 bool found_proc_dump = false;
309 for (const auto& packet : packets) {
310 for (const auto& proc_dump : packet.profile_packet().process_dumps()) {
311 found_proc_dump = true;
312 for (const auto& sample : proc_dump.samples()) {
313 if (sample.self_allocated() > 0) {
314 found_alloc = true;
315 }
316 }
317 }
318 }
319 ASSERT_TRUE(found_proc_dump);
320 ASSERT_TRUE(found_alloc);
321 }
322
AssertNoProfileContents(const std::vector<protos::gen::TracePacket> & packets)323 void AssertNoProfileContents(
324 const std::vector<protos::gen::TracePacket>& packets) {
325 // If profile packets are present, they must be empty.
326 for (const auto& packet : packets) {
327 ASSERT_EQ(packet.profile_packet().process_dumps_size(), 0);
328 }
329 }
330
TEST(HeapprofdCtsTest,DebuggableAppRuntime)331 TEST(HeapprofdCtsTest, DebuggableAppRuntime) {
332 std::string app_name = "android.perfetto.cts.app.debuggable";
333 const auto& packets =
334 ProfileRuntime(app_name, kMallocActivity, /*heap_names=*/{});
335 AssertExpectedMallocsPresent(packets);
336 StopApp(app_name);
337 }
338
TEST(HeapprofdCtsTest,DebuggableAppStartup)339 TEST(HeapprofdCtsTest, DebuggableAppStartup) {
340 std::string app_name = "android.perfetto.cts.app.debuggable";
341 const auto& packets =
342 ProfileStartup(app_name, kMallocActivity, /*heap_names=*/{});
343 AssertExpectedMallocsPresent(packets);
344 StopApp(app_name);
345 }
346
TEST(HeapprofdCtsTest,ProfileableAppRuntime)347 TEST(HeapprofdCtsTest, ProfileableAppRuntime) {
348 std::string app_name = "android.perfetto.cts.app.profileable";
349 const auto& packets =
350 ProfileRuntime(app_name, kMallocActivity, /*heap_names=*/{});
351 AssertExpectedMallocsPresent(packets);
352 StopApp(app_name);
353 }
354
TEST(HeapprofdCtsTest,ProfileableAppStartup)355 TEST(HeapprofdCtsTest, ProfileableAppStartup) {
356 std::string app_name = "android.perfetto.cts.app.profileable";
357 const auto& packets =
358 ProfileStartup(app_name, kMallocActivity, /*heap_names=*/{});
359 AssertExpectedMallocsPresent(packets);
360 StopApp(app_name);
361 }
362
TEST(HeapprofdCtsTest,ReleaseAppRuntime)363 TEST(HeapprofdCtsTest, ReleaseAppRuntime) {
364 std::string app_name = "android.perfetto.cts.app.release";
365 const auto& packets =
366 ProfileRuntime(app_name, kMallocActivity, /*heap_names=*/{});
367
368 if (IsUserBuild())
369 AssertNoProfileContents(packets);
370 else
371 AssertExpectedMallocsPresent(packets);
372 StopApp(app_name);
373 }
374
TEST(HeapprofdCtsTest,ReleaseAppStartup)375 TEST(HeapprofdCtsTest, ReleaseAppStartup) {
376 std::string app_name = "android.perfetto.cts.app.release";
377 const auto& packets =
378 ProfileStartup(app_name, kMallocActivity, /*heap_names=*/{});
379
380 if (IsUserBuild())
381 AssertNoProfileContents(packets);
382 else
383 AssertExpectedMallocsPresent(packets);
384 StopApp(app_name);
385 }
386
TEST(HeapprofdCtsTest,NonProfileableAppRuntime)387 TEST(HeapprofdCtsTest, NonProfileableAppRuntime) {
388 std::string app_name = "android.perfetto.cts.app.nonprofileable";
389 const auto& packets =
390 ProfileRuntime(app_name, kMallocActivity, /*heap_names=*/{});
391 if (IsUserBuild())
392 AssertNoProfileContents(packets);
393 else
394 AssertExpectedMallocsPresent(packets);
395 StopApp(app_name);
396 }
397
TEST(HeapprofdCtsTest,NonProfileableAppStartup)398 TEST(HeapprofdCtsTest, NonProfileableAppStartup) {
399 std::string app_name = "android.perfetto.cts.app.nonprofileable";
400 const auto& packets =
401 ProfileStartup(app_name, kMallocActivity, /*heap_names=*/{});
402 if (IsUserBuild())
403 AssertNoProfileContents(packets);
404 else
405 AssertExpectedMallocsPresent(packets);
406 StopApp(app_name);
407 }
408
TEST(HeapprofdCtsTest,JavaHeapRuntime)409 TEST(HeapprofdCtsTest, JavaHeapRuntime) {
410 std::string app_name = "android.perfetto.cts.app.debuggable";
411 const auto& packets = ProfileRuntime(app_name, kJavaAllocActivity,
412 /*heap_names=*/{"com.android.art"});
413 AssertHasSampledAllocs(packets);
414 StopApp(app_name);
415 }
416
TEST(HeapprofdCtsTest,JavaHeapStartup)417 TEST(HeapprofdCtsTest, JavaHeapStartup) {
418 std::string app_name = "android.perfetto.cts.app.debuggable";
419 const auto& packets = ProfileStartup(app_name, kJavaAllocActivity,
420 /*heap_names=*/{"com.android.art"});
421 AssertHasSampledAllocs(packets);
422 StopApp(app_name);
423 }
424
TEST(HeapprofdCtsTest,ProfilePlatformProcess)425 TEST(HeapprofdCtsTest, ProfilePlatformProcess) {
426 int target_pid = PidForProcessName("/system/bin/traced_probes");
427 ASSERT_GT(target_pid, 0) << "failed to find pid for target process";
428
429 // Construct config.
430 TraceConfig trace_config;
431 trace_config.add_buffers()->set_size_kb(20 * 1024);
432 trace_config.set_duration_ms(3000);
433 trace_config.set_data_source_stop_timeout_ms(8000);
434 trace_config.set_unique_session_name(RandomSessionName().c_str());
435
436 // process.stats to cause work in traced_probes
437 protos::gen::ProcessStatsConfig ps_config;
438 ps_config.set_proc_stats_poll_ms(100);
439 ps_config.set_record_thread_names(true);
440
441 auto* ds_config = trace_config.add_data_sources()->mutable_config();
442 ds_config->set_name("linux.process_stats");
443 ds_config->set_process_stats_config_raw(ps_config.SerializeAsString());
444
445 // profile native heap of traced_probes
446 protos::gen::HeapprofdConfig heapprofd_config;
447 heapprofd_config.set_sampling_interval_bytes(kTestSamplingInterval);
448 heapprofd_config.add_pid(static_cast<uint64_t>(target_pid));
449 heapprofd_config.set_block_client(true);
450
451 ds_config = trace_config.add_data_sources()->mutable_config();
452 ds_config->set_name("android.heapprofd");
453 ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
454
455 // Collect trace.
456 base::TestTaskRunner task_runner;
457 TestHelper helper(&task_runner);
458 helper.ConnectConsumer();
459 helper.WaitForConsumerConnect();
460
461 helper.StartTracing(trace_config);
462 helper.WaitForTracingDisabled(15000 /*ms*/);
463 helper.ReadData();
464 helper.WaitForReadData();
465 auto packets = helper.trace();
466
467 int target_pid_after = PidForProcessName("/system/bin/traced_probes");
468 ASSERT_EQ(target_pid, target_pid_after) << "traced_probes died during test";
469
470 if (IsUserBuild())
471 AssertNoProfileContents(packets);
472 else
473 AssertHasSampledAllocs(packets);
474 }
475
476 } // namespace
477 } // namespace perfetto
478