• 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/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::__anonb25907b10111::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 base::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 base::OkStatus();
70     }
71   }
72   return base::ErrStatus(
73       "experimental_slice_layout must have filter_track_ids constraint");
74 }
75 
ComputeTable(const std::vector<Constraint> & cs,const std::vector<Order> &,const BitVector &,std::unique_ptr<Table> & table_return)76 base::Status ExperimentalSliceLayoutGenerator::ComputeTable(
77     const std::vector<Constraint>& cs,
78     const std::vector<Order>&,
79     const BitVector&,
80     std::unique_ptr<Table>& table_return) {
81   std::set<TrackId> selected_tracks;
82   std::string filter_string = "";
83   for (const auto& c : cs) {
84     bool is_filter_track_ids = c.col_idx == kFilterTrackIdsColumnIndex;
85     bool is_equal = c.op == FilterOp::kEq;
86     bool is_string = c.value.type == SqlValue::kString;
87     if (is_filter_track_ids && is_equal && is_string) {
88       filter_string = c.value.AsString();
89       for (base::StringSplitter sp(filter_string, ','); sp.Next();) {
90         base::Optional<uint32_t> maybe = base::CStringToUInt32(sp.cur_token());
91         if (maybe) {
92           selected_tracks.insert(TrackId{maybe.value()});
93         }
94       }
95     }
96   }
97 
98   StringPool::Id filter_id =
99       string_pool_->InternString(base::StringView(filter_string));
100 
101   // Try and find the table in the cache.
102   auto it = layout_table_cache_.find(filter_id);
103   if (it != layout_table_cache_.end()) {
104     table_return.reset(new Table(it->second.Copy()));
105     return base::OkStatus();
106   }
107 
108   // Find all the slices for the tracks we want to filter and create a RowMap
109   // out of them.
110   // TODO(lalitm): Update this to use iterator (as this code will be slow after
111   // the event table is implemented).
112   // TODO(lalitm): consider generalising this by adding OR constraint support to
113   // Constraint and Table::Filter. We definitely want to wait until we have more
114   // usecases before implementing that though because it will be a significant
115   // amount of work.
116   RowMap rm;
117   for (uint32_t i = 0; i < slice_table_->row_count(); ++i) {
118     if (selected_tracks.count(slice_table_->track_id()[i]) > 0) {
119       rm.Insert(i);
120     }
121   }
122 
123   // Apply the row map to the table to cut down on the number of rows we have to
124   // go through.
125   Table filtered_table = slice_table_->Apply(std::move(rm));
126 
127   // Compute the table and add it to the cache for future use.
128   Table layout_table = ComputeLayoutTable(filtered_table, filter_id);
129   auto res = layout_table_cache_.emplace(filter_id, std::move(layout_table));
130 
131   table_return.reset(new Table(res.first->second.Copy()));
132   return base::OkStatus();
133 }
134 
135 // Build up a table of slice id -> root slice id by observing each
136 // (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)137 tables::SliceTable::Id ExperimentalSliceLayoutGenerator::InsertSlice(
138     std::map<tables::SliceTable::Id, tables::SliceTable::Id>& id_map,
139     tables::SliceTable::Id id,
140     base::Optional<tables::SliceTable::Id> parent_id) {
141   if (parent_id) {
142     tables::SliceTable::Id root_id = id_map[parent_id.value()];
143     id_map[id] = root_id;
144     return root_id;
145   } else {
146     id_map[id] = id;
147     return id;
148   }
149 }
150 
151 // The problem we're trying to solve is this: given a number of tracks each of
152 // which contain a number of 'stalactites' - depth 0 slices and all their
153 // children - layout the stalactites to minimize vertical depth without
154 // changing the horizontal (time) position. So given two tracks:
155 // Track A:
156 //     aaaaaaaaa       aaa
157 //                      aa
158 //                       a
159 // Track B:
160 //      bbb       bbb    bbb
161 //       b         b      b
162 // The result could be something like:
163 //     aaaaaaaaa  bbb  aaa
164 //                 b    aa
165 //      bbb              a
166 //       b
167 //                       bbb
168 //                        b
169 // We do this by computing an additional column: layout_depth. layout_depth
170 // tells us the vertical position of each slice in each stalactite.
171 //
172 // The algorithm works in three passes:
173 // 1. For each stalactite find the 'bounding box' (start, end, & max depth)
174 // 2. Considering each stalactite bounding box in start ts order pick a
175 //    layout_depth for the root slice of stalactite to avoid collisions with
176 //    all previous stalactite's we've considered.
177 // 3. Go though each slice and give it a layout_depth by summing it's
178 //    current depth and the root layout_depth of the stalactite it belongs to.
179 //
ComputeLayoutTable(const Table & table,StringPool::Id filter_id)180 Table ExperimentalSliceLayoutGenerator::ComputeLayoutTable(
181     const Table& table,
182     StringPool::Id filter_id) {
183   std::map<tables::SliceTable::Id, GroupInfo> groups;
184   // Map of id -> root_id
185   std::map<tables::SliceTable::Id, tables::SliceTable::Id> id_map;
186 
187   const auto& id_col = table.GetIdColumnByName<tables::SliceTable::Id>("id");
188   const auto& parent_id_col =
189       table.GetTypedColumnByName<base::Optional<tables::SliceTable::Id>>(
190           "parent_id");
191   const auto& depth_col = table.GetTypedColumnByName<uint32_t>("depth");
192   const auto& ts_col = table.GetTypedColumnByName<int64_t>("ts");
193   const auto& dur_col = table.GetTypedColumnByName<int64_t>("dur");
194 
195   // Step 1:
196   // Find the bounding box (start ts, end ts, and max depth) for each group
197   // TODO(lalitm): Update this to use iterator (as this code will be slow after
198   // the event table is implemented)
199   for (uint32_t i = 0; i < table.row_count(); ++i) {
200     tables::SliceTable::Id id = id_col[i];
201     base::Optional<tables::SliceTable::Id> parent_id = parent_id_col[i];
202     uint32_t depth = depth_col[i];
203     int64_t start = ts_col[i];
204     int64_t dur = dur_col[i];
205     int64_t end = dur == -1 ? std::numeric_limits<int64_t>::max() : start + dur;
206     InsertSlice(id_map, id, parent_id);
207     std::map<tables::SliceTable::Id, GroupInfo>::iterator it;
208     bool inserted;
209     std::tie(it, inserted) = groups.emplace(
210         std::piecewise_construct, std::forward_as_tuple(id_map[id]),
211         std::forward_as_tuple(start, end, depth + 1));
212     if (!inserted) {
213       it->second.max_height = std::max(it->second.max_height, depth + 1);
214       it->second.end = std::max(it->second.end, end);
215     }
216   }
217 
218   // Sort the groups by ts
219   std::vector<GroupInfo*> sorted_groups;
220   sorted_groups.resize(groups.size());
221   size_t idx = 0;
222   for (auto& group : groups) {
223     sorted_groups[idx++] = &group.second;
224   }
225   std::sort(std::begin(sorted_groups), std::end(sorted_groups),
226             [](const GroupInfo* group1, const GroupInfo* group2) {
227               return group1->start < group2->start;
228             });
229 
230   // Step 2:
231   // Go though each group and choose a depth for the root slice.
232   // We keep track of those groups where the start time has passed but the
233   // end time has not in this vector:
234   std::vector<GroupInfo*> still_open;
235   for (GroupInfo* group : sorted_groups) {
236     int64_t start = group->start;
237     uint32_t max_height = group->max_height;
238 
239     // Discard all 'closed' groups where that groups end_ts is < our start_ts:
240     {
241       auto it = still_open.begin();
242       while (it != still_open.end()) {
243         if ((*it)->end <= start) {
244           it = still_open.erase(it);
245         } else {
246           ++it;
247         }
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     uint32_t layout_depth = 0;
255     bool done = false;
256     while (!done) {
257       done = true;
258       uint32_t start_depth = layout_depth;
259       uint32_t end_depth = layout_depth + max_height;
260       for (const auto& open : still_open) {
261         bool top = open->layout_depth <= start_depth &&
262                    start_depth < open->layout_depth + open->max_height;
263         bool bottom = open->layout_depth < end_depth &&
264                       end_depth <= open->layout_depth + open->max_height;
265         if (top || bottom) {
266           // This is extremely dumb, we can make a much better guess for what
267           // depth to try next but it is a little complicated to get right.
268           layout_depth++;
269           done = false;
270           break;
271         }
272       }
273     }
274 
275     // Add this group to the open groups & re
276     still_open.push_back(group);
277 
278     // Set our root layout depth:
279     group->layout_depth = layout_depth;
280   }
281 
282   // Step 3: Add the two new columns layout_depth and filter_track_ids:
283   std::unique_ptr<NullableVector<int64_t>> layout_depth_column(
284       new NullableVector<int64_t>());
285   std::unique_ptr<NullableVector<StringPool::Id>> filter_column(
286       new NullableVector<StringPool::Id>());
287 
288   for (uint32_t i = 0; i < table.row_count(); ++i) {
289     tables::SliceTable::Id id = id_col[i];
290     uint32_t depth = depth_col[i];
291     // Each slice depth is it's current slice depth + root slice depth of the
292     // group:
293     layout_depth_column->Append(depth + groups.at(id_map[id]).layout_depth);
294     // We must set this to the value we got in the constraint to ensure our
295     // rows are not filtered out:
296     filter_column->Append(filter_id);
297   }
298   return table
299       .ExtendWithColumn("layout_depth", std::move(layout_depth_column),
300                         TypedColumn<int64_t>::default_flags())
301       .ExtendWithColumn("filter_track_ids", std::move(filter_column),
302                         TypedColumn<StringPool::Id>::default_flags());
303 }
304 
305 }  // namespace trace_processor
306 }  // namespace perfetto
307