blob: c8065fbf95388622ae1ec355cd98e47ed93b02a0 [file] [log] [blame]
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../../frontend/tracks/custom_sql_table_slice_track';
import {
ARG_PREFIX,
SqlTableSliceTrackDetailsPanel,
} from './sql_table_slice_track_details_tab';
import {createPerfettoTable} from '../../../trace_processor/sql_utils';
import {Trace} from '../../trace';
import {TrackEventSelection} from '../../selection';
import {sqlNameSafe} from '../../../base/string_utils';
import {Engine} from '../../../trace_processor/engine';
export interface QuerySliceTrackArgs {
// The trace object used to run queries.
readonly trace: Trace;
// A unique, reproducible ID for this track.
readonly uri: string;
// The query and optional column remapping.
readonly data: SqlDataSource;
// Optional: Which columns should be used for ts, dur, and name. If omitted,
// the defaults 'ts', 'dur', and 'name' will be used.
readonly columns?: Partial<SliceColumnMapping>;
// Optional: A list of column names which are displayed in the details panel
// when a slice is selected.
readonly argColumns?: string[];
}
export interface SqlDataSource {
// SQL source selecting the necessary data.
readonly sqlSource: string;
// Optional: Rename columns from the query result.
// If omitted, original column names from the query are used instead.
// The caller is responsible for ensuring that the number of items in this
// list matches the number of columns returned by sqlSource.
readonly columns?: string[];
}
export interface SliceColumnMapping {
readonly ts: string;
readonly dur: string;
readonly name: string;
}
/**
* Creates a slice track based on a query with automatic slice layout.
*
* The query must provide the following columns:
* - ts: INTEGER - The timestamp of the start of each slice.
* - dur: INTEGER - The length of each slice.
* - name: TEXT - A name to show on each slice, which is also used to derive the
* color.
*
* The column names don't have to be 'ts', 'dur', and 'name' and can be remapped
* if convenient using the config.columns parameter.
*
* An optional set of columns can be provided which will be displayed in the
* details panel when a slice is selected.
*
* The layout (vertical depth) of each slice will be determined automatically to
* avoid overlapping slices.
*/
export async function createQuerySliceTrack(args: QuerySliceTrackArgs) {
const tableName = `__query_slice_track_${sqlNameSafe(args.uri)}`;
await createPerfettoTableForTrack(
args.trace.engine,
tableName,
args.data,
args.columns,
args.argColumns,
);
return new SqlTableSliceTrack(args.trace, args.uri, tableName);
}
async function createPerfettoTableForTrack(
engine: Engine,
tableName: string,
data: SqlDataSource,
columns: Partial<SliceColumnMapping> = {},
argColumns: string[] = [],
) {
const {ts = 'ts', dur = 'dur', name = 'name'} = columns;
// If the view has clashing names (e.g. "name" coming from joining two
// different tables, we will see names like "name_1", "name_2", but they
// won't be addressable from the SQL. So we explicitly name them through a
// list of columns passed to CTE.
const dataColumns =
data.columns !== undefined ? `(${data.columns.join(', ')})` : '';
const query = `
with data${dataColumns} as (
${data.sqlSource}
),
prepared_data as (
select
${ts} as ts,
ifnull(cast(${dur} as int), -1) as dur,
printf('%s', ${name}) as name
${argColumns.length > 0 ? ',' : ''}
${argColumns.map((c) => `${c} as ${ARG_PREFIX}${c}`).join(',\n')}
from data
)
select
row_number() over (order by ts) as id,
*
from prepared_data
order by ts
`;
return await createPerfettoTable(engine, tableName, query);
}
class SqlTableSliceTrack extends CustomSqlTableSliceTrack {
constructor(
trace: Trace,
uri: string,
private readonly sqlTableName: string,
) {
super(trace, uri);
}
override async getSqlDataSource(): Promise<CustomSqlTableDefConfig> {
return {
sqlTableName: this.sqlTableName,
};
}
override detailsPanel({eventId}: TrackEventSelection) {
return new SqlTableSliceTrackDetailsPanel(
this.trace,
this.sqlTableName,
eventId,
);
}
}