xref: /aosp_15_r20/external/perfetto/ui/src/components/tracks/query_slice_track.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2024 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {
16  CustomSqlTableDefConfig,
17  CustomSqlTableSliceTrack,
18} from './custom_sql_table_slice_track';
19import {
20  ARG_PREFIX,
21  SqlTableSliceTrackDetailsPanel,
22} from './sql_table_slice_track_details_tab';
23import {createPerfettoTable} from '../../trace_processor/sql_utils';
24import {Trace} from '../../public/trace';
25import {TrackEventSelection} from '../../public/selection';
26import {sqlNameSafe} from '../../base/string_utils';
27import {Engine} from '../../trace_processor/engine';
28
29export interface QuerySliceTrackArgs {
30  // The trace object used to run queries.
31  readonly trace: Trace;
32
33  // A unique, reproducible ID for this track.
34  readonly uri: string;
35
36  // The query and optional column remapping.
37  readonly data: SqlDataSource;
38
39  // Optional: Which columns should be used for ts, dur, and name. If omitted,
40  // the defaults 'ts', 'dur', and 'name' will be used.
41  readonly columns?: Partial<SliceColumnMapping>;
42
43  // Optional: A list of column names which are displayed in the details panel
44  // when a slice is selected.
45  readonly argColumns?: string[];
46}
47
48export interface SqlDataSource {
49  // SQL source selecting the necessary data.
50  readonly sqlSource: string;
51
52  // Optional: Rename columns from the query result.
53  // If omitted, original column names from the query are used instead.
54  // The caller is responsible for ensuring that the number of items in this
55  // list matches the number of columns returned by sqlSource.
56  readonly columns?: string[];
57}
58
59export interface SliceColumnMapping {
60  readonly ts: string;
61  readonly dur: string;
62  readonly name: string;
63}
64
65/**
66 * Creates a slice track based on a query with automatic slice layout.
67 *
68 * The query must provide the following columns:
69 * - ts: INTEGER - The timestamp of the start of each slice.
70 * - dur: INTEGER - The length of each slice.
71 * - name: TEXT - A name to show on each slice, which is also used to derive the
72 *   color.
73 *
74 * The column names don't have to be 'ts', 'dur', and 'name' and can be remapped
75 * if convenient using the config.columns parameter.
76 *
77 * An optional set of columns can be provided which will be displayed in the
78 * details panel when a slice is selected.
79 *
80 * The layout (vertical depth) of each slice will be determined automatically to
81 * avoid overlapping slices.
82 */
83export async function createQuerySliceTrack(args: QuerySliceTrackArgs) {
84  const tableName = `__query_slice_track_${sqlNameSafe(args.uri)}`;
85  await createPerfettoTableForTrack(
86    args.trace.engine,
87    tableName,
88    args.data,
89    args.columns,
90    args.argColumns,
91  );
92  return new SqlTableSliceTrack(args.trace, args.uri, tableName);
93}
94
95async function createPerfettoTableForTrack(
96  engine: Engine,
97  tableName: string,
98  data: SqlDataSource,
99  columns: Partial<SliceColumnMapping> = {},
100  argColumns: string[] = [],
101) {
102  const {ts = 'ts', dur = 'dur', name = 'name'} = columns;
103
104  // If the view has clashing names (e.g. "name" coming from joining two
105  // different tables, we will see names like "name_1", "name_2", but they
106  // won't be addressable from the SQL. So we explicitly name them through a
107  // list of columns passed to CTE.
108  const dataColumns =
109    data.columns !== undefined ? `(${data.columns.join(', ')})` : '';
110
111  const query = `
112    with data${dataColumns} as (
113      ${data.sqlSource}
114    ),
115    prepared_data as (
116      select
117        ${ts} as ts,
118        ifnull(cast(${dur} as int), -1) as dur,
119        printf('%s', ${name}) as name
120        ${argColumns.length > 0 ? ',' : ''}
121        ${argColumns.map((c) => `${c} as ${ARG_PREFIX}${c}`).join(',\n')}
122      from data
123    )
124    select
125      row_number() over (order by ts) as id,
126      *
127    from prepared_data
128    order by ts
129  `;
130
131  return await createPerfettoTable(engine, tableName, query);
132}
133
134class SqlTableSliceTrack extends CustomSqlTableSliceTrack {
135  constructor(
136    trace: Trace,
137    uri: string,
138    private readonly sqlTableName: string,
139  ) {
140    super(trace, uri);
141  }
142
143  override async getSqlDataSource(): Promise<CustomSqlTableDefConfig> {
144    return {
145      sqlTableName: this.sqlTableName,
146    };
147  }
148
149  override detailsPanel({eventId}: TrackEventSelection) {
150    return new SqlTableSliceTrackDetailsPanel(
151      this.trace,
152      this.sqlTableName,
153      eventId,
154    );
155  }
156}
157