• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {sqliteString} from '../base/string_utils';
18import {Area, PivotTableQuery, PivotTableState} from '../common/state';
19import {getSelectedTrackKeys} from '../controller/aggregation/slice_aggregation_controller';
20
21import {Aggregation, TableColumn} from './pivot_table_types';
22import {SqlTables} from './sql_table/well_known_tables';
23
24export interface Table {
25  name: string;
26  displayName: string;
27  columns: string[];
28}
29
30export const sliceTable = {
31  name: SqlTables.slice.name,
32  displayName: 'slice',
33  columns: [
34    'type',
35    'ts',
36    'dur',
37    'category',
38    'name',
39    'depth',
40    'pid',
41    'process_name',
42    'tid',
43    'thread_name',
44  ],
45};
46
47// Columns of `slice` table available for aggregation.
48export const sliceAggregationColumns = [
49  'ts',
50  'dur',
51  'depth',
52  'thread_ts',
53  'thread_dur',
54  'thread_instruction_count',
55  'thread_instruction_delta',
56];
57
58// List of available tables to query, used to populate selectors of pivot
59// columns in the UI.
60export const tables: Table[] = [sliceTable];
61
62// Queried "table column" is either:
63// 1. A real one, represented as object with table and column name.
64// 2. Pseudo-column 'count' that's rendered as '1' in SQL to use in queries like
65// `select sum(1), name from slice group by name`.
66
67export interface RegularColumn {
68  kind: 'regular';
69  table: string;
70  column: string;
71}
72
73export interface ArgumentColumn {
74  kind: 'argument';
75  argument: string;
76}
77
78// Exception thrown by query generator in case incoming parameters are not
79// suitable in order to build a correct query; these are caught by the UI and
80// displayed to the user.
81export class QueryGeneratorError extends Error {}
82
83// Internal column name for different rollover levels of aggregate columns.
84function aggregationAlias(aggregationIndex: number): string {
85  return `agg_${aggregationIndex}`;
86}
87
88export function areaFilters(area: Area): string[] {
89  return [
90    `ts + dur > ${area.start}`,
91    `ts < ${area.end}`,
92    `track_id in (${getSelectedTrackKeys(area).join(', ')})`,
93  ];
94}
95
96export function expression(column: TableColumn): string {
97  switch (column.kind) {
98    case 'regular':
99      return `${column.table}.${column.column}`;
100    case 'argument':
101      return extractArgumentExpression(column.argument, SqlTables.slice.name);
102  }
103}
104
105function aggregationExpression(aggregation: Aggregation): string {
106  if (aggregation.aggregationFunction === 'COUNT') {
107    return 'COUNT()';
108  }
109  return `${aggregation.aggregationFunction}(${expression(
110    aggregation.column,
111  )})`;
112}
113
114export function extractArgumentExpression(argument: string, table?: string) {
115  const prefix = table === undefined ? '' : `${table}.`;
116  return `extract_arg(${prefix}arg_set_id, ${sqliteString(argument)})`;
117}
118
119export function aggregationIndex(pivotColumns: number, aggregationNo: number) {
120  return pivotColumns + aggregationNo;
121}
122
123export function generateQueryFromState(
124  state: PivotTableState,
125): PivotTableQuery {
126  if (state.selectionArea === undefined) {
127    throw new QueryGeneratorError('Should not be called without area');
128  }
129
130  const sliceTableAggregations = [...state.selectedAggregations.values()];
131  if (sliceTableAggregations.length === 0) {
132    throw new QueryGeneratorError('No aggregations selected');
133  }
134
135  const pivots = state.selectedPivots;
136
137  const aggregations = sliceTableAggregations.map(
138    (agg, index) =>
139      `${aggregationExpression(agg)} as ${aggregationAlias(index)}`,
140  );
141  const countIndex = aggregations.length;
142  // Extra count aggregation, needed in order to compute combined averages.
143  aggregations.push('COUNT() as hidden_count');
144
145  const renderedPivots = pivots.map(expression);
146  const sortClauses: string[] = [];
147  for (let i = 0; i < sliceTableAggregations.length; i++) {
148    const sortDirection = sliceTableAggregations[i].sortDirection;
149    if (sortDirection !== undefined) {
150      sortClauses.push(`${aggregationAlias(i)} ${sortDirection}`);
151    }
152  }
153
154  const whereClause = state.constrainToArea
155    ? `where ${areaFilters(state.selectionArea).join(' and\n')}`
156    : '';
157  const text = `
158    INCLUDE PERFETTO MODULE slices.slices;
159
160    select
161      ${renderedPivots.concat(aggregations).join(',\n')}
162    from ${SqlTables.slice.name}
163    ${whereClause}
164    group by ${renderedPivots.join(', ')}
165    ${sortClauses.length > 0 ? 'order by ' + sortClauses.join(', ') : ''}
166  `;
167
168  return {
169    text,
170    metadata: {
171      pivotColumns: pivots,
172      aggregationColumns: sliceTableAggregations,
173      countIndex,
174    },
175  };
176}
177