blob: fd23491adc2fe0cbb6af01389aa65e94c0f89207 [file] [log] [blame]
Alexander Timin833fbe12023-03-17 15:19:31 +00001// 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
Steve Golton50388462023-04-05 16:14:38 +010015import m from 'mithril';
Alexander Timin2369c4c2023-03-21 17:20:46 +000016import {v4 as uuidv4} from 'uuid';
17
18import {assertExists} from '../base/logging';
Alexander Timin833fbe12023-03-17 15:19:31 +000019import {QueryResponse, runQuery} from '../common/queries';
Alexander Timin2369c4c2023-03-21 17:20:46 +000020import {QueryError} from '../common/query_result';
Hector Dearmanba396db2023-07-03 18:25:08 +010021import {raf} from '../core/raf_scheduler';
Alexander Timin2369c4c2023-03-21 17:20:46 +000022import {
23 AddDebugTrackMenu,
24 uuidToViewName,
25} from '../tracks/debug/add_debug_track_menu';
26
Alexander Timin833fbe12023-03-17 15:19:31 +000027import {
28 addTab,
29 BottomTab,
30 bottomTabRegistry,
31 closeTab,
32 NewBottomTabArgs,
33} from './bottom_tab';
Alexander Timin833fbe12023-03-17 15:19:31 +000034import {QueryTable} from './query_table';
Alexander Timin2369c4c2023-03-21 17:20:46 +000035import {Button} from './widgets/button';
36import {Popup, PopupPosition} from './widgets/popup';
Alexander Timin833fbe12023-03-17 15:19:31 +000037
Alexander Timin833fbe12023-03-17 15:19:31 +000038export function runQueryInNewTab(query: string, title: string, tag?: string) {
39 return addTab({
40 kind: QueryResultTab.kind,
41 tag,
42 config: {
43 query,
44 title,
45 },
46 });
47}
48
49interface QueryResultTabConfig {
50 readonly query: string;
51 readonly title: string;
52 // Optional data to display in this tab instead of fetching it again
53 // (e.g. when duplicating an existing tab which already has the data).
54 readonly prefetchedResponse?: QueryResponse;
55}
56
57export class QueryResultTab extends BottomTab<QueryResultTabConfig> {
Hector Dearmand3ccdc82023-06-19 14:34:14 +010058 static readonly kind = 'dev.perfetto.QueryResultTab';
Alexander Timin833fbe12023-03-17 15:19:31 +000059
60 queryResponse?: QueryResponse;
Alexander Timin2369c4c2023-03-21 17:20:46 +000061 sqlViewName?: string;
Alexander Timin833fbe12023-03-17 15:19:31 +000062
63 static create(args: NewBottomTabArgs): QueryResultTab {
64 return new QueryResultTab(args);
65 }
66
67 constructor(args: NewBottomTabArgs) {
68 super(args);
69
Harkiran Bolaria3f538522023-04-19 16:18:57 +000070 this.initTrack(args);
71 }
72
73 async initTrack(args: NewBottomTabArgs) {
74 let uuid = '';
Alexander Timin833fbe12023-03-17 15:19:31 +000075 if (this.config.prefetchedResponse !== undefined) {
76 this.queryResponse = this.config.prefetchedResponse;
Harkiran Bolaria3f538522023-04-19 16:18:57 +000077 uuid = args.uuid;
Alexander Timin833fbe12023-03-17 15:19:31 +000078 } else {
Harkiran Bolaria3f538522023-04-19 16:18:57 +000079 const result = await runQuery(this.config.query, this.engine);
80 this.queryResponse = result;
Hector Dearmanba396db2023-07-03 18:25:08 +010081 raf.scheduleFullRedraw();
Harkiran Bolaria3f538522023-04-19 16:18:57 +000082 if (result.error !== undefined) {
83 return;
84 }
Alexander Timin2369c4c2023-03-21 17:20:46 +000085
Harkiran Bolaria3f538522023-04-19 16:18:57 +000086 uuid = uuidv4();
87 }
Alexander Timin2369c4c2023-03-21 17:20:46 +000088
Harkiran Bolaria3f538522023-04-19 16:18:57 +000089 if (uuid !== '') {
90 this.sqlViewName = await this.createViewForDebugTrack(uuid);
91 if (this.sqlViewName) {
Hector Dearmanba396db2023-07-03 18:25:08 +010092 raf.scheduleFullRedraw();
Harkiran Bolaria3f538522023-04-19 16:18:57 +000093 }
Alexander Timin833fbe12023-03-17 15:19:31 +000094 }
95 }
96
97 getTitle(): string {
98 const suffix =
99 this.queryResponse ? ` (${this.queryResponse.rows.length})` : '';
100 return `${this.config.title}${suffix}`;
101 }
102
Alexander Timin2369c4c2023-03-21 17:20:46 +0000103 viewTab(): m.Child {
Alexander Timin833fbe12023-03-17 15:19:31 +0000104 return m(QueryTable, {
105 query: this.config.query,
106 resp: this.queryResponse,
Steve Goltonc4ca6122023-06-20 07:18:41 +0100107 fillParent: true,
Alexander Timin833fbe12023-03-17 15:19:31 +0000108 onClose: () => closeTab(this.uuid),
Alexander Timin2369c4c2023-03-21 17:20:46 +0000109 contextButtons: [
110 this.sqlViewName === undefined ?
111 null :
112 m(Popup,
113 {
114 trigger: m(Button, {label: 'Show debug track', minimal: true}),
115 position: PopupPosition.Top,
116 },
117 m(AddDebugTrackMenu, {
118 sqlViewName: this.sqlViewName,
119 columns: assertExists(this.queryResponse).columns,
120 engine: this.engine,
121 })),
122 ],
Alexander Timin833fbe12023-03-17 15:19:31 +0000123 });
124 }
125
Alexander Timin00035a02023-03-17 15:32:45 +0000126 isLoading() {
127 return this.queryResponse === undefined;
128 }
129
Alexander Timin833fbe12023-03-17 15:19:31 +0000130 renderTabCanvas() {}
Harkiran Bolaria3f538522023-04-19 16:18:57 +0000131
132 async createViewForDebugTrack(uuid: string): Promise<string> {
133 const viewId = uuidToViewName(uuid);
134 // Assuming that the query results come from a SELECT query, try creating a
135 // view to allow us to reuse it for further queries.
Igor Kraskevich5e87c592023-06-22 08:37:38 +0000136 const hasValidQueryResponse =
137 this.queryResponse && this.queryResponse.error === undefined;
138 const sqlQuery = hasValidQueryResponse ?
139 this.queryResponse!.lastStatementSql :
140 this.config.query;
Harkiran Bolaria3f538522023-04-19 16:18:57 +0000141 try {
Igor Kraskevich5e87c592023-06-22 08:37:38 +0000142 const createViewResult =
143 await this.engine.query(`create view ${viewId} as ${sqlQuery}`);
Harkiran Bolaria3f538522023-04-19 16:18:57 +0000144 if (createViewResult.error()) {
145 // If it failed, do nothing.
146 return '';
147 }
148 } catch (e) {
149 if (e instanceof QueryError) {
150 // If it failed, do nothing.
151 return '';
152 }
153 throw e;
154 }
155 return viewId;
156 }
Alexander Timin833fbe12023-03-17 15:19:31 +0000157}
158
159bottomTabRegistry.register(QueryResultTab);