1/* 2 * Copyright (C) 2025 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 17syntax = "proto2"; 18 19package perfetto.protos; 20 21// Represents a PerfettoSQL query as a protobuf. 22// 23// SQL is amazing for writing interactive queries and human readability and 24// development but it is really bad for machine readability. Specifically, given 25// an SQL query, it's very hard to figure out what the author "intended" from 26// that as the same query can be written in countless different ways. This 27// makes building automated tools which take SQL and represent the data flow 28// visually very difficult to build. 29// 30// The goal of this proto is *not* in any way to replace SQL. In fact that's 31// an explicit *non-goal*. Instead, the idea here is this proto encodes it is a 32// very restricted, well-defined subset of the functionality of SQL that we see 33// a lot of usage of when writing PerfettoSQL queries. 34// 35// Basically, trace analysis has a lot of common "patterns" when it comes to 36// writing queries and this proto aims to have a central place codifying those 37// so all Perfetto tooling can share a common interchange format. Specifically, 38// unlike SQL which is quite optimized for human readability, this proto is more 39// designed for easy machine consumption with a secondary goal to still be 40// pretty easy for humans to read/write/modify in small doses. Note that it 41// *will* be verbose to deal with complex instances of this proto. 42// 43// It will always be easy to go from this proto to PerfettoSQL: trace processor 44// exposes APIs for this. It's also easy to bring SQL directly into the proto 45// world through use of the `Sql` source (see below). 46message PerfettoSqlStructuredQuery { 47 // A table or view acting as the source of the query, possibly living in a 48 // PerfettoSQL module. 49 message Table { 50 // The name of the table or view to query. Required. 51 optional string table_name = 1; 52 53 // The name of the module this table lives in. Optional, does not need to 54 // be specified if the table exists in the global scope (e.g. track table, 55 // slice table, any table/function in the prelude). Required otherwise. 56 optional string module_name = 2; 57 58 // The name of the columns of this table which will be used by this query. 59 // Required. 60 // 61 // Note: specifying this field is *mandatory* at all times. In the future, 62 // this may become option for public tables in the standard library. 63 repeated string column_names = 3; 64 } 65 66 // A set of slices which will act as the source for the query. This is 67 // basically equivalent to doing a query on the "slice" table (+ the 68 // appropriate joins) followed by a filter on various columns. 69 // 70 // This message exists for *pure* human convinience as we expect this pattern 71 // to be very commonly used by users. 72 // 73 // Produces a source with the schema 74 // (id, ts, dur, slice_name, thread_name, process_name, track_name). 75 message SimpleSlices { 76 // Glob for the name of the slices. Optional. 77 optional string slice_name_glob = 1; 78 79 // Glob for the thread name of the slices. Optional. 80 optional string thread_name_glob = 2; 81 82 // Glob for the process name of the slices. Optional. 83 optional string process_name_glob = 3; 84 85 // Glob for the track name of the slices. Optional. 86 optional string track_name_glob = 4; 87 } 88 89 // An arbitrary SQL query to use as the source for the query. 90 message Sql { 91 // The SQL string. Required. 92 // 93 // Note: this string is currently required to be a single well-formed select 94 // statement. This means you cannot use CREATE PERFETTO TABLE/VIEW but *can* 95 // use WITH clauses, UNION and INTERSECT. This restriction may be dropped 96 // in the future. 97 optional string sql = 1; 98 99 // The name of columns which will be returned by the SQL. Required. 100 repeated string column_names = 2; 101 102 // SQL string that has to be run before running the SQL. Supports multi 103 // statement queries. Optional. 104 optional string preamble = 3; 105 } 106 107 // Performs a "time-based" intersection of data from `base` with multiple 108 // sets of intervals. 109 // 110 // Examples: 111 // The best way to understand this operator is through example usecases 112 // 1) Compute the CPU usage during some CUJs: 113 // * `base` points to a query contain CPU scheduling data. 114 // * `interval_intersect` points to a query containing the CUJ boundaries. 115 // 2) Compute the memory usage of a process during an app startup 116 // * `base` points to a query contain the memory usage of that process over 117 // time. 118 // * `interval_intersect` points to a structued query containing the app 119 // startups. 120 // 3) Compute dropped frames during an layout while scrolling 121 // * `base` points to a strucuted query containing the dropped frames 122 // over time. 123 // * `interval_intersect` points to two structured queries: the first 124 // containing the layout intervals, the second containing the scroll 125 // intervals. 126 // 127 // Schema: 128 // 1) Every query in `interval_intersect` must have both `ts` and 129 // `dur` columns. It must also have an `id` column: this is necessary 130 // because of the current implementation; this may be relaxed in the 131 // future. 132 // * Both `ts` and `dur` columns. In this case, the `base` interval 133 // must overlap with one interval from each of `interval_intersect` 134 // to be included. 135 // * `ts` column without `dur` column. In this case, the `base` 136 // timestamp must lie *between* one interval from each of 137 // `interval_intersect` to be included. 138 // 3) The query in `base` must also have an `id` column: this is necessary 139 // because of the current implementation; this may be relaxed in the 140 // future. 141 // 142 // Handling of `dur`: 143 // The `dur` column is treated specially. It is changed to have the amount of 144 // time for which intervals in `base` overlaps with all structured queries in 145 // `interval_intersect`. 146 // 147 // Overlap with multiple intervals: 148 // If one row in `base` overlaps with *multiple* sets of intervals from each 149 // query in `interval_intersect`, then *multiple* rows will be 150 // produced, one for each sets of overlaps. 151 // 152 // Example in ASCII art: 153 // base: [-----------] [--------] 154 // ii1 : [-----------] 155 // ii2 : [---------------] [-----] 156 // output: [--] [--] 157 message IntervalIntersect { 158 // The base query 159 optional PerfettoSqlStructuredQuery base = 1; 160 repeated PerfettoSqlStructuredQuery interval_intersect = 2; 161 } 162 163 // An opaque id field for the query. The convention is to use underscores 164 // and lower case (foo_bar) but this is not enforced. Optional in the general 165 // case but strongly recommended for good error messages. Required in cases 166 // where this query is used as a "shared" query. 167 optional string id = 1; 168 169 // Represents the "source" of the query which will be translared to an SQL 170 // "FROM" clause. One of the following is required. 171 oneof source { 172 // Source is an SQL table, possible in a PerfettoSQL module. 173 Table table = 2; 174 175 // Source is an arbitrary snippet of SQL. 176 Sql sql = 3; 177 178 // Source is a simple set of slices. 179 SimpleSlices simple_slices = 4; 180 181 // Source is a nested query. Useful for aliasing columns, 182 // filtering etc. 183 PerfettoSqlStructuredQuery inner_query = 5; 184 185 // Source is a nested query with the given id which should be 186 // looked up in some external data structure. 187 // 188 // This field is quite special and cannot be used in all StructuredQuery 189 // contexts. It exists to share some common structured queries between many 190 // other structured queries and is only available in contexts where this is 191 // supported. 192 // 193 // Contexts where this is supported that we are aware of: 194 // 1) Trace-Based Metrics v2 195 // 196 // Please see the documentation of the embedding system for more context. 197 string inner_query_id = 6; 198 199 // Source is an interval intersect operation. See IntervalIntersect 200 // documentation for more information. 201 IntervalIntersect interval_intersect = 7; 202 } 203 204 // Represents a single filter on a column. 205 message Filter { 206 // The column name to be filtered. Required. 207 optional string column_name = 1; 208 209 // The operator to use to perform filtering. Required. 210 enum Operator { 211 UNKNOWN = 0; 212 EQUAL = 1; 213 NOT_EQUAL = 2; 214 LESS_THAN = 3; 215 LESS_THAN_EQUAL = 4; 216 GREATER_THAN = 5; 217 GREATER_THAN_EQUAL = 6; 218 IS_NULL = 8; 219 IS_NOT_NULL = 9; 220 221 // Unix GLOB. Only makes sense for string columns. 222 GLOB = 7; 223 } 224 optional Operator op = 2; 225 226 // The RHS for filtering. All values specified here will be ORed together 227 // allowing easy IN and GLOB IN filtering. If operation is different than 228 // IS_NULL or IS_NOT_NULL, at least one of these fields must be non-empty. 229 // Only the first non-empty field will be considered. 230 repeated string string_rhs = 3; 231 repeated double double_rhs = 4; 232 repeated int64 int64_rhs = 5; 233 } 234 235 // A set of filters which are ANDed together. Optional, can be empty. 236 repeated Filter filters = 8; 237 238 // Represents a GROUP BY + aggregation operation in SQL. Optional. 239 message GroupBy { 240 // The column names to group by. At least one column is required. 241 repeated string column_names = 1; 242 243 // The list of aggregations to perform. 244 message Aggregate { 245 enum Op { 246 UNSPECIFIED = 0; 247 COUNT = 1; 248 SUM = 2; 249 MIN = 3; 250 MAX = 4; 251 MEAN = 5; 252 MEDIAN = 6; 253 DURATION_WEIGHTED_MEAN = 7; 254 } 255 256 optional string column_name = 1; 257 optional Op op = 2; 258 optional string result_column_name = 3; 259 } 260 repeated Aggregate aggregates = 2; 261 } 262 optional GroupBy group_by = 9; 263 264 // Represents the selection of columns from the source. Maps to a SELECT 265 // operation. Optional. 266 // 267 // Depending on whether `group_by` was specified the columns available from 268 // the source will be different: 269 // * if `group_by` is specified, all the columns in `group_by.column_names` 270 // and `group_by.aggregates.result_column_name` are available. 271 // * if `group_by` is not specified, all columns in the source are eligible. 272 // 273 // If this is *not* specified, all columns from the source will be output. 274 message SelectColumn { 275 // The existing name of the column from the source. Required. 276 optional string column_name = 1; 277 278 // The new name of the column. If not set, the name of the column is 279 // `column_name`. Optional. 280 optional string alias = 2; 281 } 282 repeated SelectColumn select_columns = 10; 283} 284