1-- 2-- Copyright 2024 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-- https://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-- sqlformat file off 17 18CREATE PERFETTO MACRO _ii_df_agg(x ColumnName, y ColumnName) 19RETURNS _ProjectionFragment AS __intrinsic_stringify!($x), input.$y; 20 21CREATE PERFETTO MACRO _ii_df_bind(x Expr, y Expr) 22RETURNS Expr AS __intrinsic_table_ptr_bind($x, __intrinsic_stringify!($y)); 23 24CREATE PERFETTO MACRO _ii_df_select(x ColumnName, y Expr) 25RETURNS _ProjectionFragment AS $x AS $y; 26 27CREATE PERFETTO MACRO __first_arg(x Expr, y Expr) 28RETURNS Expr AS $x; 29 30CREATE PERFETTO MACRO _interval_agg( 31 tab TableOrSubquery, 32 agg_columns _ColumnNameList 33) 34RETURNS TableOrSubquery AS 35( 36 SELECT __intrinsic_interval_tree_intervals_agg( 37 input.id, 38 input.ts, 39 input.dur 40 __intrinsic_token_apply_prefix!( 41 _ii_df_agg, 42 $agg_columns, 43 $agg_columns 44 ) 45 ) 46 FROM (SELECT * FROM $tab ORDER BY ts) input 47); 48 49CREATE PERFETTO MACRO _interval_intersect( 50 tabs _TableNameList, 51 agg_columns _ColumnNameList 52) 53RETURNS TableOrSubquery AS 54( 55 SELECT 56 c0 AS ts, 57 c1 AS dur, 58 -- Columns for tables ids, in the order of provided tables. 59 __intrinsic_token_apply!( 60 __first_arg, 61 (c2 AS id_0, c3 AS id_1, c4 AS id_2, c5 AS id_3, c6 AS id_4), 62 $tabs 63 ) 64 -- Columns for partitions, one for each column with partition. 65 __intrinsic_token_apply_prefix!( 66 _ii_df_select, 67 (c7, c8, c9, c10), 68 $agg_columns 69 ) 70 -- Interval intersect result table. 71 FROM __intrinsic_table_ptr( 72 __intrinsic_interval_intersect( 73 __intrinsic_token_apply!( 74 _interval_agg, 75 $tabs, 76 ($agg_columns, $agg_columns, $agg_columns, $agg_columns, $agg_columns) 77 ), 78 __intrinsic_stringify!($agg_columns) 79 ) 80 ) 81 82 -- Bind the resulting columns 83 WHERE __intrinsic_table_ptr_bind(c0, 'ts') 84 AND __intrinsic_table_ptr_bind(c1, 'dur') 85 -- Id columns 86 AND __intrinsic_table_ptr_bind(c2, 'id_0') 87 AND __intrinsic_table_ptr_bind(c3, 'id_1') 88 AND __intrinsic_table_ptr_bind(c4, 'id_2') 89 AND __intrinsic_table_ptr_bind(c5, 'id_3') 90 AND __intrinsic_table_ptr_bind(c6, 'id_4') 91 92 -- Partition columns. 93 __intrinsic_token_apply_and_prefix!( 94 _ii_df_bind, 95 (c7, c8, c9, c10), 96 $agg_columns 97 ) 98); 99 100CREATE PERFETTO MACRO _interval_intersect_single( 101 ts Expr, 102 dur Expr, 103 t TableOrSubquery 104) 105RETURNS TableOrSubquery AS 106( 107 SELECT 108 id_0 AS id, 109 ts, 110 dur 111 FROM _interval_intersect!( 112 ($t, (SELECT 0 AS id, $ts AS ts, $dur AS dur)), 113 () 114 ) 115); 116 117-- Given a list of intervals (ts, dur), this macro generates a list of interval 118-- end points as well as the intervals that intersect with those points. 119-- 120-- e.g. input (10, 20), (20, 25) 121-- 122-- 10 30 123-- A |-------| 124-- B|----------| 125-- 20 45 126-- 127-- would generate the output 128-- 129-- ts,dur,group_id,id,interval_ends_at_ts 130-- 10,10,1,A,0 131-- 20,10,2,A,0 132-- 20,10,2,B,0 133-- 30,15,3,A,1 134-- 30,15,3,B,0 135-- 45,0,4,B,1 136-- 137-- Runtime is O(n log n + m), where n is the number of intervals and m 138-- is the size of the output. 139CREATE PERFETTO MACRO interval_self_intersect( 140 -- Table or subquery containing interval data. 141 intervals TableOrSubquery) 142RETURNS TableOrSubquery 143AS 144( 145 WITH RECURSIVE 146 _end_points AS ( 147 SELECT 148 ts, 149 id, 150 TRUE AS is_start 151 FROM $intervals 152 UNION ALL 153 SELECT 154 ts + dur AS ts, 155 id, 156 FALSE AS is_start 157 FROM $intervals 158 ), 159 _with_next_ts AS ( 160 SELECT 161 *, 162 LEAD(ts, 1, NULL) OVER (ORDER BY ts) AS next_ts 163 FROM _end_points 164 ORDER BY ts 165 ), 166 _group_by_ts AS ( 167 SELECT 168 ts, 169 MAX(next_ts) AS next_group_ts, 170 ROW_NUMBER() OVER (ORDER BY ts) AS group_id 171 FROM _with_next_ts 172 GROUP BY ts 173 ), 174 _end_points_w_group_info AS ( 175 SELECT * 176 FROM _with_next_ts 177 JOIN _group_by_ts USING (ts) 178 ), 179 -- Algorithm: Consider endpoints from left to right (increasing group_id). 180 -- As we scan, we keep a set of open intervals: 181 -- + if a new interval opens at ts, add it to the set 182 -- + if a current interval closes at ts, remove it from the set 183 -- At each timestamp (start or end), we record this set of open intervals 184 scan(group_id, ts, dur, id) AS ( 185 -- Base case: we open intervals 186 SELECT 187 group_id, 188 ts, 189 IFNULL(next_group_ts - ts, 0) AS dur, 190 id 191 FROM _end_points_w_group_info 192 WHERE is_start = 1 193 UNION ALL 194 -- Recursive: look at intervals from previous sequence number 195 -- and keep all that remain open 196 SELECT 197 cur.group_id, 198 cur.ts, 199 IFNULL(next_group_ts - cur.ts, 0) AS dur, 200 prev.id 201 FROM 202 _end_points_w_group_info cur 203 JOIN 204 scan prev ON (cur.group_id = prev.group_id + 1) 205 WHERE 206 prev.id <> cur.id 207 -- this order by makes the join more efficient 208 ORDER BY group_id ASC 209 ) 210 SELECT ts, dur, group_id, id, FALSE AS interval_ends_at_ts FROM scan 211 UNION ALL 212 SELECT 213 ts, 214 IFNULL(next_ts - ts, 0) AS dur, 215 group_id, 216 id, 217 TRUE AS interval_ends_at_ts 218 FROM _end_points_w_group_info WHERE is_start = 0 219); 220