Use a shared async disposable to improve drilldown performance on initial load
diff --git a/ui/src/components/aggregation_adapter.ts b/ui/src/components/aggregation_adapter.ts index 46f9324..a361a79 100644 --- a/ui/src/components/aggregation_adapter.ts +++ b/ui/src/components/aggregation_adapter.ts
@@ -52,9 +52,10 @@ import {AsyncMemo, AtomicTaskQueue} from '../base/async_memo'; import {Memo} from '../base/memo'; import type {ColumnSchema} from './widgets/datagrid/datagrid_schema'; +import {SharedAsyncDisposable} from '../base/shared_disposable'; export interface AggregationData extends AsyncDisposable { - readonly tableName: string; + readonly sqlTable: DisposableSqlEntity; readonly barChartData?: ReadonlyArray<BarChartData>; } @@ -62,7 +63,7 @@ table: DisposableSqlEntity, ): AggregationData { return { - tableName: table.name, + sqlTable: table, [Symbol.asyncDispose]: () => table[Symbol.asyncDispose](), }; } @@ -230,6 +231,7 @@ interface PreparedAggregation extends AsyncDisposable { readonly data: AggregationData; readonly dataSource: SQLDataSource; + readonly sharedTable: SharedAsyncDisposable<DisposableSqlEntity>; } /** @@ -287,6 +289,7 @@ if (!currentAggregation) return undefined; const data = await currentAggregation.prepareData(trace.engine); + const sharedTable = SharedAsyncDisposable.wrap(data.sqlTable); const dataSource = createAggregationDataSource( trace, aggregator, @@ -296,9 +299,10 @@ return { data, dataSource, + sharedTable, [Symbol.asyncDispose]: async () => { dataSource.dispose(); - await data[Symbol.asyncDispose](); + await sharedTable[Symbol.asyncDispose](); }, }; }, @@ -324,7 +328,7 @@ }; } - const {data, dataSource} = preparedAggregation; + const {data, dataSource, sharedTable} = preparedAggregation; const dataGridState: DataGridState = { columns: dataModel.columns, @@ -348,20 +352,14 @@ ], pivot: undefined, }; - const drilldownSelection: AreaSelection = { - ...selection, - trackUris: [...selection.trackUris], - tracks: [...selection.tracks], - }; addEphemeralTab(trace, `aggregation_drilldown_${aggregator.id}`, { getTitle: () => `${aggregator.getTabName()} drill-down`, render: () => m(AggregationDrilldownPanel, { trace, aggregator, - aggregation: currentAggregation, - selection: drilldownSelection, - queue, + aggregationData: data, + sharedTable, initialDataModel, }), });
diff --git a/ui/src/components/aggregation_drilldown_panel.ts b/ui/src/components/aggregation_drilldown_panel.ts index 5f54663..c51c076 100644 --- a/ui/src/components/aggregation_drilldown_panel.ts +++ b/ui/src/components/aggregation_drilldown_panel.ts
@@ -13,17 +13,13 @@ // limitations under the License. import m from 'mithril'; -import {AsyncMemo, type AtomicTaskQueue} from '../base/async_memo'; -import type {AreaSelection} from '../public/selection'; +import {AtomicTaskQueue} from '../base/async_memo'; import type {Trace} from '../public/trace'; import {Button} from '../widgets/button'; import {DetailsShell} from '../widgets/details_shell'; -import {EmptyState} from '../widgets/empty_state'; import {ExportButton} from '../widgets/export_button'; import {Popup, PopupPosition} from '../widgets/popup'; -import {Spinner} from '../widgets/spinner'; import type { - Aggregation, AggregationData, Aggregator, DataGridState, @@ -33,6 +29,8 @@ import type {DataGridApi} from './widgets/datagrid/datagrid'; import type {Column, Filter, Pivot} from './widgets/datagrid/model'; import {SQLDataSource} from './widgets/datagrid/sql_data_source'; +import type {SharedAsyncDisposable} from '../base/shared_disposable'; +import type {DisposableSqlEntity} from '../trace_processor/sql_utils'; export interface DataGridModel { readonly columns?: readonly Column[]; @@ -40,11 +38,6 @@ readonly filters: readonly Filter[]; } -interface PreparedAggregation extends AsyncDisposable { - readonly data: AggregationData; - readonly dataSource: SQLDataSource; -} - export function createAggregationDataSource( trace: Trace, aggregator: Aggregator, @@ -53,7 +46,7 @@ ): SQLDataSource { const gridConfig = aggregator.getGridConfig(data); const sqlConfig = gridConfig.sqlConfig?.(data) ?? { - tableOrSubquery: data.tableName, + tableOrSubquery: data.sqlTable.name, }; return new SQLDataSource({ queue, @@ -65,71 +58,32 @@ interface AggregationDrilldownPanelAttrs { readonly trace: Trace; readonly aggregator: Aggregator; - readonly aggregation: Aggregation; - readonly selection: AreaSelection; - readonly queue: AtomicTaskQueue; readonly initialDataModel: DataGridModel; + readonly sharedTable: SharedAsyncDisposable<DisposableSqlEntity>; + readonly aggregationData: AggregationData; } export class AggregationDrilldownPanel implements m.ClassComponent<AggregationDrilldownPanelAttrs> { - private readonly preparedAggregationSlot: AsyncMemo<PreparedAggregation>; private readonly initialDataModel: DataGridModel; private dataModel: DataGridModel; private dataGridApi?: DataGridApi; + private table: SharedAsyncDisposable<DisposableSqlEntity>; + private datasource: SQLDataSource; constructor({attrs}: m.Vnode<AggregationDrilldownPanelAttrs>) { - this.preparedAggregationSlot = new AsyncMemo(attrs.queue); this.initialDataModel = attrs.initialDataModel; this.dataModel = attrs.initialDataModel; + this.table = attrs.sharedTable.clone(); + this.datasource = createAggregationDataSource( + attrs.trace, + attrs.aggregator, + attrs.aggregationData, + new AtomicTaskQueue(), + ); } view({attrs}: m.Vnode<AggregationDrilldownPanelAttrs>): m.Children { - const {trace, aggregator, aggregation, selection, queue} = attrs; - const preparedAggregation = this.preparedAggregationSlot.use({ - key: { - start: selection.start, - end: selection.end, - trackUris: selection.trackUris, - }, - compute: async () => { - const data = await aggregation.prepareData(trace.engine); - const dataSource = createAggregationDataSource( - trace, - aggregator, - data, - queue, - ); - return { - data, - dataSource, - [Symbol.asyncDispose]: async () => { - dataSource.dispose(); - await data[Symbol.asyncDispose](); - }, - }; - }, - }).data; - - if (!preparedAggregation) { - return m( - DetailsShell, - { - title: 'Area Selection Drill-down', - description: aggregator.getTabName(), - }, - m( - EmptyState, - { - icon: 'mediation', - title: 'Computing aggregation ...', - className: 'pf-aggregation-loading', - }, - m(Spinner, {easing: true}), - ), - ); - } - - const {data, dataSource} = preparedAggregation; + const {trace, aggregator, aggregationData} = attrs; const dataGridState: DataGridState = { columns: this.dataModel.columns, pivot: this.dataModel.pivot, @@ -150,13 +104,12 @@ { title: 'Area Selection Drill-down', description: aggregator.getTabName(), - buttons: this.renderButtons(trace, dataSource), + buttons: this.renderButtons(trace, this.datasource), }, m(AggregationPanel, { controls: aggregator.renderTopbarControls?.(), - dataSource, - gridConfig: aggregator.getGridConfig(data), - barChartData: data.barChartData, + dataSource: this.datasource, + gridConfig: aggregator.getGridConfig(aggregationData), onReady: (api: DataGridApi) => { this.dataGridApi = api; }, @@ -214,6 +167,6 @@ } onremove(): void { - this.preparedAggregationSlot.dispose(); + this.table[Symbol.asyncDispose](); } }
diff --git a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/slice_selection_aggregator.ts b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/slice_selection_aggregator.ts index b9b6f6d..6fe8b07 100644 --- a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/slice_selection_aggregator.ts +++ b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/slice_selection_aggregator.ts
@@ -380,8 +380,8 @@ }, // The aggregation table has an `arg_set_id` column, so we can expose a // parameterized `args.*` column to the datagrid. - sqlConfig: ({tableName}): SQLTableSchema => ({ - tableOrSubquery: tableName, + sqlConfig: ({sqlTable}): SQLTableSchema => ({ + tableOrSubquery: sqlTable.name, columns: { args: { expression: (alias, key) =>