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