blob: f0efe264e81ae6660d7f9b50633a58d357b184e5 [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';
Hector Dearmanba396db2023-07-03 18:25:08 +010020import {raf} from '../core/raf_scheduler';
Hector Dearmanea4719d2023-10-31 09:11:09 +000021import {QueryError} from '../trace_processor/query_result';
Alexander Timin2369c4c2023-03-21 17:20:46 +000022import {
23 AddDebugTrackMenu,
24 uuidToViewName,
25} from '../tracks/debug/add_debug_track_menu';
Steve Golton6caf3c02023-09-07 08:09:06 +010026import {Button} from '../widgets/button';
27import {PopupMenu2} from '../widgets/menu';
28import {PopupPosition} from '../widgets/popup';
Alexander Timin2369c4c2023-03-21 17:20:46 +000029
Alexander Timin833fbe12023-03-17 15:19:31 +000030import {
31 addTab,
32 BottomTab,
33 bottomTabRegistry,
34 closeTab,
35 NewBottomTabArgs,
36} from './bottom_tab';
Steve Golton17a785c2023-11-07 16:05:06 +000037import {globals} from './globals';
Alexander Timin833fbe12023-03-17 15:19:31 +000038import {QueryTable} from './query_table';
39
Alexander Timin833fbe12023-03-17 15:19:31 +000040export function runQueryInNewTab(query: string, title: string, tag?: string) {
41 return addTab({
42 kind: QueryResultTab.kind,
43 tag,
44 config: {
45 query,
46 title,
47 },
48 });
49}
50
Steve Golton17a785c2023-11-07 16:05:06 +000051globals.registerOpenQueryHandler(runQueryInNewTab);
52
Alexander Timin833fbe12023-03-17 15:19:31 +000053interface QueryResultTabConfig {
54 readonly query: string;
55 readonly title: string;
56 // Optional data to display in this tab instead of fetching it again
57 // (e.g. when duplicating an existing tab which already has the data).
58 readonly prefetchedResponse?: QueryResponse;
59}
60
61export class QueryResultTab extends BottomTab<QueryResultTabConfig> {
Hector Dearmand3ccdc82023-06-19 14:34:14 +010062 static readonly kind = 'dev.perfetto.QueryResultTab';
Alexander Timin833fbe12023-03-17 15:19:31 +000063
64 queryResponse?: QueryResponse;
Alexander Timin2369c4c2023-03-21 17:20:46 +000065 sqlViewName?: string;
Alexander Timin833fbe12023-03-17 15:19:31 +000066
67 static create(args: NewBottomTabArgs): QueryResultTab {
68 return new QueryResultTab(args);
69 }
70
71 constructor(args: NewBottomTabArgs) {
72 super(args);
73
Harkiran Bolaria3f538522023-04-19 16:18:57 +000074 this.initTrack(args);
75 }
76
77 async initTrack(args: NewBottomTabArgs) {
78 let uuid = '';
Alexander Timin833fbe12023-03-17 15:19:31 +000079 if (this.config.prefetchedResponse !== undefined) {
80 this.queryResponse = this.config.prefetchedResponse;
Harkiran Bolaria3f538522023-04-19 16:18:57 +000081 uuid = args.uuid;
Alexander Timin833fbe12023-03-17 15:19:31 +000082 } else {
Harkiran Bolaria3f538522023-04-19 16:18:57 +000083 const result = await runQuery(this.config.query, this.engine);
84 this.queryResponse = result;
Hector Dearmanba396db2023-07-03 18:25:08 +010085 raf.scheduleFullRedraw();
Harkiran Bolaria3f538522023-04-19 16:18:57 +000086 if (result.error !== undefined) {
87 return;
88 }
Alexander Timin2369c4c2023-03-21 17:20:46 +000089
Harkiran Bolaria3f538522023-04-19 16:18:57 +000090 uuid = uuidv4();
91 }
Alexander Timin2369c4c2023-03-21 17:20:46 +000092
Harkiran Bolaria3f538522023-04-19 16:18:57 +000093 if (uuid !== '') {
94 this.sqlViewName = await this.createViewForDebugTrack(uuid);
95 if (this.sqlViewName) {
Hector Dearmanba396db2023-07-03 18:25:08 +010096 raf.scheduleFullRedraw();
Harkiran Bolaria3f538522023-04-19 16:18:57 +000097 }
Alexander Timin833fbe12023-03-17 15:19:31 +000098 }
99 }
100
101 getTitle(): string {
102 const suffix =
103 this.queryResponse ? ` (${this.queryResponse.rows.length})` : '';
104 return `${this.config.title}${suffix}`;
105 }
106
Alexander Timin2369c4c2023-03-21 17:20:46 +0000107 viewTab(): m.Child {
Alexander Timin833fbe12023-03-17 15:19:31 +0000108 return m(QueryTable, {
109 query: this.config.query,
110 resp: this.queryResponse,
Steve Goltonc4ca6122023-06-20 07:18:41 +0100111 fillParent: true,
Alexander Timin833fbe12023-03-17 15:19:31 +0000112 onClose: () => closeTab(this.uuid),
Alexander Timin2369c4c2023-03-21 17:20:46 +0000113 contextButtons: [
114 this.sqlViewName === undefined ?
115 null :
Steve Golton1d61fca2023-07-21 07:27:21 +0100116 m(PopupMenu2,
Alexander Timin2369c4c2023-03-21 17:20:46 +0000117 {
118 trigger: m(Button, {label: 'Show debug track', minimal: true}),
Steve Golton1d61fca2023-07-21 07:27:21 +0100119 popupPosition: PopupPosition.Top,
Alexander Timin2369c4c2023-03-21 17:20:46 +0000120 },
121 m(AddDebugTrackMenu, {
Alexander Timin2a32d682023-07-20 12:07:48 -0700122 dataSource: {
123 sqlSource: `select * from ${this.sqlViewName}`,
124 columns: assertExists(this.queryResponse).columns,
125 },
Alexander Timin2369c4c2023-03-21 17:20:46 +0000126 engine: this.engine,
127 })),
128 ],
Alexander Timin833fbe12023-03-17 15:19:31 +0000129 });
130 }
131
Alexander Timin00035a02023-03-17 15:32:45 +0000132 isLoading() {
133 return this.queryResponse === undefined;
134 }
135
Harkiran Bolaria3f538522023-04-19 16:18:57 +0000136 async createViewForDebugTrack(uuid: string): Promise<string> {
137 const viewId = uuidToViewName(uuid);
138 // Assuming that the query results come from a SELECT query, try creating a
139 // view to allow us to reuse it for further queries.
Igor Kraskevich5e87c592023-06-22 08:37:38 +0000140 const hasValidQueryResponse =
141 this.queryResponse && this.queryResponse.error === undefined;
142 const sqlQuery = hasValidQueryResponse ?
143 this.queryResponse!.lastStatementSql :
144 this.config.query;
Harkiran Bolaria3f538522023-04-19 16:18:57 +0000145 try {
Igor Kraskevich5e87c592023-06-22 08:37:38 +0000146 const createViewResult =
147 await this.engine.query(`create view ${viewId} as ${sqlQuery}`);
Harkiran Bolaria3f538522023-04-19 16:18:57 +0000148 if (createViewResult.error()) {
149 // If it failed, do nothing.
150 return '';
151 }
152 } catch (e) {
153 if (e instanceof QueryError) {
154 // If it failed, do nothing.
155 return '';
156 }
157 throw e;
158 }
159 return viewId;
160 }
Alexander Timin833fbe12023-03-17 15:19:31 +0000161}
162
163bottomTabRegistry.register(QueryResultTab);