• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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