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 {CPU_PROFILE_TRACK_KIND} from '../../public/track_kinds'; 16import {Trace} from '../../public/trace'; 17import {PerfettoPlugin} from '../../public/plugin'; 18import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result'; 19import {createCpuProfileTrack} from './cpu_profile_track'; 20import {getThreadUriPrefix} from '../../public/utils'; 21import {exists} from '../../base/utils'; 22import {TrackNode} from '../../public/workspace'; 23import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups'; 24import {AreaSelection, areaSelectionsEqual} from '../../public/selection'; 25import { 26 metricsFromTableOrSubquery, 27 QueryFlamegraph, 28} from '../../components/query_flamegraph'; 29import {Flamegraph} from '../../widgets/flamegraph'; 30 31export default class implements PerfettoPlugin { 32 static readonly id = 'dev.perfetto.CpuProfile'; 33 static readonly dependencies = [ProcessThreadGroupsPlugin]; 34 35 async onTraceLoad(ctx: Trace): Promise<void> { 36 const result = await ctx.engine.query(` 37 with thread_cpu_sample as ( 38 select distinct utid 39 from cpu_profile_stack_sample 40 ) 41 select 42 utid, 43 tid, 44 upid, 45 thread.name as threadName 46 from thread_cpu_sample 47 join thread using(utid) 48 where not is_idle 49 `); 50 51 const it = result.iter({ 52 utid: NUM, 53 upid: NUM_NULL, 54 tid: NUM_NULL, 55 threadName: STR_NULL, 56 }); 57 for (; it.valid(); it.next()) { 58 const utid = it.utid; 59 const upid = it.upid; 60 const threadName = it.threadName; 61 const uri = `${getThreadUriPrefix(upid, utid)}_cpu_samples`; 62 const title = `${threadName} (CPU Stack Samples)`; 63 ctx.tracks.registerTrack({ 64 uri, 65 title, 66 tags: { 67 kind: CPU_PROFILE_TRACK_KIND, 68 utid, 69 ...(exists(upid) && {upid}), 70 }, 71 track: createCpuProfileTrack(ctx, uri, utid), 72 }); 73 const group = ctx.plugins 74 .getPlugin(ProcessThreadGroupsPlugin) 75 .getGroupForThread(utid); 76 const track = new TrackNode({uri, title, sortOrder: -40}); 77 group?.addChildInOrder(track); 78 } 79 80 ctx.selection.registerAreaSelectionTab(createAreaSelectionTab(ctx)); 81 } 82} 83 84function createAreaSelectionTab(trace: Trace) { 85 let previousSelection: undefined | AreaSelection; 86 let flamegraph: undefined | QueryFlamegraph; 87 88 return { 89 id: 'cpu_profile_flamegraph', 90 name: 'CPU Profile Sample Flamegraph', 91 render(selection: AreaSelection) { 92 const changed = 93 previousSelection === undefined || 94 !areaSelectionsEqual(previousSelection, selection); 95 96 if (changed) { 97 flamegraph = computeCpuProfileFlamegraph(trace, selection); 98 previousSelection = selection; 99 } 100 101 if (flamegraph === undefined) { 102 return undefined; 103 } 104 105 return {isLoading: false, content: flamegraph.render()}; 106 }, 107 }; 108} 109 110function computeCpuProfileFlamegraph(trace: Trace, selection: AreaSelection) { 111 const utids = []; 112 for (const trackInfo of selection.tracks) { 113 if (trackInfo?.tags?.kind === CPU_PROFILE_TRACK_KIND) { 114 utids.push(trackInfo.tags?.utid); 115 } 116 } 117 if (utids.length === 0) { 118 return undefined; 119 } 120 const metrics = metricsFromTableOrSubquery( 121 ` 122 ( 123 select 124 id, 125 parent_id as parentId, 126 name, 127 mapping_name, 128 source_file, 129 cast(line_number AS text) as line_number, 130 self_count 131 from _callstacks_for_callsites!(( 132 select p.callsite_id 133 from cpu_profile_stack_sample p 134 where p.ts >= ${selection.start} 135 and p.ts <= ${selection.end} 136 and p.utid in (${utids.join(',')}) 137 )) 138 ) 139 `, 140 [ 141 { 142 name: 'CPU Profile Samples', 143 unit: '', 144 columnName: 'self_count', 145 }, 146 ], 147 'include perfetto module callstacks.stack_profile', 148 [{name: 'mapping_name', displayName: 'Mapping'}], 149 [ 150 { 151 name: 'source_file', 152 displayName: 'Source File', 153 mergeAggregation: 'ONE_OR_NULL', 154 }, 155 { 156 name: 'line_number', 157 displayName: 'Line Number', 158 mergeAggregation: 'ONE_OR_NULL', 159 }, 160 ], 161 ); 162 return new QueryFlamegraph(trace, metrics, { 163 state: Flamegraph.createDefaultState(metrics), 164 }); 165} 166