// Copyright (C) 2024 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 {NUM, STR_NULL} from '../../trace_processor/query_result'; import {AsyncSliceTrack} from '../dev.perfetto.AsyncSlices/async_slice_track'; import {PerfettoPlugin} from '../../public/plugin'; import {Trace} from '../../public/trace'; import {TrackNode} from '../../public/workspace'; import {SLICE_TRACK_KIND} from '../../public/track_kinds'; import {SuspendResumeDetailsPanel} from './suspend_resume_details'; import {Slice} from '../../public/track'; import {OnSliceClickArgs} from '../../components/tracks/base_slice_track'; import {ThreadMap} from '../dev.perfetto.Thread/threads'; import ThreadPlugin from '../dev.perfetto.Thread'; import AsyncSlicesPlugin from '../dev.perfetto.AsyncSlices'; // SuspendResumeSliceTrack exists so as to override the `onSliceClick` function // in AsyncSliceTrack. // TODO(stevegolton): Remove this? class SuspendResumeSliceTrack extends AsyncSliceTrack { constructor( trace: Trace, uri: string, maxDepth: number, trackIds: number[], private readonly threads: ThreadMap, ) { super(trace, uri, maxDepth, trackIds); } onSliceClick(args: OnSliceClickArgs) { this.trace.selection.selectTrackEvent(this.uri, args.slice.id); } override detailsPanel() { return new SuspendResumeDetailsPanel(this.trace, this.threads); } } export default class implements PerfettoPlugin { static readonly id = 'org.kernel.SuspendResumeLatency'; static readonly dependencies = [ThreadPlugin, AsyncSlicesPlugin]; async onTraceLoad(ctx: Trace): Promise { const threads = ctx.plugins.getPlugin(ThreadPlugin).getThreadMap(); const {engine} = ctx; const rawGlobalAsyncTracks = await engine.query(` with global_tracks_grouped as ( select name, group_concat(distinct t.id) as trackIds, count() as trackCount from track t where t.name = "Suspend/Resume Latency" ) select t.trackIds as trackIds, case when t.trackCount > 0 then __max_layout_depth(t.trackCount, t.trackIds) else 0 end as maxDepth from global_tracks_grouped t `); const it = rawGlobalAsyncTracks.iter({ trackIds: STR_NULL, maxDepth: NUM, }); // If no Suspend/Resume tracks exist, then nothing to do. if (it.trackIds == null) { return; } const rawTrackIds = it.trackIds; const trackIds = rawTrackIds.split(',').map((v) => Number(v)); const maxDepth = it.maxDepth; const uri = `/suspend_resume_latency`; const displayName = `Suspend/Resume Latency`; ctx.tracks.registerTrack({ uri, title: displayName, tags: { trackIds, kind: SLICE_TRACK_KIND, }, track: new SuspendResumeSliceTrack(ctx, uri, maxDepth, trackIds, threads), }); // Display the track in the UI. const track = new TrackNode({uri, title: displayName}); ctx.workspace.addChildInOrder(track); } }