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_slice_layout.h"
18
19 #include <optional>
20
21 #include "perfetto/ext/base/string_splitter.h"
22 #include "perfetto/ext/base/string_utils.h"
23 #include "src/trace_processor/prelude/table_functions/tables_py.h"
24 #include "src/trace_processor/sqlite/sqlite_utils.h"
25
26 namespace perfetto {
27 namespace trace_processor {
28 namespace tables {
29
30 ExperimentalSliceLayoutTable::~ExperimentalSliceLayoutTable() = default;
31 }
32
33 namespace {
34
35 struct GroupInfo {
GroupInfoperfetto::trace_processor::__anon78fe73ff0111::GroupInfo36 GroupInfo(int64_t _start, int64_t _end, uint32_t _max_depth)
37 : start(_start), end(_end), layout_depth(0), max_depth(_max_depth) {}
38 int64_t start;
39 int64_t end;
40 uint32_t layout_depth;
41 uint32_t max_depth;
42 };
43
44 static constexpr uint32_t kFilterTrackIdsColumnIndex =
45 tables::ExperimentalSliceLayoutTable::ColumnIndex::filter_track_ids;
46
47 } // namespace
48
ExperimentalSliceLayout(StringPool * string_pool,const tables::SliceTable * table)49 ExperimentalSliceLayout::ExperimentalSliceLayout(
50 StringPool* string_pool,
51 const tables::SliceTable* table)
52 : string_pool_(string_pool),
53 slice_table_(table),
54 empty_string_id_(string_pool_->InternString("")) {}
55 ExperimentalSliceLayout::~ExperimentalSliceLayout() = default;
56
CreateSchema()57 Table::Schema ExperimentalSliceLayout::CreateSchema() {
58 return tables::ExperimentalSliceLayoutTable::ComputeStaticSchema();
59 }
60
TableName()61 std::string ExperimentalSliceLayout::TableName() {
62 return tables::ExperimentalSliceLayoutTable::Name();
63 }
64
EstimateRowCount()65 uint32_t ExperimentalSliceLayout::EstimateRowCount() {
66 return slice_table_->row_count();
67 }
68
ValidateConstraints(const QueryConstraints & cs)69 base::Status ExperimentalSliceLayout::ValidateConstraints(
70 const QueryConstraints& cs) {
71 for (const auto& c : cs.constraints()) {
72 if (c.column == kFilterTrackIdsColumnIndex && sqlite_utils::IsOpEq(c.op)) {
73 return base::OkStatus();
74 }
75 }
76 return base::ErrStatus(
77 "experimental_slice_layout must have filter_track_ids constraint");
78 }
79
ComputeTable(const std::vector<Constraint> & cs,const std::vector<Order> &,const BitVector &,std::unique_ptr<Table> & table_return)80 base::Status ExperimentalSliceLayout::ComputeTable(
81 const std::vector<Constraint>& cs,
82 const std::vector<Order>&,
83 const BitVector&,
84 std::unique_ptr<Table>& table_return) {
85 std::set<TrackId> selected_tracks;
86 std::string filter_string = "";
87 for (const auto& c : cs) {
88 bool is_filter_track_ids = c.col_idx == kFilterTrackIdsColumnIndex;
89 bool is_equal = c.op == FilterOp::kEq;
90 bool is_string = c.value.type == SqlValue::kString;
91 if (is_filter_track_ids && is_equal && is_string) {
92 filter_string = c.value.AsString();
93 for (base::StringSplitter sp(filter_string, ','); sp.Next();) {
94 std::optional<uint32_t> maybe = base::CStringToUInt32(sp.cur_token());
95 if (maybe) {
96 selected_tracks.insert(TrackId{maybe.value()});
97 }
98 }
99 }
100 }
101
102 StringPool::Id filter_id =
103 string_pool_->InternString(base::StringView(filter_string));
104
105 // Try and find the table in the cache.
106 auto cache_it = layout_table_cache_.find(filter_id);
107 if (cache_it != layout_table_cache_.end()) {
108 table_return.reset(new Table(cache_it->second->Copy()));
109 return base::OkStatus();
110 }
111
112 // Find all the slices for the tracks we want to filter and create a vector of
113 // row numbers out of them.
114 std::vector<tables::SliceTable::RowNumber> rows;
115 for (auto it = slice_table_->IterateRows(); it; ++it) {
116 if (selected_tracks.count(it.track_id())) {
117 rows.emplace_back(it.row_number());
118 }
119 }
120
121 // Compute the table and add it to the cache for future use.
122 std::unique_ptr<Table> layout_table =
123 ComputeLayoutTable(std::move(rows), filter_id);
124 auto res = layout_table_cache_.emplace(filter_id, std::move(layout_table));
125
126 table_return.reset(new Table(res.first->second->Copy()));
127 return base::OkStatus();
128 }
129
130 // Build up a table of slice id -> root slice id by observing each
131 // (id, opt_parent_id) pair in order.
InsertSlice(std::map<tables::SliceTable::Id,tables::SliceTable::Id> & id_map,tables::SliceTable::Id id,std::optional<tables::SliceTable::Id> parent_id)132 tables::SliceTable::Id ExperimentalSliceLayout::InsertSlice(
133 std::map<tables::SliceTable::Id, tables::SliceTable::Id>& id_map,
134 tables::SliceTable::Id id,
135 std::optional<tables::SliceTable::Id> parent_id) {
136 if (parent_id) {
137 tables::SliceTable::Id root_id = id_map[parent_id.value()];
138 id_map[id] = root_id;
139 return root_id;
140 } else {
141 id_map[id] = id;
142 return id;
143 }
144 }
145
146 // The problem we're trying to solve is this: given a number of tracks each of
147 // which contain a number of 'stalactites' - depth 0 slices and all their
148 // children - layout the stalactites to minimize vertical depth without
149 // changing the horizontal (time) position. So given two tracks:
150 // Track A:
151 // aaaaaaaaa aaa
152 // aa
153 // a
154 // Track B:
155 // bbb bbb bbb
156 // b b b
157 // The result could be something like:
158 // aaaaaaaaa bbb aaa
159 // b aa
160 // bbb a
161 // b
162 // bbb
163 // b
164 // We do this by computing an additional column: layout_depth. layout_depth
165 // tells us the vertical position of each slice in each stalactite.
166 //
167 // The algorithm works in three passes:
168 // 1. For each stalactite find the 'bounding box' (start, end, & max depth)
169 // 2. Considering each stalactite bounding box in start ts order pick a
170 // layout_depth for the root slice of stalactite to avoid collisions with
171 // all previous stalactite's we've considered.
172 // 3. Go though each slice and give it a layout_depth by summing it's
173 // current depth and the root layout_depth of the stalactite it belongs to.
174 //
ComputeLayoutTable(std::vector<tables::SliceTable::RowNumber> rows,StringPool::Id filter_id)175 std::unique_ptr<Table> ExperimentalSliceLayout::ComputeLayoutTable(
176 std::vector<tables::SliceTable::RowNumber> rows,
177 StringPool::Id filter_id) {
178 std::map<tables::SliceTable::Id, GroupInfo> groups;
179 // Map of id -> root_id
180 std::map<tables::SliceTable::Id, tables::SliceTable::Id> id_map;
181
182 // Step 1:
183 // Find the bounding box (start ts, end ts, and max depth) for each group
184 // TODO(lalitm): Update this to use iterator (as this code will be slow after
185 // the event table is implemented)
186 for (tables::SliceTable::RowNumber i : rows) {
187 auto ref = i.ToRowReference(*slice_table_);
188
189 tables::SliceTable::Id id = ref.id();
190 uint32_t depth = ref.depth();
191 int64_t start = ref.ts();
192 int64_t dur = ref.dur();
193 int64_t end = dur == -1 ? std::numeric_limits<int64_t>::max() : start + dur;
194 InsertSlice(id_map, id, ref.parent_id());
195 std::map<tables::SliceTable::Id, GroupInfo>::iterator it;
196 bool inserted;
197 std::tie(it, inserted) = groups.emplace(
198 std::piecewise_construct, std::forward_as_tuple(id_map[id]),
199 std::forward_as_tuple(start, end, depth));
200 if (!inserted) {
201 it->second.max_depth = std::max(it->second.max_depth, depth);
202 it->second.end = std::max(it->second.end, end);
203 }
204 }
205
206 // Sort the groups by ts
207 std::vector<GroupInfo*> sorted_groups;
208 sorted_groups.resize(groups.size());
209 size_t idx = 0;
210 for (auto& group : groups) {
211 sorted_groups[idx++] = &group.second;
212 }
213 std::sort(std::begin(sorted_groups), std::end(sorted_groups),
214 [](const GroupInfo* group1, const GroupInfo* group2) {
215 return group1->start < group2->start;
216 });
217
218 // Step 2:
219 // Go though each group and choose a depth for the root slice.
220 // We keep track of those groups where the start time has passed but the
221 // end time has not in this vector:
222 std::vector<GroupInfo*> still_open;
223 for (GroupInfo* group : sorted_groups) {
224 int64_t start = group->start;
225 uint32_t max_depth = group->max_depth;
226
227 // Discard all 'closed' groups where that groups end_ts is < our start_ts:
228 {
229 auto it = still_open.begin();
230 while (it != still_open.end()) {
231 if ((*it)->end <= start) {
232 it = still_open.erase(it);
233 } else {
234 ++it;
235 }
236 }
237 }
238
239 // Find a start layout depth for this group s.t. our start depth +
240 // our max depth will not intersect with the start depth + max depth for
241 // any of the open groups:
242 uint32_t layout_depth = 0;
243 bool done = false;
244 while (!done) {
245 done = true;
246 uint32_t start_depth = layout_depth;
247 uint32_t end_depth = layout_depth + max_depth;
248 for (const auto& open : still_open) {
249 uint32_t open_start_depth = open->layout_depth;
250 uint32_t open_end_depth = open->layout_depth + open->max_depth;
251 bool fully_above_open = end_depth < open_start_depth;
252 bool fully_below_open = open_end_depth < start_depth;
253 if (!fully_above_open && !fully_below_open) {
254 // This is extremely dumb, we can make a much better guess for what
255 // depth to try next but it is a little complicated to get right.
256 layout_depth++;
257 done = false;
258 break;
259 }
260 }
261 }
262
263 // Add this group to the open groups & re
264 still_open.push_back(group);
265
266 // Set our root layout depth:
267 group->layout_depth = layout_depth;
268 }
269
270 // Step 3: Add the two new columns layout_depth and filter_track_ids:
271 ColumnStorage<uint32_t> layout_depth_column;
272 ColumnStorage<StringPool::Id> filter_column;
273
274 for (tables::SliceTable::RowNumber i : rows) {
275 auto ref = i.ToRowReference(*slice_table_);
276
277 // Each slice depth is it's current slice depth + root slice depth of the
278 // group:
279 uint32_t group_depth = groups.at(id_map[ref.id()]).layout_depth;
280 layout_depth_column.Append(ref.depth() + group_depth);
281 // We must set this to the value we got in the constraint to ensure our
282 // rows are not filtered out:
283 filter_column.Append(filter_id);
284 }
285
286 return tables::ExperimentalSliceLayoutTable::SelectAndExtendParent(
287 *slice_table_, std::move(rows), std::move(layout_depth_column),
288 std::move(filter_column));
289 }
290
291 } // namespace trace_processor
292 } // namespace perfetto
293