• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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