1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {NUM, STR_NULL} from '../../trace_processor/query_result'; 16import {AsyncSliceTrack} from '../dev.perfetto.AsyncSlices/async_slice_track'; 17import {PerfettoPlugin} from '../../public/plugin'; 18import {Trace} from '../../public/trace'; 19import {TrackNode} from '../../public/workspace'; 20import {SLICE_TRACK_KIND} from '../../public/track_kinds'; 21import {SuspendResumeDetailsPanel} from './suspend_resume_details'; 22import {Slice} from '../../public/track'; 23import {OnSliceClickArgs} from '../../components/tracks/base_slice_track'; 24import {ThreadMap} from '../dev.perfetto.Thread/threads'; 25import ThreadPlugin from '../dev.perfetto.Thread'; 26import AsyncSlicesPlugin from '../dev.perfetto.AsyncSlices'; 27 28// SuspendResumeSliceTrack exists so as to override the `onSliceClick` function 29// in AsyncSliceTrack. 30// TODO(stevegolton): Remove this? 31class SuspendResumeSliceTrack extends AsyncSliceTrack { 32 constructor( 33 trace: Trace, 34 uri: string, 35 maxDepth: number, 36 trackIds: number[], 37 private readonly threads: ThreadMap, 38 ) { 39 super(trace, uri, maxDepth, trackIds); 40 } 41 42 onSliceClick(args: OnSliceClickArgs<Slice>) { 43 this.trace.selection.selectTrackEvent(this.uri, args.slice.id); 44 } 45 46 override detailsPanel() { 47 return new SuspendResumeDetailsPanel(this.trace, this.threads); 48 } 49} 50 51export default class implements PerfettoPlugin { 52 static readonly id = 'org.kernel.SuspendResumeLatency'; 53 static readonly dependencies = [ThreadPlugin, AsyncSlicesPlugin]; 54 55 async onTraceLoad(ctx: Trace): Promise<void> { 56 const threads = ctx.plugins.getPlugin(ThreadPlugin).getThreadMap(); 57 const {engine} = ctx; 58 const rawGlobalAsyncTracks = await engine.query(` 59 with global_tracks_grouped as ( 60 select 61 name, 62 group_concat(distinct t.id) as trackIds, 63 count() as trackCount 64 from track t 65 where t.name = "Suspend/Resume Latency" 66 ) 67 select 68 t.trackIds as trackIds, 69 case 70 when 71 t.trackCount > 0 72 then 73 __max_layout_depth(t.trackCount, t.trackIds) 74 else 0 75 end as maxDepth 76 from global_tracks_grouped t 77 `); 78 const it = rawGlobalAsyncTracks.iter({ 79 trackIds: STR_NULL, 80 maxDepth: NUM, 81 }); 82 // If no Suspend/Resume tracks exist, then nothing to do. 83 if (it.trackIds == null) { 84 return; 85 } 86 const rawTrackIds = it.trackIds; 87 const trackIds = rawTrackIds.split(',').map((v) => Number(v)); 88 const maxDepth = it.maxDepth; 89 90 const uri = `/suspend_resume_latency`; 91 const displayName = `Suspend/Resume Latency`; 92 ctx.tracks.registerTrack({ 93 uri, 94 title: displayName, 95 tags: { 96 trackIds, 97 kind: SLICE_TRACK_KIND, 98 }, 99 track: new SuspendResumeSliceTrack(ctx, uri, maxDepth, trackIds, threads), 100 }); 101 102 // Display the track in the UI. 103 const track = new TrackNode({uri, title: displayName}); 104 ctx.workspace.addChildInOrder(track); 105 } 106} 107