1 /* 2 * Copyright (C) 2022 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 #ifndef SRC_TRACE_PROCESSOR_DB_VIEW_H_ 18 #define SRC_TRACE_PROCESSOR_DB_VIEW_H_ 19 20 #include <stdint.h> 21 22 #include <iterator> 23 #include <memory> 24 #include <numeric> 25 #include <optional> 26 #include <string> 27 #include <vector> 28 29 #include "perfetto/base/logging.h" 30 #include "perfetto/base/status.h" 31 #include "perfetto/ext/base/flat_hash_map.h" 32 #include "perfetto/ext/base/small_vector.h" 33 #include "perfetto/trace_processor/iterator.h" 34 #include "src/trace_processor/containers/bit_vector.h" 35 #include "src/trace_processor/containers/row_map.h" 36 #include "src/trace_processor/containers/string_pool.h" 37 #include "src/trace_processor/db/table.h" 38 39 namespace perfetto { 40 namespace trace_processor { 41 42 // Implementation of a "SQL view" on top of trace processor 43 // columnar tables. 44 // 45 // Supported operations are: 46 // 1) joining tables together by id 47 // 2) exporting columns with different names 48 // 49 // Note: unlike traditional SQL views, this class does *not* 50 // allow arbitrary joins. Instead, it only supports joins between 51 // tables on ids and only supports a single constraint per table. 52 // 53 // Concretely, suppose you have two tables A and B with A having 54 // a column named b_id containing references to rows in B. This 55 // class allows defining a view AB which contains the rows of A, 56 // transparently extended with the columns from B. 57 // 58 // We implement this specially in trace processor instead of doing 59 // this in SQL for a few reasons: 60 // 1) The views we write using this class are used in highly 61 // performance sensitive contexts so avoiding the "virtual table" 62 // overhead from SQLite makes a meaningful difference. 63 // 2) In trace processor, we have more knowledge of the semantics 64 // of tables (i.e. ids are unique, sorted and non-null). While 65 // we can expose knowledge of some of this context of SQLite, 66 // it will never do as good a job of ordering joins as we can 67 // do ourselves. 68 // 3) By looking at which columns are used, we can potentially skip 69 // filtering/sorting some tables in the join which can massively 70 // speed up queries. Because SQLite lacks the semantic knowledge 71 // (see 2), it refuses to skip any join as rows could potentially 72 // be filtered out (even though, we know they wouldn't be). 73 // 74 // Design doc: go/perfetto-cpp-views 75 class View { 76 public: 77 // Bitflags which can be set to modify how joins between tables are 78 // performed. Multiple flags can be set by bitwise-oring them together. 79 enum JoinFlag : uint32_t { 80 // Flag to be specified if the join has no special properties. That is 81 // the join is standard inner join. 82 kNoFlag = 0, 83 84 // Indicates that the right hand side of the join is for a column containing 85 // strongly typed ids but the left side only contains serialized uint32_t. 86 // This means both sides will be type-checked based on serialized types 87 // rather than actual column types. 88 // 89 // This flag is not utilized by this class but any wrapping logic (e.g. the 90 // view macros) can make use of this to have very strict type checking 91 // except where this flag is specified. 92 // 93 // The motivation for this flag comes from thread/process table where we 94 // use uint32_t as ids for these tables: this is because this was standard 95 // convention before typechecked tables which we didn't change because it 96 // was a) too much effort to change b) made the code messier (as UniqueTid 97 // and UniquePid are used as indices into vectors in several places in trace 98 // processor). 99 kTypeCheckSerialized = 1 << 0, 100 101 // Indicates that the right table's id column will contain every potential 102 // id which can appear in the left table. 103 // 104 // As a rule of thumb, this flag can be specified whenever the right table 105 // is a "root" table; it's possible that there are other cases but this 106 // would need case-by-case consideration. 107 kIdAlwaysPresent = 1 << 0, 108 }; 109 110 // References a new table which should be introduced into the view by joining 111 // it with an existing table. 112 // 113 // Note that all |const char*| varaibles below should be long lived string 114 // literals (generally coming from macro defintions). 115 struct JoinTable { 116 // The table which is being joined into this view. This table will be 117 // on the RHS of the join. 118 Table* table; 119 120 // The name of this table; only used to allow referencing this table in 121 // later |Join| structs. 122 const char* table_name; 123 124 // The name of the id column |table|. 125 // Note: in practice this will always be "id" but we allow specifiying it 126 // to allow generality. 127 const char* col; 128 129 // A table previously introduced into this view which will act as the LHS of 130 // the join. 131 const char* prev_table_name; 132 133 // The name of the column in the table given by |prev_table_name| which will 134 // contain ids for |table|. 135 const char* prev_col; 136 137 // Set of bitwise-ORed flags modifying how the join should be perfomed. See 138 // |JoinFlag| enum for potential flags. 139 uint32_t join_flags; 140 }; 141 142 // Stores information about an output column for this view. 143 struct OutputColumn { 144 // The name of the column being exposed. 145 const char* col_name; 146 147 // The name of the source table this column comes from. 148 const char* source_table_name; 149 150 // The name of the column in the source table. 151 const char* source_col_name; 152 }; 153 154 View(); 155 156 View(const View&) noexcept = delete; 157 View& operator=(const View&) = delete; 158 159 View(View&&) noexcept = default; 160 View& operator=(View&&) = default; 161 162 virtual ~View(); 163 164 // Creates a new View from the given parameters. 165 base::Status Create(Table* root_table, 166 const char* root_table_name, 167 std::initializer_list<JoinTable> joins, 168 std::initializer_list<OutputColumn> columns, 169 View* view); 170 171 Table Query(const std::vector<Constraint>& cs, 172 const std::vector<Order>& ob, 173 const BitVector& cols_used) const; 174 175 uint32_t GetColumnCount() const; 176 uint32_t EstimateRowCount() const; 177 schema()178 const Table::Schema& schema() const { return schema_; } 179 180 protected: 181 // Constructor variant of Create, exposed for subclasses; any errors will 182 // simply be PERFETTO_FATAL-ed. 183 View(Table* root_table, 184 const char* root_table_name, 185 std::initializer_list<JoinTable> joins, 186 std::initializer_list<OutputColumn> columns); 187 188 private: 189 // The tables participating in a view laid out in a tree structure. 190 // 191 // The parent represents the LHS of the join with each child being a separate 192 // table being joined on the RHS of the join. This structure allows enforces, 193 // at the type-system level, that each joined table has preceisely one join 194 // condition. 195 // 196 // Note, however, that the same table pointer *can* appear multiple times in 197 // different parts of the tree but only when the "name" of the table is also 198 // different (by having a different name we can disambiguate which column we 199 // need to choose when constructing the final output table). 200 struct TableNode { 201 /// The table for the root of this tree. 202 // 203 // For all except the root node, this table will always be on the right side 204 // of the join for its parent and the left side of the join for any nodes in 205 // |children|. 206 Table* table; 207 208 // The index of the id column in |table|. 209 // In practice, this will always be zero (as id columns are implicitly the 210 // first column) but having this allows flexibility for the future. 211 std::optional<uint32_t> join_col_idx; 212 213 // The index of the column in the parent table which is selecting the rows 214 // in |table|. 215 std::optional<uint32_t> parent_join_col_idx; 216 217 // Set of bitwise-ORed flags modifying how the join should be perfomed. See 218 // |JoinFlag| struct for potential flags. 219 uint32_t join_flags; 220 221 // The child tables participating in the join. 222 using Children = base::SmallVector<std::unique_ptr<TableNode>, 4>; 223 Children children; 224 }; 225 using SourceColumn = std::pair<const TableNode*, uint32_t /* column_idx */>; 226 227 // Helper class for performing the join algorithm. 228 // 229 // This is useful to split up the algorithm into functions without having to 230 // constantly pass the state data structures between functions. 231 class QueryHelper { 232 public: 233 // Contains transient state about a single table which is used while 234 // querying a view. 235 struct NodeState { 236 // "Input" parameters. 237 // The following are set by BuildNodeStateMap and used in the other 238 // functions. 239 240 // The set of filter constraints on this table. 241 std::vector<Constraint> cs; 242 243 // Whether any column from this table is used by SQLite or if this table 244 // is an ancestor of such a table. 245 bool is_used; 246 247 // Whether joining this table with its parent can cause rows to be removed 248 // from the parent. This is true either if: 249 // 1) this table is filtered (i.e. |cs| is not empty). 250 // 2) this table does not have every id (i.e. it's not a root table) 251 // 3) this table is an ancestor of a table which |removes_parent_rows|. 252 bool removes_parent_rows; 253 254 // "Output" parameters. 255 // These are modified throughout every function and will be incrementally 256 // refined to the final until used to build the output table in 257 // |BuildTable|. 258 259 // The current output table. At the end of |FilterAndJoinRecursive|, this 260 // contains the table 261 Table output; 262 263 // The current RowMap which needs to be applied to |output| to accurately 264 // join with the parent. Built by |FilterAndJoinRecursive| and applied 265 // recursively downwards in |ApplyRowMapRecursive| 266 RowMap parent_join_rm; 267 }; 268 using NodeStateMap = base::FlatHashMap<const TableNode*, NodeState>; 269 270 QueryHelper(TableNode* root_node, 271 const std::vector<SourceColumn>&, 272 const std::vector<Constraint>&, 273 const BitVector& cols_used); 274 275 // See definition of View::Query for information about these functions. 276 static NodeStateMap BuildNodeStateMap(TableNode* root_node, 277 const std::vector<SourceColumn>&, 278 const std::vector<Constraint>&, 279 const BitVector& cols_used); 280 void FilterAndJoinRecursive(TableNode* node); ApplyRowMapRecursive(TableNode * root)281 void ApplyRowMapRecursive(TableNode* root) { 282 // To avoid the root node parent_rm emptying it, create a RowMap which 283 // will simply select all the rows. 284 auto& root_state = *state_.Find(root); 285 return ApplyRowMapRecursive(root, 286 RowMap(0, root_state.output.row_count())); 287 } 288 Table BuildTable(TableNode* root, 289 const Table::Schema& schema, 290 const std::vector<SourceColumn>&, 291 const BitVector& cols_used); 292 293 private: 294 void ApplyRowMapRecursive(TableNode* node, RowMap rm); 295 296 NodeStateMap state_; 297 }; 298 299 View(std::unique_ptr<TableNode> root, 300 std::vector<SourceColumn>, 301 Table::Schema schema); 302 303 // Implements a post-order DFS on the |TableNode| struct. Useful for 304 // compactly writing a tree traversal with a focus on what's happening. 305 template <typename Fn> PostOrderDfs(TableNode * node,Fn fn)306 static void PostOrderDfs(TableNode* node, Fn fn) { 307 for (const auto& child : node->children) { 308 PostOrderDfs(child.get(), fn); 309 } 310 fn(node); 311 } 312 313 std::unique_ptr<TableNode> root_node_; 314 std::vector<SourceColumn> source_col_by_output_idx_; 315 Table::Schema schema_; 316 }; 317 318 } // namespace trace_processor 319 } // namespace perfetto 320 321 #endif // SRC_TRACE_PROCESSOR_DB_VIEW_H_ 322