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