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