diff --git a/ui/src/components/details/connected_flows.ts b/ui/src/components/details/connected_flows.ts new file mode 100644 index 00000000000..a6870352eb0 --- /dev/null +++ b/ui/src/components/details/connected_flows.ts @@ -0,0 +1,97 @@ +// 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 { + 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()) { + 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}; +} diff --git a/ui/src/components/details/thread_slice_details_tab.ts b/ui/src/components/details/thread_slice_details_tab.ts index d29ecbacb90..b15305cc83c 100644 --- a/ui/src/components/details/thread_slice_details_tab.ts +++ b/ui/src/components/details/thread_slice_details_tab.ts @@ -23,7 +23,6 @@ import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout'; import {MenuItem, PopupMenu} from '../../widgets/menu'; import {Section} from '../../widgets/section'; import {Tree} from '../../widgets/tree'; -import type {FlowPoint} from '../../core/flow_types'; import {hasArgs} from './args'; import { type DistributionScope, @@ -40,12 +39,10 @@ import { import {asSliceSqlId} from '../sql_utils/core_types'; import {DurationWidget} from '../widgets/duration'; import {Grid, GridCell, GridHeaderCell} from '../../widgets/grid'; -import {ensureIsInstance} from '../../base/assert'; import type {Trace} from '../../public/trace'; import type {TrackEventDetailsPanel} from '../../public/details_panel'; import type {TrackEventSelection} from '../../public/selection'; import {extensions} from '../extensions'; -import {TraceImpl} from '../../core/trace_impl'; import {renderSliceArguments} from './slice_args'; import {SLICE_TABLE} from '../widgets/sql/table_definitions'; import {TrackEventRef} from '../widgets/track_event_ref'; @@ -59,6 +56,11 @@ import { titleWithHelp, } from '../distribution_panel'; import type {Dataset} from '../../trace_processor/dataset'; +import { + type DirectlyConnectedFlows, + type FlowRow, + getConnectedFlows, +} from './connected_flows'; interface ContextMenuItem { name: string; @@ -221,17 +223,18 @@ export interface ThreadSliceDetailsPanelAttrs { export class ThreadSliceDetailsPanel implements TrackEventDetailsPanel { private sliceDetails?: SliceDetails; private breakdownByThreadState?: BreakdownByThreadState; + private connectedFlows: DirectlyConnectedFlows = { + preceding: [], + following: [], + }; private distributionLoaded = false; private distributionScope: DistributionScope = 'track'; private cachedWholeTraceDataset?: {dataset: Dataset | undefined}; - private readonly trace: TraceImpl; + private readonly trace: Trace; private readonly attrs: ThreadSliceDetailsPanelAttrs; constructor(trace: Trace, attrs?: ThreadSliceDetailsPanelAttrs) { - // Rationale for the assertIsInstance: ThreadSliceDetailsPanel requires a - // TraceImpl (because of flows) but here we must take a Trace interface, - // because this track is exposed to plugins (which see only Trace). - this.trace = ensureIsInstance(trace, TraceImpl); + this.trace = trace; this.attrs = attrs ?? {}; } @@ -240,16 +243,15 @@ export class ThreadSliceDetailsPanel implements TrackEventDetailsPanel { const {eventId} = selection; const details = await getSliceDetails(trace.engine, eventId); - if ( - details !== undefined && - details.thread !== undefined && - details.dur > 0 - ) { - this.breakdownByThreadState = await breakDownIntervalByThreadState( - trace.engine, - TimeSpan.fromTimeAndDuration(details.ts, details.dur), - details.thread.utid, - ); + if (details !== undefined) { + this.connectedFlows = await getConnectedFlows(trace.engine, details.id); + if (details.thread !== undefined && details.dur > 0) { + this.breakdownByThreadState = await breakDownIntervalByThreadState( + trace.engine, + TimeSpan.fromTimeAndDuration(details.ts, details.dur), + details.thread.utid, + ); + } } this.sliceDetails = details; @@ -335,8 +337,7 @@ export class ThreadSliceDetailsPanel implements TrackEventDetailsPanel { } private renderPrecedingFlows(slice: SliceDetails): m.Children { - const flows = this.trace.flows.connectedFlows; - const inFlows = flows.filter(({end}) => end.sliceId === slice.id); + const inFlows = this.connectedFlows.preceding; if (inFlows.length > 0) { const isRunTask = @@ -358,18 +359,18 @@ export class ThreadSliceDetailsPanel implements TrackEventDetailsPanel { m(TrackEventRef, { trace: this.trace, table: 'slice', - id: flow.begin.sliceId, - name: flow.begin.sliceChromeCustomName ?? flow.begin.sliceName, + id: flow.sliceId, + name: flow.sliceChromeCustomName ?? flow.sliceName, }), ), m( GridCell, m(DurationWidget, { trace: this.trace, - dur: flow.end.sliceStartTs - flow.begin.sliceEndTs, + dur: slice.ts - flow.sliceEndTs, }), ), - m(GridCell, this.getThreadNameForFlow(flow.begin, !isRunTask)), + m(GridCell, this.getThreadNameForFlow(flow, !isRunTask)), ]), }), ); @@ -379,8 +380,7 @@ export class ThreadSliceDetailsPanel implements TrackEventDetailsPanel { } private renderFollowingFlows(slice: SliceDetails): m.Children { - const flows = this.trace.flows.connectedFlows; - const outFlows = flows.filter(({begin}) => begin.sliceId === slice.id); + const outFlows = this.connectedFlows.following; if (outFlows.length > 0) { const isPostTask = @@ -402,18 +402,18 @@ export class ThreadSliceDetailsPanel implements TrackEventDetailsPanel { m(TrackEventRef, { trace: this.trace, table: 'slice', - id: flow.end.sliceId, - name: flow.end.sliceChromeCustomName ?? flow.end.sliceName, + id: flow.sliceId, + name: flow.sliceChromeCustomName ?? flow.sliceName, }), ), m( GridCell, m(DurationWidget, { trace: this.trace, - dur: flow.end.sliceStartTs - flow.begin.sliceEndTs, + dur: flow.sliceStartTs - (slice.ts + slice.dur), }), ), - m(GridCell, this.getThreadNameForFlow(flow.end, !isPostTask)), + m(GridCell, this.getThreadNameForFlow(flow, !isPostTask)), ]), }), ); @@ -423,7 +423,7 @@ export class ThreadSliceDetailsPanel implements TrackEventDetailsPanel { } private getThreadNameForFlow( - flow: FlowPoint, + flow: FlowRow, includeProcessName: boolean, ): string { return includeProcessName diff --git a/ui/src/core/embedder/default_plugins.ts b/ui/src/core/embedder/default_plugins.ts index 0008eac4cc9..4370fd8137d 100644 --- a/ui/src/core/embedder/default_plugins.ts +++ b/ui/src/core/embedder/default_plugins.ts @@ -64,7 +64,7 @@ export const defaultPlugins = [ 'dev.perfetto.ExtensionServers', 'dev.perfetto.FlagsPage', 'dev.perfetto.FirefoxProfilerMarkers', - 'dev.perfetto.FlowEventsPanel', + 'dev.perfetto.FlowEvents', 'dev.perfetto.Frames', 'dev.perfetto.Ftrace', 'dev.perfetto.GlobalGroups', diff --git a/ui/src/core/trace_impl.ts b/ui/src/core/trace_impl.ts index c9a357881ef..3297b913165 100644 --- a/ui/src/core/trace_impl.ts +++ b/ui/src/core/trace_impl.ts @@ -32,7 +32,6 @@ import type {SidebarMenuItem} from '../public/sidebar'; import {ScrollHelper} from './scroll_helper'; import type {Selection, SelectionOpts} from '../public/selection'; import type {SearchResult} from '../public/search'; -import {FlowManager} from './flow_manager'; import type {AppImpl, OpenTraceArrayBufArgs} from './app_impl'; import type {PluginManagerImpl} from './plugin_manager'; import type {RouteArgs} from '../public/route_schema'; @@ -76,7 +75,6 @@ export class TraceImpl implements Trace, Disposable { readonly tracks = new TrackManagerImpl(); readonly workspaces = new WorkspaceManagerImpl(); readonly notes = new NoteManagerImpl(); - readonly flows: FlowManager; readonly scrollHelper: ScrollHelper; readonly trash = new DisposableStack(); readonly onTraceReady = new EvtSource(); @@ -126,12 +124,6 @@ export class TraceImpl implements Trace, Disposable { } }; - this.flows = new FlowManager( - engine.getProxy('FlowManager'), - this.tracks, - this.selection, - ); - this.search = new SearchManagerImpl({ timeline: this.timeline, trackManager: this.tracks, @@ -209,8 +201,6 @@ export class TraceImpl implements Trace, Disposable { if (switchToCurrentSelectionTab && selection.kind !== 'empty') { this.tabs.showCurrentSelectionTab(); } - - this.flows.updateFlows(selection); } private onResultStep(searchResult: SearchResult) { diff --git a/ui/src/core_plugins/dev.perfetto.CoreCommands/index.ts b/ui/src/core_plugins/dev.perfetto.CoreCommands/index.ts index 3c7e62df006..5683a644286 100644 --- a/ui/src/core_plugins/dev.perfetto.CoreCommands/index.ts +++ b/ui/src/core_plugins/dev.perfetto.CoreCommands/index.ts @@ -617,34 +617,6 @@ export default class CoreCommands implements PerfettoPlugin { defaultHotkey: 'Escape', }); - ctx.commands.registerCommand({ - id: 'dev.perfetto.NextFlow', - name: 'Next flow', - callback: () => ctx.flows.focusOtherFlow('Forward'), - defaultHotkey: 'Mod+]', - }); - - ctx.commands.registerCommand({ - id: 'dev.perfetto.PrevFlow', - name: 'Prev flow', - callback: () => ctx.flows.focusOtherFlow('Backward'), - defaultHotkey: 'Mod+[', - }); - - ctx.commands.registerCommand({ - id: 'dev.perfetto.MoveNextFlow', - name: 'Move next flow', - callback: () => ctx.flows.moveByFocusedFlow('Forward'), - defaultHotkey: ']', - }); - - ctx.commands.registerCommand({ - id: 'dev.perfetto.MovePrevFlow', - name: 'Move prev flow', - callback: () => ctx.flows.moveByFocusedFlow('Backward'), - defaultHotkey: '[', - }); - // Provides a test bed for resolving events using a SQL table name and ID // which is used in deep-linking, amongst other places. ctx.commands.registerCommand({ diff --git a/ui/src/core_plugins/dev.perfetto.FlowEventsPanel/flow_events_panel.ts b/ui/src/core_plugins/dev.perfetto.FlowEvents/flow_events_panel.ts similarity index 96% rename from ui/src/core_plugins/dev.perfetto.FlowEventsPanel/flow_events_panel.ts rename to ui/src/core_plugins/dev.perfetto.FlowEvents/flow_events_panel.ts index 87be6f8f5ca..f7edb3b858c 100644 --- a/ui/src/core_plugins/dev.perfetto.FlowEventsPanel/flow_events_panel.ts +++ b/ui/src/core_plugins/dev.perfetto.FlowEvents/flow_events_panel.ts @@ -18,11 +18,13 @@ import {Checkbox} from '../../widgets/checkbox'; import {Intent} from '../../widgets/common'; import {Icon} from '../../widgets/icon'; import {Tooltip} from '../../widgets/tooltip'; -import {ALL_CATEGORIES, getFlowCategories} from '../../core/flow_types'; +import {ALL_CATEGORIES, getFlowCategories} from './flow_types'; +import type {FlowManager} from './flow_manager'; import type {TraceImpl} from '../../core/trace_impl'; export interface FlowEventsAreaSelectedPanelAttrs { trace: TraceImpl; + flows: FlowManager; } export class FlowEventsAreaSelectedPanel implements m.ClassComponent { @@ -32,8 +34,7 @@ export class FlowEventsAreaSelectedPanel implements m.ClassComponent(); diff --git a/ui/src/core_plugins/dev.perfetto.Timeline/flow_events_renderer.ts b/ui/src/core_plugins/dev.perfetto.FlowEvents/flow_events_renderer.ts similarity index 93% rename from ui/src/core_plugins/dev.perfetto.Timeline/flow_events_renderer.ts rename to ui/src/core_plugins/dev.perfetto.FlowEvents/flow_events_renderer.ts index 785bd8145b3..62480c39c82 100644 --- a/ui/src/core_plugins/dev.perfetto.Timeline/flow_events_renderer.ts +++ b/ui/src/core_plugins/dev.perfetto.FlowEvents/flow_events_renderer.ts @@ -25,7 +25,8 @@ import { ALL_CATEGORIES, type Flow, getFlowCategories, -} from '../../core/flow_types'; +} from './flow_types'; +import type {FlowManager} from './flow_manager'; import type {TraceImpl} from '../../core/trace_impl'; import type {TrackNode} from '../../public/workspace'; @@ -56,10 +57,11 @@ export interface TrackInfo { * Renders the flows overlay on top of the timeline, given the set of panels and * a canvas to draw on. * - * Note: the actual flow data is retrieved from trace.flows, which are produced + * Note: the actual flow data is retrieved from flows, which are produced * by FlowManager. * - * @param trace - The Trace instance, which holds onto the FlowManager. + * @param trace - The Trace instance. + * @param flows - The FlowManager instance. * @param ctx - The canvas to draw on. * @param size - The size of the canvas. * @param tracks - A list of tracks and their vertical positions on the canvas. @@ -71,6 +73,7 @@ export interface TrackInfo { */ export function renderFlows( trace: TraceImpl, + flows: FlowManager, ctx: CanvasRenderingContext2D, size: Size2D, tracks: ReadonlyArray, @@ -107,8 +110,8 @@ export function renderFlows( flow.end.sliceId === trace.timeline.highlightedSliceId || flow.begin.sliceId === trace.timeline.highlightedSliceId; const focused = - flow.id === trace.flows.focusedFlowIdLeft || - flow.id === trace.flows.focusedFlowIdRight; + flow.id === flows.focusedFlowIdLeft || + flow.id === flows.focusedFlowIdRight; let intensity = DEFAULT_FLOW_INTENSITY; let width = DEFAULT_FLOW_WIDTH; @@ -188,17 +191,17 @@ export function renderFlows( }; // Render the connected flows - trace.flows.connectedFlows.forEach((flow) => { + flows.connectedFlows.forEach((flow) => { drawFlow(flow, CONNECTED_FLOW_HUE); }); // Render the selected flows - trace.flows.selectedFlows.forEach((flow) => { + flows.selectedFlows.forEach((flow) => { const categories = getFlowCategories(flow); for (const cat of categories) { if ( - trace.flows.visibleCategories.get(cat) || - trace.flows.visibleCategories.get(ALL_CATEGORIES) + flows.visibleCategories.get(cat) || + flows.visibleCategories.get(ALL_CATEGORIES) ) { drawFlow(flow, SELECTED_FLOW_HUE); break; diff --git a/ui/src/core/flow_manager.ts b/ui/src/core_plugins/dev.perfetto.FlowEvents/flow_manager.ts similarity index 95% rename from ui/src/core/flow_manager.ts rename to ui/src/core_plugins/dev.perfetto.FlowEvents/flow_manager.ts index d13018b0fb4..b153ecc0200 100644 --- a/ui/src/core/flow_manager.ts +++ b/ui/src/core_plugins/dev.perfetto.FlowEvents/flow_manager.ts @@ -12,18 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {Time} from '../base/time'; -import {featureFlags} from './feature_flags'; +import {Time} from '../../base/time'; +import {featureFlags} from '../../core/feature_flags'; import type {FlowDirection, Flow} from './flow_types'; -import {asSliceSqlId} from '../components/sql_utils/core_types'; -import {LONG, NUM, STR_NULL} from '../trace_processor/query_result'; -import type {Track, TrackManager} from '../public/track'; +import {asSliceSqlId} from '../../components/sql_utils/core_types'; +import {LONG, NUM, STR_NULL} from '../../trace_processor/query_result'; +import type {Track, TrackManager} from '../../public/track'; import type { AreaSelection, Selection, SelectionManager, -} from '../public/selection'; -import type {Engine} from '../trace_processor/engine'; +} from '../../public/selection'; +import type {Engine} from '../../trace_processor/engine'; +import type {Raf} from '../../public/raf'; const SHOW_INDIRECT_PRECEDING_FLOWS_FLAG = featureFlags.register({ id: 'showIndirectPrecedingFlows', @@ -47,6 +48,7 @@ export class FlowManager { private engine: Engine, private trackMgr: TrackManager, private selectionMgr: SelectionManager, + private raf?: Raf, ) {} // TODO(primiano): the only reason why this is not done in the constructor is @@ -75,10 +77,10 @@ export class FlowManager { when name="ThreadControllerImpl::RunTask" or name="ThreadPool_RunTask" then printf("RunTask(posted_from=%s:%s)", - EXTRACT_ARG(arg_set_id, "task.posted_from.file_name"), - EXTRACT_ARG(arg_set_id, "task.posted_from.function_name")) - end - from slice where id=$slice_id' + EXTRACT_ARG(arg_set_id, "task.posted_from.file_name"), + EXTRACT_ARG(arg_set_id, "task.posted_from.function_name")) + end + from slice where id=$slice_id' );`); } @@ -448,13 +450,18 @@ export class FlowManager { } } } + this.raf?.scheduleCanvasRedraw(); } private setSelectedFlows(selectedFlows: Flow[]) { this._selectedFlows = selectedFlows; + this.raf?.scheduleCanvasRedraw(); } updateFlows(selection: Selection) { + if (this._curSelection === selection) { + return; + } this.initialize(); this._curSelection = selection; @@ -477,7 +484,7 @@ export class FlowManager { if (selection.kind === 'area') { this.areaSelected(selection); } else { - this.setConnectedFlows([]); + this.setSelectedFlows([]); } } diff --git a/ui/src/core/flow_types.ts b/ui/src/core_plugins/dev.perfetto.FlowEvents/flow_types.ts similarity index 94% rename from ui/src/core/flow_types.ts rename to ui/src/core_plugins/dev.perfetto.FlowEvents/flow_types.ts index d5f73e2fc44..9f97a933e65 100644 --- a/ui/src/core/flow_types.ts +++ b/ui/src/core_plugins/dev.perfetto.FlowEvents/flow_types.ts @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type {time, duration} from '../base/time'; -import type {SliceSqlId} from '../components/sql_utils/core_types'; +import type {time, duration} from '../../base/time'; +import type {SliceSqlId} from '../../components/sql_utils/core_types'; export interface Flow { id: number; diff --git a/ui/src/core_plugins/dev.perfetto.FlowEvents/index.ts b/ui/src/core_plugins/dev.perfetto.FlowEvents/index.ts new file mode 100644 index 00000000000..9692b55fca8 --- /dev/null +++ b/ui/src/core_plugins/dev.perfetto.FlowEvents/index.ts @@ -0,0 +1,99 @@ +// 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 m from 'mithril'; +import type {TraceImpl} from '../../core/trace_impl'; +import type {PerfettoPlugin} from '../../public/plugin'; +import {FlowEventsAreaSelectedPanel} from './flow_events_panel'; +import {renderFlows} from './flow_events_renderer'; +import {FlowManager} from './flow_manager'; + +// Any track that wants to use this plugin to render flows should +export const FLOWS_SLICE_TRACK = 'flows_slice_track'; + +export default class FlowEventsPlugin implements PerfettoPlugin { + static readonly id = 'dev.perfetto.FlowEvents'; + static readonly description = + 'Handles rendering flows on top of slice tracks originating from the ' + + 'slice table, and for the flows area selection panel.'; + + async onTraceLoad(trace: TraceImpl): Promise { + const flows = new FlowManager( + trace.engine, + trace.tracks, + trace.selection, + trace.raf, + ); + + trace.tracks.registerOverlay({ + render(ctx, timescale, size, tracks) { + flows.updateFlows(trace.selection.selection); + renderFlows( + trace, + flows, + ctx, + size, + tracks, + trace.workspaces.currentWorkspace.tracks, + timescale, + ); + }, + }); + + // This type assertion is allowed because we're a core plugin. + trace.selection.registerAreaSelectionTab({ + id: 'flow_events', + name: 'Flow Events', + priority: -100, + render() { + if (flows.selectedFlows.length > 0) { + return { + isLoading: false, + content: m(FlowEventsAreaSelectedPanel, {trace, flows}), + }; + } else { + return undefined; + } + }, + }); + + trace.commands.registerCommand({ + id: 'dev.perfetto.NextFlow', + name: 'Next flow', + callback: () => flows.focusOtherFlow('Forward'), + defaultHotkey: 'Mod+]', + }); + + trace.commands.registerCommand({ + id: 'dev.perfetto.PrevFlow', + name: 'Prev flow', + callback: () => flows.focusOtherFlow('Backward'), + defaultHotkey: 'Mod+[', + }); + + trace.commands.registerCommand({ + id: 'dev.perfetto.MoveNextFlow', + name: 'Move next flow', + callback: () => flows.moveByFocusedFlow('Forward'), + defaultHotkey: ']', + }); + + trace.commands.registerCommand({ + id: 'dev.perfetto.MovePrevFlow', + name: 'Move prev flow', + callback: () => flows.moveByFocusedFlow('Backward'), + defaultHotkey: '[', + }); + } +} diff --git a/ui/src/core_plugins/dev.perfetto.FlowEventsPanel/index.ts b/ui/src/core_plugins/dev.perfetto.FlowEventsPanel/index.ts deleted file mode 100644 index 89d46266904..00000000000 --- a/ui/src/core_plugins/dev.perfetto.FlowEventsPanel/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2025 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 m from 'mithril'; -import type {PerfettoPlugin} from '../../public/plugin'; -import type {TraceImpl} from '../../core/trace_impl'; -import {FlowEventsAreaSelectedPanel} from './flow_events_panel'; - -/** - * This plugin is a core plugin because for now flows are stored in the core and - * not exposed to plugins. In the future once we normalize how flows should - * work, we can reassess this and move it into wherever it needs to be. - */ -export default class implements PerfettoPlugin { - static readonly id = 'dev.perfetto.FlowEventsPanel'; - - async onTraceLoad(trace: TraceImpl): Promise { - // This type assertion is allowed because we're a core plugin. - trace.selection.registerAreaSelectionTab({ - id: 'flow_events', - name: 'Flow Events', - priority: -100, - render() { - if (trace.flows.selectedFlows.length > 0) { - return { - isLoading: false, - content: m(FlowEventsAreaSelectedPanel, {trace}), - }; - } else { - return undefined; - } - }, - }); - } -} diff --git a/ui/src/core_plugins/dev.perfetto.Timeline/track_tree_view.ts b/ui/src/core_plugins/dev.perfetto.Timeline/track_tree_view.ts index e611d9a879d..8c85aa14bb9 100644 --- a/ui/src/core_plugins/dev.perfetto.Timeline/track_tree_view.ts +++ b/ui/src/core_plugins/dev.perfetto.Timeline/track_tree_view.ts @@ -60,7 +60,6 @@ import { COLOR_TIMELINE_OVERLAY, TRACK_SHELL_WIDTH, } from '../../frontend/css_constants'; -import {renderFlows} from './flow_events_renderer'; import {generateTicks, getMaxMajorTicks, TickType} from './gridline_helper'; import { shiftDragPanInteraction, @@ -341,7 +340,6 @@ export class TrackTreeView implements m.ClassComponent { virtualCanvasSize, renderedTracks, canvasRect, - rootNode, renderer, ); @@ -429,7 +427,6 @@ export class TrackTreeView implements m.ClassComponent { size: Size2D, renderedTracks: ReadonlyArray, floatingCanvasRect: Rect2D, - rootNode: TrackNode, renderer: Renderer, ) { const timelineRect = new Rect2D({ @@ -480,7 +477,6 @@ export class TrackTreeView implements m.ClassComponent { renderer, ); - renderFlows(this.trace, ctx, size, renderedTracks, rootNode, timescale); this.drawHoveredNoteVertical(ctx, timescale, size); this.drawHoveredCursorVertical(ctx, timescale, size); this.drawNoteVerticals(ctx, timescale, size);