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/prelude/table_functions/experimental_flamegraph.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/sqlite/sqlite_utils.h"
27 #include "src/trace_processor/types/trace_processor_context.h"
28
29 namespace perfetto {
30 namespace trace_processor {
31
32 namespace {
33
extractProfileType(std::string & profile_name)34 ExperimentalFlamegraph::ProfileType extractProfileType(
35 std::string& profile_name) {
36 if (profile_name == "graph") {
37 return ExperimentalFlamegraph::ProfileType::kGraph;
38 }
39 if (profile_name == "native") {
40 return ExperimentalFlamegraph::ProfileType::kHeapProfile;
41 }
42 if (profile_name == "perf") {
43 return ExperimentalFlamegraph::ProfileType::kPerf;
44 }
45 PERFETTO_FATAL("Could not recognize profile type: %s.", profile_name.c_str());
46 }
47
IsValidTimestampOp(int op)48 bool IsValidTimestampOp(int op) {
49 return sqlite_utils::IsOpEq(op) || sqlite_utils::IsOpGt(op) ||
50 sqlite_utils::IsOpLe(op) || sqlite_utils::IsOpLt(op) ||
51 sqlite_utils::IsOpGe(op);
52 }
53
IsValidFilterOp(FilterOp filterOp)54 bool IsValidFilterOp(FilterOp filterOp) {
55 return filterOp == FilterOp::kEq || filterOp == FilterOp::kGt ||
56 filterOp == FilterOp::kLe || filterOp == FilterOp::kLt ||
57 filterOp == FilterOp::kGe;
58 }
59
60 // For filtering, this method uses the same constraints as
61 // ExperimentalFlamegraph::ValidateConstraints and should therefore
62 // be kept in sync.
GetFlamegraphInputValues(const std::vector<Constraint> & cs)63 ExperimentalFlamegraph::InputValues GetFlamegraphInputValues(
64 const std::vector<Constraint>& cs) {
65 using T = tables::ExperimentalFlamegraphNodesTable;
66
67 auto ts_fn = [](const Constraint& c) {
68 return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::ts) &&
69 IsValidFilterOp(c.op);
70 };
71 auto upid_fn = [](const Constraint& c) {
72 return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::upid) &&
73 c.op == FilterOp::kEq;
74 };
75 auto upid_group_fn = [](const Constraint& c) {
76 return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::upid_group) &&
77 c.op == FilterOp::kEq;
78 };
79 auto profile_type_fn = [](const Constraint& c) {
80 return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::profile_type) &&
81 c.op == FilterOp::kEq;
82 };
83 auto focus_str_fn = [](const Constraint& c) {
84 return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::focus_str) &&
85 c.op == FilterOp::kEq;
86 };
87
88 auto ts_it = std::find_if(cs.begin(), cs.end(), ts_fn);
89 auto upid_it = std::find_if(cs.begin(), cs.end(), upid_fn);
90 auto upid_group_it = std::find_if(cs.begin(), cs.end(), upid_group_fn);
91 auto profile_type_it = std::find_if(cs.begin(), cs.end(), profile_type_fn);
92 auto focus_str_it = std::find_if(cs.begin(), cs.end(), focus_str_fn);
93
94 // We should always have valid iterators here because BestIndex should only
95 // allow the constraint set to be chosen when we have an equality constraint
96 // on upid and a constraint on ts.
97 PERFETTO_CHECK(ts_it != cs.end());
98 PERFETTO_CHECK(upid_it != cs.end() || upid_group_it != cs.end());
99 PERFETTO_CHECK(profile_type_it != cs.end());
100
101 std::string profile_name(profile_type_it->value.AsString());
102 ExperimentalFlamegraph::ProfileType profile_type =
103 extractProfileType(profile_name);
104 int64_t ts = -1;
105 std::vector<TimeConstraints> time_constraints = {};
106
107 for (; ts_it != cs.end(); ts_it++) {
108 if (ts_it->col_idx != static_cast<uint32_t>(T::ColumnIndex::ts)) {
109 continue;
110 }
111
112 if (profile_type == ExperimentalFlamegraph::ProfileType::kPerf) {
113 PERFETTO_CHECK(ts_it->op != FilterOp::kEq);
114 time_constraints.push_back(
115 TimeConstraints{ts_it->op, ts_it->value.AsLong()});
116 } else {
117 PERFETTO_CHECK(ts_it->op == FilterOp::kEq);
118 ts = ts_it->value.AsLong();
119 }
120 }
121
122 std::optional<UniquePid> upid;
123 std::optional<std::string> upid_group;
124 if (upid_it != cs.end()) {
125 upid = static_cast<UniquePid>(upid_it->value.AsLong());
126 } else {
127 upid_group = upid_group_it->value.AsString();
128 }
129
130 std::string focus_str =
131 focus_str_it != cs.end() ? focus_str_it->value.AsString() : "";
132 return ExperimentalFlamegraph::InputValues{
133 profile_type, ts, std::move(time_constraints),
134 upid, upid_group, focus_str};
135 }
136
137 class Matcher {
138 public:
Matcher(const std::string & str)139 explicit Matcher(const std::string& str) : focus_str_(base::ToLower(str)) {}
140 Matcher(const Matcher&) = delete;
141 Matcher& operator=(const Matcher&) = delete;
142
matches(const std::string & s) const143 bool matches(const std::string& s) const {
144 // TODO(149833691): change to regex.
145 // We cannot use regex.h (does not exist in windows) or std regex (throws
146 // exceptions).
147 return base::Contains(base::ToLower(s), focus_str_);
148 }
149
150 private:
151 const std::string focus_str_;
152 };
153
154 enum class FocusedState {
155 kNotFocused,
156 kFocusedPropagating,
157 kFocusedNotPropagating,
158 };
159
160 using tables::ExperimentalFlamegraphNodesTable;
ComputeFocusedState(const ExperimentalFlamegraphNodesTable & table,const Matcher & focus_matcher)161 std::vector<FocusedState> ComputeFocusedState(
162 const ExperimentalFlamegraphNodesTable& table,
163 const Matcher& focus_matcher) {
164 // Each row corresponds to a node in the flame chart tree with its parent
165 // ptr. Root trees (no parents) will have a null parent ptr.
166 std::vector<FocusedState> focused(table.row_count());
167
168 for (uint32_t i = 0; i < table.row_count(); ++i) {
169 auto parent_id = table.parent_id()[i];
170 // Constraint: all descendants MUST come after their parents.
171 PERFETTO_DCHECK(!parent_id.has_value() || *parent_id < table.id()[i]);
172
173 if (focus_matcher.matches(table.name().GetString(i).ToStdString())) {
174 // Mark as focused
175 focused[i] = FocusedState::kFocusedPropagating;
176 auto current = parent_id;
177 // Mark all parent nodes as focused
178 while (current.has_value()) {
179 auto current_idx = *table.id().IndexOf(*current);
180 if (focused[current_idx] != FocusedState::kNotFocused) {
181 // We have already visited these nodes, skip
182 break;
183 }
184 focused[current_idx] = FocusedState::kFocusedNotPropagating;
185 current = table.parent_id()[current_idx];
186 }
187 } else if (parent_id.has_value() &&
188 focused[*table.id().IndexOf(*parent_id)] ==
189 FocusedState::kFocusedPropagating) {
190 // Focus cascades downwards.
191 focused[i] = FocusedState::kFocusedPropagating;
192 } else {
193 focused[i] = FocusedState::kNotFocused;
194 }
195 }
196 return focused;
197 }
198
199 struct CumulativeCounts {
200 int64_t size;
201 int64_t count;
202 int64_t alloc_size;
203 int64_t alloc_count;
204 };
FocusTable(TraceStorage * storage,std::unique_ptr<ExperimentalFlamegraphNodesTable> in,const std::string & focus_str)205 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> FocusTable(
206 TraceStorage* storage,
207 std::unique_ptr<ExperimentalFlamegraphNodesTable> in,
208 const std::string& focus_str) {
209 if (in->row_count() == 0 || focus_str.empty()) {
210 return in;
211 }
212 std::vector<FocusedState> focused_state =
213 ComputeFocusedState(*in, Matcher(focus_str));
214 std::unique_ptr<ExperimentalFlamegraphNodesTable> tbl(
215 new tables::ExperimentalFlamegraphNodesTable(
216 storage->mutable_string_pool()));
217
218 // Recompute cumulative counts
219 std::vector<CumulativeCounts> node_to_cumulatives(in->row_count());
220 for (int64_t idx = in->row_count() - 1; idx >= 0; --idx) {
221 auto i = static_cast<uint32_t>(idx);
222 if (focused_state[i] == FocusedState::kNotFocused) {
223 continue;
224 }
225 auto& cumulatives = node_to_cumulatives[i];
226 cumulatives.size += in->size()[i];
227 cumulatives.count += in->count()[i];
228 cumulatives.alloc_size += in->alloc_size()[i];
229 cumulatives.alloc_count += in->alloc_count()[i];
230
231 auto parent_id = in->parent_id()[i];
232 if (parent_id.has_value()) {
233 auto& parent_cumulatives =
234 node_to_cumulatives[*in->id().IndexOf(*parent_id)];
235 parent_cumulatives.size += cumulatives.size;
236 parent_cumulatives.count += cumulatives.count;
237 parent_cumulatives.alloc_size += cumulatives.alloc_size;
238 parent_cumulatives.alloc_count += cumulatives.alloc_count;
239 }
240 }
241
242 // Mapping between the old rows ('node') to the new identifiers.
243 std::vector<ExperimentalFlamegraphNodesTable::Id> node_to_id(in->row_count());
244 for (uint32_t i = 0; i < in->row_count(); ++i) {
245 if (focused_state[i] == FocusedState::kNotFocused) {
246 continue;
247 }
248
249 tables::ExperimentalFlamegraphNodesTable::Row alloc_row{};
250 // We must reparent the rows as every insertion will get its own
251 // identifier.
252 auto original_parent_id = in->parent_id()[i];
253 if (original_parent_id.has_value()) {
254 auto original_idx = *in->id().IndexOf(*original_parent_id);
255 alloc_row.parent_id = node_to_id[original_idx];
256 }
257
258 alloc_row.ts = in->ts()[i];
259 alloc_row.upid = in->upid()[i];
260 alloc_row.profile_type = in->profile_type()[i];
261 alloc_row.depth = in->depth()[i];
262 alloc_row.name = in->name()[i];
263 alloc_row.map_name = in->map_name()[i];
264 alloc_row.count = in->count()[i];
265 alloc_row.size = in->size()[i];
266 alloc_row.alloc_count = in->alloc_count()[i];
267 alloc_row.alloc_size = in->alloc_size()[i];
268
269 const auto& cumulative = node_to_cumulatives[i];
270 alloc_row.cumulative_count = cumulative.count;
271 alloc_row.cumulative_size = cumulative.size;
272 alloc_row.cumulative_alloc_count = cumulative.alloc_count;
273 alloc_row.cumulative_alloc_size = cumulative.alloc_size;
274 node_to_id[i] = tbl->Insert(alloc_row).id;
275 }
276 return tbl;
277 }
278 } // namespace
279
ExperimentalFlamegraph(TraceProcessorContext * context)280 ExperimentalFlamegraph::ExperimentalFlamegraph(TraceProcessorContext* context)
281 : context_(context) {}
282
283 ExperimentalFlamegraph::~ExperimentalFlamegraph() = default;
284
285 // For filtering, this method uses the same constraints as
286 // ExperimentalFlamegraph::GetFlamegraphInputValues and should
287 // therefore be kept in sync.
ValidateConstraints(const QueryConstraints & qc)288 base::Status ExperimentalFlamegraph::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 ExperimentalFlamegraph::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::kHeapProfile) {
338 table = BuildHeapProfileFlamegraph(context_->storage.get(), *values.upid,
339 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 ExperimentalFlamegraph::CreateSchema() {
361 return tables::ExperimentalFlamegraphNodesTable::ComputeStaticSchema();
362 }
363
TableName()364 std::string ExperimentalFlamegraph::TableName() {
365 return "experimental_flamegraph";
366 }
367
EstimateRowCount()368 uint32_t ExperimentalFlamegraph::EstimateRowCount() {
369 // TODO(lalitm): return a better estimate here when possible.
370 return 1024;
371 }
372
373 } // namespace trace_processor
374 } // namespace perfetto
375