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 {assertExists} from '../base/logging'; 16import {uuidv4} from '../base/uuid'; 17import {Actions, AddTrackArgs} from '../common/actions'; 18import {InThreadTrackSortKey} from '../common/state'; 19import {Engine, NUM, TrackDescriptor} from '../public'; 20import {globals} from './globals'; 21import {VisualisedArgsTrack} from './visualized_args_track'; 22 23const VISUALISED_ARGS_SLICE_TRACK_URI_PREFIX = 'perfetto.VisualisedArgs'; 24 25// We need to add tracks from the core and from plugins. In order to add a debug 26// track we need to pass a context through with we can add the track. This is 27// different for plugins vs the core. This interface defines the generic shape 28// of this context, which can be supplied from a plugin or built from globals. 29// 30// TODO(stevegolton): In the future, both the core and plugins should have 31// access to some Context object which implements the various things we want to 32// do in a generic way, so that we don't have to do this mangling to get this to 33// work. 34interface Context { 35 engine: Engine; 36 registerTrack(track: TrackDescriptor): unknown; 37} 38 39export async function addVisualisedArgTracks(ctx: Context, argName: string) { 40 const escapedArgName = argName.replace(/[^a-zA-Z]/g, '_'); 41 const tableName = `__arg_visualisation_helper_${escapedArgName}_slice`; 42 43 const result = await ctx.engine.query(` 44 drop table if exists ${tableName}; 45 46 create table ${tableName} as 47 with slice_with_arg as ( 48 select 49 slice.id, 50 slice.track_id, 51 slice.ts, 52 slice.dur, 53 slice.thread_dur, 54 NULL as cat, 55 args.display_value as name 56 from slice 57 join args using (arg_set_id) 58 where args.key='${argName}' 59 ) 60 select 61 *, 62 (select count() 63 from ancestor_slice(s1.id) s2 64 join slice_with_arg s3 on s2.id=s3.id 65 ) as depth 66 from slice_with_arg s1 67 order by id; 68 69 select 70 track_id as trackId, 71 max(depth) as maxDepth 72 from ${tableName} 73 group by track_id; 74 `); 75 76 const tracksToAdd: AddTrackArgs[] = []; 77 const it = result.iter({trackId: NUM, maxDepth: NUM}); 78 const addedTrackKeys: string[] = []; 79 for (; it.valid(); it.next()) { 80 const trackId = it.trackId; 81 const maxDepth = it.maxDepth; 82 const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId); 83 const track = globals.state.tracks[assertExists(trackKey)]; 84 const utid = (track.trackSortKey as {utid?: number}).utid; 85 const key = uuidv4(); 86 addedTrackKeys.push(key); 87 88 const uri = `${VISUALISED_ARGS_SLICE_TRACK_URI_PREFIX}#${uuidv4()}`; 89 ctx.registerTrack({ 90 uri, 91 tags: { 92 metric: true, // TODO(stevegolton): Is this track really a metric? 93 }, 94 trackFactory: (trackCtx) => { 95 return new VisualisedArgsTrack({ 96 engine: ctx.engine, 97 trackKey: trackCtx.trackKey, 98 trackId, 99 maxDepth, 100 argName, 101 }); 102 }, 103 }); 104 105 tracksToAdd.push({ 106 key, 107 trackGroup: track.trackGroup, 108 name: argName, 109 trackSortKey: 110 utid === undefined 111 ? track.trackSortKey 112 : {utid, priority: InThreadTrackSortKey.VISUALISED_ARGS_TRACK}, 113 uri, 114 }); 115 } 116 117 globals.dispatchMultiple([ 118 Actions.addTracks({tracks: tracksToAdd}), 119 Actions.sortThreadTracks({}), 120 ]); 121} 122