1 /*
2 * Copyright (C) 2021 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 "flamegraph_construction_algorithms.h"
18
19 #include <set>
20 #include <unordered_set>
21
22 #include "perfetto/ext/base/string_splitter.h"
23 #include "perfetto/ext/base/string_utils.h"
24
25 namespace perfetto {
26 namespace trace_processor {
27
28 namespace {
29 struct MergedCallsite {
30 StringId frame_name;
31 StringId mapping_name;
32 base::Optional<StringId> source_file;
33 base::Optional<uint32_t> line_number;
34 base::Optional<uint32_t> parent_idx;
operator <perfetto::trace_processor::__anon476d2bee0111::MergedCallsite35 bool operator<(const MergedCallsite& o) const {
36 return std::tie(frame_name, mapping_name, parent_idx) <
37 std::tie(o.frame_name, o.mapping_name, o.parent_idx);
38 }
39 };
40
41 struct FlamegraphTableAndMergedCallsites {
42 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> tbl;
43 std::vector<uint32_t> callsite_to_merged_callsite;
44 };
45
GetMergedCallsites(TraceStorage * storage,uint32_t callstack_row)46 std::vector<MergedCallsite> GetMergedCallsites(TraceStorage* storage,
47 uint32_t callstack_row) {
48 const tables::StackProfileCallsiteTable& callsites_tbl =
49 storage->stack_profile_callsite_table();
50 const tables::StackProfileFrameTable& frames_tbl =
51 storage->stack_profile_frame_table();
52 const tables::SymbolTable& symbols_tbl = storage->symbol_table();
53 const tables::StackProfileMappingTable& mapping_tbl =
54 storage->stack_profile_mapping_table();
55
56 uint32_t frame_idx =
57 *frames_tbl.id().IndexOf(callsites_tbl.frame_id()[callstack_row]);
58
59 uint32_t mapping_idx =
60 *mapping_tbl.id().IndexOf(frames_tbl.mapping()[frame_idx]);
61 StringId mapping_name = mapping_tbl.name()[mapping_idx];
62
63 base::Optional<uint32_t> symbol_set_id =
64 frames_tbl.symbol_set_id()[frame_idx];
65
66 if (!symbol_set_id) {
67 StringId frame_name = frames_tbl.name()[frame_idx];
68 base::Optional<StringId> deobfuscated_name =
69 frames_tbl.deobfuscated_name()[frame_idx];
70 return {{deobfuscated_name ? *deobfuscated_name : frame_name, mapping_name,
71 base::nullopt, base::nullopt, base::nullopt}};
72 }
73
74 std::vector<MergedCallsite> result;
75 // id == symbol_set_id for the bottommost frame.
76 // TODO(lalitm): Encode this optimization in the table and remove this
77 // custom optimization.
78 uint32_t symbol_set_idx = *symbols_tbl.id().IndexOf(SymbolId(*symbol_set_id));
79 for (uint32_t i = symbol_set_idx;
80 i < symbols_tbl.row_count() &&
81 symbols_tbl.symbol_set_id()[i] == *symbol_set_id;
82 ++i) {
83 result.emplace_back(MergedCallsite{
84 symbols_tbl.name()[i], mapping_name, symbols_tbl.source_file()[i],
85 symbols_tbl.line_number()[i], base::nullopt});
86 }
87 std::reverse(result.begin(), result.end());
88 return result;
89 }
90 } // namespace
91
BuildFlamegraphTableTreeStructure(TraceStorage * storage,base::Optional<UniquePid> upid,base::Optional<std::string> upid_group,int64_t default_timestamp,StringId profile_type)92 static FlamegraphTableAndMergedCallsites BuildFlamegraphTableTreeStructure(
93 TraceStorage* storage,
94 base::Optional<UniquePid> upid,
95 base::Optional<std::string> upid_group,
96 int64_t default_timestamp,
97 StringId profile_type) {
98 const tables::StackProfileCallsiteTable& callsites_tbl =
99 storage->stack_profile_callsite_table();
100
101 std::vector<uint32_t> callsite_to_merged_callsite(callsites_tbl.row_count(),
102 0);
103 std::map<MergedCallsite, uint32_t> merged_callsites_to_table_idx;
104
105 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> tbl(
106 new tables::ExperimentalFlamegraphNodesTable(
107 storage->mutable_string_pool(), nullptr));
108
109 // FORWARD PASS:
110 // Aggregate callstacks by frame name / mapping name. Use symbolization
111 // data.
112 for (uint32_t i = 0; i < callsites_tbl.row_count(); ++i) {
113 base::Optional<uint32_t> parent_idx;
114
115 auto opt_parent_id = callsites_tbl.parent_id()[i];
116 if (opt_parent_id) {
117 parent_idx = callsites_tbl.id().IndexOf(*opt_parent_id);
118 // Make sure what we index into has been populated already.
119 PERFETTO_CHECK(*parent_idx < i);
120 parent_idx = callsite_to_merged_callsite[*parent_idx];
121 }
122
123 auto callsites = GetMergedCallsites(storage, i);
124 // Loop below needs to run at least once for parent_idx to get updated.
125 PERFETTO_CHECK(!callsites.empty());
126 std::map<MergedCallsite, uint32_t> callsites_to_rowid;
127 for (MergedCallsite& merged_callsite : callsites) {
128 merged_callsite.parent_idx = parent_idx;
129 auto it = merged_callsites_to_table_idx.find(merged_callsite);
130 if (it == merged_callsites_to_table_idx.end()) {
131 std::tie(it, std::ignore) = merged_callsites_to_table_idx.emplace(
132 merged_callsite, merged_callsites_to_table_idx.size());
133 tables::ExperimentalFlamegraphNodesTable::Row row{};
134 if (parent_idx) {
135 row.depth = tbl->depth()[*parent_idx] + 1;
136 } else {
137 row.depth = 0;
138 }
139
140 // The 'ts' column is given a default value, taken from the query.
141 // So if the query is:
142 // `select * form experimental_flamegraph
143 // where ts = 605908369259172
144 // and upid = 1
145 // and profile_type = 'native'`
146 // then row.ts == 605908369259172, for all rows
147 // This is not accurate. However, at present there is no other
148 // straightforward way of assigning timestamps to non-leaf nodes in the
149 // flamegraph tree. Non-leaf nodes would have to be assigned >= 1
150 // timestamps, which would increase data size without an advantage.
151 row.ts = default_timestamp;
152 if (upid) {
153 row.upid = *upid;
154 }
155 if (upid_group) {
156 row.upid_group = storage->InternString(base::StringView(*upid_group));
157 }
158 row.profile_type = profile_type;
159 row.name = merged_callsite.frame_name;
160 row.map_name = merged_callsite.mapping_name;
161 if (parent_idx)
162 row.parent_id = tbl->id()[*parent_idx];
163 tbl->Insert(row);
164 callsites_to_rowid[merged_callsite] =
165 static_cast<uint32_t>(merged_callsites_to_table_idx.size() - 1);
166
167 PERFETTO_CHECK(merged_callsites_to_table_idx.size() ==
168 tbl->row_count());
169 } else {
170 MergedCallsite saved_callsite = it->first;
171 callsites_to_rowid.erase(saved_callsite);
172 if (saved_callsite.source_file != merged_callsite.source_file) {
173 saved_callsite.source_file = base::nullopt;
174 }
175 if (saved_callsite.line_number != merged_callsite.line_number) {
176 saved_callsite.line_number = base::nullopt;
177 }
178 callsites_to_rowid[saved_callsite] = it->second;
179 }
180 parent_idx = it->second;
181 }
182
183 for (const auto& it : callsites_to_rowid) {
184 if (it.first.source_file) {
185 tbl->mutable_source_file()->Set(it.second, *it.first.source_file);
186 }
187 if (it.first.line_number) {
188 tbl->mutable_line_number()->Set(it.second, *it.first.line_number);
189 }
190 }
191
192 PERFETTO_CHECK(parent_idx);
193 callsite_to_merged_callsite[i] = *parent_idx;
194 }
195
196 return {std::move(tbl), callsite_to_merged_callsite};
197 }
198
199 static std::unique_ptr<tables::ExperimentalFlamegraphNodesTable>
BuildFlamegraphTableHeapSizeAndCount(std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> tbl,const std::vector<uint32_t> & callsite_to_merged_callsite,const Table & filtered)200 BuildFlamegraphTableHeapSizeAndCount(
201 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> tbl,
202 const std::vector<uint32_t>& callsite_to_merged_callsite,
203 const Table& filtered) {
204 for (auto it = filtered.IterateRows(); it; it.Next()) {
205 int64_t size =
206 it.Get(static_cast<uint32_t>(
207 tables::HeapProfileAllocationTable::ColumnIndex::size))
208 .long_value;
209 int64_t count =
210 it.Get(static_cast<uint32_t>(
211 tables::HeapProfileAllocationTable::ColumnIndex::count))
212 .long_value;
213 int64_t callsite_id =
214 it
215 .Get(static_cast<uint32_t>(
216 tables::HeapProfileAllocationTable::ColumnIndex::callsite_id))
217 .long_value;
218
219 PERFETTO_CHECK((size <= 0 && count <= 0) || (size >= 0 && count >= 0));
220 uint32_t merged_idx =
221 callsite_to_merged_callsite[static_cast<unsigned long>(callsite_id)];
222 // On old heapprofd producers, the count field is incorrectly set and we
223 // zero it in proto_trace_parser.cc.
224 // As such, we cannot depend on count == 0 to imply size == 0, so we check
225 // for both of them separately.
226 if (size > 0) {
227 tbl->mutable_alloc_size()->Set(merged_idx,
228 tbl->alloc_size()[merged_idx] + size);
229 }
230 if (count > 0) {
231 tbl->mutable_alloc_count()->Set(merged_idx,
232 tbl->alloc_count()[merged_idx] + count);
233 }
234
235 tbl->mutable_size()->Set(merged_idx, tbl->size()[merged_idx] + size);
236 tbl->mutable_count()->Set(merged_idx, tbl->count()[merged_idx] + count);
237 }
238
239 // BACKWARD PASS:
240 // Propagate sizes to parents.
241 for (int64_t i = tbl->row_count() - 1; i >= 0; --i) {
242 auto idx = static_cast<uint32_t>(i);
243
244 tbl->mutable_cumulative_size()->Set(
245 idx, tbl->cumulative_size()[idx] + tbl->size()[idx]);
246 tbl->mutable_cumulative_count()->Set(
247 idx, tbl->cumulative_count()[idx] + tbl->count()[idx]);
248
249 tbl->mutable_cumulative_alloc_size()->Set(
250 idx, tbl->cumulative_alloc_size()[idx] + tbl->alloc_size()[idx]);
251 tbl->mutable_cumulative_alloc_count()->Set(
252 idx, tbl->cumulative_alloc_count()[idx] + tbl->alloc_count()[idx]);
253
254 auto parent = tbl->parent_id()[idx];
255 if (parent) {
256 uint32_t parent_idx = *tbl->id().IndexOf(
257 tables::ExperimentalFlamegraphNodesTable::Id(*parent));
258 tbl->mutable_cumulative_size()->Set(
259 parent_idx,
260 tbl->cumulative_size()[parent_idx] + tbl->cumulative_size()[idx]);
261 tbl->mutable_cumulative_count()->Set(
262 parent_idx,
263 tbl->cumulative_count()[parent_idx] + tbl->cumulative_count()[idx]);
264
265 tbl->mutable_cumulative_alloc_size()->Set(
266 parent_idx, tbl->cumulative_alloc_size()[parent_idx] +
267 tbl->cumulative_alloc_size()[idx]);
268 tbl->mutable_cumulative_alloc_count()->Set(
269 parent_idx, tbl->cumulative_alloc_count()[parent_idx] +
270 tbl->cumulative_alloc_count()[idx]);
271 }
272 }
273
274 return tbl;
275 }
276
277 static std::unique_ptr<tables::ExperimentalFlamegraphNodesTable>
BuildFlamegraphTableCallstackSizeAndCount(std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> tbl,const std::vector<uint32_t> & callsite_to_merged_callsite,const Table & filtered)278 BuildFlamegraphTableCallstackSizeAndCount(
279 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> tbl,
280 const std::vector<uint32_t>& callsite_to_merged_callsite,
281 const Table& filtered) {
282 for (auto it = filtered.IterateRows(); it; it.Next()) {
283 int64_t callsite_id =
284 it.Get(static_cast<uint32_t>(
285 tables::PerfSampleTable::ColumnIndex::callsite_id))
286 .long_value;
287 int64_t ts =
288 it.Get(static_cast<uint32_t>(tables::PerfSampleTable::ColumnIndex::ts))
289 .long_value;
290
291 uint32_t merged_idx =
292 callsite_to_merged_callsite[static_cast<unsigned long>(callsite_id)];
293 tbl->mutable_size()->Set(merged_idx, tbl->size()[merged_idx] + 1);
294 tbl->mutable_count()->Set(merged_idx, tbl->count()[merged_idx] + 1);
295 tbl->mutable_ts()->Set(merged_idx, ts);
296 }
297
298 // BACKWARD PASS:
299 // Propagate sizes to parents.
300 for (int64_t i = tbl->row_count() - 1; i >= 0; --i) {
301 auto idx = static_cast<uint32_t>(i);
302
303 tbl->mutable_cumulative_size()->Set(
304 idx, tbl->cumulative_size()[idx] + tbl->size()[idx]);
305 tbl->mutable_cumulative_count()->Set(
306 idx, tbl->cumulative_count()[idx] + tbl->count()[idx]);
307
308 auto parent = tbl->parent_id()[idx];
309 if (parent) {
310 uint32_t parent_idx = *tbl->id().IndexOf(
311 tables::ExperimentalFlamegraphNodesTable::Id(*parent));
312 tbl->mutable_cumulative_size()->Set(
313 parent_idx,
314 tbl->cumulative_size()[parent_idx] + tbl->cumulative_size()[idx]);
315 tbl->mutable_cumulative_count()->Set(
316 parent_idx,
317 tbl->cumulative_count()[parent_idx] + tbl->cumulative_count()[idx]);
318 }
319 }
320 return tbl;
321 }
322
323 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable>
BuildNativeHeapProfileFlamegraph(TraceStorage * storage,UniquePid upid,int64_t timestamp)324 BuildNativeHeapProfileFlamegraph(TraceStorage* storage,
325 UniquePid upid,
326 int64_t timestamp) {
327 const tables::HeapProfileAllocationTable& allocation_tbl =
328 storage->heap_profile_allocation_table();
329 // PASS OVER ALLOCATIONS:
330 // Aggregate allocations into the newly built tree.
331 auto filtered = allocation_tbl.Filter(
332 {allocation_tbl.ts().le(timestamp), allocation_tbl.upid().eq(upid)});
333 if (filtered.row_count() == 0) {
334 return nullptr;
335 }
336 StringId profile_type = storage->InternString("native");
337 FlamegraphTableAndMergedCallsites table_and_callsites =
338 BuildFlamegraphTableTreeStructure(storage, upid, base::nullopt, timestamp,
339 profile_type);
340 return BuildFlamegraphTableHeapSizeAndCount(
341 std::move(table_and_callsites.tbl),
342 table_and_callsites.callsite_to_merged_callsite, filtered);
343 }
344
345 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable>
BuildNativeCallStackSamplingFlamegraph(TraceStorage * storage,base::Optional<UniquePid> upid,base::Optional<std::string> upid_group,const std::vector<TimeConstraints> & time_constraints)346 BuildNativeCallStackSamplingFlamegraph(
347 TraceStorage* storage,
348 base::Optional<UniquePid> upid,
349 base::Optional<std::string> upid_group,
350 const std::vector<TimeConstraints>& time_constraints) {
351 // 1.Extract required upids from input.
352 std::unordered_set<UniquePid> upids;
353 if (upid) {
354 upids.insert(*upid);
355 } else {
356 for (base::StringSplitter sp(*upid_group, ','); sp.Next();) {
357 base::Optional<uint32_t> maybe = base::CStringToUInt32(sp.cur_token());
358 if (maybe) {
359 upids.insert(*maybe);
360 }
361 }
362 }
363
364 // 2.Create set of all utids mapped to the given vector of upids
365 std::set<tables::ThreadTable::Id> utids;
366 RowMap threads_in_pid_rm;
367 for (uint32_t i = 0; i < storage->thread_table().row_count(); ++i) {
368 base::Optional<uint32_t> row_upid = storage->thread_table().upid()[i];
369 if (row_upid && upids.count(*row_upid) > 0) {
370 threads_in_pid_rm.Insert(i);
371 }
372 }
373
374 for (auto it = threads_in_pid_rm.IterateRows(); it; it.Next()) {
375 utids.insert(storage->thread_table().id()[it.index()]);
376 }
377
378 // 3.Get all row indices in perf_sample that correspond to the requested utids
379 std::vector<uint32_t> cs_rows;
380 for (uint32_t i = 0; i < storage->perf_sample_table().row_count(); ++i) {
381 if (utids.find(static_cast<tables::ThreadTable::Id>(
382 storage->perf_sample_table().utid()[i])) != utids.end()) {
383 cs_rows.push_back(i);
384 }
385 }
386
387 // 4.Filter rows that correspond to the selected utids
388 RowMap filtered_rm = RowMap(std::move(cs_rows));
389 Table filtered = storage->perf_sample_table().Apply(std::move(filtered_rm));
390
391 // 5.Filter rows by time constraints
392 for (const auto& tc : time_constraints) {
393 if (!(tc.op == FilterOp::kGt || tc.op == FilterOp::kLt ||
394 tc.op == FilterOp::kGe || tc.op == FilterOp::kLe)) {
395 PERFETTO_FATAL("Filter operation %d not permitted for perf.",
396 static_cast<int>(tc.op));
397 }
398 Constraint cs = Constraint{
399 static_cast<uint32_t>(tables::PerfSampleTable::ColumnIndex::ts), tc.op,
400 SqlValue::Long(tc.value)};
401 filtered = filtered.Filter({cs});
402 }
403 if (filtered.row_count() == 0) {
404 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> empty_tbl(
405 new tables::ExperimentalFlamegraphNodesTable(
406 storage->mutable_string_pool(), nullptr));
407 return empty_tbl;
408 }
409
410 // The logic underneath is selecting a default timestamp to be used by all
411 // frames which do not have a timestamp. The timestamp is taken from the query
412 // value and it's not meaningful for the row. It prevents however the rows
413 // with no timestamp from being filtered out by Sqlite, after we create the
414 // table ExperimentalFlamegraphNodesTable in this class.
415 int64_t default_timestamp = 0;
416 if (!time_constraints.empty()) {
417 auto& tc = time_constraints[0];
418 if (tc.op == FilterOp::kGt) {
419 default_timestamp = tc.value + 1;
420 } else if (tc.op == FilterOp::kLt) {
421 default_timestamp = tc.value - 1;
422 } else {
423 default_timestamp = tc.value;
424 }
425 }
426 StringId profile_type = storage->InternString("perf");
427 FlamegraphTableAndMergedCallsites table_and_callsites =
428 BuildFlamegraphTableTreeStructure(storage, upid, upid_group,
429 default_timestamp, profile_type);
430 return BuildFlamegraphTableCallstackSizeAndCount(
431 std::move(table_and_callsites.tbl),
432 table_and_callsites.callsite_to_merged_callsite, filtered);
433 }
434
435 } // namespace trace_processor
436 } // namespace perfetto
437