blob: 19712a104df1c81df80f0035a6a810ffe8389198 [file]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type {Engine} from '../../../trace_processor/engine';
import {
NUM,
STR_NULL,
type QueryResult,
} from '../../../trace_processor/query_result';
import {
ChartSource,
SQLChartLoader,
type QueryConfig,
type ChartLoaderResult,
inFilter,
} from './chart_sql_source';
import type {ChartAggregation} from './chart_utils';
import type {SankeyData, SankeyNode, SankeyLink} from './sankey';
/**
* Configuration for SQLSankeyLoader.
*/
export interface SQLSankeyLoaderOpts {
/** The trace processor engine to run queries against. */
readonly engine: Engine;
/**
* SQL query that provides the raw data.
* Must include source, target, and value columns.
*/
readonly query: string;
/** Column name for the source node of each link. */
readonly sourceColumn: string;
/** Column name for the target node of each link. */
readonly targetColumn: string;
/** Column name for the link value (measure). */
readonly valueColumn: string;
}
/**
* Per-use configuration for the sankey loader.
*/
export interface SankeyLoaderConfig {
/** Aggregation function to apply to the value column. Defaults to 'SUM'. */
readonly aggregation?: ChartAggregation;
/** Maximum number of links to return. Defaults to no limit. */
readonly limit?: number;
/** Filter to only include specific source nodes. */
readonly sourceFilter?: ReadonlyArray<string | number>;
/** Filter to only include specific target nodes. */
readonly targetFilter?: ReadonlyArray<string | number>;
}
/** Result returned by the sankey loader. */
export type SankeyLoaderResult = ChartLoaderResult<SankeyData>;
/**
* SQL-based sankey loader with async loading and caching.
*
* Builds a flat graph of nodes and weighted links from two dimensions
* (source, target) and one measure (value). Uses QuerySlot for caching
* and request deduplication.
*/
export class SQLSankeyLoader extends SQLChartLoader<
SankeyLoaderConfig,
SankeyData
> {
private readonly sourceColumn: string;
private readonly targetColumn: string;
private readonly valueColumn: string;
constructor(opts: SQLSankeyLoaderOpts) {
super(
opts.engine,
new ChartSource({
query: opts.query,
schema: {
[opts.sourceColumn]: 'text',
[opts.targetColumn]: 'text',
[opts.valueColumn]: 'real',
},
}),
);
this.sourceColumn = opts.sourceColumn;
this.targetColumn = opts.targetColumn;
this.valueColumn = opts.valueColumn;
}
protected buildQueryConfig(config: SankeyLoaderConfig): QueryConfig {
const aggregation = config.aggregation ?? 'SUM';
const filters = [
...inFilter(this.sourceColumn, config.sourceFilter),
...inFilter(this.targetColumn, config.targetFilter),
];
return {
type: 'aggregated',
dimensions: [
{column: this.sourceColumn, alias: '_source'},
{column: this.targetColumn, alias: '_target'},
],
measures: [{column: this.valueColumn, aggregation}],
filters,
limit: config.limit,
orderDirection: 'desc',
};
}
protected parseResult(queryResult: QueryResult): SankeyData {
const nodeSet = new Set<string>();
const links: SankeyLink[] = [];
const iter = queryResult.iter({
_source: STR_NULL,
_target: STR_NULL,
_value: NUM,
});
for (; iter.valid(); iter.next()) {
const source = iter._source ?? '(null)';
const target = iter._target ?? '(null)';
nodeSet.add(source);
nodeSet.add(target);
links.push({source, target, value: iter._value});
}
const nodes: SankeyNode[] = Array.from(nodeSet, (name) => ({name}));
return {nodes, links};
}
}