• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/dynamic/experimental_flamegraph_generator.h"
18 
19 #include <unordered_set>
20 
21 #include "perfetto/ext/base/string_splitter.h"
22 #include "perfetto/ext/base/string_utils.h"
23 
24 #include "src/trace_processor/importers/proto/heap_graph_tracker.h"
25 #include "src/trace_processor/importers/proto/heap_profile_tracker.h"
26 #include "src/trace_processor/types/trace_processor_context.h"
27 
28 namespace perfetto {
29 namespace trace_processor {
30 
31 namespace {
32 
extractProfileType(std::string & profile_name)33 ExperimentalFlamegraphGenerator::ProfileType extractProfileType(
34     std::string& profile_name) {
35   if (profile_name == "graph") {
36     return ExperimentalFlamegraphGenerator::ProfileType::kGraph;
37   }
38   if (profile_name == "native") {
39     return ExperimentalFlamegraphGenerator::ProfileType::kNative;
40   }
41   if (profile_name == "perf") {
42     return ExperimentalFlamegraphGenerator::ProfileType::kPerf;
43   }
44   PERFETTO_FATAL("Could not recognize profile type: %s.", profile_name.c_str());
45 }
46 
IsValidTimestampOp(int op)47 bool IsValidTimestampOp(int op) {
48   return op == SQLITE_INDEX_CONSTRAINT_EQ || op == SQLITE_INDEX_CONSTRAINT_GT ||
49          op == SQLITE_INDEX_CONSTRAINT_LE || op == SQLITE_INDEX_CONSTRAINT_LT ||
50          op == SQLITE_INDEX_CONSTRAINT_GE;
51 }
52 
IsValidFilterOp(FilterOp filterOp)53 bool IsValidFilterOp(FilterOp filterOp) {
54   return filterOp == FilterOp::kEq || filterOp == FilterOp::kGt ||
55          filterOp == FilterOp::kLe || filterOp == FilterOp::kLt ||
56          filterOp == FilterOp::kGe;
57 }
58 
59 // For filtering, this method uses the same constraints as
60 // ExperimentalFlamegraphGenerator::ValidateConstraints and should therefore
61 // be kept in sync.
GetFlamegraphInputValues(const std::vector<Constraint> & cs)62 ExperimentalFlamegraphGenerator::InputValues GetFlamegraphInputValues(
63     const std::vector<Constraint>& cs) {
64   using T = tables::ExperimentalFlamegraphNodesTable;
65 
66   auto ts_fn = [](const Constraint& c) {
67     return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::ts) &&
68            IsValidFilterOp(c.op);
69   };
70   auto upid_fn = [](const Constraint& c) {
71     return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::upid) &&
72            c.op == FilterOp::kEq;
73   };
74   auto upid_group_fn = [](const Constraint& c) {
75     return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::upid_group) &&
76            c.op == FilterOp::kEq;
77   };
78   auto profile_type_fn = [](const Constraint& c) {
79     return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::profile_type) &&
80            c.op == FilterOp::kEq;
81   };
82   auto focus_str_fn = [](const Constraint& c) {
83     return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::focus_str) &&
84            c.op == FilterOp::kEq;
85   };
86 
87   auto ts_it = std::find_if(cs.begin(), cs.end(), ts_fn);
88   auto upid_it = std::find_if(cs.begin(), cs.end(), upid_fn);
89   auto upid_group_it = std::find_if(cs.begin(), cs.end(), upid_group_fn);
90   auto profile_type_it = std::find_if(cs.begin(), cs.end(), profile_type_fn);
91   auto focus_str_it = std::find_if(cs.begin(), cs.end(), focus_str_fn);
92 
93   // We should always have valid iterators here because BestIndex should only
94   // allow the constraint set to be chosen when we have an equality constraint
95   // on upid and a constraint on ts.
96   PERFETTO_CHECK(ts_it != cs.end());
97   PERFETTO_CHECK(upid_it != cs.end() || upid_group_it != cs.end());
98   PERFETTO_CHECK(profile_type_it != cs.end());
99 
100   std::string profile_name(profile_type_it->value.AsString());
101   ExperimentalFlamegraphGenerator::ProfileType profile_type =
102       extractProfileType(profile_name);
103   int64_t ts = -1;
104   std::vector<TimeConstraints> time_constraints = {};
105 
106   for (; ts_it != cs.end(); ts_it++) {
107     if (ts_it->col_idx != static_cast<uint32_t>(T::ColumnIndex::ts)) {
108       continue;
109     }
110 
111     if (profile_type == ExperimentalFlamegraphGenerator::ProfileType::kPerf) {
112       PERFETTO_CHECK(ts_it->op != FilterOp::kEq);
113       time_constraints.push_back(
114           TimeConstraints{ts_it->op, ts_it->value.AsLong()});
115     } else {
116       PERFETTO_CHECK(ts_it->op == FilterOp::kEq);
117       ts = ts_it->value.AsLong();
118     }
119   }
120 
121   base::Optional<UniquePid> upid;
122   base::Optional<std::string> upid_group;
123   if (upid_it != cs.end()) {
124     upid = static_cast<UniquePid>(upid_it->value.AsLong());
125   } else {
126     upid_group = upid_group_it->value.AsString();
127   }
128 
129   std::string focus_str =
130       focus_str_it != cs.end() ? focus_str_it->value.AsString() : "";
131   return ExperimentalFlamegraphGenerator::InputValues{
132       profile_type, ts,         std::move(time_constraints),
133       upid,         upid_group, focus_str};
134 }
135 
136 class Matcher {
137  public:
Matcher(const std::string & str)138   explicit Matcher(const std::string& str) : focus_str_(base::ToLower(str)) {}
139   Matcher(const Matcher&) = delete;
140   Matcher& operator=(const Matcher&) = delete;
141 
matches(const std::string & s) const142   bool matches(const std::string& s) const {
143     // TODO(149833691): change to regex.
144     // We cannot use regex.h (does not exist in windows) or std regex (throws
145     // exceptions).
146     return base::Contains(base::ToLower(s), focus_str_);
147   }
148 
149  private:
150   const std::string focus_str_;
151 };
152 
153 enum class FocusedState {
154   kNotFocused,
155   kFocusedPropagating,
156   kFocusedNotPropagating,
157 };
158 
159 using tables::ExperimentalFlamegraphNodesTable;
ComputeFocusedState(const ExperimentalFlamegraphNodesTable & table,const Matcher & focus_matcher)160 std::vector<FocusedState> ComputeFocusedState(
161     const ExperimentalFlamegraphNodesTable& table,
162     const Matcher& focus_matcher) {
163   // Each row corresponds to a node in the flame chart tree with its parent
164   // ptr. Root trees (no parents) will have a null parent ptr.
165   std::vector<FocusedState> focused(table.row_count());
166 
167   for (uint32_t i = 0; i < table.row_count(); ++i) {
168     auto parent_id = table.parent_id()[i];
169     // Constraint: all descendants MUST come after their parents.
170     PERFETTO_DCHECK(!parent_id.has_value() || *parent_id < table.id()[i]);
171 
172     if (focus_matcher.matches(table.name().GetString(i).ToStdString())) {
173       // Mark as focused
174       focused[i] = FocusedState::kFocusedPropagating;
175       auto current = parent_id;
176       // Mark all parent nodes as focused
177       while (current.has_value()) {
178         auto current_idx = *table.id().IndexOf(*current);
179         if (focused[current_idx] != FocusedState::kNotFocused) {
180           // We have already visited these nodes, skip
181           break;
182         }
183         focused[current_idx] = FocusedState::kFocusedNotPropagating;
184         current = table.parent_id()[current_idx];
185       }
186     } else if (parent_id.has_value() &&
187                focused[*table.id().IndexOf(*parent_id)] ==
188                    FocusedState::kFocusedPropagating) {
189       // Focus cascades downwards.
190       focused[i] = FocusedState::kFocusedPropagating;
191     } else {
192       focused[i] = FocusedState::kNotFocused;
193     }
194   }
195   return focused;
196 }
197 
198 struct CumulativeCounts {
199   int64_t size;
200   int64_t count;
201   int64_t alloc_size;
202   int64_t alloc_count;
203 };
FocusTable(TraceStorage * storage,std::unique_ptr<ExperimentalFlamegraphNodesTable> in,const std::string & focus_str)204 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> FocusTable(
205     TraceStorage* storage,
206     std::unique_ptr<ExperimentalFlamegraphNodesTable> in,
207     const std::string& focus_str) {
208   if (in->row_count() == 0 || focus_str.empty()) {
209     return in;
210   }
211   std::vector<FocusedState> focused_state =
212       ComputeFocusedState(*in, Matcher(focus_str));
213   std::unique_ptr<ExperimentalFlamegraphNodesTable> tbl(
214       new tables::ExperimentalFlamegraphNodesTable(
215           storage->mutable_string_pool(), nullptr));
216 
217   // Recompute cumulative counts
218   std::vector<CumulativeCounts> node_to_cumulatives(in->row_count());
219   for (int64_t idx = in->row_count() - 1; idx >= 0; --idx) {
220     auto i = static_cast<uint32_t>(idx);
221     if (focused_state[i] == FocusedState::kNotFocused) {
222       continue;
223     }
224     auto& cumulatives = node_to_cumulatives[i];
225     cumulatives.size += in->size()[i];
226     cumulatives.count += in->count()[i];
227     cumulatives.alloc_size += in->alloc_size()[i];
228     cumulatives.alloc_count += in->alloc_count()[i];
229 
230     auto parent_id = in->parent_id()[i];
231     if (parent_id.has_value()) {
232       auto& parent_cumulatives =
233           node_to_cumulatives[*in->id().IndexOf(*parent_id)];
234       parent_cumulatives.size += cumulatives.size;
235       parent_cumulatives.count += cumulatives.count;
236       parent_cumulatives.alloc_size += cumulatives.alloc_size;
237       parent_cumulatives.alloc_count += cumulatives.alloc_count;
238     }
239   }
240 
241   // Mapping between the old rows ('node') to the new identifiers.
242   std::vector<ExperimentalFlamegraphNodesTable::Id> node_to_id(in->row_count());
243   for (uint32_t i = 0; i < in->row_count(); ++i) {
244     if (focused_state[i] == FocusedState::kNotFocused) {
245       continue;
246     }
247 
248     tables::ExperimentalFlamegraphNodesTable::Row alloc_row{};
249     // We must reparent the rows as every insertion will get its own
250     // identifier.
251     auto original_parent_id = in->parent_id()[i];
252     if (original_parent_id.has_value()) {
253       auto original_idx = *in->id().IndexOf(*original_parent_id);
254       alloc_row.parent_id = node_to_id[original_idx];
255     }
256 
257     alloc_row.ts = in->ts()[i];
258     alloc_row.upid = in->upid()[i];
259     alloc_row.profile_type = in->profile_type()[i];
260     alloc_row.depth = in->depth()[i];
261     alloc_row.name = in->name()[i];
262     alloc_row.map_name = in->map_name()[i];
263     alloc_row.count = in->count()[i];
264     alloc_row.size = in->size()[i];
265     alloc_row.alloc_count = in->alloc_count()[i];
266     alloc_row.alloc_size = in->alloc_size()[i];
267 
268     const auto& cumulative = node_to_cumulatives[i];
269     alloc_row.cumulative_count = cumulative.count;
270     alloc_row.cumulative_size = cumulative.size;
271     alloc_row.cumulative_alloc_count = cumulative.alloc_count;
272     alloc_row.cumulative_alloc_size = cumulative.alloc_size;
273     node_to_id[i] = tbl->Insert(alloc_row).id;
274   }
275   return tbl;
276 }
277 }  // namespace
278 
ExperimentalFlamegraphGenerator(TraceProcessorContext * context)279 ExperimentalFlamegraphGenerator::ExperimentalFlamegraphGenerator(
280     TraceProcessorContext* context)
281     : context_(context) {}
282 
283 ExperimentalFlamegraphGenerator::~ExperimentalFlamegraphGenerator() = default;
284 
285 // For filtering, this method uses the same constraints as
286 // ExperimentalFlamegraphGenerator::GetFlamegraphInputValues and should
287 // therefore be kept in sync.
ValidateConstraints(const QueryConstraints & qc)288 base::Status ExperimentalFlamegraphGenerator::ValidateConstraints(
289     const QueryConstraints& qc) {
290   using T = tables::ExperimentalFlamegraphNodesTable;
291 
292   const auto& cs = qc.constraints();
293 
294   auto ts_fn = [](const QueryConstraints::Constraint& c) {
295     return c.column == static_cast<int>(T::ColumnIndex::ts) &&
296            IsValidTimestampOp(c.op);
297   };
298   bool has_ts_cs = std::find_if(cs.begin(), cs.end(), ts_fn) != cs.end();
299 
300   auto upid_fn = [](const QueryConstraints::Constraint& c) {
301     return c.column == static_cast<int>(T::ColumnIndex::upid) &&
302            c.op == SQLITE_INDEX_CONSTRAINT_EQ;
303   };
304   bool has_upid_cs = std::find_if(cs.begin(), cs.end(), upid_fn) != cs.end();
305 
306   auto upid_group_fn = [](const QueryConstraints::Constraint& c) {
307     return c.column == static_cast<int>(T::ColumnIndex::upid_group) &&
308            c.op == SQLITE_INDEX_CONSTRAINT_EQ;
309   };
310   bool has_upid_group_cs =
311       std::find_if(cs.begin(), cs.end(), upid_group_fn) != cs.end();
312 
313   auto profile_type_fn = [](const QueryConstraints::Constraint& c) {
314     return c.column == static_cast<int>(T::ColumnIndex::profile_type) &&
315            c.op == SQLITE_INDEX_CONSTRAINT_EQ;
316   };
317   bool has_profile_type_cs =
318       std::find_if(cs.begin(), cs.end(), profile_type_fn) != cs.end();
319 
320   return has_ts_cs && (has_upid_cs || has_upid_group_cs) && has_profile_type_cs
321              ? base::OkStatus()
322              : base::ErrStatus("Failed to find required constraints");
323 }
324 
ComputeTable(const std::vector<Constraint> & cs,const std::vector<Order> &,const BitVector &,std::unique_ptr<Table> & table_return)325 base::Status ExperimentalFlamegraphGenerator::ComputeTable(
326     const std::vector<Constraint>& cs,
327     const std::vector<Order>&,
328     const BitVector&,
329     std::unique_ptr<Table>& table_return) {
330   // Get the input column values and compute the flamegraph using them.
331   auto values = GetFlamegraphInputValues(cs);
332 
333   std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> table;
334   if (values.profile_type == ProfileType::kGraph) {
335     auto* tracker = HeapGraphTracker::GetOrCreate(context_);
336     table = tracker->BuildFlamegraph(values.ts, *values.upid);
337   } else if (values.profile_type == ProfileType::kNative) {
338     table = BuildNativeHeapProfileFlamegraph(context_->storage.get(),
339                                              *values.upid, values.ts);
340   } else if (values.profile_type == ProfileType::kPerf) {
341     table = BuildNativeCallStackSamplingFlamegraph(
342         context_->storage.get(), values.upid, values.upid_group,
343         values.time_constraints);
344   }
345   if (!values.focus_str.empty()) {
346     table =
347         FocusTable(context_->storage.get(), std::move(table), values.focus_str);
348     // The pseudocolumns must be populated because as far as SQLite is
349     // concerned these are equality constraints.
350     auto focus_id =
351         context_->storage->InternString(base::StringView(values.focus_str));
352     for (uint32_t i = 0; i < table->row_count(); ++i) {
353       table->mutable_focus_str()->Set(i, focus_id);
354     }
355   }
356   table_return = std::move(table);
357   return base::OkStatus();
358 }
359 
CreateSchema()360 Table::Schema ExperimentalFlamegraphGenerator::CreateSchema() {
361   return tables::ExperimentalFlamegraphNodesTable::Schema();
362 }
363 
TableName()364 std::string ExperimentalFlamegraphGenerator::TableName() {
365   return "experimental_flamegraph";
366 }
367 
EstimateRowCount()368 uint32_t ExperimentalFlamegraphGenerator::EstimateRowCount() {
369   // TODO(lalitm): return a better estimate here when possible.
370   return 1024;
371 }
372 
373 }  // namespace trace_processor
374 }  // namespace perfetto
375