1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2024 The Android Open Source Project 2*6dbdd20aSAndroid Build Coastguard Worker// 3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*6dbdd20aSAndroid Build Coastguard Worker// 7*6dbdd20aSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*6dbdd20aSAndroid Build Coastguard Worker// 9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License. 14*6dbdd20aSAndroid Build Coastguard Worker 15*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril'; 16*6dbdd20aSAndroid Build Coastguard Workerimport {AsyncLimiter} from '../base/async_limiter'; 17*6dbdd20aSAndroid Build Coastguard Workerimport {AsyncDisposableStack} from '../base/disposable_stack'; 18*6dbdd20aSAndroid Build Coastguard Workerimport {assertExists} from '../base/logging'; 19*6dbdd20aSAndroid Build Coastguard Workerimport {Monitor} from '../base/monitor'; 20*6dbdd20aSAndroid Build Coastguard Workerimport {uuidv4Sql} from '../base/uuid'; 21*6dbdd20aSAndroid Build Coastguard Workerimport {Engine} from '../trace_processor/engine'; 22*6dbdd20aSAndroid Build Coastguard Workerimport { 23*6dbdd20aSAndroid Build Coastguard Worker createPerfettoIndex, 24*6dbdd20aSAndroid Build Coastguard Worker createPerfettoTable, 25*6dbdd20aSAndroid Build Coastguard Worker} from '../trace_processor/sql_utils'; 26*6dbdd20aSAndroid Build Coastguard Workerimport { 27*6dbdd20aSAndroid Build Coastguard Worker NUM, 28*6dbdd20aSAndroid Build Coastguard Worker NUM_NULL, 29*6dbdd20aSAndroid Build Coastguard Worker STR, 30*6dbdd20aSAndroid Build Coastguard Worker STR_NULL, 31*6dbdd20aSAndroid Build Coastguard Worker UNKNOWN, 32*6dbdd20aSAndroid Build Coastguard Worker} from '../trace_processor/query_result'; 33*6dbdd20aSAndroid Build Coastguard Workerimport { 34*6dbdd20aSAndroid Build Coastguard Worker Flamegraph, 35*6dbdd20aSAndroid Build Coastguard Worker FlamegraphQueryData, 36*6dbdd20aSAndroid Build Coastguard Worker FlamegraphState, 37*6dbdd20aSAndroid Build Coastguard Worker FlamegraphView, 38*6dbdd20aSAndroid Build Coastguard Worker} from '../widgets/flamegraph'; 39*6dbdd20aSAndroid Build Coastguard Workerimport {Trace} from '../public/trace'; 40*6dbdd20aSAndroid Build Coastguard Worker 41*6dbdd20aSAndroid Build Coastguard Workerexport interface QueryFlamegraphColumn { 42*6dbdd20aSAndroid Build Coastguard Worker // The name of the column in SQL. 43*6dbdd20aSAndroid Build Coastguard Worker readonly name: string; 44*6dbdd20aSAndroid Build Coastguard Worker 45*6dbdd20aSAndroid Build Coastguard Worker // The human readable name describing the contents of the column. 46*6dbdd20aSAndroid Build Coastguard Worker readonly displayName: string; 47*6dbdd20aSAndroid Build Coastguard Worker} 48*6dbdd20aSAndroid Build Coastguard Worker 49*6dbdd20aSAndroid Build Coastguard Workerexport interface AggQueryFlamegraphColumn extends QueryFlamegraphColumn { 50*6dbdd20aSAndroid Build Coastguard Worker // The aggregation to be run when nodes are merged together in the flamegraph. 51*6dbdd20aSAndroid Build Coastguard Worker // 52*6dbdd20aSAndroid Build Coastguard Worker // TODO(lalitm): consider adding extra functions here (e.g. a top 5 or similar). 53*6dbdd20aSAndroid Build Coastguard Worker readonly mergeAggregation: 'ONE_OR_NULL' | 'SUM'; 54*6dbdd20aSAndroid Build Coastguard Worker} 55*6dbdd20aSAndroid Build Coastguard Worker 56*6dbdd20aSAndroid Build Coastguard Workerexport interface QueryFlamegraphMetric { 57*6dbdd20aSAndroid Build Coastguard Worker // The human readable name of the metric: will be shown to the user to change 58*6dbdd20aSAndroid Build Coastguard Worker // between metrics. 59*6dbdd20aSAndroid Build Coastguard Worker readonly name: string; 60*6dbdd20aSAndroid Build Coastguard Worker 61*6dbdd20aSAndroid Build Coastguard Worker // The human readable SI-style unit of `selfValue`. Values will be shown to 62*6dbdd20aSAndroid Build Coastguard Worker // the user suffixed with this. 63*6dbdd20aSAndroid Build Coastguard Worker readonly unit: string; 64*6dbdd20aSAndroid Build Coastguard Worker 65*6dbdd20aSAndroid Build Coastguard Worker // SQL statement which need to be run in preparation for being able to execute 66*6dbdd20aSAndroid Build Coastguard Worker // `statement`. 67*6dbdd20aSAndroid Build Coastguard Worker readonly dependencySql?: string; 68*6dbdd20aSAndroid Build Coastguard Worker 69*6dbdd20aSAndroid Build Coastguard Worker // A single SQL statement which returns the columns `id`, `parentId`, `name` 70*6dbdd20aSAndroid Build Coastguard Worker // `selfValue`, all columns specified by `unaggregatableProperties` and 71*6dbdd20aSAndroid Build Coastguard Worker // `aggregatableProperties`. 72*6dbdd20aSAndroid Build Coastguard Worker readonly statement: string; 73*6dbdd20aSAndroid Build Coastguard Worker 74*6dbdd20aSAndroid Build Coastguard Worker // Additional contextual columns containing data which should not be merged 75*6dbdd20aSAndroid Build Coastguard Worker // between sibling nodes, even if they have the same name. 76*6dbdd20aSAndroid Build Coastguard Worker // 77*6dbdd20aSAndroid Build Coastguard Worker // Examples include the mapping that a name comes from, the heap graph root 78*6dbdd20aSAndroid Build Coastguard Worker // type etc. 79*6dbdd20aSAndroid Build Coastguard Worker // 80*6dbdd20aSAndroid Build Coastguard Worker // Note: the name is always unaggregatable and should not be specified here. 81*6dbdd20aSAndroid Build Coastguard Worker readonly unaggregatableProperties?: ReadonlyArray<QueryFlamegraphColumn>; 82*6dbdd20aSAndroid Build Coastguard Worker 83*6dbdd20aSAndroid Build Coastguard Worker // Additional contextual columns containing data which will be displayed to 84*6dbdd20aSAndroid Build Coastguard Worker // the user if there is no merging. If there is merging, currently the value 85*6dbdd20aSAndroid Build Coastguard Worker // will not be shown. 86*6dbdd20aSAndroid Build Coastguard Worker // 87*6dbdd20aSAndroid Build Coastguard Worker // Examples include the source file and line number. 88*6dbdd20aSAndroid Build Coastguard Worker readonly aggregatableProperties?: ReadonlyArray<AggQueryFlamegraphColumn>; 89*6dbdd20aSAndroid Build Coastguard Worker} 90*6dbdd20aSAndroid Build Coastguard Worker 91*6dbdd20aSAndroid Build Coastguard Workerexport interface QueryFlamegraphState { 92*6dbdd20aSAndroid Build Coastguard Worker state: FlamegraphState; 93*6dbdd20aSAndroid Build Coastguard Worker} 94*6dbdd20aSAndroid Build Coastguard Worker 95*6dbdd20aSAndroid Build Coastguard Worker// Given a table and columns on those table (corresponding to metrics), 96*6dbdd20aSAndroid Build Coastguard Worker// returns an array of `QueryFlamegraphMetric` structs which can be passed 97*6dbdd20aSAndroid Build Coastguard Worker// in QueryFlamegraph's attrs. 98*6dbdd20aSAndroid Build Coastguard Worker// 99*6dbdd20aSAndroid Build Coastguard Worker// `tableOrSubquery` should have the columns `id`, `parentId`, `name` and all 100*6dbdd20aSAndroid Build Coastguard Worker// columns specified by `tableMetrics[].name`, `unaggregatableProperties` and 101*6dbdd20aSAndroid Build Coastguard Worker// `aggregatableProperties`. 102*6dbdd20aSAndroid Build Coastguard Workerexport function metricsFromTableOrSubquery( 103*6dbdd20aSAndroid Build Coastguard Worker tableOrSubquery: string, 104*6dbdd20aSAndroid Build Coastguard Worker tableMetrics: ReadonlyArray<{name: string; unit: string; columnName: string}>, 105*6dbdd20aSAndroid Build Coastguard Worker dependencySql?: string, 106*6dbdd20aSAndroid Build Coastguard Worker unaggregatableProperties?: ReadonlyArray<QueryFlamegraphColumn>, 107*6dbdd20aSAndroid Build Coastguard Worker aggregatableProperties?: ReadonlyArray<AggQueryFlamegraphColumn>, 108*6dbdd20aSAndroid Build Coastguard Worker): QueryFlamegraphMetric[] { 109*6dbdd20aSAndroid Build Coastguard Worker const metrics = []; 110*6dbdd20aSAndroid Build Coastguard Worker for (const {name, unit, columnName} of tableMetrics) { 111*6dbdd20aSAndroid Build Coastguard Worker metrics.push({ 112*6dbdd20aSAndroid Build Coastguard Worker name, 113*6dbdd20aSAndroid Build Coastguard Worker unit, 114*6dbdd20aSAndroid Build Coastguard Worker dependencySql, 115*6dbdd20aSAndroid Build Coastguard Worker statement: ` 116*6dbdd20aSAndroid Build Coastguard Worker select *, ${columnName} as value 117*6dbdd20aSAndroid Build Coastguard Worker from ${tableOrSubquery} 118*6dbdd20aSAndroid Build Coastguard Worker `, 119*6dbdd20aSAndroid Build Coastguard Worker unaggregatableProperties, 120*6dbdd20aSAndroid Build Coastguard Worker aggregatableProperties, 121*6dbdd20aSAndroid Build Coastguard Worker }); 122*6dbdd20aSAndroid Build Coastguard Worker } 123*6dbdd20aSAndroid Build Coastguard Worker return metrics; 124*6dbdd20aSAndroid Build Coastguard Worker} 125*6dbdd20aSAndroid Build Coastguard Worker 126*6dbdd20aSAndroid Build Coastguard Worker// A Perfetto UI component which wraps the `Flamegraph` widget and fetches the 127*6dbdd20aSAndroid Build Coastguard Worker// data for the widget by querying an `Engine`. 128*6dbdd20aSAndroid Build Coastguard Workerexport class QueryFlamegraph { 129*6dbdd20aSAndroid Build Coastguard Worker private data?: FlamegraphQueryData; 130*6dbdd20aSAndroid Build Coastguard Worker private readonly selMonitor = new Monitor([() => this.state.state]); 131*6dbdd20aSAndroid Build Coastguard Worker private readonly queryLimiter = new AsyncLimiter(); 132*6dbdd20aSAndroid Build Coastguard Worker 133*6dbdd20aSAndroid Build Coastguard Worker constructor( 134*6dbdd20aSAndroid Build Coastguard Worker private readonly trace: Trace, 135*6dbdd20aSAndroid Build Coastguard Worker private readonly metrics: ReadonlyArray<QueryFlamegraphMetric>, 136*6dbdd20aSAndroid Build Coastguard Worker private state: QueryFlamegraphState, 137*6dbdd20aSAndroid Build Coastguard Worker ) {} 138*6dbdd20aSAndroid Build Coastguard Worker 139*6dbdd20aSAndroid Build Coastguard Worker render() { 140*6dbdd20aSAndroid Build Coastguard Worker if (this.selMonitor.ifStateChanged()) { 141*6dbdd20aSAndroid Build Coastguard Worker const metric = assertExists( 142*6dbdd20aSAndroid Build Coastguard Worker this.metrics.find( 143*6dbdd20aSAndroid Build Coastguard Worker (x) => this.state.state.selectedMetricName === x.name, 144*6dbdd20aSAndroid Build Coastguard Worker ), 145*6dbdd20aSAndroid Build Coastguard Worker ); 146*6dbdd20aSAndroid Build Coastguard Worker const engine = this.trace.engine; 147*6dbdd20aSAndroid Build Coastguard Worker const state = this.state; 148*6dbdd20aSAndroid Build Coastguard Worker this.data = undefined; 149*6dbdd20aSAndroid Build Coastguard Worker this.queryLimiter.schedule(async () => { 150*6dbdd20aSAndroid Build Coastguard Worker this.data = undefined; 151*6dbdd20aSAndroid Build Coastguard Worker this.data = await computeFlamegraphTree(engine, metric, state.state); 152*6dbdd20aSAndroid Build Coastguard Worker }); 153*6dbdd20aSAndroid Build Coastguard Worker } 154*6dbdd20aSAndroid Build Coastguard Worker return m(Flamegraph, { 155*6dbdd20aSAndroid Build Coastguard Worker metrics: this.metrics, 156*6dbdd20aSAndroid Build Coastguard Worker data: this.data, 157*6dbdd20aSAndroid Build Coastguard Worker state: this.state.state, 158*6dbdd20aSAndroid Build Coastguard Worker onStateChange: (state) => { 159*6dbdd20aSAndroid Build Coastguard Worker this.state.state = state; 160*6dbdd20aSAndroid Build Coastguard Worker this.trace.scheduleFullRedraw(); 161*6dbdd20aSAndroid Build Coastguard Worker }, 162*6dbdd20aSAndroid Build Coastguard Worker }); 163*6dbdd20aSAndroid Build Coastguard Worker } 164*6dbdd20aSAndroid Build Coastguard Worker} 165*6dbdd20aSAndroid Build Coastguard Worker 166*6dbdd20aSAndroid Build Coastguard Workerasync function computeFlamegraphTree( 167*6dbdd20aSAndroid Build Coastguard Worker engine: Engine, 168*6dbdd20aSAndroid Build Coastguard Worker { 169*6dbdd20aSAndroid Build Coastguard Worker dependencySql, 170*6dbdd20aSAndroid Build Coastguard Worker statement, 171*6dbdd20aSAndroid Build Coastguard Worker unaggregatableProperties, 172*6dbdd20aSAndroid Build Coastguard Worker aggregatableProperties, 173*6dbdd20aSAndroid Build Coastguard Worker }: QueryFlamegraphMetric, 174*6dbdd20aSAndroid Build Coastguard Worker {filters, view}: FlamegraphState, 175*6dbdd20aSAndroid Build Coastguard Worker): Promise<FlamegraphQueryData> { 176*6dbdd20aSAndroid Build Coastguard Worker const showStack = filters 177*6dbdd20aSAndroid Build Coastguard Worker .filter((x) => x.kind === 'SHOW_STACK') 178*6dbdd20aSAndroid Build Coastguard Worker .map((x) => x.filter); 179*6dbdd20aSAndroid Build Coastguard Worker const hideStack = filters 180*6dbdd20aSAndroid Build Coastguard Worker .filter((x) => x.kind === 'HIDE_STACK') 181*6dbdd20aSAndroid Build Coastguard Worker .map((x) => x.filter); 182*6dbdd20aSAndroid Build Coastguard Worker const showFromFrame = filters 183*6dbdd20aSAndroid Build Coastguard Worker .filter((x) => x.kind === 'SHOW_FROM_FRAME') 184*6dbdd20aSAndroid Build Coastguard Worker .map((x) => x.filter); 185*6dbdd20aSAndroid Build Coastguard Worker const hideFrame = filters 186*6dbdd20aSAndroid Build Coastguard Worker .filter((x) => x.kind === 'HIDE_FRAME') 187*6dbdd20aSAndroid Build Coastguard Worker .map((x) => x.filter); 188*6dbdd20aSAndroid Build Coastguard Worker 189*6dbdd20aSAndroid Build Coastguard Worker // Pivot also essentially acts as a "show stack" filter so treat it like one. 190*6dbdd20aSAndroid Build Coastguard Worker const showStackAndPivot = [...showStack]; 191*6dbdd20aSAndroid Build Coastguard Worker if (view.kind === 'PIVOT') { 192*6dbdd20aSAndroid Build Coastguard Worker showStackAndPivot.push(view.pivot); 193*6dbdd20aSAndroid Build Coastguard Worker } 194*6dbdd20aSAndroid Build Coastguard Worker 195*6dbdd20aSAndroid Build Coastguard Worker const showStackFilter = 196*6dbdd20aSAndroid Build Coastguard Worker showStackAndPivot.length === 0 197*6dbdd20aSAndroid Build Coastguard Worker ? '0' 198*6dbdd20aSAndroid Build Coastguard Worker : showStackAndPivot 199*6dbdd20aSAndroid Build Coastguard Worker .map( 200*6dbdd20aSAndroid Build Coastguard Worker (x, i) => `((name like '${makeSqlFilter(x)}' escape '\\') << ${i})`, 201*6dbdd20aSAndroid Build Coastguard Worker ) 202*6dbdd20aSAndroid Build Coastguard Worker .join(' | '); 203*6dbdd20aSAndroid Build Coastguard Worker const showStackBits = (1 << showStackAndPivot.length) - 1; 204*6dbdd20aSAndroid Build Coastguard Worker 205*6dbdd20aSAndroid Build Coastguard Worker const hideStackFilter = 206*6dbdd20aSAndroid Build Coastguard Worker hideStack.length === 0 207*6dbdd20aSAndroid Build Coastguard Worker ? 'false' 208*6dbdd20aSAndroid Build Coastguard Worker : hideStack 209*6dbdd20aSAndroid Build Coastguard Worker .map((x) => `name like '${makeSqlFilter(x)}' escape '\\'`) 210*6dbdd20aSAndroid Build Coastguard Worker .join(' OR '); 211*6dbdd20aSAndroid Build Coastguard Worker 212*6dbdd20aSAndroid Build Coastguard Worker const showFromFrameFilter = 213*6dbdd20aSAndroid Build Coastguard Worker showFromFrame.length === 0 214*6dbdd20aSAndroid Build Coastguard Worker ? '0' 215*6dbdd20aSAndroid Build Coastguard Worker : showFromFrame 216*6dbdd20aSAndroid Build Coastguard Worker .map( 217*6dbdd20aSAndroid Build Coastguard Worker (x, i) => `((name like '${makeSqlFilter(x)}' escape '\\') << ${i})`, 218*6dbdd20aSAndroid Build Coastguard Worker ) 219*6dbdd20aSAndroid Build Coastguard Worker .join(' | '); 220*6dbdd20aSAndroid Build Coastguard Worker const showFromFrameBits = (1 << showFromFrame.length) - 1; 221*6dbdd20aSAndroid Build Coastguard Worker 222*6dbdd20aSAndroid Build Coastguard Worker const hideFrameFilter = 223*6dbdd20aSAndroid Build Coastguard Worker hideFrame.length === 0 224*6dbdd20aSAndroid Build Coastguard Worker ? 'false' 225*6dbdd20aSAndroid Build Coastguard Worker : hideFrame 226*6dbdd20aSAndroid Build Coastguard Worker .map((x) => `name like '${makeSqlFilter(x)}' escape '\\'`) 227*6dbdd20aSAndroid Build Coastguard Worker .join(' OR '); 228*6dbdd20aSAndroid Build Coastguard Worker 229*6dbdd20aSAndroid Build Coastguard Worker const pivotFilter = getPivotFilter(view); 230*6dbdd20aSAndroid Build Coastguard Worker 231*6dbdd20aSAndroid Build Coastguard Worker const unagg = unaggregatableProperties ?? []; 232*6dbdd20aSAndroid Build Coastguard Worker const unaggCols = unagg.map((x) => x.name); 233*6dbdd20aSAndroid Build Coastguard Worker 234*6dbdd20aSAndroid Build Coastguard Worker const agg = aggregatableProperties ?? []; 235*6dbdd20aSAndroid Build Coastguard Worker const aggCols = agg.map((x) => x.name); 236*6dbdd20aSAndroid Build Coastguard Worker 237*6dbdd20aSAndroid Build Coastguard Worker const groupingColumns = `(${(unaggCols.length === 0 ? ['groupingColumn'] : unaggCols).join()})`; 238*6dbdd20aSAndroid Build Coastguard Worker const groupedColumns = `(${(aggCols.length === 0 ? ['groupedColumn'] : aggCols).join()})`; 239*6dbdd20aSAndroid Build Coastguard Worker 240*6dbdd20aSAndroid Build Coastguard Worker if (dependencySql !== undefined) { 241*6dbdd20aSAndroid Build Coastguard Worker await engine.query(dependencySql); 242*6dbdd20aSAndroid Build Coastguard Worker } 243*6dbdd20aSAndroid Build Coastguard Worker await engine.query(`include perfetto module viz.flamegraph;`); 244*6dbdd20aSAndroid Build Coastguard Worker 245*6dbdd20aSAndroid Build Coastguard Worker const uuid = uuidv4Sql(); 246*6dbdd20aSAndroid Build Coastguard Worker await using disposable = new AsyncDisposableStack(); 247*6dbdd20aSAndroid Build Coastguard Worker 248*6dbdd20aSAndroid Build Coastguard Worker disposable.use( 249*6dbdd20aSAndroid Build Coastguard Worker await createPerfettoTable( 250*6dbdd20aSAndroid Build Coastguard Worker engine, 251*6dbdd20aSAndroid Build Coastguard Worker `_flamegraph_materialized_statement_${uuid}`, 252*6dbdd20aSAndroid Build Coastguard Worker statement, 253*6dbdd20aSAndroid Build Coastguard Worker ), 254*6dbdd20aSAndroid Build Coastguard Worker ); 255*6dbdd20aSAndroid Build Coastguard Worker disposable.use( 256*6dbdd20aSAndroid Build Coastguard Worker await createPerfettoIndex( 257*6dbdd20aSAndroid Build Coastguard Worker engine, 258*6dbdd20aSAndroid Build Coastguard Worker `_flamegraph_materialized_statement_${uuid}_index`, 259*6dbdd20aSAndroid Build Coastguard Worker `_flamegraph_materialized_statement_${uuid}(parentId)`, 260*6dbdd20aSAndroid Build Coastguard Worker ), 261*6dbdd20aSAndroid Build Coastguard Worker ); 262*6dbdd20aSAndroid Build Coastguard Worker 263*6dbdd20aSAndroid Build Coastguard Worker // TODO(lalitm): this doesn't need to be called unless we have 264*6dbdd20aSAndroid Build Coastguard Worker // a non-empty set of filters. 265*6dbdd20aSAndroid Build Coastguard Worker disposable.use( 266*6dbdd20aSAndroid Build Coastguard Worker await createPerfettoTable( 267*6dbdd20aSAndroid Build Coastguard Worker engine, 268*6dbdd20aSAndroid Build Coastguard Worker `_flamegraph_source_${uuid}`, 269*6dbdd20aSAndroid Build Coastguard Worker ` 270*6dbdd20aSAndroid Build Coastguard Worker select * 271*6dbdd20aSAndroid Build Coastguard Worker from _viz_flamegraph_prepare_filter!( 272*6dbdd20aSAndroid Build Coastguard Worker ( 273*6dbdd20aSAndroid Build Coastguard Worker select 274*6dbdd20aSAndroid Build Coastguard Worker s.id, 275*6dbdd20aSAndroid Build Coastguard Worker s.parentId, 276*6dbdd20aSAndroid Build Coastguard Worker s.name, 277*6dbdd20aSAndroid Build Coastguard Worker s.value, 278*6dbdd20aSAndroid Build Coastguard Worker ${(unaggCols.length === 0 279*6dbdd20aSAndroid Build Coastguard Worker ? [`'' as groupingColumn`] 280*6dbdd20aSAndroid Build Coastguard Worker : unaggCols.map((x) => `s.${x}`) 281*6dbdd20aSAndroid Build Coastguard Worker ).join()}, 282*6dbdd20aSAndroid Build Coastguard Worker ${(aggCols.length === 0 283*6dbdd20aSAndroid Build Coastguard Worker ? [`'' as groupedColumn`] 284*6dbdd20aSAndroid Build Coastguard Worker : aggCols.map((x) => `s.${x}`) 285*6dbdd20aSAndroid Build Coastguard Worker ).join()} 286*6dbdd20aSAndroid Build Coastguard Worker from _flamegraph_materialized_statement_${uuid} s 287*6dbdd20aSAndroid Build Coastguard Worker ), 288*6dbdd20aSAndroid Build Coastguard Worker (${showStackFilter}), 289*6dbdd20aSAndroid Build Coastguard Worker (${hideStackFilter}), 290*6dbdd20aSAndroid Build Coastguard Worker (${showFromFrameFilter}), 291*6dbdd20aSAndroid Build Coastguard Worker (${hideFrameFilter}), 292*6dbdd20aSAndroid Build Coastguard Worker (${pivotFilter}), 293*6dbdd20aSAndroid Build Coastguard Worker ${1 << showStackAndPivot.length}, 294*6dbdd20aSAndroid Build Coastguard Worker ${groupingColumns} 295*6dbdd20aSAndroid Build Coastguard Worker ) 296*6dbdd20aSAndroid Build Coastguard Worker `, 297*6dbdd20aSAndroid Build Coastguard Worker ), 298*6dbdd20aSAndroid Build Coastguard Worker ); 299*6dbdd20aSAndroid Build Coastguard Worker // TODO(lalitm): this doesn't need to be called unless we have 300*6dbdd20aSAndroid Build Coastguard Worker // a non-empty set of filters. 301*6dbdd20aSAndroid Build Coastguard Worker disposable.use( 302*6dbdd20aSAndroid Build Coastguard Worker await createPerfettoTable( 303*6dbdd20aSAndroid Build Coastguard Worker engine, 304*6dbdd20aSAndroid Build Coastguard Worker `_flamegraph_filtered_${uuid}`, 305*6dbdd20aSAndroid Build Coastguard Worker ` 306*6dbdd20aSAndroid Build Coastguard Worker select * 307*6dbdd20aSAndroid Build Coastguard Worker from _viz_flamegraph_filter_frames!( 308*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_source_${uuid}, 309*6dbdd20aSAndroid Build Coastguard Worker ${showFromFrameBits} 310*6dbdd20aSAndroid Build Coastguard Worker ) 311*6dbdd20aSAndroid Build Coastguard Worker `, 312*6dbdd20aSAndroid Build Coastguard Worker ), 313*6dbdd20aSAndroid Build Coastguard Worker ); 314*6dbdd20aSAndroid Build Coastguard Worker disposable.use( 315*6dbdd20aSAndroid Build Coastguard Worker await createPerfettoTable( 316*6dbdd20aSAndroid Build Coastguard Worker engine, 317*6dbdd20aSAndroid Build Coastguard Worker `_flamegraph_accumulated_${uuid}`, 318*6dbdd20aSAndroid Build Coastguard Worker ` 319*6dbdd20aSAndroid Build Coastguard Worker select * 320*6dbdd20aSAndroid Build Coastguard Worker from _viz_flamegraph_accumulate!( 321*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_filtered_${uuid}, 322*6dbdd20aSAndroid Build Coastguard Worker ${showStackBits} 323*6dbdd20aSAndroid Build Coastguard Worker ) 324*6dbdd20aSAndroid Build Coastguard Worker `, 325*6dbdd20aSAndroid Build Coastguard Worker ), 326*6dbdd20aSAndroid Build Coastguard Worker ); 327*6dbdd20aSAndroid Build Coastguard Worker disposable.use( 328*6dbdd20aSAndroid Build Coastguard Worker await createPerfettoTable( 329*6dbdd20aSAndroid Build Coastguard Worker engine, 330*6dbdd20aSAndroid Build Coastguard Worker `_flamegraph_hash_${uuid}`, 331*6dbdd20aSAndroid Build Coastguard Worker ` 332*6dbdd20aSAndroid Build Coastguard Worker select * 333*6dbdd20aSAndroid Build Coastguard Worker from _viz_flamegraph_downwards_hash!( 334*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_source_${uuid}, 335*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_filtered_${uuid}, 336*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_accumulated_${uuid}, 337*6dbdd20aSAndroid Build Coastguard Worker ${groupingColumns}, 338*6dbdd20aSAndroid Build Coastguard Worker ${groupedColumns}, 339*6dbdd20aSAndroid Build Coastguard Worker ${view.kind === 'BOTTOM_UP' ? 'FALSE' : 'TRUE'} 340*6dbdd20aSAndroid Build Coastguard Worker ) 341*6dbdd20aSAndroid Build Coastguard Worker union all 342*6dbdd20aSAndroid Build Coastguard Worker select * 343*6dbdd20aSAndroid Build Coastguard Worker from _viz_flamegraph_upwards_hash!( 344*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_source_${uuid}, 345*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_filtered_${uuid}, 346*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_accumulated_${uuid}, 347*6dbdd20aSAndroid Build Coastguard Worker ${groupingColumns}, 348*6dbdd20aSAndroid Build Coastguard Worker ${groupedColumns} 349*6dbdd20aSAndroid Build Coastguard Worker ) 350*6dbdd20aSAndroid Build Coastguard Worker order by hash 351*6dbdd20aSAndroid Build Coastguard Worker `, 352*6dbdd20aSAndroid Build Coastguard Worker ), 353*6dbdd20aSAndroid Build Coastguard Worker ); 354*6dbdd20aSAndroid Build Coastguard Worker disposable.use( 355*6dbdd20aSAndroid Build Coastguard Worker await createPerfettoTable( 356*6dbdd20aSAndroid Build Coastguard Worker engine, 357*6dbdd20aSAndroid Build Coastguard Worker `_flamegraph_merged_${uuid}`, 358*6dbdd20aSAndroid Build Coastguard Worker ` 359*6dbdd20aSAndroid Build Coastguard Worker select * 360*6dbdd20aSAndroid Build Coastguard Worker from _viz_flamegraph_merge_hashes!( 361*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_hash_${uuid}, 362*6dbdd20aSAndroid Build Coastguard Worker ${groupingColumns}, 363*6dbdd20aSAndroid Build Coastguard Worker ${computeGroupedAggExprs(agg)} 364*6dbdd20aSAndroid Build Coastguard Worker ) 365*6dbdd20aSAndroid Build Coastguard Worker `, 366*6dbdd20aSAndroid Build Coastguard Worker ), 367*6dbdd20aSAndroid Build Coastguard Worker ); 368*6dbdd20aSAndroid Build Coastguard Worker disposable.use( 369*6dbdd20aSAndroid Build Coastguard Worker await createPerfettoTable( 370*6dbdd20aSAndroid Build Coastguard Worker engine, 371*6dbdd20aSAndroid Build Coastguard Worker `_flamegraph_layout_${uuid}`, 372*6dbdd20aSAndroid Build Coastguard Worker ` 373*6dbdd20aSAndroid Build Coastguard Worker select * 374*6dbdd20aSAndroid Build Coastguard Worker from _viz_flamegraph_local_layout!( 375*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_merged_${uuid} 376*6dbdd20aSAndroid Build Coastguard Worker ); 377*6dbdd20aSAndroid Build Coastguard Worker `, 378*6dbdd20aSAndroid Build Coastguard Worker ), 379*6dbdd20aSAndroid Build Coastguard Worker ); 380*6dbdd20aSAndroid Build Coastguard Worker const res = await engine.query(` 381*6dbdd20aSAndroid Build Coastguard Worker select * 382*6dbdd20aSAndroid Build Coastguard Worker from _viz_flamegraph_global_layout!( 383*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_merged_${uuid}, 384*6dbdd20aSAndroid Build Coastguard Worker _flamegraph_layout_${uuid}, 385*6dbdd20aSAndroid Build Coastguard Worker ${groupingColumns}, 386*6dbdd20aSAndroid Build Coastguard Worker ${groupedColumns} 387*6dbdd20aSAndroid Build Coastguard Worker ) 388*6dbdd20aSAndroid Build Coastguard Worker `); 389*6dbdd20aSAndroid Build Coastguard Worker 390*6dbdd20aSAndroid Build Coastguard Worker const it = res.iter({ 391*6dbdd20aSAndroid Build Coastguard Worker id: NUM, 392*6dbdd20aSAndroid Build Coastguard Worker parentId: NUM, 393*6dbdd20aSAndroid Build Coastguard Worker depth: NUM, 394*6dbdd20aSAndroid Build Coastguard Worker name: STR, 395*6dbdd20aSAndroid Build Coastguard Worker selfValue: NUM, 396*6dbdd20aSAndroid Build Coastguard Worker cumulativeValue: NUM, 397*6dbdd20aSAndroid Build Coastguard Worker parentCumulativeValue: NUM_NULL, 398*6dbdd20aSAndroid Build Coastguard Worker xStart: NUM, 399*6dbdd20aSAndroid Build Coastguard Worker xEnd: NUM, 400*6dbdd20aSAndroid Build Coastguard Worker ...Object.fromEntries(unaggCols.map((m) => [m, STR_NULL])), 401*6dbdd20aSAndroid Build Coastguard Worker ...Object.fromEntries(aggCols.map((m) => [m, UNKNOWN])), 402*6dbdd20aSAndroid Build Coastguard Worker }); 403*6dbdd20aSAndroid Build Coastguard Worker let postiveRootsValue = 0; 404*6dbdd20aSAndroid Build Coastguard Worker let negativeRootsValue = 0; 405*6dbdd20aSAndroid Build Coastguard Worker let minDepth = 0; 406*6dbdd20aSAndroid Build Coastguard Worker let maxDepth = 0; 407*6dbdd20aSAndroid Build Coastguard Worker const nodes = []; 408*6dbdd20aSAndroid Build Coastguard Worker for (; it.valid(); it.next()) { 409*6dbdd20aSAndroid Build Coastguard Worker const properties = new Map<string, string>(); 410*6dbdd20aSAndroid Build Coastguard Worker for (const a of [...agg, ...unagg]) { 411*6dbdd20aSAndroid Build Coastguard Worker const r = it.get(a.name); 412*6dbdd20aSAndroid Build Coastguard Worker if (r !== null) { 413*6dbdd20aSAndroid Build Coastguard Worker properties.set(a.displayName, r as string); 414*6dbdd20aSAndroid Build Coastguard Worker } 415*6dbdd20aSAndroid Build Coastguard Worker } 416*6dbdd20aSAndroid Build Coastguard Worker nodes.push({ 417*6dbdd20aSAndroid Build Coastguard Worker id: it.id, 418*6dbdd20aSAndroid Build Coastguard Worker parentId: it.parentId, 419*6dbdd20aSAndroid Build Coastguard Worker depth: it.depth, 420*6dbdd20aSAndroid Build Coastguard Worker name: it.name, 421*6dbdd20aSAndroid Build Coastguard Worker selfValue: it.selfValue, 422*6dbdd20aSAndroid Build Coastguard Worker cumulativeValue: it.cumulativeValue, 423*6dbdd20aSAndroid Build Coastguard Worker parentCumulativeValue: it.parentCumulativeValue ?? undefined, 424*6dbdd20aSAndroid Build Coastguard Worker xStart: it.xStart, 425*6dbdd20aSAndroid Build Coastguard Worker xEnd: it.xEnd, 426*6dbdd20aSAndroid Build Coastguard Worker properties, 427*6dbdd20aSAndroid Build Coastguard Worker }); 428*6dbdd20aSAndroid Build Coastguard Worker if (it.depth === 1) { 429*6dbdd20aSAndroid Build Coastguard Worker postiveRootsValue += it.cumulativeValue; 430*6dbdd20aSAndroid Build Coastguard Worker } else if (it.depth === -1) { 431*6dbdd20aSAndroid Build Coastguard Worker negativeRootsValue += it.cumulativeValue; 432*6dbdd20aSAndroid Build Coastguard Worker } 433*6dbdd20aSAndroid Build Coastguard Worker minDepth = Math.min(minDepth, it.depth); 434*6dbdd20aSAndroid Build Coastguard Worker maxDepth = Math.max(maxDepth, it.depth); 435*6dbdd20aSAndroid Build Coastguard Worker } 436*6dbdd20aSAndroid Build Coastguard Worker const sumQuery = await engine.query( 437*6dbdd20aSAndroid Build Coastguard Worker `select sum(value) v from _flamegraph_source_${uuid}`, 438*6dbdd20aSAndroid Build Coastguard Worker ); 439*6dbdd20aSAndroid Build Coastguard Worker const unfilteredCumulativeValue = sumQuery.firstRow({v: NUM_NULL}).v ?? 0; 440*6dbdd20aSAndroid Build Coastguard Worker return { 441*6dbdd20aSAndroid Build Coastguard Worker nodes, 442*6dbdd20aSAndroid Build Coastguard Worker allRootsCumulativeValue: 443*6dbdd20aSAndroid Build Coastguard Worker view.kind === 'BOTTOM_UP' ? negativeRootsValue : postiveRootsValue, 444*6dbdd20aSAndroid Build Coastguard Worker unfilteredCumulativeValue, 445*6dbdd20aSAndroid Build Coastguard Worker minDepth, 446*6dbdd20aSAndroid Build Coastguard Worker maxDepth, 447*6dbdd20aSAndroid Build Coastguard Worker }; 448*6dbdd20aSAndroid Build Coastguard Worker} 449*6dbdd20aSAndroid Build Coastguard Worker 450*6dbdd20aSAndroid Build Coastguard Workerfunction makeSqlFilter(x: string) { 451*6dbdd20aSAndroid Build Coastguard Worker if (x.startsWith('^') && x.endsWith('$')) { 452*6dbdd20aSAndroid Build Coastguard Worker return x.slice(1, -1); 453*6dbdd20aSAndroid Build Coastguard Worker } 454*6dbdd20aSAndroid Build Coastguard Worker return `%${x}%`; 455*6dbdd20aSAndroid Build Coastguard Worker} 456*6dbdd20aSAndroid Build Coastguard Worker 457*6dbdd20aSAndroid Build Coastguard Workerfunction getPivotFilter(view: FlamegraphView) { 458*6dbdd20aSAndroid Build Coastguard Worker if (view.kind === 'PIVOT') { 459*6dbdd20aSAndroid Build Coastguard Worker return `name like '${makeSqlFilter(view.pivot)}'`; 460*6dbdd20aSAndroid Build Coastguard Worker } 461*6dbdd20aSAndroid Build Coastguard Worker if (view.kind === 'BOTTOM_UP') { 462*6dbdd20aSAndroid Build Coastguard Worker return 'value > 0'; 463*6dbdd20aSAndroid Build Coastguard Worker } 464*6dbdd20aSAndroid Build Coastguard Worker return '0'; 465*6dbdd20aSAndroid Build Coastguard Worker} 466*6dbdd20aSAndroid Build Coastguard Worker 467*6dbdd20aSAndroid Build Coastguard Workerfunction computeGroupedAggExprs(agg: ReadonlyArray<AggQueryFlamegraphColumn>) { 468*6dbdd20aSAndroid Build Coastguard Worker const aggFor = (x: AggQueryFlamegraphColumn) => { 469*6dbdd20aSAndroid Build Coastguard Worker switch (x.mergeAggregation) { 470*6dbdd20aSAndroid Build Coastguard Worker case 'ONE_OR_NULL': 471*6dbdd20aSAndroid Build Coastguard Worker return `IIF(COUNT() = 1, ${x.name}, NULL) AS ${x.name}`; 472*6dbdd20aSAndroid Build Coastguard Worker case 'SUM': 473*6dbdd20aSAndroid Build Coastguard Worker return `SUM(${x.name}) AS ${x.name}`; 474*6dbdd20aSAndroid Build Coastguard Worker } 475*6dbdd20aSAndroid Build Coastguard Worker }; 476*6dbdd20aSAndroid Build Coastguard Worker return `(${agg.length === 0 ? 'groupedColumn' : agg.map((x) => aggFor(x)).join(',')})`; 477*6dbdd20aSAndroid Build Coastguard Worker} 478