xref: /aosp_15_r20/external/perfetto/test/cts/heapprofd_test_cts.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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