tp: Stub of frames stdlib support

Implements basic frames timeline (Choreographer#doFrame, DrawFrames, actual and expected frame timeline) support in a nicely aggregated tables and implements basic per frame metrics as defined by go/android-performance-metrics-glossary docs.

Change-Id: I358dacdcb967a72538c351f110d2ed2392edc73e
diff --git a/Android.bp b/Android.bp
index 722da60..7f9dcb1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12319,6 +12319,8 @@
         "src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/freezer.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/input.sql",
diff --git a/BUILD b/BUILD
index d678720..3b56b52 100644
--- a/BUILD
+++ b/BUILD
@@ -2366,6 +2366,15 @@
     ],
 )
 
+# GN target: //src/trace_processor/perfetto_sql/stdlib/android/frames:frames
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_android_frames_frames",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql",
+    ],
+)
+
 # GN target: //src/trace_processor/perfetto_sql/stdlib/android/startup:startup
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_stdlib_android_startup_startup",
@@ -2576,6 +2585,7 @@
     name = "src_trace_processor_perfetto_sql_stdlib_stdlib",
     deps = [
         ":src_trace_processor_perfetto_sql_stdlib_android_android",
+        ":src_trace_processor_perfetto_sql_stdlib_android_frames_frames",
         ":src_trace_processor_perfetto_sql_stdlib_android_startup_startup",
         ":src_trace_processor_perfetto_sql_stdlib_chrome_chrome_sql",
         ":src_trace_processor_perfetto_sql_stdlib_common_common",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
index 87f8dec..6eda0bc 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
@@ -15,7 +15,10 @@
 import("../../../../../gn/perfetto_sql.gni")
 
 perfetto_sql_source_set("android") {
-  deps = [ "startup" ]
+  deps = [
+    "frames",
+    "startup",
+  ]
   sources = [
     "anrs.sql",
     "app_process_starts.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn
new file mode 100644
index 0000000..bc434d5
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("frames") {
+  sources = [
+    "per_frame_metrics.sql",
+    "timeline.sql",
+  ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql b/src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql
new file mode 100644
index 0000000..cda1361
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql
@@ -0,0 +1,197 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE time.conversion;
+INCLUDE PERFETTO MODULE android.frames.timeline;
+
+-- The amount by which each frame missed of hit its deadline. Positive if the
+-- deadline was not missed. Frames are considered janky if `overrun` is
+-- negative.
+-- Calculated as the difference between the end of the
+-- `expected_frame_timeline_slice` and `actual_frame_timeline_slice` for the
+-- frame.
+-- Availability: from S (API 31).
+-- For Googlers: more details in go/android-performance-metrics-glossary.
+CREATE PERFETTO TABLE android_frames_overrun(
+    -- Frame id.
+    frame_id INT,
+    -- Difference between `expected` and `actual` frame ends. Positive if frame
+    -- didn't miss deadline.
+    overrun INT
+) AS
+SELECT
+    frame_id,
+    (exp_slice.ts + exp_slice.dur) - (act_slice.ts + act_slice.dur) AS overrun
+FROM _distinct_from_actual_timeline_slice act
+JOIN _distinct_from_expected_timeline_slice exp USING (frame_id)
+JOIN slice act_slice ON (act.id = act_slice.id)
+JOIN slice exp_slice ON (exp.id = exp_slice.id);
+
+-- How much time did the frame's Choreographer callbacks take.
+CREATE PERFETTO TABLE android_frames_ui_time(
+    -- Frame id
+    frame_id INT,
+    -- UI time duration
+    ui_time INT
+) AS
+SELECT
+    frame_id,
+    dur AS ui_time
+FROM android_frames_choreographer_do_frame f
+JOIN slice USING (id);
+
+-- App Vsync delay for a frame. The time between the VSYNC-app signal and the
+-- start of Choreographer work.
+-- Calculated as time difference between the actual frame start (from
+-- `actual_frame_timeline_slice`) and start of the `Choreographer#doFrame`
+-- slice.
+-- NOTE: Sometimes because of data losses `app_vsync_delay` can be negative.
+-- The frames where it happens are filtered out.
+-- For Googlers: more details in go/android-performance-metrics-glossary.
+CREATE PERFETTO TABLE android_app_vsync_delay_per_frame(
+    -- Frame id
+    frame_id INT,
+    -- App VSYNC delay.
+    app_vsync_delay INT
+) AS
+-- As there can be multiple `DrawFrame` slices, the `frames_surface_slices`
+-- table contains multiple rows for the same `frame_id` which only differ on
+-- `draw_frame_id`. As we don't care about `draw_frame_id` we can just collapse
+-- them.
+WITH distinct_frames AS (
+    SELECT
+        frame_id,
+        do_frame_id,
+        actual_frame_timeline_id
+    FROM android_frames
+    GROUP BY 1
+)
+SELECT
+    frame_id,
+    act.ts - do_frame.ts AS app_vsync_delay
+FROM distinct_frames f
+JOIN slice act ON (f.actual_frame_timeline_id = act.id)
+JOIN slice do_frame ON (f.do_frame_id = do_frame.id)
+WHERE act.ts >= do_frame.ts;
+
+-- How much time did the frame take across the UI Thread + RenderThread.
+-- Calculated as sum of `app VSYNC delay` `Choreographer#doFrame` slice
+-- duration and summed durations of all `DrawFrame` slices associated with this
+-- frame.
+-- Availability: from N (API 24).
+-- For Googlers: more details in go/android-performance-metrics-glossary.
+CREATE PERFETTO TABLE android_cpu_time_per_frame(
+    -- Frame id
+    frame_id INT,
+    -- Difference between actual timeline of the frame and
+    -- `Choreographer#doFrame`. See `android_app_vsync_delay_per_frame` table for more details.
+    app_vsync_delay INT,
+    -- Duration of `Choreographer#doFrame` slice.
+    do_frame_dur INT,
+    -- Duration of `DrawFrame` slice. Summed duration of all `DrawFrame`
+    -- slices, if more than one. See `android_frames_draw_frame` for more details.
+    draw_frame_dur INT,
+    -- CPU time across the UI Thread + RenderThread.
+    cpu_time INT
+) AS
+WITH all_draw_frames AS (
+SELECT
+    frame_id,
+    SUM(dur) as draw_frame_dur
+FROM android_frames_draw_frame
+JOIN slice USING (id)
+GROUP BY frame_id
+),
+distinct_frames AS (
+    SELECT
+        frame_id,
+        do_frame_id,
+        actual_frame_timeline_id
+    FROM android_frames
+    GROUP BY 1
+)
+SELECT
+    frame_id,
+    app_vsync_delay,
+    do_frame.dur AS do_frame_dur,
+    draw_frame_dur,
+    app_vsync_delay + do_frame.dur + draw_frame_dur AS cpu_time
+FROM android_app_vsync_delay_per_frame
+JOIN all_draw_frames USING (frame_id)
+JOIN distinct_frames f USING (frame_id)
+JOIN slice do_frame ON (f.do_frame_id = do_frame.id);
+
+-- CPU time of frames which don't have `android_cpu_time_per_frame` available.
+-- Calculated as UI time of the frame + 5ms.
+-- For Googlers: more details in go/android-performance-metrics-glossary.
+CREATE PERFETTO TABLE _cpu_time_per_frame_fallback(
+    -- Frame id.
+    frame_id INT,
+    -- Estimated cpu time.
+    estimated_cpu_time INT
+) AS
+SELECT
+    frame_id,
+    ui_time + time_from_ms(5) AS estimated_cpu_time
+FROM android_frames_ui_time;
+
+CREATE PERFETTO TABLE _estimated_cpu_time_per_frame(
+    frame_id INT,
+    cpu_time INT
+) AS
+SELECT
+    frame_id,
+    IIF(r.cpu_time IS NULL, f.estimated_cpu_time, r.cpu_time) AS cpu_time
+FROM _cpu_time_per_frame_fallback f
+LEFT JOIN android_cpu_time_per_frame r USING (frame_id);
+
+-- Aggregated stats of the frame.
+--
+-- For Googlers: more details in go/android-performance-metrics-glossary.
+CREATE PERFETTO TABLE android_frame_stats(
+    -- Frame id.
+    frame_id INT,
+    -- The amount by which each frame missed of hit its deadline. See
+    -- `android_frames_overrun` for details.
+    overrun INT,
+    -- How much time did the frame take across the UI Thread + RenderThread.
+    cpu_time INT,
+    -- How much time did the frame's Choreographer callbacks take.
+    ui_time INT,
+    -- Was frame janky.
+    was_jank BOOL,
+    -- CPU time of the frame took over 20ms.
+    was_slow_frame BOOL,
+    -- CPU time of the frame took over 50ms.
+    was_big_jank BOOL,
+    -- CPU time of the frame took over 200ms.
+    was_huge_jank BOOL
+) AS
+SELECT
+    frame_id,
+    overrun,
+    cpu_time,
+    ui_time,
+    IIF(overrun < 0, 1, NULL) AS was_jank,
+    IIF(cpu_time > time_from_ms(20), 1, NULL) AS was_slow_frame,
+    IIF(cpu_time > time_from_ms(50), 1, NULL) AS was_big_jank,
+    IIF(cpu_time > time_from_ms(200), 1, NULL) AS was_huge_jank
+FROM android_frames_overrun
+JOIN android_frames_ui_time USING (frame_id)
+-- Because some frames might not have CPU time calculated properly (data loss
+-- or too old API), we will use fallback cpu time from
+-- `_cpu_time_per_frame_fallback`.
+JOIN _estimated_cpu_time_per_frame USING (frame_id);
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
new file mode 100644
index 0000000..e6963b2
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
@@ -0,0 +1,104 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+-- Parses the slice name to fetch `frame_id` from `slice` table.
+-- Use with caution. Slice names are a flaky source of ids and the resulting
+-- table might require some further operations.
+CREATE PERFETTO FUNCTION _get_frame_table_with_id(
+    -- String just before id.
+    glob_str STRING
+) RETURNS TABLE (
+    -- `slice.id` of the frame slice.
+    id INT,
+    -- Parsed frame id.
+    frame_id INT
+) AS
+WITH all_found AS (
+    SELECT
+        id,
+        cast_int!(STR_SPLIT(name, ' ', 1)) AS frame_id
+    FROM slice
+    WHERE name GLOB $glob_str
+)
+SELECT *
+FROM all_found
+-- Casting string to int returns 0 if the string can't be cast.
+WHERE frame_id != 0;
+
+-- All of the `Choreographer#doFrame` slices with their frame id.
+CREATE PERFETTO TABLE android_frames_choreographer_do_frame(
+    -- `slice.id`
+    id INT,
+    -- Frame id
+    frame_id INT
+) AS
+SELECT * FROM _get_frame_table_with_id('Choreographer#doFrame*');
+
+-- All of the `DrawFrame` slices with their frame id.
+-- There might be multiple DrawFrames slices for a single vsync (frame id).
+-- This happens when we are drawing multiple layers (e.g. status bar and
+-- notifications).
+CREATE PERFETTO TABLE android_frames_draw_frame(
+    -- `slice.id`
+    id INT,
+    -- Frame id
+    frame_id INT
+) AS
+SELECT * FROM _get_frame_table_with_id('DrawFrame*');
+
+-- `actual_frame_timeline_slice` returns the same slice on different tracks.
+-- We are getting the first slice with one frame id.
+CREATE PERFETTO TABLE _distinct_from_actual_timeline_slice AS
+SELECT
+    id,
+    cast_int!(name) AS frame_id
+FROM actual_frame_timeline_slice
+GROUP BY 2;
+
+-- `expected_frame_timeline_slice` returns the same slice on different tracks.
+-- We are getting the first slice with one frame id.
+CREATE PERFETTO TABLE _distinct_from_expected_timeline_slice AS
+SELECT
+    id,
+    cast_int!(name) AS frame_id
+FROM expected_frame_timeline_slice
+GROUP BY 2;
+
+-- All slices related to one frame. Aggregates `Choreographer#doFrame`,
+-- `DrawFrame`, `actual_frame_timeline_slice` and
+-- `expected_frame_timeline_slice` slices.
+CREATE PERFETTO TABLE android_frames(
+    -- Frame id.
+    frame_id INT,
+    -- `slice.id` of "Choreographer#doFrame" slice.
+    do_frame_id INT,
+    -- `slice.id` of "DrawFrame" slice.
+    draw_frame_id INT,
+    -- `slice.id` from `actual_frame_timeline_slice`
+    actual_frame_timeline_id INT,
+    -- `slice.id` from `expected_frame_timeline_slice`
+    expected_frame_timeline_id INT
+) AS
+SELECT
+    frame_id,
+    do_frame.id AS do_frame_id,
+    draw_frame.id AS draw_frame_id,
+    act.id AS actual_frame_timeline_id,
+    exp.id AS expected_frame_timeline_id
+FROM android_frames_choreographer_do_frame do_frame
+JOIN android_frames_draw_frame draw_frame USING (frame_id)
+JOIN _distinct_from_actual_timeline_slice act USING (frame_id)
+JOIN _distinct_from_expected_timeline_slice exp USING (frame_id)
+ORDER BY frame_id;
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index c3cde34..36872ed 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -46,21 +46,22 @@
 from diff_tests.metrics.startup.tests_lock_contention import StartupLockContention
 from diff_tests.metrics.startup.tests_metrics import StartupMetrics
 from diff_tests.metrics.webview.tests import WebView
+from diff_tests.parser.android_fs.tests import AndroidFs
 from diff_tests.parser.android.tests import AndroidParser
 from diff_tests.parser.android.tests_bugreport import AndroidBugreport
 from diff_tests.parser.android.tests_games import AndroidGames
+from diff_tests.parser.android.tests_protolog import ProtoLog
+from diff_tests.parser.android.tests_shell_transitions import ShellTransitions
 from diff_tests.parser.android.tests_surfaceflinger_layers import SurfaceFlingerLayers
 from diff_tests.parser.android.tests_surfaceflinger_transactions import SurfaceFlingerTransactions
-from diff_tests.parser.android.tests_shell_transitions import ShellTransitions
-from diff_tests.parser.android.tests_protolog import ProtoLog
-from diff_tests.parser.android_fs.tests import AndroidFs
 from diff_tests.parser.atrace.tests import Atrace
 from diff_tests.parser.atrace.tests_error_handling import AtraceErrorHandling
 from diff_tests.parser.chrome.tests import ChromeParser
-from diff_tests.parser.chrome.tests_v8 import ChromeV8Parser
 from diff_tests.parser.chrome.tests_memory_snapshots import ChromeMemorySnapshots
+from diff_tests.parser.chrome.tests_v8 import ChromeV8Parser
 from diff_tests.parser.cros.tests import Cros
 from diff_tests.parser.fs.tests import Fs
+from diff_tests.parser.ftrace.ftrace_crop_tests import FtraceCrop
 from diff_tests.parser.fuchsia.tests import Fuchsia
 from diff_tests.parser.graphics.tests import GraphicsParser
 from diff_tests.parser.graphics.tests_drm_related_ftrace_events import GraphicsDrmRelatedFtraceEvents
@@ -91,22 +92,23 @@
 from diff_tests.parser.translated_args.tests import TranslatedArgs
 from diff_tests.parser.ufs.tests import Ufs
 from diff_tests.stdlib.android.tests import AndroidStdlib
+from diff_tests.stdlib.android.frames_tests import Frames
 from diff_tests.stdlib.chrome.chrome_stdlib_testsuites import CHROME_STDLIB_TESTSUITES
 from diff_tests.stdlib.common.tests import StdlibCommon
 from diff_tests.stdlib.common.tests import StdlibCommon
 from diff_tests.stdlib.counters.tests import StdlibCounterIntervals
 from diff_tests.stdlib.dynamic_tables.tests import DynamicTables
-from diff_tests.stdlib.intervals.tests import StdlibIntervals
-from diff_tests.stdlib.intervals.intersect_tests import IntervalsIntersect
 from diff_tests.stdlib.graphs.dominator_tree_tests import DominatorTree
 from diff_tests.stdlib.graphs.search_tests import GraphSearchTests
+from diff_tests.stdlib.intervals.intersect_tests import IntervalsIntersect
+from diff_tests.stdlib.intervals.tests import StdlibIntervals
 from diff_tests.stdlib.linux.tests import LinuxStdlib
 from diff_tests.stdlib.memory.heap_graph_dominator_tree_tests import HeapGraphDominatorTree
 from diff_tests.stdlib.pkvm.tests import Pkvm
 from diff_tests.stdlib.prelude.math_functions_tests import PreludeMathFunctions
 from diff_tests.stdlib.prelude.pprof_functions_tests import PreludePprofFunctions
-from diff_tests.stdlib.prelude.window_functions_tests import PreludeWindowFunctions
 from diff_tests.stdlib.prelude.slices_tests import PreludeSlices
+from diff_tests.stdlib.prelude.window_functions_tests import PreludeWindowFunctions
 from diff_tests.stdlib.sched.tests import StdlibSched
 from diff_tests.stdlib.slices.tests import Slices
 from diff_tests.stdlib.span_join.tests_left_join import SpanJoinLeftJoin
@@ -125,7 +127,6 @@
 from diff_tests.tables.tests import Tables
 from diff_tests.tables.tests_counters import TablesCounters
 from diff_tests.tables.tests_sched import TablesSched
-from diff_tests.parser.ftrace.ftrace_crop_tests import FtraceCrop
 
 sys.path.pop()
 
@@ -242,6 +243,7 @@
   stdlib_tests = [
       *AndroidStdlib(index_path, 'stdlib/android', 'AndroidStdlib').fetch(),
       *DominatorTree(index_path, 'stdlib/graphs', 'DominatorTree').fetch(),
+      *Frames(index_path, 'stdlib/android', 'Frames').fetch(),
       *GraphSearchTests(index_path, 'stdlib/graphs',
                         'GraphSearchTests').fetch(),
       *StdlibCounterIntervals(index_path, 'stdlib/counters',
diff --git a/test/trace_processor/diff_tests/stdlib/android/frames_tests.py b/test/trace_processor/diff_tests/stdlib/android/frames_tests.py
new file mode 100644
index 0000000..687afde
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/android/frames_tests.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Path
+from python.generators.diff_tests.testing import Csv, TextProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class Frames(TestSuite):
+
+  def test_android_frames_choreographer_do_frame(self):
+    return DiffTestBlueprint(
+        trace=Path('../../metrics/graphics/android_jank_cuj.py'),
+        query="""
+        INCLUDE PERFETTO MODULE android.frames.timeline;
+
+        SELECT * FROM android_frames_choreographer_do_frame;
+        """,
+        out=Csv("""
+        "id","frame_id"
+        2,10
+        15,20
+        22,30
+        35,40
+        46,60
+        55,90
+        63,100
+        73,110
+        79,120
+        87,130
+        93,140
+        99,145
+        102,150
+        108,160
+        140,1000
+        """))
+
+  def test_android_frames_draw_frame(self):
+    return DiffTestBlueprint(
+        trace=Path('../../metrics/graphics/android_jank_cuj.py'),
+        query="""
+        INCLUDE PERFETTO MODULE android.frames.timeline;
+
+        SELECT * FROM android_frames_draw_frame;
+        """,
+        out=Csv("""
+      "id","frame_id"
+      8,10
+      16,20
+      23,30
+      41,40
+      50,60
+      57,90
+      60,90
+      66,100
+      69,100
+      74,110
+      80,120
+      89,130
+      95,140
+      100,145
+      105,150
+      109,160
+      146,1000
+        """))
+
+  def test_android_frames(self):
+    return DiffTestBlueprint(
+        trace=Path('../../metrics/graphics/android_jank_cuj.py'),
+        query="""
+        INCLUDE PERFETTO MODULE android.frames.timeline;
+
+        SELECT * FROM android_frames;
+        """,
+        out=Csv("""
+        "frame_id","do_frame_id","draw_frame_id","actual_frame_timeline_id","expected_frame_timeline_id"
+        10,2,8,1,0
+        20,15,16,12,11
+        30,22,23,21,20
+        40,35,41,37,36
+        60,46,50,48,47
+        90,55,57,54,53
+        90,55,60,54,53
+        100,63,66,65,64
+        100,63,69,65,64
+        110,73,74,71,70
+        120,79,80,78,77
+        130,87,89,85,84
+        140,93,95,94,91
+        145,99,100,98,97
+        150,102,105,104,103
+        160,108,109,132,107
+        1000,140,146,138,137
+        """))
+
+  def test_android_frames_overrun(self):
+    return DiffTestBlueprint(
+        trace=Path('../../metrics/graphics/android_jank_cuj.py'),
+        query="""
+        INCLUDE PERFETTO MODULE android.frames.per_frame_metrics;
+
+        SELECT * FROM android_frames_overrun;
+        """,
+        out=Csv("""
+        "frame_id","overrun"
+        10,0
+        20,-8000000
+        30,-5000000
+        40,-20000000
+        60,10000000
+        90,-3000000
+        100,-2000000
+        110,-41000000
+        120,-41000000
+        130,18000000
+        140,-5600000
+        145,0
+        150,5000000
+        160,-266000000
+        190,0
+        200,-16000000
+        1000,-480000000
+        """))
+
+  def test_android_app_vsync_delay_per_frame(self):
+    return DiffTestBlueprint(
+        trace=Path('../../metrics/graphics/android_jank_cuj.py'),
+        query="""
+        INCLUDE PERFETTO MODULE android.frames.per_frame_metrics;
+
+        SELECT * FROM android_app_vsync_delay_per_frame;
+        """,
+        out=Csv("""
+        "frame_id","app_vsync_delay"
+        10,0
+        30,0
+        40,0
+        60,0
+        90,0
+        100,0
+        110,0
+        120,0
+        140,100000
+        150,500000
+        160,270000000
+        1000,0
+        """))
+
+  def test_android_cpu_time_per_frame(self):
+    return DiffTestBlueprint(
+        trace=Path('../../metrics/graphics/android_jank_cuj.py'),
+        query="""
+        INCLUDE PERFETTO MODULE android.frames.per_frame_metrics;
+
+        SELECT * FROM android_cpu_time_per_frame;
+        """,
+        out=Csv("""
+        "frame_id","app_vsync_delay","do_frame_dur","draw_frame_dur","cpu_time"
+        10,0,5000000,1000000,6000000
+        30,0,3000000,19000000,22000000
+        40,0,13000000,7000000,20000000
+        60,0,10000000,9000000,19000000
+        90,0,15000000,8000000,23000000
+        100,0,15000000,8000000,23000000
+        110,0,15000000,2000000,17000000
+        120,0,15000000,2000000,17000000
+        140,100000,1500000,17000000,18600000
+        150,500000,2000000,13800000,16300000
+        160,270000000,2000000,1000000,273000000
+        1000,0,100000000,150000000,250000000
+        """))
+
+  def test_android_frame_stats(self):
+    return DiffTestBlueprint(
+        trace=Path('../../metrics/graphics/android_jank_cuj.py'),
+        query="""
+        INCLUDE PERFETTO MODULE android.frames.per_frame_metrics;
+
+        SELECT * FROM android_frame_stats;
+        """,
+        out=Csv("""
+        "frame_id","overrun","cpu_time","ui_time","was_jank","was_slow_frame","was_big_jank","was_huge_jank"
+        10,0,6000000,5000000,"[NULL]","[NULL]","[NULL]","[NULL]"
+        20,-8000000,8000000,3000000,1,"[NULL]","[NULL]","[NULL]"
+        30,-5000000,22000000,3000000,1,1,"[NULL]","[NULL]"
+        40,-20000000,20000000,13000000,1,"[NULL]","[NULL]","[NULL]"
+        60,10000000,19000000,10000000,"[NULL]","[NULL]","[NULL]","[NULL]"
+        90,-3000000,23000000,15000000,1,1,"[NULL]","[NULL]"
+        100,-2000000,23000000,15000000,1,1,"[NULL]","[NULL]"
+        110,-41000000,17000000,15000000,1,"[NULL]","[NULL]","[NULL]"
+        120,-41000000,17000000,15000000,1,"[NULL]","[NULL]","[NULL]"
+        130,18000000,10000000,5000000,"[NULL]","[NULL]","[NULL]","[NULL]"
+        140,-5600000,18600000,1500000,1,"[NULL]","[NULL]","[NULL]"
+        145,0,25000000,20000000,"[NULL]",1,"[NULL]","[NULL]"
+        150,5000000,16300000,2000000,"[NULL]","[NULL]","[NULL]","[NULL]"
+        160,-266000000,273000000,2000000,1,1,1,1
+        1000,-480000000,250000000,100000000,1,1,1,1
+        """))