1// Copyright (C) 2021 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 {PerfettoPlugin} from '../../public/plugin'; 17import {Trace} from '../../public/trace'; 18import {COUNTER_TRACK_KIND, SLICE_TRACK_KIND} from '../../public/track_kinds'; 19import {getTrackName} from '../../public/utils'; 20import {TrackNode} from '../../public/workspace'; 21import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result'; 22import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups'; 23import StandardGroupsPlugin from '../dev.perfetto.StandardGroups'; 24import {SLICE_TRACK_SCHEMAS} from './slice_tracks'; 25import {TraceProcessorCounterTrack} from './trace_processor_counter_track'; 26import {COUNTER_TRACK_SCHEMAS} from './counter_tracks'; 27import {createTraceProcessorSliceTrack} from './trace_processor_slice_track'; 28import {TopLevelTrackGroup, TrackGroupSchema} from './types'; 29import {removeFalsyValues} from '../../base/array_utils'; 30 31export default class implements PerfettoPlugin { 32 static readonly id = 'dev.perfetto.TraceProcessorTrack'; 33 static readonly dependencies = [ 34 ProcessThreadGroupsPlugin, 35 StandardGroupsPlugin, 36 ]; 37 38 private groups = new Map<string, TrackNode>(); 39 40 async onTraceLoad(ctx: Trace): Promise<void> { 41 await this.addCounters(ctx); 42 await this.addSlices(ctx); 43 } 44 45 private async addCounters(ctx: Trace) { 46 const result = await ctx.engine.query(` 47 include perfetto module viz.threads; 48 49 with tracks_summary as ( 50 select 51 ct.type, 52 ct.name, 53 ct.id, 54 ct.unit, 55 extract_arg(ct.dimension_arg_set_id, 'utid') as utid, 56 extract_arg(ct.dimension_arg_set_id, 'upid') as upid 57 from counter_track ct 58 join _counter_track_summary using (id) 59 order by ct.name 60 ) 61 select 62 s.*, 63 thread.tid, 64 thread.name as threadName, 65 ifnull(p.pid, tp.pid) as pid, 66 ifnull(p.name, tp.name) as processName, 67 ifnull(thread.is_main_thread, 0) as isMainThread, 68 ifnull(k.is_kernel_thread, 0) AS isKernelThread 69 from tracks_summary s 70 left join process p on s.upid = p.upid 71 left join thread using (utid) 72 left join _threads_with_kernel_flag k using (utid) 73 left join process tp on thread.upid = tp.upid 74 order by lower(s.name) 75 `); 76 77 const schemas = new Map(COUNTER_TRACK_SCHEMAS.map((x) => [x.type, x])); 78 const it = result.iter({ 79 id: NUM, 80 type: STR, 81 name: STR_NULL, 82 unit: STR_NULL, 83 utid: NUM_NULL, 84 upid: NUM_NULL, 85 threadName: STR_NULL, 86 processName: STR_NULL, 87 tid: NUM_NULL, 88 pid: NUM_NULL, 89 isMainThread: NUM, 90 isKernelThread: NUM, 91 }); 92 for (; it.valid(); it.next()) { 93 const { 94 type, 95 id: trackId, 96 name, 97 unit, 98 utid, 99 upid, 100 threadName, 101 processName, 102 tid, 103 pid, 104 isMainThread, 105 isKernelThread, 106 } = it; 107 const schema = schemas.get(type); 108 if (schema === undefined) { 109 continue; 110 } 111 const {group, topLevelGroup} = schema; 112 const title = getTrackName({ 113 name, 114 tid, 115 threadName, 116 pid, 117 processName, 118 upid, 119 utid, 120 kind: COUNTER_TRACK_KIND, 121 threadTrack: utid !== undefined, 122 }); 123 const uri = `/counter_${trackId}`; 124 ctx.tracks.registerTrack({ 125 uri, 126 title, 127 tags: { 128 kind: COUNTER_TRACK_KIND, 129 trackIds: [trackId], 130 upid: upid ?? undefined, 131 utid: utid ?? undefined, 132 ...(isKernelThread === 1 && {kernelThread: true}), 133 }, 134 chips: removeFalsyValues([ 135 isKernelThread === 0 && isMainThread === 1 && 'main thread', 136 ]), 137 track: new TraceProcessorCounterTrack( 138 ctx, 139 uri, 140 { 141 yMode: schema.mode, 142 yRangeSharingKey: schema.shareYAxis ? it.type : undefined, 143 unit: unit ?? undefined, 144 }, 145 trackId, 146 title, 147 ), 148 }); 149 this.addTrack( 150 ctx, 151 topLevelGroup, 152 group, 153 upid, 154 utid, 155 new TrackNode({ 156 uri, 157 title, 158 sortOrder: utid !== undefined || upid !== undefined ? 30 : 0, 159 }), 160 ); 161 } 162 } 163 164 private async addSlices(ctx: Trace) { 165 const result = await ctx.engine.query(` 166 include perfetto module viz.threads; 167 168 with grouped as materialized ( 169 select 170 t.type, 171 t.name, 172 extract_arg(t.dimension_arg_set_id, 'utid') as utid, 173 extract_arg(t.dimension_arg_set_id, 'upid') as upid, 174 group_concat(t.id) as trackIds, 175 count() as trackCount 176 from _slice_track_summary s 177 join track t using (id) 178 group by type, upid, utid, name 179 ) 180 select 181 s.type, 182 s.name, 183 s.utid, 184 ifnull(s.upid, tp.upid) as upid, 185 s.trackIds as trackIds, 186 __max_layout_depth(s.trackCount, s.trackIds) as maxDepth, 187 thread.tid, 188 thread.name as threadName, 189 ifnull(p.pid, tp.pid) as pid, 190 ifnull(p.name, tp.name) as processName, 191 ifnull(thread.is_main_thread, 0) as isMainThread, 192 ifnull(k.is_kernel_thread, 0) AS isKernelThread 193 from grouped s 194 left join process p on s.upid = p.upid 195 left join thread using (utid) 196 left join _threads_with_kernel_flag k using (utid) 197 left join process tp on thread.upid = tp.upid 198 order by lower(s.name) 199 `); 200 201 const schemas = new Map(SLICE_TRACK_SCHEMAS.map((x) => [x.type, x])); 202 const it = result.iter({ 203 type: STR, 204 name: STR_NULL, 205 utid: NUM_NULL, 206 upid: NUM_NULL, 207 trackIds: STR, 208 maxDepth: NUM, 209 tid: NUM_NULL, 210 threadName: STR_NULL, 211 pid: NUM_NULL, 212 processName: STR_NULL, 213 isMainThread: NUM, 214 isKernelThread: NUM, 215 }); 216 for (; it.valid(); it.next()) { 217 const { 218 trackIds: rawTrackIds, 219 type, 220 name, 221 maxDepth, 222 utid, 223 upid, 224 threadName, 225 processName, 226 tid, 227 pid, 228 isMainThread, 229 isKernelThread, 230 } = it; 231 const schema = schemas.get(type); 232 if (schema === undefined) { 233 continue; 234 } 235 const trackIds = rawTrackIds.split(',').map((v) => Number(v)); 236 const {group, topLevelGroup} = schema; 237 const title = getTrackName({ 238 name, 239 tid, 240 threadName, 241 pid, 242 processName, 243 upid, 244 utid, 245 kind: SLICE_TRACK_KIND, 246 threadTrack: utid !== undefined, 247 }); 248 const uri = `/slice_${trackIds[0]}`; 249 ctx.tracks.registerTrack({ 250 uri, 251 title, 252 tags: { 253 kind: SLICE_TRACK_KIND, 254 trackIds: trackIds, 255 upid: upid ?? undefined, 256 utid: utid ?? undefined, 257 ...(isKernelThread === 1 && {kernelThread: true}), 258 }, 259 chips: removeFalsyValues([ 260 isKernelThread === 0 && isMainThread === 1 && 'main thread', 261 ]), 262 track: createTraceProcessorSliceTrack({ 263 trace: ctx, 264 uri, 265 maxDepth, 266 trackIds, 267 }), 268 }); 269 this.addTrack( 270 ctx, 271 topLevelGroup, 272 group, 273 upid, 274 utid, 275 new TrackNode({ 276 uri, 277 title, 278 sortOrder: utid !== undefined || upid !== undefined ? 20 : 0, 279 }), 280 ); 281 } 282 } 283 284 private addTrack( 285 ctx: Trace, 286 topLevelGroup: TopLevelTrackGroup, 287 group: string | TrackGroupSchema | undefined, 288 upid: number | null, 289 utid: number | null, 290 track: TrackNode, 291 ) { 292 switch (topLevelGroup) { 293 case 'PROCESS': { 294 const process = assertExists( 295 ctx.plugins 296 .getPlugin(ProcessThreadGroupsPlugin) 297 .getGroupForProcess(assertExists(upid)), 298 ); 299 this.getGroupByName(process, group, upid).addChildInOrder(track); 300 break; 301 } 302 case 'THREAD': { 303 const thread = assertExists( 304 ctx.plugins 305 .getPlugin(ProcessThreadGroupsPlugin) 306 .getGroupForThread(assertExists(utid)), 307 ); 308 this.getGroupByName(thread, group, utid).addChildInOrder(track); 309 break; 310 } 311 case undefined: { 312 this.getGroupByName(ctx.workspace.tracks, group, upid).addChildInOrder( 313 track, 314 ); 315 break; 316 } 317 default: { 318 const standardGroup = ctx.plugins 319 .getPlugin(StandardGroupsPlugin) 320 .getOrCreateStandardGroup(ctx.workspace, topLevelGroup); 321 this.getGroupByName(standardGroup, group, null).addChildInOrder(track); 322 break; 323 } 324 } 325 } 326 327 private getGroupByName( 328 node: TrackNode, 329 group: string | TrackGroupSchema | undefined, 330 scopeId: number | null, 331 ) { 332 if (group === undefined) { 333 return node; 334 } 335 // This is potentially dangerous - ids MUST be unique within the entire 336 // workspace - this seems to indicate that we could end up duplicating ids in 337 // different nodes. 338 const name = typeof group === 'string' ? group : group.name; 339 const expanded = 340 typeof group === 'string' ? false : group.expanded ?? false; 341 const groupId = `tp_group_${scopeId}_${name.toLowerCase().replace(' ', '_')}`; 342 const groupNode = this.groups.get(groupId); 343 if (groupNode) { 344 return groupNode; 345 } 346 const newGroup = new TrackNode({ 347 uri: `/${group}`, 348 isSummary: true, 349 title: name, 350 collapsed: !expanded, 351 }); 352 node.addChildInOrder(newGroup); 353 this.groups.set(groupId, newGroup); 354 return newGroup; 355 } 356} 357