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