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