• 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 {
19  Area,
20  PivotTableQuery,
21  PivotTableState,
22} from '../common/state';
23import {
24  getSelectedTrackIds,
25} from '../controller/aggregation/slice_aggregation_controller';
26
27import {globals} from './globals';
28import {
29  Aggregation,
30  TableColumn,
31} from './pivot_table_types';
32
33export interface Table {
34  name: string;
35  columns: string[];
36}
37
38export const sliceTable = {
39  name: 'slice',
40  columns: ['type', 'ts', 'dur', 'category', 'name', 'depth'],
41};
42
43// Columns of `slice` table available for aggregation.
44export const sliceAggregationColumns = [
45  'ts',
46  'dur',
47  'depth',
48  'thread_ts',
49  'thread_dur',
50  'thread_instruction_count',
51  'thread_instruction_delta',
52];
53
54// List of available tables to query, used to populate selectors of pivot
55// columns in the UI.
56export const tables: Table[] = [
57  sliceTable,
58  {
59    name: 'process',
60    columns: [
61      'type',
62      'pid',
63      'name',
64      'parent_upid',
65      'uid',
66      'android_appid',
67      'cmdline',
68    ],
69  },
70  {name: 'thread', columns: ['type', 'name', 'tid', 'upid', 'is_main_thread']},
71  {name: 'thread_track', columns: ['type', 'name', 'utid']},
72];
73
74// Queried "table column" is either:
75// 1. A real one, represented as object with table and column name.
76// 2. Pseudo-column 'count' that's rendered as '1' in SQL to use in queries like
77// `select sum(1), name from slice group by name`.
78
79export interface RegularColumn {
80  kind: 'regular';
81  table: string;
82  column: string;
83}
84
85export interface ArgumentColumn {
86  kind: 'argument';
87  argument: string;
88}
89
90// Exception thrown by query generator in case incoming parameters are not
91// suitable in order to build a correct query; these are caught by the UI and
92// displayed to the user.
93export class QueryGeneratorError extends Error {}
94
95// Internal column name for different rollover levels of aggregate columns.
96function aggregationAlias(aggregationIndex: number): string {
97  return `agg_${aggregationIndex}`;
98}
99
100export function areaFilter(area: Area): string {
101  return `
102    ts + dur > ${area.start}
103    and ts < ${area.end}
104    and track_id in (${getSelectedTrackIds(area).join(', ')})
105  `;
106}
107
108export function expression(column: TableColumn): string {
109  switch (column.kind) {
110    case 'regular':
111      return `${column.table}.${column.column}`;
112    case 'argument':
113      return extractArgumentExpression(column.argument, 'slice');
114  }
115}
116
117function aggregationExpression(aggregation: Aggregation): string {
118  if (aggregation.aggregationFunction === 'COUNT') {
119    return 'COUNT()';
120  }
121  return `${aggregation.aggregationFunction}(${
122      expression(aggregation.column)})`;
123}
124
125export function extractArgumentExpression(argument: string, table?: string) {
126  const prefix = table === undefined ? '' : `${table}.`;
127  return `extract_arg(${prefix}arg_set_id, ${sqliteString(argument)})`;
128}
129
130export function aggregationIndex(pivotColumns: number, aggregationNo: number) {
131  return pivotColumns + aggregationNo;
132}
133
134export function generateQueryFromState(state: PivotTableState):
135    PivotTableQuery {
136  if (state.selectionArea === undefined) {
137    throw new QueryGeneratorError('Should not be called without area');
138  }
139
140  const sliceTableAggregations = [...state.selectedAggregations.values()];
141  if (sliceTableAggregations.length === 0) {
142    throw new QueryGeneratorError('No aggregations selected');
143  }
144
145  const pivots = state.selectedPivots;
146
147  const aggregations = sliceTableAggregations.map(
148      (agg, index) =>
149          `${aggregationExpression(agg)} as ${aggregationAlias(index)}`);
150  const countIndex = aggregations.length;
151  // Extra count aggregation, needed in order to compute combined averages.
152  aggregations.push('COUNT() as hidden_count');
153
154  const renderedPivots = pivots.map(expression);
155  const sortClauses: string[] = [];
156  for (let i = 0; i < sliceTableAggregations.length; i++) {
157    const sortDirection = sliceTableAggregations[i].sortDirection;
158    if (sortDirection !== undefined) {
159      sortClauses.push(`${aggregationAlias(i)} ${sortDirection}`);
160    }
161  }
162
163  const joins = `
164    left join thread_track on thread_track.id = slice.track_id
165    left join thread using (utid)
166    left join process using (upid)
167  `;
168
169  const whereClause = state.constrainToArea ?
170      `where ${areaFilter(globals.state.areas[state.selectionArea.areaId])}` :
171      '';
172  const text = `
173    select
174      ${renderedPivots.concat(aggregations).join(',\n')}
175    from slice
176    ${pivots.length > 0 ? joins : ''}
177    ${whereClause}
178    group by ${renderedPivots.join(', ')}
179    ${sortClauses.length > 0 ? ('order by ' + sortClauses.join(', ')) : ''}
180  `;
181
182  return {
183    text,
184    metadata: {
185      pivotColumns: pivots,
186      aggregationColumns: sliceTableAggregations,
187      countIndex,
188    },
189  };
190}
191