blob: 965072083ee32ea0c980c6fe8943a3d0bc3c3de5 [file]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use size 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 {Time, type time} from '../../base/time';
import type {Engine} from '../../trace_processor/engine';
import {LONG, NUM, STR_NULL} from '../../trace_processor/query_result';
import {asSliceSqlId, type SliceSqlId} from '../sql_utils/core_types';
export interface FlowRow {
readonly id: number;
readonly sliceId: SliceSqlId;
readonly sliceName: string;
readonly sliceChromeCustomName?: string;
readonly sliceStartTs: time;
readonly sliceEndTs: time;
readonly threadName: string;
readonly processName: string;
}
export interface DirectlyConnectedFlows {
readonly preceding: readonly FlowRow[];
readonly following: readonly FlowRow[];
}
export async function getConnectedFlows(
engine: Engine,
sliceId: number,
): Promise<DirectlyConnectedFlows> {
const query = `
INCLUDE PERFETTO MODULE slices.flow;
select
f.id as id,
f.slice_out as sliceOut,
f.slice_in as sliceIn,
t.id as otherSliceId,
t.name as otherSliceName,
t.ts as otherSliceStartTs,
(t.ts + t.dur) as otherSliceEndTs,
(thread.name || ' ' || thread.tid) as otherThreadName,
(process.name || ' ' || process.pid) as otherProcessName
from directly_connected_flow(${sliceId}) f
join slice t on (case when f.slice_in = ${sliceId} then f.slice_out else f.slice_in end) = t.id
left join thread_track track on track.id = t.track_id
left join thread using (utid)
left join process using (upid)
`;
const result = await engine.query(query);
const preceding: FlowRow[] = [];
const following: FlowRow[] = [];
const it = result.iter({
id: NUM,
sliceOut: NUM,
sliceIn: NUM,
otherSliceId: NUM,
otherSliceName: STR_NULL,
otherSliceStartTs: LONG,
otherSliceEndTs: LONG,
otherThreadName: STR_NULL,
otherProcessName: STR_NULL,
});
const nullToStr = (s: null | string): string => (s === null ? 'NULL' : s);
for (; it.valid(); it.next()) {
// Only include flows directly connected to the slice (one hop),
// not the full transitive closure from directly_connected_flow.
if (it.sliceIn !== sliceId && it.sliceOut !== sliceId) {
continue;
}
const row: FlowRow = {
id: it.id,
sliceId: asSliceSqlId(it.otherSliceId),
sliceName: nullToStr(it.otherSliceName),
sliceStartTs: Time.fromRaw(it.otherSliceStartTs),
sliceEndTs: Time.fromRaw(it.otherSliceEndTs),
threadName: nullToStr(it.otherThreadName),
processName: nullToStr(it.otherProcessName),
};
if (it.sliceIn === sliceId) {
preceding.push(row);
} else {
following.push(row);
}
}
return {preceding, following};
}