1-- 2-- Copyright 2023 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 16INCLUDE PERFETTO MODULE intervals.intersect; 17 18-- Compute the distribution of the overlap of the given intervals over time. 19-- 20-- Each interval is a (ts, dur) pair and the overlap represented as a (ts, value) 21-- counter, with the value corresponding to the number of intervals that overlap 22-- the given timestamp and interval until the next timestamp. 23CREATE PERFETTO MACRO intervals_overlap_count( 24 -- Table or subquery containing interval data. 25 segments TableOrSubquery, 26 -- Column containing interval starts (usually `ts`). 27 ts_column ColumnName, 28 -- Column containing interval durations (usually `dur`). 29 dur_column ColumnName 30) 31-- The returned table has the schema (ts TIMESTAMP, value LONG). 32-- |ts| is the timestamp when the number of open segments changed. |value| is 33-- the number of open segments. 34RETURNS TableOrSubquery AS 35( 36 -- Algorithm: for each segment, emit a +1 at the start and a -1 at the end. 37 -- Then, merge events with the same timestamp and compute a cumulative sum. 38 WITH 39 _starts AS ( 40 SELECT 41 1 AS delta, 42 $ts_column AS ts 43 FROM $segments 44 ), 45 _ends AS ( 46 SELECT 47 -1 AS delta, 48 $ts_column + $dur_column AS ts 49 FROM $segments 50 WHERE 51 $dur_column != -1 52 ), 53 _events AS ( 54 SELECT 55 * 56 FROM _starts 57 UNION ALL 58 SELECT 59 * 60 FROM _ends 61 ), 62 -- Merge events with the same timestamp to avoid artifacts in the data. 63 _merged_events AS ( 64 SELECT 65 ts, 66 sum(delta) AS delta 67 FROM _events 68 GROUP BY 69 ts 70 ) 71 SELECT 72 ts, 73 sum(delta) OVER (ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS value 74 FROM _merged_events 75 ORDER BY 76 ts 77); 78 79-- Compute the distribution of the overlap of the given intervals over time from 80-- slices in a same group. 81-- 82-- Each interval is a (ts, dur, group) triple and the overlap represented as a 83-- (ts, value, group) counter, with the value corresponding to the number of 84-- intervals that belong to the same group and overlap the given timestamp and 85-- interval until the next timestamp. 86CREATE PERFETTO MACRO intervals_overlap_count_by_group( 87 -- Table or subquery containing interval data. 88 segments TableOrSubquery, 89 -- Column containing interval starts (usually `ts`). 90 ts_column ColumnName, 91 -- Column containing interval durations (usually `dur`). 92 dur_column ColumnName, 93 -- Column containing group name for grouping. 94 group_column ColumnName 95) 96-- The returned table has the schema (ts INT64, value UINT32, group_name) where 97-- the type of group_name is the same as that in |segments|. 98-- |ts| is the timestamp when the number of open segments changed. |value| is 99-- the number of open segments. |group_name| is the name of a group used for the 100-- overlap calculation. 101RETURNS TableOrSubquery AS 102( 103 -- Algorithm: for each segment, emit a +1 at the start and a -1 at the end. 104 -- Then, merge events with the same timestamp and compute a cumulative sum for 105 -- each group. 106 WITH 107 _starts AS ( 108 SELECT 109 1 AS delta, 110 $ts_column AS ts, 111 $group_column AS group_name 112 FROM $segments 113 ), 114 _ends AS ( 115 SELECT 116 -1 AS delta, 117 $ts_column + $dur_column AS ts, 118 $group_column AS group_name 119 FROM $segments 120 WHERE 121 $dur_column != -1 122 ), 123 _events AS ( 124 SELECT 125 * 126 FROM _starts 127 UNION ALL 128 SELECT 129 * 130 FROM _ends 131 ), 132 -- Merge events with the same timestamp to avoid artifacts in the data. 133 _merged_events AS ( 134 SELECT 135 ts, 136 sum(delta) AS delta, 137 group_name 138 FROM _events 139 GROUP BY 140 ts, 141 group_name 142 ) 143 SELECT 144 ts, 145 sum(delta) OVER (PARTITION BY group_name ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS value, 146 group_name 147 FROM _merged_events 148 ORDER BY 149 ts 150); 151 152-- Returns whether |intervals| contains any overlapping intervals. Useful for 153-- checking if provided table/subquery can be used for intervals_intersect 154-- macro. 155CREATE PERFETTO MACRO _intervals_overlap_in_table( 156 -- Table/subquery of intervals with |ts| and |dur| columns. 157 intervals TableOrSubquery 158) 159-- Returns 1 if table contains overlapping intervals. Otherwise returns 0. 160RETURNS Expr AS 161( 162 WITH 163 ts_with_next AS ( 164 SELECT 165 ts + dur AS ts_end, 166 -- The last slice will have |next_ts == NULL|, but it's not an issue as if 167 -- it's the last slice we know that it will not overlap with the next one. 168 lead(ts) OVER (ORDER BY ts) AS next_ts 169 FROM $intervals 170 WHERE 171 dur != -1 172 ), 173 filtered AS ( 174 SELECT 175 * 176 FROM ts_with_next 177 WHERE 178 ts_end > next_ts 179 LIMIT 1 180 ) 181 SELECT 182 count() AS has_overlaps 183 FROM filtered 184); 185 186-- Merges a |roots_table| and |children_table| into one table. See _intervals_flatten 187-- that accepts the output of this macro to flatten intervals. 188 189-- See: _intervals_merge_root_and_children_by_intersection. 190CREATE PERFETTO MACRO _intervals_merge_root_and_children( 191 -- Table or subquery containing all the root intervals: (id, ts, dur). 192 -- Note that parent_id is not necessary in this table as it will be NULL anyways. 193 roots_table TableOrSubquery, 194 -- Table or subquery containing all the child intervals: 195 -- (root_id, id, parent_id, ts, dur) 196 children_table TableOrSubquery 197) 198-- The returned table has the schema (root_id LONG, root_ts TIMESTAMP, root_dur, LONG, 199-- id LONG, parent_id LONG, ts TIMESTAMP, dur LONG). 200RETURNS TableOrSubquery AS 201( 202 WITH 203 _roots AS ( 204 SELECT 205 id AS root_id, 206 ts AS root_ts, 207 dur AS root_dur 208 FROM ( 209 $roots_table 210 ) 211 WHERE 212 dur > 0 213 ), 214 _children AS ( 215 SELECT 216 * 217 FROM ( 218 $children_table 219 ) 220 WHERE 221 dur > 0 222 ), 223 _roots_without_children AS ( 224 SELECT 225 root_id 226 FROM _roots 227 EXCEPT 228 SELECT DISTINCT 229 parent_id AS root_id 230 FROM _children 231 ) 232 SELECT 233 _roots.root_id, 234 _roots.root_ts, 235 _roots.root_dur, 236 _children.id, 237 _children.parent_id, 238 _children.ts, 239 _children.dur 240 FROM _children 241 JOIN _roots 242 USING (root_id) 243 UNION ALL 244 -- Handle singleton roots 245 SELECT 246 root_id, 247 root_ts, 248 root_dur, 249 NULL AS id, 250 NULL AS parent_id, 251 NULL AS ts, 252 NULL AS dur 253 FROM _roots_without_children 254 JOIN _roots 255 USING (root_id) 256); 257 258-- Merges a |roots_table| and |children_table| into one table. See _intervals_flatten 259-- that accepts the output of this macro to flatten intervals. 260 261-- This is very similar to _intervals_merge_root_and_children but there is no explicit 262-- root_id shared between the root and the children. Instead an _interval_intersect is 263-- used to derive the root and child relationships. 264CREATE PERFETTO MACRO _intervals_merge_root_and_children_by_intersection( 265 -- Table or subquery containing all the root intervals: (id, ts, dur). 266 -- Note that parent_id is not necessary in this table as it will be NULL anyways. 267 roots_table TableOrSubquery, 268 -- Table or subquery containing all the child intervals: 269 -- (root_id, id, parent_id, ts, dur) 270 children_table TableOrSubquery, 271 -- intersection key used in deriving the root child relationships. 272 key ColumnName 273) 274RETURNS TableOrSubQuery AS 275( 276 WITH 277 _roots AS ( 278 SELECT 279 * 280 FROM $roots_table 281 WHERE 282 dur > 0 283 ORDER BY 284 ts 285 ), 286 _children AS ( 287 SELECT 288 * 289 FROM $children_table 290 WHERE 291 dur > 0 292 ORDER BY 293 ts 294 ) 295 SELECT 296 ii.ts, 297 ii.dur, 298 _children.id, 299 iif(_children.parent_id IS NULL, id_1, _children.parent_id) AS parent_id, 300 _roots.id AS root_id, 301 _roots.ts AS root_ts, 302 _roots.dur AS root_dur, 303 ii.$key 304 FROM _interval_intersect!((_children, _roots), ($key)) AS ii 305 JOIN _children 306 ON _children.id = id_0 307 JOIN _roots 308 ON _roots.id = id_1 309); 310 311-- Partition and flatten a hierarchy of intervals into non-overlapping intervals where 312-- each resulting interval is the leaf in the hierarchy at any given time. The result also 313-- denotes the 'self-time' of each interval. 314-- 315-- Each interval is a (root_id, root_ts, root_dur, id, parent_id, ts, dur) and the overlap is 316-- represented as a (root_id, id, parent_id, ts, dur). 317-- Note that, children intervals must not be longer than any ancestor interval. 318-- See _intervals_merge_root_and_children that can be used to generate input to this macro 319-- from two different root and children tables. 320CREATE PERFETTO MACRO _intervals_flatten( 321 children_with_roots_table TableOrSubquery 322) 323-- The returned table has the schema (root_id LONG, id LONG, ts TIMESTAMP, dur LONG). 324RETURNS TableOrSubquery AS 325( 326 -- Algorithm: Sort all the start and end timestamps of the children within a root. 327 -- The interval duration between one timestamp and the next is one result. 328 -- If the timestamp is a start, the id is the id of the interval, if it's an end, 329 -- it's the parent_id. 330 -- Special case the edges of the roots and roots without children. 331 WITH 332 _children_with_roots AS ( 333 SELECT 334 * 335 FROM ( 336 $children_with_roots_table 337 ) 338 WHERE 339 root_dur > 0 AND ( 340 dur IS NULL OR dur > 0 341 ) 342 ), 343 _ends AS ( 344 SELECT 345 root_id, 346 root_ts, 347 root_dur, 348 coalesce(parent_id, root_id) AS id, 349 ts + dur AS ts 350 FROM _children_with_roots 351 WHERE 352 id IS NOT NULL 353 ), 354 _events AS ( 355 SELECT 356 root_id, 357 root_ts, 358 root_dur, 359 id, 360 ts, 361 1 AS priority 362 FROM _children_with_roots 363 UNION ALL 364 SELECT 365 root_id, 366 root_ts, 367 root_dur, 368 id, 369 ts, 370 0 AS priority 371 FROM _ends 372 ), 373 _events_deduped AS ( 374 SELECT 375 root_id, 376 root_ts, 377 root_dur, 378 id, 379 ts 380 FROM _events 381 GROUP BY 382 root_id, 383 ts 384 HAVING 385 priority = max(priority) 386 ), 387 _intervals AS ( 388 SELECT 389 root_id, 390 root_ts, 391 root_dur, 392 id, 393 ts, 394 lead(ts) OVER (PARTITION BY root_id ORDER BY ts) - ts AS dur 395 FROM _events_deduped 396 ), 397 _only_middle AS ( 398 SELECT 399 * 400 FROM _intervals 401 WHERE 402 dur > 0 403 ), 404 _only_start AS ( 405 SELECT 406 root_id, 407 root_id AS id, 408 root_ts AS ts, 409 min(ts) - root_ts AS dur 410 FROM _only_middle 411 GROUP BY 412 root_id 413 HAVING 414 dur > 0 415 ), 416 _only_end AS ( 417 SELECT 418 root_id, 419 root_id AS id, 420 max(ts + dur) AS ts, 421 root_ts + root_dur - max(ts + dur) AS dur 422 FROM _only_middle 423 GROUP BY 424 root_id 425 HAVING 426 dur > 0 427 ), 428 _only_singleton AS ( 429 SELECT 430 root_id, 431 root_id AS id, 432 root_ts AS ts, 433 root_dur AS dur 434 FROM _children_with_roots 435 WHERE 436 id IS NULL 437 GROUP BY 438 root_id 439 ) 440 SELECT 441 root_id, 442 id, 443 ts, 444 dur 445 FROM _only_middle 446 UNION ALL 447 SELECT 448 root_id, 449 id, 450 ts, 451 dur 452 FROM _only_start 453 UNION ALL 454 SELECT 455 root_id, 456 id, 457 ts, 458 dur 459 FROM _only_end 460 UNION ALL 461 SELECT 462 root_id, 463 id, 464 ts, 465 dur 466 FROM _only_singleton 467); 468 469-- Merge intervals intervals when they overlap to generate a minimum covering set of 470-- intervals with no overlap. The intervals are closed (contain both endpoints) and 471-- we consider two intervals overlapping 472-- (a) the intervals overlap or 473-- (b) if the end point of one interval is within epsilon of the start point of 474-- the other 475CREATE PERFETTO MACRO interval_remove_overlap( 476 -- Table or subquery containing interval data. 477 intervals TableOrSubquery, 478 -- Constant expression for a tolerance in testing overlap (usually `0`) 479 epsilon Expr 480) 481RETURNS TableOrSubquery AS 482( 483 -- Algorithm: use intervals_overlap_count to generate a counter track. Pass over 484 -- the counter track from left to right, creating an interval when the counter 485 -- first becomes non-zero and ending an interval when it becomes zero again. 486 WITH 487 _w_prev_count AS ( 488 SELECT 489 ts, 490 value, 491 lag(value, 1, 0) OVER (ORDER BY ts) AS prev_value 492 FROM intervals_overlap_count !($intervals, ts, (dur + $epsilon)) 493 ORDER BY 494 ts ASC 495 ), 496 _end_points AS ( 497 SELECT 498 ts, 499 value 500 FROM _w_prev_count 501 WHERE 502 -- start of merged intervals 503 prev_value = 0 504 -- end of merged intervals 505 OR value = 0 506 ), 507 _together AS ( 508 SELECT 509 ts - iif(value = 0, $epsilon, 0) AS ts, 510 value, 511 lag(ts, 1, NULL) OVER (ORDER BY ts) AS prev_ts 512 FROM _end_points 513 ) 514 SELECT 515 prev_ts AS ts, 516 ts - prev_ts AS dur 517 FROM _together 518 WHERE 519 value = 0 520); 521