1// Copyright (C) 2023 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 {v4 as uuidv4} from 'uuid'; 16 17import {AsyncDisposable, AsyncDisposableStack} from '../../base/disposable'; 18import {Actions} from '../../common/actions'; 19import {generateSqlWithInternalLayout} from '../../common/internal_layout_utils'; 20import {LegacySelection} from '../../common/state'; 21import {OnSliceClickArgs} from '../base_slice_track'; 22import {GenericSliceDetailsTabConfigBase} from '../generic_slice_details_tab'; 23import {globals} from '../globals'; 24import {NamedSliceTrack, NamedSliceTrackTypes} from '../named_slice_track'; 25import {NewTrackArgs} from '../track'; 26import {createView} from '../../trace_processor/sql_utils'; 27 28export interface CustomSqlImportConfig { 29 modules: string[]; 30} 31 32export interface CustomSqlTableDefConfig { 33 // Table name 34 sqlTableName: string; 35 // Table columns 36 columns?: string[]; 37 whereClause?: string; 38 disposable?: AsyncDisposable; 39} 40 41export interface CustomSqlDetailsPanelConfig { 42 // Type of details panel to create 43 kind: string; 44 // Config for the details panel 45 config: GenericSliceDetailsTabConfigBase; 46} 47 48export abstract class CustomSqlTableSliceTrack< 49 T extends NamedSliceTrackTypes, 50> extends NamedSliceTrack<T> { 51 protected readonly tableName; 52 53 constructor(args: NewTrackArgs) { 54 super(args); 55 this.tableName = `customsqltableslicetrack_${uuidv4() 56 .split('-') 57 .join('_')}`; 58 } 59 60 abstract getSqlDataSource(): 61 | CustomSqlTableDefConfig 62 | Promise<CustomSqlTableDefConfig>; 63 64 // Override by subclasses. 65 abstract getDetailsPanel( 66 args: OnSliceClickArgs<NamedSliceTrackTypes['slice']>, 67 ): CustomSqlDetailsPanelConfig; 68 69 getSqlImports(): CustomSqlImportConfig { 70 return { 71 modules: [] as string[], 72 }; 73 } 74 75 async onInit() { 76 await this.loadImports(); 77 const config = await Promise.resolve(this.getSqlDataSource()); 78 let columns = ['*']; 79 if (config.columns !== undefined) { 80 columns = config.columns; 81 } 82 const trash = new AsyncDisposableStack(); 83 config.disposable && trash.use(config.disposable); 84 trash.use( 85 await createView( 86 this.engine, 87 this.tableName, 88 generateSqlWithInternalLayout({ 89 columns: columns, 90 sourceTable: config.sqlTableName, 91 ts: 'ts', 92 dur: 'dur', 93 whereClause: config.whereClause, 94 }), 95 ), 96 ); 97 return trash; 98 } 99 100 getSqlSource(): string { 101 return `SELECT * FROM ${this.tableName}`; 102 } 103 104 isSelectionHandled(selection: LegacySelection) { 105 if (selection.kind !== 'GENERIC_SLICE') { 106 return false; 107 } 108 return selection.trackKey === this.trackKey; 109 } 110 111 onSliceClick(args: OnSliceClickArgs<NamedSliceTrackTypes['slice']>) { 112 if (this.getDetailsPanel(args) === undefined) { 113 return; 114 } 115 116 const detailsPanelConfig = this.getDetailsPanel(args); 117 globals.makeSelection( 118 Actions.selectGenericSlice({ 119 id: args.slice.id, 120 sqlTableName: this.tableName, 121 start: args.slice.ts, 122 duration: args.slice.dur, 123 trackKey: this.trackKey, 124 detailsPanelConfig: { 125 kind: detailsPanelConfig.kind, 126 config: detailsPanelConfig.config, 127 }, 128 }), 129 ); 130 } 131 132 async loadImports() { 133 for (const importModule of this.getSqlImports().modules) { 134 await this.engine.query(`INCLUDE PERFETTO MODULE ${importModule};`); 135 } 136 } 137} 138