1// Copyright (C) 2025 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 {Trace} from '../../public/trace'; 16import {PerfettoPlugin} from '../../public/plugin'; 17import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups'; 18import TraceProcessorTrackPlugin from '../dev.perfetto.TraceProcessorTrack'; 19import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result'; 20import {TrackNode} from '../../public/workspace'; 21import {assertExists, assertTrue} from '../../base/logging'; 22import {COUNTER_TRACK_KIND, SLICE_TRACK_KIND} from '../../public/track_kinds'; 23import {createTraceProcessorSliceTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_slice_track'; 24import {TraceProcessorCounterTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_counter_track'; 25import {getTrackName} from '../../public/utils'; 26 27export default class implements PerfettoPlugin { 28 static readonly id = 'dev.perfetto.TrackEvent'; 29 static readonly dependencies = [ 30 ProcessThreadGroupsPlugin, 31 TraceProcessorTrackPlugin, 32 ]; 33 34 private parentTrackNodes = new Map<string, TrackNode>(); 35 36 async onTraceLoad(ctx: Trace): Promise<void> { 37 const res = await ctx.engine.query(` 38 include perfetto module viz.summary.track_event; 39 select 40 ifnull(g.upid, t.upid) as upid, 41 g.utid, 42 g.parent_id as parentId, 43 g.is_counter AS isCounter, 44 g.name, 45 g.unit, 46 g.builtin_counter_type as builtinCounterType, 47 g.has_data AS hasData, 48 g.has_children AS hasChildren, 49 g.track_ids as trackIds, 50 g.order_id as orderId, 51 t.name as threadName, 52 t.tid as tid, 53 ifnull(p.pid, tp.pid) as pid, 54 ifnull(p.name, tp.name) as processName 55 from _track_event_tracks_ordered_groups g 56 left join process p using (upid) 57 left join thread t using (utid) 58 left join process tp on tp.upid = t.upid 59 `); 60 const it = res.iter({ 61 upid: NUM_NULL, 62 utid: NUM_NULL, 63 parentId: NUM_NULL, 64 isCounter: NUM, 65 name: STR_NULL, 66 unit: STR_NULL, 67 builtinCounterType: STR_NULL, 68 hasData: NUM, 69 hasChildren: NUM, 70 trackIds: STR, 71 orderId: NUM, 72 threadName: STR_NULL, 73 tid: NUM_NULL, 74 pid: NUM_NULL, 75 processName: STR_NULL, 76 }); 77 const processGroupsPlugin = ctx.plugins.getPlugin( 78 ProcessThreadGroupsPlugin, 79 ); 80 const trackIdToTrackNode = new Map<number, TrackNode>(); 81 for (; it.valid(); it.next()) { 82 const { 83 upid, 84 utid, 85 parentId, 86 isCounter, 87 name, 88 unit, 89 builtinCounterType, 90 hasData, 91 hasChildren, 92 trackIds: rawTrackIds, 93 orderId, 94 threadName, 95 tid, 96 pid, 97 processName, 98 } = it; 99 100 // Don't add track_event tracks which don't have any data and don't have 101 // any children. 102 if (!hasData && !hasChildren) { 103 continue; 104 } 105 106 const kind = isCounter ? COUNTER_TRACK_KIND : SLICE_TRACK_KIND; 107 const trackIds = rawTrackIds.split(',').map((v) => Number(v)); 108 const title = getTrackName({ 109 name, 110 utid, 111 upid, 112 kind, 113 threadTrack: utid !== null, 114 threadName, 115 processName, 116 tid, 117 pid, 118 }); 119 const uri = `/track_event_${trackIds[0]}`; 120 if (hasData && isCounter) { 121 // Don't show any builtin counter. 122 if (builtinCounterType !== null) { 123 continue; 124 } 125 assertTrue(trackIds.length === 1); 126 const trackId = trackIds[0]; 127 ctx.tracks.registerTrack({ 128 uri, 129 title, 130 tags: { 131 kind, 132 trackIds: [trackIds[0]], 133 upid: upid ?? undefined, 134 utid: utid ?? undefined, 135 }, 136 track: new TraceProcessorCounterTrack( 137 ctx, 138 uri, 139 { 140 unit: unit ?? undefined, 141 }, 142 trackId, 143 title, 144 ), 145 }); 146 } else if (hasData) { 147 ctx.tracks.registerTrack({ 148 uri, 149 title, 150 tags: { 151 kind, 152 trackIds: trackIds, 153 upid: upid ?? undefined, 154 utid: utid ?? undefined, 155 }, 156 track: createTraceProcessorSliceTrack({trace: ctx, uri, trackIds}), 157 }); 158 } 159 const parent = this.findParentTrackNode( 160 ctx, 161 processGroupsPlugin, 162 trackIdToTrackNode, 163 parentId ?? undefined, 164 upid ?? undefined, 165 utid ?? undefined, 166 hasChildren, 167 ); 168 const node = new TrackNode({ 169 title, 170 sortOrder: orderId, 171 isSummary: hasData === 0, 172 uri: uri, 173 }); 174 parent.addChildInOrder(node); 175 trackIdToTrackNode.set(trackIds[0], node); 176 } 177 } 178 179 private findParentTrackNode( 180 ctx: Trace, 181 processGroupsPlugin: ProcessThreadGroupsPlugin, 182 trackIdToTrackNode: Map<number, TrackNode>, 183 parentId: number | undefined, 184 upid: number | undefined, 185 utid: number | undefined, 186 hasChildren: number, 187 ): TrackNode { 188 if (parentId !== undefined) { 189 return assertExists(trackIdToTrackNode.get(parentId)); 190 } 191 if (utid !== undefined) { 192 return assertExists(processGroupsPlugin.getGroupForThread(utid)); 193 } 194 if (upid !== undefined) { 195 return assertExists(processGroupsPlugin.getGroupForProcess(upid)); 196 } 197 if (hasChildren) { 198 return ctx.workspace.tracks; 199 } 200 const id = `/track_event_root`; 201 let node = this.parentTrackNodes.get(id); 202 if (node === undefined) { 203 node = new TrackNode({ 204 title: 'Global Track Events', 205 isSummary: true, 206 }); 207 ctx.workspace.addChildInOrder(node); 208 this.parentTrackNodes.set(id, node); 209 } 210 return node; 211 } 212} 213