1 /*
2 * Copyright (C) 2020 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 "src/trace_processor/importers/proto/heap_graph_tracker.h"
18
19 #include "perfetto/base/logging.h"
20 #include "src/trace_processor/importers/common/process_tracker.h"
21 #include "src/trace_processor/util/profiler_util.h"
22 #include "test/gtest_and_gmock.h"
23
24 namespace perfetto {
25 namespace trace_processor {
26 namespace {
27
28 using ::testing::UnorderedElementsAre;
29
TEST(HeapGraphTrackerTest,PackageFromLocationApp)30 TEST(HeapGraphTrackerTest, PackageFromLocationApp) {
31 std::unique_ptr<TraceStorage> storage(new TraceStorage());
32
33 const char data_app_path[] =
34 "/data/app/org.perfetto.test-6XfQhnaSkFwGK0sYL9is0G==/base.apk";
35 EXPECT_EQ(PackageFromLocation(storage.get(), data_app_path),
36 "org.perfetto.test");
37
38 const char with_extra_dir[] =
39 "/data/app/~~ASDFGH1234QWerT==/"
40 "com.perfetto.test-MNBVCX7890SDTst6==/test.apk";
41 EXPECT_EQ(PackageFromLocation(storage.get(), with_extra_dir),
42 "com.perfetto.test");
43
44 const char odex[] =
45 "/data/app/com.google.android.apps.wellbeing-"
46 "qfQCaB4uJ7P0OPpZQqOu0Q==/oat/arm64/base.odex";
47 EXPECT_EQ(PackageFromLocation(storage.get(), odex),
48 "com.google.android.apps.wellbeing");
49
50 const char inmem_dex[] =
51 "[anon:dalvik-classes.dex extracted in memory from "
52 "/data/app/~~uUgHYtbjPNr2VFa3byIF4Q==/"
53 "com.perfetto.example-aC94wTfXRC60l2HJU5YvjQ==/base.apk]";
54 EXPECT_EQ(PackageFromLocation(storage.get(), inmem_dex),
55 "com.perfetto.example");
56 }
57
TEST(HeapGraphTrackerTest,PopulateNativeSize)58 TEST(HeapGraphTrackerTest, PopulateNativeSize) {
59 constexpr uint64_t kSeqId = 1;
60 constexpr UniquePid kPid = 1;
61 constexpr int64_t kTimestamp = 1;
62
63 TraceProcessorContext context;
64 context.storage.reset(new TraceStorage());
65 context.process_tracker.reset(new ProcessTracker(&context));
66 context.process_tracker->GetOrCreateProcess(kPid);
67
68 HeapGraphTracker tracker(context.storage.get());
69
70 constexpr uint64_t kLocation = 0;
71 tracker.AddInternedLocationName(kSeqId, kLocation,
72 context.storage->InternString("location"));
73
74 enum Fields : uint64_t { kReferent = 1, kThunk, kThis0, kNext };
75
76 tracker.AddInternedFieldName(kSeqId, kReferent,
77 "java.lang.ref.Reference.referent");
78 tracker.AddInternedFieldName(kSeqId, kThunk, "sun.misc.Cleaner.thunk");
79 tracker.AddInternedFieldName(
80 kSeqId, kThis0,
81 "libcore.util.NativeAllocationRegistry$CleanerThunk.this$0");
82 tracker.AddInternedFieldName(kSeqId, kNext, "sun.misc.Cleaner.next");
83
84 enum Types : uint64_t {
85 kTypeBitmap = 1,
86 kTypeCleaner,
87 kTypeCleanerThunk,
88 kTypeNativeAllocationRegistry,
89 };
90
91 tracker.AddInternedType(
92 kSeqId, kTypeBitmap,
93 context.storage->InternString("android.graphics.Bitmap"), kLocation,
94 /*object_size=*/0,
95 /*field_name_ids=*/{}, /*superclass_id=*/0,
96 /*classloader_id=*/0, /*no_fields=*/false,
97 protos::pbzero::HeapGraphType::KIND_NORMAL);
98
99 tracker.AddInternedType(kSeqId, kTypeCleaner,
100 context.storage->InternString("sun.misc.Cleaner"),
101 kLocation, /*object_size=*/0,
102 /*field_name_ids=*/{kReferent, kThunk, kNext},
103 /*superclass_id=*/0,
104 /*classloader_id=*/0, /*no_fields=*/false,
105 protos::pbzero::HeapGraphType::KIND_NORMAL);
106
107 tracker.AddInternedType(
108 kSeqId, kTypeCleanerThunk,
109 context.storage->InternString(
110 "libcore.util.NativeAllocationRegistry$CleanerThunk"),
111 kLocation, /*object_size=*/0,
112 /*field_name_ids=*/{kThis0}, /*superclass_id=*/0,
113 /*classloader_id=*/0, /*no_fields=*/false,
114 protos::pbzero::HeapGraphType::KIND_NORMAL);
115
116 tracker.AddInternedType(
117 kSeqId, kTypeNativeAllocationRegistry,
118 context.storage->InternString("libcore.util.NativeAllocationRegistry"),
119 kLocation, /*object_size=*/0,
120 /*field_name_ids=*/{}, /*superclass_id=*/0,
121 /*classloader_id=*/0, /*no_fields=*/false,
122 protos::pbzero::HeapGraphType::KIND_NORMAL);
123
124 enum Objects : uint64_t {
125 kObjBitmap = 1,
126 kObjCleaner,
127 kObjThunk,
128 kObjNativeAllocationRegistry,
129 };
130
131 {
132 HeapGraphTracker::SourceObject obj;
133 obj.object_id = kObjBitmap;
134 obj.type_id = kTypeBitmap;
135
136 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
137 }
138
139 {
140 HeapGraphTracker::SourceObject obj;
141 obj.object_id = kObjCleaner;
142 obj.type_id = kTypeCleaner;
143 obj.referred_objects = {kObjBitmap, kObjThunk, 0};
144
145 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
146 }
147
148 {
149 HeapGraphTracker::SourceObject obj;
150 obj.object_id = kObjThunk;
151 obj.type_id = kTypeCleanerThunk;
152 obj.referred_objects = {kObjNativeAllocationRegistry};
153
154 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
155 }
156
157 {
158 HeapGraphTracker::SourceObject obj;
159 obj.object_id = kObjNativeAllocationRegistry;
160 obj.type_id = kTypeNativeAllocationRegistry;
161
162 // NativeAllocationRegistry.size least significant bit is used to encode the
163 // source of the allocation (1: malloc, 0: other).
164 obj.native_allocation_registry_size = 24242 | 1;
165
166 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
167 }
168
169 tracker.FinalizeProfile(kSeqId);
170
171 const auto& objs_table = context.storage->heap_graph_object_table();
172 const auto& class_table = context.storage->heap_graph_class_table();
173 size_t count_bitmaps = 0;
174 for (uint32_t obj_row = 0; obj_row < objs_table.row_count(); ++obj_row) {
175 std::optional<uint32_t> class_row =
176 class_table.id().IndexOf(objs_table.type_id()[obj_row]);
177 ASSERT_TRUE(class_row.has_value());
178 if (context.storage->string_pool().Get(class_table.name()[*class_row]) ==
179 "android.graphics.Bitmap") {
180 EXPECT_EQ(objs_table.native_size()[obj_row], 24242);
181 count_bitmaps++;
182 } else {
183 EXPECT_EQ(objs_table.native_size()[obj_row], 0)
184 << context.storage->string_pool()
185 .Get(class_table.name()[*class_row])
186 .c_str()
187 << " has non zero native_size";
188 }
189 }
190 EXPECT_EQ(count_bitmaps, 1u);
191 }
192
TEST(HeapGraphTrackerTest,BuildFlamegraph)193 TEST(HeapGraphTrackerTest, BuildFlamegraph) {
194 // 4@A 5@B
195 // \ /
196 // 2@Y 3@Y
197 // \ /
198 // 1@X
199
200 constexpr uint64_t kSeqId = 1;
201 constexpr UniquePid kPid = 1;
202 constexpr int64_t kTimestamp = 1;
203
204 TraceProcessorContext context;
205 context.storage.reset(new TraceStorage());
206 context.process_tracker.reset(new ProcessTracker(&context));
207 context.process_tracker->GetOrCreateProcess(kPid);
208
209 HeapGraphTracker tracker(context.storage.get());
210
211 constexpr uint64_t kField = 1;
212 constexpr uint64_t kLocation = 0;
213
214 constexpr uint64_t kX = 1;
215 constexpr uint64_t kY = 2;
216 constexpr uint64_t kA = 3;
217 constexpr uint64_t kB = 4;
218
219 base::StringView field = base::StringView("foo");
220 StringPool::Id x = context.storage->InternString("X");
221 StringPool::Id y = context.storage->InternString("Y");
222 StringPool::Id a = context.storage->InternString("A");
223 StringPool::Id b = context.storage->InternString("B");
224
225 tracker.AddInternedFieldName(kSeqId, kField, field);
226
227 tracker.AddInternedLocationName(kSeqId, kLocation,
228 context.storage->InternString("location"));
229 tracker.AddInternedType(kSeqId, kX, x, kLocation, /*object_size=*/0,
230 /*field_name_ids=*/{}, /*superclass_id=*/0,
231 /*classloader_id=*/0, /*no_fields=*/false,
232 protos::pbzero::HeapGraphType::KIND_NORMAL);
233 tracker.AddInternedType(kSeqId, kY, y, kLocation, /*object_size=*/0,
234 /*field_name_ids=*/{}, /*superclass_id=*/0,
235 /*classloader_id=*/0, /*no_fields=*/false,
236 protos::pbzero::HeapGraphType::KIND_NORMAL);
237 tracker.AddInternedType(kSeqId, kA, a, kLocation, /*object_size=*/0,
238 /*field_name_ids=*/{}, /*superclass_id=*/0,
239 /*classloader_id=*/0, /*no_fields=*/false,
240 protos::pbzero::HeapGraphType::KIND_NORMAL);
241 tracker.AddInternedType(kSeqId, kB, b, kLocation, /*object_size=*/0,
242 /*field_name_ids=*/{}, /*superclass_id=*/0,
243 /*classloader_id=*/0, /*no_fields=*/false,
244 protos::pbzero::HeapGraphType::KIND_NORMAL);
245 {
246 HeapGraphTracker::SourceObject obj;
247 obj.object_id = 1;
248 obj.self_size = 1;
249 obj.type_id = kX;
250 obj.field_name_ids = {kField, kField};
251 obj.referred_objects = {2, 3};
252
253 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
254 }
255
256 {
257 HeapGraphTracker::SourceObject obj;
258 obj.object_id = 2;
259 obj.self_size = 2;
260 obj.type_id = kY;
261 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
262 }
263
264 {
265 HeapGraphTracker::SourceObject obj;
266 obj.object_id = 3;
267 obj.self_size = 3;
268 obj.type_id = kY;
269 obj.field_name_ids = {kField, kField};
270 obj.referred_objects = {4, 5};
271
272 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
273 }
274
275 {
276 HeapGraphTracker::SourceObject obj;
277 obj.object_id = 4;
278 obj.self_size = 4;
279 obj.type_id = kA;
280 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
281 }
282
283 {
284 HeapGraphTracker::SourceObject obj;
285 obj.object_id = 5;
286 obj.self_size = 5;
287 obj.type_id = kB;
288 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
289 }
290
291 HeapGraphTracker::SourceRoot root;
292 root.root_type = protos::pbzero::HeapGraphRoot::ROOT_UNKNOWN;
293 root.object_ids.emplace_back(1);
294 tracker.AddRoot(kSeqId, kPid, kTimestamp, root);
295
296 tracker.FinalizeProfile(kSeqId);
297 std::unique_ptr<tables::ExperimentalFlamegraphTable> flame =
298 tracker.BuildFlamegraph(kPid, kTimestamp);
299 ASSERT_NE(flame, nullptr);
300
301 auto cumulative_sizes = flame->cumulative_size().ToVectorForTesting();
302 EXPECT_THAT(cumulative_sizes, UnorderedElementsAre(15, 4, 14, 5));
303
304 auto cumulative_counts = flame->cumulative_count().ToVectorForTesting();
305 EXPECT_THAT(cumulative_counts, UnorderedElementsAre(5, 4, 1, 1));
306
307 auto sizes = flame->size().ToVectorForTesting();
308 EXPECT_THAT(sizes, UnorderedElementsAre(1, 5, 4, 5));
309
310 auto counts = flame->count().ToVectorForTesting();
311 EXPECT_THAT(counts, UnorderedElementsAre(1, 2, 1, 1));
312 }
313
TEST(HeapGraphTrackerTest,BuildFlamegraphWeakReferences)314 TEST(HeapGraphTrackerTest, BuildFlamegraphWeakReferences) {
315 // Regression test for http://b.corp.google.com/issues/302662734:
316 // For weak (and other) references, we should not follow the
317 // `java.lang.ref.Reference.referent` field, but we should follow other
318 // fields.
319 //
320 // 2@A 4@B
321 // (java.lang.ref.Reference.referent) \ / (X.other)
322 // 1@X (extends WeakReference)
323
324 constexpr uint64_t kSeqId = 1;
325 constexpr UniquePid kPid = 1;
326 constexpr int64_t kTimestamp = 1;
327
328 TraceProcessorContext context;
329 context.storage.reset(new TraceStorage());
330 context.process_tracker.reset(new ProcessTracker(&context));
331 context.process_tracker->GetOrCreateProcess(kPid);
332
333 HeapGraphTracker tracker(context.storage.get());
334
335 constexpr uint64_t kLocation = 0;
336
337 base::StringView referent_field =
338 base::StringView("java.lang.ref.Reference.referent");
339 constexpr uint64_t kReferentField = 1;
340 base::StringView other_field = base::StringView("X.other");
341 constexpr uint64_t kOtherField = 2;
342
343 constexpr uint64_t kX = 1;
344 StringPool::Id x = context.storage->InternString("X");
345 constexpr uint64_t kA = 2;
346 StringPool::Id a = context.storage->InternString("A");
347 constexpr uint64_t kB = 4;
348 StringPool::Id b = context.storage->InternString("B");
349 constexpr uint64_t kWeakRef = 5;
350 StringPool::Id weak_ref = context.storage->InternString("WeakReference");
351
352 tracker.AddInternedFieldName(kSeqId, kReferentField, referent_field);
353 tracker.AddInternedFieldName(kSeqId, kOtherField, other_field);
354
355 tracker.AddInternedLocationName(kSeqId, kLocation,
356 context.storage->InternString("location"));
357
358 tracker.AddInternedType(kSeqId, kWeakRef, weak_ref, kLocation,
359 /*object_size=*/0,
360 /*field_name_ids=*/{kReferentField},
361 /*superclass_id=*/0,
362 /*classloader_id=*/0, /*no_fields=*/false,
363 protos::pbzero::HeapGraphType::KIND_WEAK_REFERENCE);
364 tracker.AddInternedType(kSeqId, kX, x, kLocation,
365 /*object_size=*/0,
366 /*field_name_ids=*/{kOtherField},
367 /*superclass_id=*/kWeakRef,
368 /*classloader_id=*/0, /*no_fields=*/false,
369 protos::pbzero::HeapGraphType::KIND_WEAK_REFERENCE);
370 tracker.AddInternedType(kSeqId, kA, a, kLocation, /*object_size=*/0,
371 /*field_name_ids=*/{}, /*superclass_id=*/0,
372 /*classloader_id=*/0, /*no_fields=*/false,
373 protos::pbzero::HeapGraphType::KIND_NORMAL);
374 tracker.AddInternedType(kSeqId, kB, b, kLocation, /*object_size=*/0,
375 /*field_name_ids=*/{}, /*superclass_id=*/0,
376 /*classloader_id=*/0, /*no_fields=*/false,
377 protos::pbzero::HeapGraphType::KIND_NORMAL);
378 {
379 HeapGraphTracker::SourceObject obj;
380 obj.object_id = 1;
381 obj.self_size = 1;
382 obj.type_id = kX;
383 obj.referred_objects = {/*X.other*/ 4,
384 /*java.lang.ref.Reference.referent*/ 2};
385 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
386 }
387
388 {
389 HeapGraphTracker::SourceObject obj;
390 obj.object_id = 2;
391 obj.self_size = 2;
392 obj.type_id = kA;
393 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
394 }
395
396 {
397 HeapGraphTracker::SourceObject obj;
398 obj.object_id = 4;
399 obj.self_size = 4;
400 obj.type_id = kB;
401 tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
402 }
403
404 HeapGraphTracker::SourceRoot root;
405 root.root_type = protos::pbzero::HeapGraphRoot::ROOT_UNKNOWN;
406 root.object_ids.emplace_back(1);
407 tracker.AddRoot(kSeqId, kPid, kTimestamp, root);
408
409 tracker.FinalizeProfile(kSeqId);
410 std::unique_ptr<tables::ExperimentalFlamegraphTable> flame =
411 tracker.BuildFlamegraph(kPid, kTimestamp);
412 ASSERT_NE(flame, nullptr);
413
414 auto cumulative_sizes = flame->cumulative_size().ToVectorForTesting();
415 EXPECT_THAT(cumulative_sizes, UnorderedElementsAre(4, 4 + 1));
416
417 auto cumulative_counts = flame->cumulative_count().ToVectorForTesting();
418 EXPECT_THAT(cumulative_counts, UnorderedElementsAre(1, 1 + 1));
419
420 auto sizes = flame->size().ToVectorForTesting();
421 EXPECT_THAT(sizes, UnorderedElementsAre(1, 4));
422
423 auto counts = flame->count().ToVectorForTesting();
424 EXPECT_THAT(counts, UnorderedElementsAre(1, 1));
425 }
426
427 static const char kArray[] = "X[]";
428 static const char kDoubleArray[] = "X[][]";
429 static const char kNoArray[] = "X";
430 static const char kLongNoArray[] = "ABCDE";
431 static const char kStaticClassNoArray[] = "java.lang.Class<abc>";
432 static const char kStaticClassArray[] = "java.lang.Class<abc[]>";
433
TEST(HeapGraphTrackerTest,NormalizeTypeName)434 TEST(HeapGraphTrackerTest, NormalizeTypeName) {
435 // sizeof(...) - 1 below to get rid of the null-byte.
436 EXPECT_EQ(NormalizeTypeName(base::StringView(kArray, sizeof(kArray) - 1))
437 .ToStdString(),
438 "X");
439 EXPECT_EQ(NormalizeTypeName(
440 base::StringView(kDoubleArray, sizeof(kDoubleArray) - 1))
441 .ToStdString(),
442 "X");
443 EXPECT_EQ(NormalizeTypeName(base::StringView(kNoArray, sizeof(kNoArray) - 1))
444 .ToStdString(),
445 "X");
446 EXPECT_EQ(NormalizeTypeName(
447 base::StringView(kLongNoArray, sizeof(kLongNoArray) - 1))
448 .ToStdString(),
449 "ABCDE");
450 EXPECT_EQ(NormalizeTypeName(base::StringView(kStaticClassNoArray,
451 sizeof(kStaticClassNoArray) - 1))
452 .ToStdString(),
453 "abc");
454 EXPECT_EQ(NormalizeTypeName(base::StringView(kStaticClassArray,
455 sizeof(kStaticClassArray) - 1))
456 .ToStdString(),
457 "abc");
458 }
459
TEST(HeapGraphTrackerTest,NumberOfArray)460 TEST(HeapGraphTrackerTest, NumberOfArray) {
461 // sizeof(...) - 1 below to get rid of the null-byte.
462 EXPECT_EQ(NumberOfArrays(base::StringView(kArray, sizeof(kArray) - 1)), 1u);
463 EXPECT_EQ(
464 NumberOfArrays(base::StringView(kDoubleArray, sizeof(kDoubleArray) - 1)),
465 2u);
466 EXPECT_EQ(NumberOfArrays(base::StringView(kNoArray, sizeof(kNoArray) - 1)),
467 0u);
468 EXPECT_EQ(
469 NumberOfArrays(base::StringView(kLongNoArray, sizeof(kLongNoArray) - 1)),
470 0u);
471 }
472
473 } // namespace
474 } // namespace trace_processor
475 } // namespace perfetto
476