• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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