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