1// Copyright (C) 2024 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 {AsyncLimiter} from '../../../base/async_limiter'; 16import {runQueryForQueryTable} from '../../../components/query_table/queries'; 17import {ChartAttrs} from '../../../components/widgets/charts/chart'; 18import {Trace} from '../../../public/trace'; 19import {Row} from '../../../trace_processor/query_result'; 20import {QueryNode} from '../query_node'; 21import {SqlTableState} from '../../../components/widgets/sql/table/state'; 22import {createTableColumnFromPerfettoSql} from '../../dev.perfetto.SqlModules/sql_modules'; 23import {analyzeNode, Query} from '../query_builder/data_source_viewer'; 24import {Filters} from '../../../components/widgets/sql/table/filters'; 25import {buildSqlQuery} from '../../../components/widgets/sql/table/query_builder'; 26 27export interface VisViewAttrs { 28 charts: Set<ChartAttrs>; 29 sqlTableState: SqlTableState; 30} 31 32export class VisViewSource { 33 readonly trace: Trace; 34 readonly queryNode: QueryNode; 35 readonly filters: Filters = new Filters(); 36 37 private asyncLimiter = new AsyncLimiter(); 38 private sqlAsyncLimiter = new AsyncLimiter(); 39 40 private _baseQuery?: Query; // Holds original data source query only 41 private _fullQuery?: string = ''; // Holds query with filter clauses 42 43 private _data?: Row[]; 44 private _visViews?: VisViewAttrs; 45 private _columns?: string[]; 46 47 constructor(trace: Trace, queryNode: QueryNode) { 48 this.trace = trace; 49 this.queryNode = queryNode; 50 this.filters.addObserver(() => this.loadData()); 51 52 this.loadBaseQuery(); 53 } 54 55 get visViews() { 56 return this._visViews; 57 } 58 59 get data() { 60 return this._data; 61 } 62 63 get columns() { 64 return this._columns; 65 } 66 67 addChart(vis: ChartAttrs) { 68 return this._visViews?.charts.add(vis); 69 } 70 71 removeChart(vis: ChartAttrs) { 72 return this._visViews?.charts.delete(vis); 73 } 74 75 private async loadData() { 76 const baseSql = this._baseQuery?.sql; 77 if (baseSql === undefined) return; 78 79 const columns = Object.fromEntries( 80 this.queryNode.sourceCols.map((col) => [ 81 col.column.name, 82 col.column.name, 83 ]), 84 ); 85 86 const query = buildSqlQuery({ 87 prefix: `WITH __data AS (${baseSql})`, 88 table: '__data', 89 columns: columns, 90 filters: this.filters.get(), 91 }); 92 93 if (query === this._fullQuery) return; 94 95 this._fullQuery = query; 96 97 this.asyncLimiter.schedule(async () => { 98 if (this._fullQuery === undefined) { 99 return; 100 } 101 const queryRes = await runQueryForQueryTable( 102 this._fullQuery, 103 this.trace.engine, 104 ); 105 106 this._data = queryRes.rows; 107 this._columns = queryRes.columns; 108 109 this.updateViews(this._data, this._columns); 110 }); 111 } 112 113 private async loadBaseQuery() { 114 this.sqlAsyncLimiter.schedule(async () => { 115 const sql = await analyzeNode(this.queryNode, this.trace.engine); 116 if (sql === undefined) { 117 throw Error(`Couldn't fetch the SQL`); 118 } 119 this._baseQuery = sql; 120 this.loadData(); 121 }); 122 } 123 124 private updateViews(data?: Row[], columns?: string[]) { 125 const queryNodeColumns = this.queryNode.sourceCols; 126 127 if ( 128 data === undefined || 129 columns === undefined || 130 queryNodeColumns === undefined || 131 this._baseQuery === undefined 132 ) { 133 return; 134 } 135 136 let newChartAttrs; 137 if (this._visViews !== undefined) { 138 newChartAttrs = Array.from(this._visViews.charts.values()).map( 139 (chartAttr) => { 140 const newChartAttr = { 141 ...chartAttr, 142 }; 143 144 newChartAttr.data = data; 145 146 return newChartAttr; 147 }, 148 ); 149 } 150 151 let sqlTableState = this.visViews?.sqlTableState; 152 153 if (sqlTableState === undefined) { 154 sqlTableState = new SqlTableState( 155 this.trace, 156 { 157 imports: this._baseQuery.modules, 158 prefix: `WITH __data AS (${this._baseQuery.sql})`, 159 name: '__data', 160 columns: queryNodeColumns.map((col) => 161 // TODO: Figure out how to not require table name here. 162 createTableColumnFromPerfettoSql(col.column, ''), 163 ), 164 }, 165 { 166 filters: this.filters, 167 }, 168 ); 169 } 170 171 const newVisViews: VisViewAttrs = { 172 charts: new Set<ChartAttrs>(newChartAttrs), 173 sqlTableState, 174 }; 175 176 this._visViews = newVisViews; 177 178 this.trace.raf.scheduleFullRedraw(); 179 } 180} 181