• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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