1// 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 15import m from 'mithril'; 16import {v4 as uuidv4} from 'uuid'; 17 18import {assertExists} from '../base/logging'; 19import {QueryResponse, runQuery} from '../common/queries'; 20import {QueryError} from '../common/query_result'; 21import { 22 AddDebugTrackMenu, 23 uuidToViewName, 24} from '../tracks/debug/add_debug_track_menu'; 25 26import { 27 addTab, 28 BottomTab, 29 bottomTabRegistry, 30 closeTab, 31 NewBottomTabArgs, 32} from './bottom_tab'; 33import {globals} from './globals'; 34import {QueryTable} from './query_table'; 35import {Button} from './widgets/button'; 36import {Popup, PopupPosition} from './widgets/popup'; 37 38 39export function runQueryInNewTab(query: string, title: string, tag?: string) { 40 return addTab({ 41 kind: QueryResultTab.kind, 42 tag, 43 config: { 44 query, 45 title, 46 }, 47 }); 48} 49 50interface QueryResultTabConfig { 51 readonly query: string; 52 readonly title: string; 53 // Optional data to display in this tab instead of fetching it again 54 // (e.g. when duplicating an existing tab which already has the data). 55 readonly prefetchedResponse?: QueryResponse; 56} 57 58export class QueryResultTab extends BottomTab<QueryResultTabConfig> { 59 static readonly kind = 'org.perfetto.QueryResultTab'; 60 61 queryResponse?: QueryResponse; 62 sqlViewName?: string; 63 64 static create(args: NewBottomTabArgs): QueryResultTab { 65 return new QueryResultTab(args); 66 } 67 68 constructor(args: NewBottomTabArgs) { 69 super(args); 70 71 this.initTrack(args); 72 } 73 74 async initTrack(args: NewBottomTabArgs) { 75 let uuid = ''; 76 if (this.config.prefetchedResponse !== undefined) { 77 this.queryResponse = this.config.prefetchedResponse; 78 uuid = args.uuid; 79 } else { 80 const result = await runQuery(this.config.query, this.engine); 81 this.queryResponse = result; 82 globals.rafScheduler.scheduleFullRedraw(); 83 if (result.error !== undefined) { 84 return; 85 } 86 87 uuid = uuidv4(); 88 } 89 90 if (uuid !== '') { 91 this.sqlViewName = await this.createViewForDebugTrack(uuid); 92 if (this.sqlViewName) { 93 globals.rafScheduler.scheduleFullRedraw(); 94 } 95 } 96 } 97 98 getTitle(): string { 99 const suffix = 100 this.queryResponse ? ` (${this.queryResponse.rows.length})` : ''; 101 return `${this.config.title}${suffix}`; 102 } 103 104 viewTab(): m.Child { 105 return m(QueryTable, { 106 query: this.config.query, 107 resp: this.queryResponse, 108 onClose: () => closeTab(this.uuid), 109 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 ], 123 }); 124 } 125 126 isLoading() { 127 return this.queryResponse === undefined; 128 } 129 130 renderTabCanvas() {} 131 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. 136 // TODO(altimin): This should get the actual query that was used to 137 // generate the results from the SQL query iterator. 138 try { 139 const createViewResult = await this.engine.query( 140 `create view ${viewId} as ${this.config.query}`); 141 if (createViewResult.error()) { 142 // If it failed, do nothing. 143 return ''; 144 } 145 } catch (e) { 146 if (e instanceof QueryError) { 147 // If it failed, do nothing. 148 return ''; 149 } 150 throw e; 151 } 152 return viewId; 153 } 154} 155 156bottomTabRegistry.register(QueryResultTab); 157