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 "src/trace_processor/heap_profile_tracker.h"
18
19 #include "src/trace_processor/stack_profile_tracker.h"
20 #include "src/trace_processor/trace_processor_context.h"
21 #include "test/gtest_and_gmock.h"
22
23 namespace perfetto {
24 namespace trace_processor {
25 namespace {
26
27 struct Packet {
28 uint32_t mapping_name_id;
29 uint32_t build_id;
30 uint32_t frame_name_id;
31 uint32_t mapping_id;
32 uint32_t frame_id;
33 };
34
35 constexpr Packet kFirstPacket{1, 2, 3, 1, 1};
36 constexpr Packet kSecondPacket{3, 2, 1, 2, 2};
37
38 constexpr auto kMappingExactOffset = 123;
39 constexpr auto kMappingStartOffset = 1231;
40 constexpr auto kMappingStart = 234;
41 constexpr auto kMappingEnd = 345;
42 constexpr auto kMappingLoadBias = 456;
43 constexpr auto kDefaultSequence = 1;
44
45 // heapprofd on Android Q has large callstack ideas, explicitly test large
46 // values.
47 constexpr auto kCallstackId = 1ull << 34;
48
49 static constexpr auto kFrameRelPc = 567;
50 static constexpr char kBuildIDName[] = "[build id]";
51 static constexpr char kBuildIDHexName[] = "5b6275696c642069645d";
52
53 using ::testing::ElementsAre;
54
55 class HeapProfileTrackerDupTest : public ::testing::Test {
56 public:
HeapProfileTrackerDupTest()57 HeapProfileTrackerDupTest() {
58 context.storage.reset(new TraceStorage());
59 stack_profile_tracker.reset(new StackProfileTracker(&context));
60 context.heap_profile_tracker.reset(new HeapProfileTracker(&context));
61
62 mapping_name = context.storage->InternString("[mapping]");
63 fully_qualified_mapping_name = context.storage->InternString("/[mapping]");
64 build = context.storage->InternString(kBuildIDName);
65 frame_name = context.storage->InternString("[frame]");
66 }
67
68 protected:
InsertMapping(const Packet & packet)69 void InsertMapping(const Packet& packet) {
70 stack_profile_tracker->AddString(packet.mapping_name_id, "[mapping]");
71
72 stack_profile_tracker->AddString(packet.build_id, kBuildIDName);
73
74 StackProfileTracker::SourceMapping first_frame;
75 first_frame.build_id = packet.build_id;
76 first_frame.exact_offset = kMappingExactOffset;
77 first_frame.start_offset = kMappingStartOffset;
78 first_frame.start = kMappingStart;
79 first_frame.end = kMappingEnd;
80 first_frame.load_bias = kMappingLoadBias;
81 first_frame.name_ids = {packet.mapping_name_id};
82
83 stack_profile_tracker->AddMapping(packet.mapping_id, first_frame);
84 }
85
InsertFrame(const Packet & packet)86 void InsertFrame(const Packet& packet) {
87 InsertMapping(packet);
88 stack_profile_tracker->AddString(packet.frame_name_id, "[frame]");
89
90 StackProfileTracker::SourceFrame first_frame;
91 first_frame.name_id = packet.frame_name_id;
92 first_frame.mapping_id = packet.mapping_id;
93 first_frame.rel_pc = kFrameRelPc;
94
95 stack_profile_tracker->AddFrame(packet.frame_id, first_frame);
96 }
97
InsertCallsite(const Packet & packet)98 void InsertCallsite(const Packet& packet) {
99 InsertFrame(packet);
100
101 StackProfileTracker::SourceCallstack first_callsite = {packet.frame_id,
102 packet.frame_id};
103 stack_profile_tracker->AddCallstack(kCallstackId, first_callsite);
104 }
105
106 StringId mapping_name;
107 StringId fully_qualified_mapping_name;
108 StringId build;
109 StringId frame_name;
110 TraceProcessorContext context;
111 std::unique_ptr<StackProfileTracker> stack_profile_tracker;
112 };
113
114 // Insert the same mapping from two different packets, with different strings
115 // interned, and assert we only store one.
TEST_F(HeapProfileTrackerDupTest,Mapping)116 TEST_F(HeapProfileTrackerDupTest, Mapping) {
117 InsertMapping(kFirstPacket);
118 context.heap_profile_tracker->FinalizeProfile(
119 kDefaultSequence, stack_profile_tracker.get(), nullptr);
120 InsertMapping(kSecondPacket);
121 context.heap_profile_tracker->FinalizeProfile(
122 kDefaultSequence, stack_profile_tracker.get(), nullptr);
123
124 EXPECT_THAT(context.storage->stack_profile_mapping_table().build_id()[0],
125 context.storage->InternString({kBuildIDHexName}));
126 EXPECT_THAT(context.storage->stack_profile_mapping_table().exact_offset()[0],
127 kMappingExactOffset);
128 EXPECT_THAT(context.storage->stack_profile_mapping_table().start_offset()[0],
129 kMappingStartOffset);
130 EXPECT_THAT(context.storage->stack_profile_mapping_table().start()[0],
131 kMappingStart);
132 EXPECT_THAT(context.storage->stack_profile_mapping_table().end()[0],
133 kMappingEnd);
134 EXPECT_THAT(context.storage->stack_profile_mapping_table().load_bias()[0],
135 kMappingLoadBias);
136 EXPECT_THAT(context.storage->stack_profile_mapping_table().name()[0],
137 fully_qualified_mapping_name);
138 }
139
140 // Insert the same mapping from two different packets, with different strings
141 // interned, and assert we only store one.
TEST_F(HeapProfileTrackerDupTest,Frame)142 TEST_F(HeapProfileTrackerDupTest, Frame) {
143 InsertFrame(kFirstPacket);
144 context.heap_profile_tracker->FinalizeProfile(
145 kDefaultSequence, stack_profile_tracker.get(), nullptr);
146 InsertFrame(kSecondPacket);
147 context.heap_profile_tracker->FinalizeProfile(
148 kDefaultSequence, stack_profile_tracker.get(), nullptr);
149
150 const auto& frames = context.storage->stack_profile_frame_table();
151 EXPECT_THAT(frames.name()[0], frame_name);
152 EXPECT_THAT(frames.mapping()[0], MappingId{0});
153 EXPECT_THAT(frames.rel_pc()[0], kFrameRelPc);
154 }
155
156 // Insert the same callstack from two different packets, assert it is only
157 // stored once.
TEST_F(HeapProfileTrackerDupTest,Callstack)158 TEST_F(HeapProfileTrackerDupTest, Callstack) {
159 InsertCallsite(kFirstPacket);
160 context.heap_profile_tracker->FinalizeProfile(
161 kDefaultSequence, stack_profile_tracker.get(), nullptr);
162 InsertCallsite(kSecondPacket);
163 context.heap_profile_tracker->FinalizeProfile(
164 kDefaultSequence, stack_profile_tracker.get(), nullptr);
165
166 const auto& callsite_table = context.storage->stack_profile_callsite_table();
167 const auto& depth = callsite_table.depth();
168 const auto& parent_id = callsite_table.parent_id();
169 const auto& frame_id = callsite_table.frame_id();
170
171 EXPECT_EQ(depth[0], 0u);
172 EXPECT_EQ(depth[1], 1u);
173
174 EXPECT_EQ(parent_id[0], base::nullopt);
175 EXPECT_EQ(parent_id[1], CallsiteId{0});
176
177 EXPECT_EQ(frame_id[0], FrameId{0});
178 EXPECT_EQ(frame_id[1], FrameId{0});
179 }
180
FindCallstack(const TraceStorage & storage,int64_t depth,base::Optional<CallsiteId> parent,FrameId frame_id)181 base::Optional<CallsiteId> FindCallstack(const TraceStorage& storage,
182 int64_t depth,
183 base::Optional<CallsiteId> parent,
184 FrameId frame_id) {
185 const auto& callsites = storage.stack_profile_callsite_table();
186 for (uint32_t i = 0; i < callsites.row_count(); ++i) {
187 if (callsites.depth()[i] == depth && callsites.parent_id()[i] == parent &&
188 callsites.frame_id()[i] == frame_id) {
189 return callsites.id()[i];
190 }
191 }
192 return base::nullopt;
193 }
194
TEST(HeapProfileTrackerTest,SourceMappingPath)195 TEST(HeapProfileTrackerTest, SourceMappingPath) {
196 TraceProcessorContext context;
197 context.storage.reset(new TraceStorage());
198 context.heap_profile_tracker.reset(new HeapProfileTracker(&context));
199
200 HeapProfileTracker* hpt = context.heap_profile_tracker.get();
201 std::unique_ptr<StackProfileTracker> spt(new StackProfileTracker(&context));
202
203 constexpr auto kBuildId = 1u;
204 constexpr auto kMappingNameId1 = 2u;
205 constexpr auto kMappingNameId2 = 3u;
206
207 spt->AddString(kBuildId, "buildid");
208 spt->AddString(kMappingNameId1, "foo");
209 spt->AddString(kMappingNameId2, "bar");
210
211 StackProfileTracker::SourceMapping mapping;
212 mapping.build_id = kBuildId;
213 mapping.exact_offset = 1;
214 mapping.start_offset = 1;
215 mapping.start = 2;
216 mapping.end = 3;
217 mapping.load_bias = 0;
218 mapping.name_ids = {kMappingNameId1, kMappingNameId2};
219 spt->AddMapping(0, mapping);
220 hpt->CommitAllocations(kDefaultSequence, spt.get(), nullptr);
221 auto foo_bar_id = context.storage->string_pool().GetId("/foo/bar");
222 ASSERT_NE(foo_bar_id, base::nullopt);
223 EXPECT_THAT(context.storage->stack_profile_mapping_table().name()[0],
224 *foo_bar_id);
225 }
226
227 // Insert multiple mappings, frames and callstacks and check result.
TEST(HeapProfileTrackerTest,Functional)228 TEST(HeapProfileTrackerTest, Functional) {
229 TraceProcessorContext context;
230 context.storage.reset(new TraceStorage());
231 context.heap_profile_tracker.reset(new HeapProfileTracker(&context));
232
233 HeapProfileTracker* hpt = context.heap_profile_tracker.get();
234 std::unique_ptr<StackProfileTracker> spt(new StackProfileTracker(&context));
235
236 uint32_t next_string_intern_id = 1;
237
238 const std::string build_ids[] = {"build1", "build2", "build3"};
239 uint32_t build_id_ids[base::ArraySize(build_ids)];
240 for (size_t i = 0; i < base::ArraySize(build_ids); ++i)
241 build_id_ids[i] = next_string_intern_id++;
242
243 const std::string mapping_names[] = {"map1", "map2", "map3"};
244 uint32_t mapping_name_ids[base::ArraySize(mapping_names)];
245 for (size_t i = 0; i < base::ArraySize(mapping_names); ++i)
246 mapping_name_ids[i] = next_string_intern_id++;
247
248 StackProfileTracker::SourceMapping mappings[base::ArraySize(mapping_names)] =
249 {};
250 mappings[0].build_id = build_id_ids[0];
251 mappings[0].exact_offset = 1;
252 mappings[0].start_offset = 1;
253 mappings[0].start = 2;
254 mappings[0].end = 3;
255 mappings[0].load_bias = 0;
256 mappings[0].name_ids = {mapping_name_ids[0], mapping_name_ids[1]};
257
258 mappings[1].build_id = build_id_ids[1];
259 mappings[1].exact_offset = 1;
260 mappings[1].start_offset = 1;
261 mappings[1].start = 2;
262 mappings[1].end = 3;
263 mappings[1].load_bias = 1;
264 mappings[1].name_ids = {mapping_name_ids[1]};
265
266 mappings[2].build_id = build_id_ids[2];
267 mappings[2].exact_offset = 1;
268 mappings[2].start_offset = 1;
269 mappings[2].start = 2;
270 mappings[2].end = 3;
271 mappings[2].load_bias = 2;
272 mappings[2].name_ids = {mapping_name_ids[2]};
273
274 const std::string function_names[] = {"fun1", "fun2", "fun3", "fun4"};
275 uint32_t function_name_ids[base::ArraySize(function_names)];
276 for (size_t i = 0; i < base::ArraySize(function_names); ++i)
277 function_name_ids[i] = next_string_intern_id++;
278
279 StackProfileTracker::SourceFrame frames[base::ArraySize(function_names)];
280 frames[0].name_id = function_name_ids[0];
281 frames[0].mapping_id = 0;
282 frames[0].rel_pc = 123;
283
284 frames[1].name_id = function_name_ids[1];
285 frames[1].mapping_id = 0;
286 frames[1].rel_pc = 123;
287
288 frames[2].name_id = function_name_ids[2];
289 frames[2].mapping_id = 1;
290 frames[2].rel_pc = 123;
291
292 frames[3].name_id = function_name_ids[3];
293 frames[3].mapping_id = 2;
294 frames[3].rel_pc = 123;
295
296 StackProfileTracker::SourceCallstack callstacks[3];
297 callstacks[0] = {2, 1, 0};
298 callstacks[1] = {2, 1, 0, 1, 0};
299 callstacks[2] = {0, 2, 0, 1, 2};
300
301 for (size_t i = 0; i < base::ArraySize(build_ids); ++i) {
302 auto interned = base::StringView(build_ids[i].data(), build_ids[i].size());
303 spt->AddString(build_id_ids[i], interned);
304 }
305 for (size_t i = 0; i < base::ArraySize(mapping_names); ++i) {
306 auto interned =
307 base::StringView(mapping_names[i].data(), mapping_names[i].size());
308 spt->AddString(mapping_name_ids[i], interned);
309 }
310 for (size_t i = 0; i < base::ArraySize(function_names); ++i) {
311 auto interned =
312 base::StringView(function_names[i].data(), function_names[i].size());
313 spt->AddString(function_name_ids[i], interned);
314 }
315
316 for (uint32_t i = 0; i < base::ArraySize(mappings); ++i)
317 spt->AddMapping(i, mappings[i]);
318 for (uint32_t i = 0; i < base::ArraySize(frames); ++i)
319 spt->AddFrame(i, frames[i]);
320 for (uint32_t i = 0; i < base::ArraySize(callstacks); ++i)
321 spt->AddCallstack(i, callstacks[i]);
322
323 hpt->CommitAllocations(kDefaultSequence, spt.get(), nullptr);
324
325 for (size_t i = 0; i < base::ArraySize(callstacks); ++i) {
326 base::Optional<CallsiteId> parent;
327 const StackProfileTracker::SourceCallstack& callstack = callstacks[i];
328 for (size_t depth = 0; depth < callstack.size(); ++depth) {
329 auto frame_id = spt->GetDatabaseFrameIdForTesting(callstack[depth]);
330 base::Optional<CallsiteId> self = FindCallstack(
331 *context.storage, static_cast<int64_t>(depth), parent, frame_id);
332 ASSERT_TRUE(self.has_value());
333 parent = self;
334 }
335 }
336
337 hpt->FinalizeProfile(kDefaultSequence, spt.get(), nullptr);
338 }
339
340 } // namespace
341 } // namespace trace_processor
342 } // namespace perfetto
343