blob: f210e048376a28a2dc560957e72060d106de5b98 [file]
// Copyright (C) 2026 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 {generateSqlWithInternalLayout} from '../../components/sql_utils/layout';
import type {SqlTableDefinition} from '../../components/widgets/sql/table/table_description';
import type {Engine} from '../../trace_processor/engine';
import {PerfettoSqlTypes} from '../../trace_processor/perfetto_sql_type';
import {EVENT_LATENCY_TRACK} from './tracks';
export const JANKY_LATENCY_NAME = 'Janky EventLatency';
/**
* Definition of the Perfetto table created by
* {@link createEventLatencyModel}, which underpins
* {@link tracks#EVENT_LATENCY_TRACK}.
*
* Note: The table contains both:
*
* 1. parent slices for entire scroll updates (e.g. 'Janky EventLatency') and
* 2. child slices for the individual stages of a scroll update (e.g.
* 'GenerationToBrowserMain'). Stages can be NESTED (i.e. the parent of
* each stage is either a scroll update or a stage).
*/
export const EVENT_LATENCY_TABLE_DEFINITION: SqlTableDefinition = {
name: EVENT_LATENCY_TRACK.tableName,
columns: [
/**
* Unique ID of the slice. Copied from the original slices in the `slice`
* table. Can be joined with `chrome_event_latencies.id`.
*/
{
column: 'id',
type: {
kind: 'joinid',
source: {table: 'slice', column: 'id'},
},
},
/** Start timestamp of the slice. */
{column: 'ts', type: PerfettoSqlTypes.TIMESTAMP},
/** Duration of the slice. */
{column: 'dur', type: PerfettoSqlTypes.DURATION},
/** Title of the slice. */
{column: 'name', type: PerfettoSqlTypes.STRING},
/** Depth of the slice on the track. */
{column: 'depth', type: PerfettoSqlTypes.INT},
/**
* ID of the scroll update event (aka LatencyInfo.ID). Can be joined with
* `chrome_event_latencies.scroll_update_id`, `chrome_scroll_update_info.id`
* and many other tables in Chrome's tracing stdlib.
*
* In general, multiple rows in this table correspond to a single row in
* `chrome_event_latencies`. One for the scroll update (parent) slice and
* zero or more for the stage (child) slices.
*/
{
column: 'scroll_update_id',
type: {
kind: 'joinid',
source: {table: 'chrome_event_latencies', column: 'scroll_update_id'},
},
},
/**
* ID of the parent scroll update slice or parent stage slice if the row
* corresponds to a stage of a scroll update. NULL if the row corresponds to
* a scroll update.
*/
{
column: 'parent_id',
type: {
kind: 'joinid',
source: {table: 'slice', column: 'id'},
},
},
],
};
export async function createEventLatencyModel(engine: Engine): Promise<void> {
await engine.query(`
INCLUDE PERFETTO MODULE chrome.event_latency;
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals;
CREATE TABLE ${EVENT_LATENCY_TRACK.tableName} AS
WITH
event_latencies AS MATERIALIZED (
${generateSqlWithInternalLayout({
columns: ['id', 'ts', 'dur', 'name', 'scroll_update_id'],
source: 'chrome_event_latencies',
ts: 'ts',
dur: 'dur',
whereClause: `
event_type IN (
'FIRST_GESTURE_SCROLL_UPDATE',
'GESTURE_SCROLL_UPDATE',
'INERTIAL_GESTURE_SCROLL_UPDATE')
AND is_presented`,
})}
),
latency_stages AS (
SELECT
stage.id,
stage.ts,
stage.dur,
stage.name,
stage.depth,
event.id as event_latency_id,
event.depth as event_latency_depth,
event.scroll_update_id,
stage.parent_id
FROM event_latencies event
JOIN descendant_slice(event.id) stage
UNION ALL
SELECT
event.id,
event.ts,
event.dur,
IIF(
id IN (SELECT id FROM chrome_janky_event_latencies_v3),
'${JANKY_LATENCY_NAME}',
name
) as name,
0 as depth,
event.id as event_latency_id,
event.depth as event_latency_depth,
event.scroll_update_id,
NULL as parent_id
FROM event_latencies event
),
-- Event latencies have already had layout computed, but the width of event latency can vary (3 or 4),
-- so we have to compute the max stage depth for each event latency depth to compute offset for each
-- event latency row.
event_latency_height_per_row AS (
SELECT
event_latency_depth,
MAX(depth) AS max_depth
FROM latency_stages
GROUP BY event_latency_depth
),
-- Compute the offset for each event latency depth using max depth info for each depth.
event_latency_layout_offset AS (
SELECT
event_latency_depth,
-- As the sum is exclusive, it will return NULL for the first row — we need to set it to 0 explicitly.
IFNULL(
SUM(max_depth + 1) OVER (
ORDER BY event_latency_depth
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0) as offset
FROM event_latency_height_per_row
)
SELECT
stage.id,
stage.ts,
stage.dur,
stage.name,
stage.depth + (
(
SELECT offset.offset
FROM event_latencies event
JOIN event_latency_layout_offset offset ON event.depth = offset.event_latency_depth
WHERE id = stage.event_latency_id
)
) AS depth,
stage.scroll_update_id,
stage.parent_id
FROM latency_stages stage;`);
}