• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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