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_slice_layout_generator.h"
18 #include "perfetto/ext/base/optional.h"
19 #include "perfetto/ext/base/string_splitter.h"
20 #include "perfetto/ext/base/string_utils.h"
21 #include "src/trace_processor/sqlite/sqlite_utils.h"
22
23 namespace perfetto {
24 namespace trace_processor {
25 namespace {
26
27 struct GroupInfo {
GroupInfoperfetto::trace_processor::__anon5bec34ec0111::GroupInfo28 GroupInfo(int64_t _start, int64_t _end, uint32_t _max_height)
29 : start(_start), end(_end), max_height(_max_height) {}
30 int64_t start;
31 int64_t end;
32 uint32_t max_height;
33 uint32_t layout_depth;
34 };
35
36 } // namespace
37
ExperimentalSliceLayoutGenerator(StringPool * string_pool,const tables::SliceTable * table)38 ExperimentalSliceLayoutGenerator::ExperimentalSliceLayoutGenerator(
39 StringPool* string_pool,
40 const tables::SliceTable* table)
41 : string_pool_(string_pool),
42 slice_table_(table),
43 empty_string_id_(string_pool_->InternString("")) {}
44 ExperimentalSliceLayoutGenerator::~ExperimentalSliceLayoutGenerator() = default;
45
CreateSchema()46 Table::Schema ExperimentalSliceLayoutGenerator::CreateSchema() {
47 Table::Schema schema = tables::SliceTable::Schema();
48 schema.columns.emplace_back(Table::Schema::Column{
49 "layout_depth", SqlValue::Type::kLong, false /* is_id */,
50 false /* is_sorted */, false /* is_hidden */});
51 schema.columns.emplace_back(Table::Schema::Column{
52 "filter_track_ids", SqlValue::Type::kString, false /* is_id */,
53 false /* is_sorted */, true /* is_hidden */});
54 return schema;
55 }
56
TableName()57 std::string ExperimentalSliceLayoutGenerator::TableName() {
58 return "experimental_slice_layout";
59 }
60
EstimateRowCount()61 uint32_t ExperimentalSliceLayoutGenerator::EstimateRowCount() {
62 return slice_table_->row_count();
63 }
64
ValidateConstraints(const QueryConstraints & cs)65 util::Status ExperimentalSliceLayoutGenerator::ValidateConstraints(
66 const QueryConstraints& cs) {
67 for (const auto& c : cs.constraints()) {
68 if (c.column == kFilterTrackIdsColumnIndex && sqlite_utils::IsOpEq(c.op)) {
69 return util::OkStatus();
70 }
71 }
72 return util::ErrStatus(
73 "experimental_slice_layout must have filter_track_ids constraint");
74 }
75
ComputeTable(const std::vector<Constraint> & cs,const std::vector<Order> &)76 std::unique_ptr<Table> ExperimentalSliceLayoutGenerator::ComputeTable(
77 const std::vector<Constraint>& cs,
78 const std::vector<Order>&) {
79 std::set<TrackId> selected_tracks;
80 std::string filter_string = "";
81 for (const auto& c : cs) {
82 bool is_filter_track_ids = c.col_idx == kFilterTrackIdsColumnIndex;
83 bool is_equal = c.op == FilterOp::kEq;
84 bool is_string = c.value.type == SqlValue::kString;
85 if (is_filter_track_ids && is_equal && is_string) {
86 filter_string = c.value.AsString();
87 for (base::StringSplitter sp(filter_string, ','); sp.Next();) {
88 base::Optional<uint32_t> maybe = base::CStringToUInt32(sp.cur_token());
89 if (maybe) {
90 selected_tracks.insert(TrackId{maybe.value()});
91 }
92 }
93 }
94 }
95
96 StringPool::Id filter_id =
97 string_pool_->InternString(base::StringView(filter_string));
98
99 // Try and find the table in the cache.
100 auto it = layout_table_cache_.find(filter_id);
101 if (it != layout_table_cache_.end()) {
102 return std::unique_ptr<Table>(new Table(it->second.Copy()));
103 }
104
105 // Find all the slices for the tracks we want to filter and create a RowMap
106 // out of them.
107 // TODO(lalitm): Update this to use iterator (as this code will be slow after
108 // the event table is implemented).
109 // TODO(lalitm): consider generalising this by adding OR constraint support to
110 // Constraint and Table::Filter. We definitely want to wait until we have more
111 // usecases before implementing that though because it will be a significant
112 // amount of work.
113 RowMap rm;
114 for (uint32_t i = 0; i < slice_table_->row_count(); ++i) {
115 if (selected_tracks.count(slice_table_->track_id()[i]) > 0) {
116 rm.Insert(i);
117 }
118 }
119
120 // Apply the row map to the table to cut down on the number of rows we have to
121 // go through.
122 Table filtered_table = slice_table_->Apply(std::move(rm));
123
124 // Compute the table and add it to the cache for future use.
125 Table layout_table = ComputeLayoutTable(filtered_table, filter_id);
126 auto res = layout_table_cache_.emplace(filter_id, std::move(layout_table));
127 return std::unique_ptr<Table>(new Table(res.first->second.Copy()));
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,base::Optional<tables::SliceTable::Id> parent_id)132 tables::SliceTable::Id ExperimentalSliceLayoutGenerator::InsertSlice(
133 std::map<tables::SliceTable::Id, tables::SliceTable::Id>& id_map,
134 tables::SliceTable::Id id,
135 base::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(const Table & table,StringPool::Id filter_id)175 Table ExperimentalSliceLayoutGenerator::ComputeLayoutTable(
176 const Table& table,
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 const auto& id_col = table.GetIdColumnByName<tables::SliceTable::Id>("id");
183 const auto& parent_id_col =
184 table.GetTypedColumnByName<base::Optional<tables::SliceTable::Id>>(
185 "parent_id");
186 const auto& depth_col = table.GetTypedColumnByName<uint32_t>("depth");
187 const auto& ts_col = table.GetTypedColumnByName<int64_t>("ts");
188 const auto& dur_col = table.GetTypedColumnByName<int64_t>("dur");
189
190 // Step 1:
191 // Find the bounding box (start ts, end ts, and max depth) for each group
192 // TODO(lalitm): Update this to use iterator (as this code will be slow after
193 // the event table is implemented)
194 for (uint32_t i = 0; i < table.row_count(); ++i) {
195 tables::SliceTable::Id id = id_col[i];
196 base::Optional<tables::SliceTable::Id> parent_id = parent_id_col[i];
197 uint32_t depth = depth_col[i];
198 int64_t start = ts_col[i];
199 int64_t dur = dur_col[i];
200 int64_t end = dur == -1 ? std::numeric_limits<int64_t>::max() : start + dur;
201 InsertSlice(id_map, id, parent_id);
202 std::map<tables::SliceTable::Id, GroupInfo>::iterator it;
203 bool inserted;
204 std::tie(it, inserted) = groups.emplace(
205 std::piecewise_construct, std::forward_as_tuple(id_map[id]),
206 std::forward_as_tuple(start, end, depth + 1));
207 if (!inserted) {
208 it->second.max_height = std::max(it->second.max_height, depth + 1);
209 it->second.end = std::max(it->second.end, end);
210 }
211 }
212
213 // Sort the groups by ts
214 std::vector<GroupInfo*> sorted_groups;
215 sorted_groups.resize(groups.size());
216 size_t idx = 0;
217 for (auto& group : groups) {
218 sorted_groups[idx++] = &group.second;
219 }
220 std::sort(std::begin(sorted_groups), std::end(sorted_groups),
221 [](const GroupInfo* group1, const GroupInfo* group2) {
222 return group1->start < group2->start;
223 });
224
225 // Step 2:
226 // Go though each group and choose a depth for the root slice.
227 // We keep track of those groups where the start time has passed but the
228 // end time has not in this vector:
229 std::vector<GroupInfo*> still_open;
230 for (GroupInfo* group : sorted_groups) {
231 int64_t start = group->start;
232 uint32_t max_height = group->max_height;
233
234 // Discard all 'closed' groups where that groups end_ts is < our start_ts:
235 {
236 auto it = still_open.begin();
237 while (it != still_open.end()) {
238 if ((*it)->end < start) {
239 it = still_open.erase(it);
240 } else {
241 ++it;
242 }
243 }
244 }
245
246 // Find a start layout depth for this group s.t. our start depth +
247 // our max depth will not intersect with the start depth + max depth for
248 // any of the open groups:
249 uint32_t layout_depth = 0;
250 bool done = false;
251 while (!done) {
252 done = true;
253 uint32_t start_depth = layout_depth;
254 uint32_t end_depth = layout_depth + max_height;
255 for (const auto& open : still_open) {
256 bool top = open->layout_depth <= start_depth &&
257 start_depth < open->layout_depth + open->max_height;
258 bool bottom = open->layout_depth < end_depth &&
259 end_depth <= open->layout_depth + open->max_height;
260 if (top || bottom) {
261 // This is extremely dumb, we can make a much better guess for what
262 // depth to try next but it is a little complicated to get right.
263 layout_depth++;
264 done = false;
265 break;
266 }
267 }
268 }
269
270 // Add this group to the open groups & re
271 still_open.push_back(group);
272
273 // Set our root layout depth:
274 group->layout_depth = layout_depth;
275 }
276
277 // Step 3: Add the two new columns layout_depth and filter_track_ids:
278 std::unique_ptr<NullableVector<int64_t>> layout_depth_column(
279 new NullableVector<int64_t>());
280 std::unique_ptr<NullableVector<StringPool::Id>> filter_column(
281 new NullableVector<StringPool::Id>());
282
283 for (uint32_t i = 0; i < table.row_count(); ++i) {
284 tables::SliceTable::Id id = id_col[i];
285 uint32_t depth = depth_col[i];
286 // Each slice depth is it's current slice depth + root slice depth of the
287 // group:
288 layout_depth_column->Append(depth + groups.at(id_map[id]).layout_depth);
289 // We must set this to the value we got in the constraint to ensure our
290 // rows are not filtered out:
291 filter_column->Append(filter_id);
292 }
293 return table
294 .ExtendWithColumn("layout_depth", std::move(layout_depth_column),
295 TypedColumn<int64_t>::default_flags())
296 .ExtendWithColumn("filter_track_ids", std::move(filter_column),
297 TypedColumn<StringPool::Id>::default_flags());
298 }
299
300 } // namespace trace_processor
301 } // namespace perfetto
302