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 {DatasetSliceTrack} from './dataset_slice_track'; 16import { 17 ARG_PREFIX, 18 DebugSliceTrackDetailsPanel, 19} from './debug_slice_track_details_panel'; 20import {createPerfettoTable} from '../../trace_processor/sql_utils'; 21import {Trace} from '../../public/trace'; 22import {sqlNameSafe} from '../../base/string_utils'; 23import {Engine} from '../../trace_processor/engine'; 24import {SourceDataset} from '../../trace_processor/dataset'; 25import {LONG, NUM, STR} from '../../trace_processor/query_result'; 26 27export interface QuerySliceTrackArgs { 28 // The trace object used to run queries. 29 readonly trace: Trace; 30 31 // A unique, reproducible ID for this track. 32 readonly uri: string; 33 34 // The query and optional column remapping. 35 readonly data: SqlDataSource; 36 37 // Optional: Which columns should be used for ts, dur, and name. If omitted, 38 // the defaults 'ts', 'dur', and 'name' will be used. 39 readonly columns?: Partial<SliceColumnMapping>; 40 41 // Optional: A list of column names which are displayed in the details panel 42 // when a slice is selected. 43 readonly argColumns?: string[]; 44} 45 46export interface SqlDataSource { 47 // SQL source selecting the necessary data. 48 readonly sqlSource: string; 49 50 // Optional: Rename columns from the query result. 51 // If omitted, original column names from the query are used instead. 52 // The caller is responsible for ensuring that the number of items in this 53 // list matches the number of columns returned by sqlSource. 54 readonly columns?: string[]; 55} 56 57export interface SliceColumnMapping { 58 readonly ts: string; 59 readonly dur: string; 60 readonly name: string; 61} 62 63/** 64 * Creates a slice track based on a query with automatic slice layout. 65 * 66 * The query must provide the following columns: 67 * - ts: INTEGER - The timestamp of the start of each slice. 68 * - dur: INTEGER - The length of each slice. 69 * - name: TEXT - A name to show on each slice, which is also used to derive the 70 * color. 71 * 72 * The column names don't have to be 'ts', 'dur', and 'name' and can be remapped 73 * if convenient using the config.columns parameter. 74 * 75 * An optional set of columns can be provided which will be displayed in the 76 * details panel when a slice is selected. 77 * 78 * The layout (vertical depth) of each slice will be determined automatically to 79 * avoid overlapping slices. 80 */ 81export async function createQuerySliceTrack(args: QuerySliceTrackArgs) { 82 const tableName = `__query_slice_track_${sqlNameSafe(args.uri)}`; 83 await createPerfettoTableForTrack( 84 args.trace.engine, 85 tableName, 86 args.data, 87 args.columns, 88 args.argColumns, 89 ); 90 return new DatasetSliceTrack({ 91 trace: args.trace, 92 uri: args.uri, 93 dataset: new SourceDataset({ 94 schema: { 95 id: NUM, 96 ts: LONG, 97 dur: LONG, 98 name: STR, 99 }, 100 src: tableName, 101 }), 102 detailsPanel: (row) => { 103 return new DebugSliceTrackDetailsPanel(args.trace, tableName, row.id); 104 }, 105 }); 106} 107 108async function createPerfettoTableForTrack( 109 engine: Engine, 110 tableName: string, 111 data: SqlDataSource, 112 columns: Partial<SliceColumnMapping> = {}, 113 argColumns: string[] = [], 114) { 115 const {ts = 'ts', dur = 'dur', name = 'name'} = columns; 116 117 // If the view has clashing names (e.g. "name" coming from joining two 118 // different tables, we will see names like "name_1", "name_2", but they 119 // won't be addressable from the SQL. So we explicitly name them through a 120 // list of columns passed to CTE. 121 const dataColumns = 122 data.columns !== undefined ? `(${data.columns.join(', ')})` : ''; 123 124 const query = ` 125 with data${dataColumns} as ( 126 ${data.sqlSource} 127 ), 128 prepared_data as ( 129 select 130 ${ts} as ts, 131 ifnull(cast(${dur} as int), -1) as dur, 132 printf('%s', ${name}) as name 133 ${argColumns.length > 0 ? ',' : ''} 134 ${argColumns.map((c) => `${c} as ${ARG_PREFIX}${c}`).join(',\n')} 135 from data 136 ) 137 select 138 row_number() over (order by ts) as id, 139 * 140 from prepared_data 141 order by ts 142 `; 143 144 return await createPerfettoTable(engine, tableName, query); 145} 146