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 { 17 metricsFromTableOrSubquery, 18 QueryFlamegraph, 19} from '../../components/query_flamegraph'; 20import {PerfettoPlugin} from '../../public/plugin'; 21import {AreaSelection, areaSelectionsEqual} from '../../public/selection'; 22import {Trace} from '../../public/trace'; 23import { 24 COUNTER_TRACK_KIND, 25 PERF_SAMPLES_PROFILE_TRACK_KIND, 26} from '../../public/track_kinds'; 27import {getThreadUriPrefix} from '../../public/utils'; 28import {TrackNode} from '../../public/workspace'; 29import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result'; 30import {Flamegraph} from '../../widgets/flamegraph'; 31import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups'; 32import StandardGroupsPlugin from '../dev.perfetto.StandardGroups'; 33import TraceProcessorTrackPlugin from '../dev.perfetto.TraceProcessorTrack'; 34import {TraceProcessorCounterTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_counter_track'; 35import { 36 createProcessPerfSamplesProfileTrack, 37 createThreadPerfSamplesProfileTrack, 38} from './perf_samples_profile_track'; 39 40function makeUriForProc(upid: number) { 41 return `/process_${upid}/perf_samples_profile`; 42} 43 44export default class implements PerfettoPlugin { 45 static readonly id = 'dev.perfetto.LinuxPerf'; 46 static readonly dependencies = [ 47 ProcessThreadGroupsPlugin, 48 StandardGroupsPlugin, 49 TraceProcessorTrackPlugin, 50 ]; 51 52 async onTraceLoad(trace: Trace): Promise<void> { 53 await this.addProcessPerfSamplesTracks(trace); 54 await this.addThreadPerfSamplesTracks(trace); 55 await this.addPerfCounterTracks(trace); 56 57 trace.onTraceReady.addListener(async () => { 58 await selectPerfSample(trace); 59 }); 60 } 61 62 private async addProcessPerfSamplesTracks(trace: Trace) { 63 const pResult = await trace.engine.query(` 64 select distinct upid 65 from perf_sample 66 join thread using (utid) 67 where callsite_id is not null and upid is not null 68 `); 69 for (const it = pResult.iter({upid: NUM}); it.valid(); it.next()) { 70 const upid = it.upid; 71 const uri = makeUriForProc(upid); 72 const title = `Process Callstacks`; 73 trace.tracks.registerTrack({ 74 uri, 75 title, 76 tags: { 77 kind: PERF_SAMPLES_PROFILE_TRACK_KIND, 78 upid, 79 }, 80 track: createProcessPerfSamplesProfileTrack(trace, uri, upid), 81 }); 82 const group = trace.plugins 83 .getPlugin(ProcessThreadGroupsPlugin) 84 .getGroupForProcess(upid); 85 const track = new TrackNode({uri, title, sortOrder: -40}); 86 group?.addChildInOrder(track); 87 } 88 } 89 90 private async addThreadPerfSamplesTracks(trace: Trace) { 91 const tResult = await trace.engine.query(` 92 select distinct 93 utid, 94 tid, 95 thread.name as threadName, 96 upid 97 from perf_sample 98 join thread using (utid) 99 where callsite_id is not null 100 `); 101 for ( 102 const it = tResult.iter({ 103 utid: NUM, 104 tid: NUM, 105 threadName: STR_NULL, 106 upid: NUM_NULL, 107 }); 108 it.valid(); 109 it.next() 110 ) { 111 const {threadName, utid, tid, upid} = it; 112 const title = 113 threadName === null 114 ? `Thread Callstacks ${tid}` 115 : `${threadName} Callstacks ${tid}`; 116 const uri = `${getThreadUriPrefix(upid, utid)}_perf_samples_profile`; 117 trace.tracks.registerTrack({ 118 uri, 119 title, 120 tags: { 121 kind: PERF_SAMPLES_PROFILE_TRACK_KIND, 122 utid, 123 upid: upid ?? undefined, 124 }, 125 track: createThreadPerfSamplesProfileTrack(trace, uri, utid), 126 }); 127 const group = trace.plugins 128 .getPlugin(ProcessThreadGroupsPlugin) 129 .getGroupForThread(utid); 130 const track = new TrackNode({uri, title, sortOrder: -50}); 131 group?.addChildInOrder(track); 132 } 133 } 134 135 private async addPerfCounterTracks(trace: Trace) { 136 const perfCountersGroup = new TrackNode({ 137 title: 'Perf Counters', 138 isSummary: true, 139 }); 140 141 const result = await trace.engine.query(` 142 select 143 id, 144 name, 145 unit, 146 extract_arg(dimension_arg_set_id, 'cpu') as cpu 147 from counter_track 148 where type = 'perf_counter' 149 order by name, cpu 150 `); 151 152 const it = result.iter({ 153 id: NUM, 154 name: STR_NULL, 155 unit: STR_NULL, 156 cpu: NUM, // Perf counters always have a cpu dimension 157 }); 158 159 for (; it.valid(); it.next()) { 160 const {id: trackId, name, unit, cpu} = it; 161 const uri = `/counter_${trackId}`; 162 const title = `Cpu ${cpu} ${name}`; 163 164 trace.tracks.registerTrack({ 165 uri, 166 title, 167 tags: { 168 kind: COUNTER_TRACK_KIND, 169 trackIds: [trackId], 170 cpu, 171 }, 172 track: new TraceProcessorCounterTrack( 173 trace, 174 uri, 175 { 176 yMode: 'rate', // Default to rate mode 177 unit: unit ?? undefined, 178 }, 179 trackId, 180 title, 181 ), 182 }); 183 const trackNode = new TrackNode({ 184 uri, 185 title, 186 }); 187 perfCountersGroup.addChildLast(trackNode); 188 } 189 190 if (perfCountersGroup.hasChildren) { 191 const hardwareGroup = trace.plugins 192 .getPlugin(StandardGroupsPlugin) 193 .getOrCreateStandardGroup(trace.workspace, 'HARDWARE'); 194 hardwareGroup.addChildInOrder(perfCountersGroup); 195 } 196 197 trace.selection.registerAreaSelectionTab(createAreaSelectionTab(trace)); 198 } 199} 200 201async function selectPerfSample(trace: Trace) { 202 const profile = await assertExists(trace.engine).query(` 203 select upid 204 from perf_sample 205 join thread using (utid) 206 where callsite_id is not null 207 order by ts desc 208 limit 1 209 `); 210 if (profile.numRows() !== 1) return; 211 const row = profile.firstRow({upid: NUM}); 212 const upid = row.upid; 213 214 // Create an area selection over the first process with a perf samples track 215 trace.selection.selectArea({ 216 start: trace.traceInfo.start, 217 end: trace.traceInfo.end, 218 trackUris: [makeUriForProc(upid)], 219 }); 220} 221 222function createAreaSelectionTab(trace: Trace) { 223 let previousSelection: undefined | AreaSelection; 224 let flamegraph: undefined | QueryFlamegraph; 225 226 return { 227 id: 'perf_sample_flamegraph', 228 name: 'Perf Sample Flamegraph', 229 render(selection: AreaSelection) { 230 const changed = 231 previousSelection === undefined || 232 !areaSelectionsEqual(previousSelection, selection); 233 234 if (changed) { 235 flamegraph = computePerfSampleFlamegraph(trace, selection); 236 previousSelection = selection; 237 } 238 239 if (flamegraph === undefined) { 240 return undefined; 241 } 242 243 return {isLoading: false, content: flamegraph.render()}; 244 }, 245 }; 246} 247 248function getUpidsFromPerfSampleAreaSelection(currentSelection: AreaSelection) { 249 const upids = []; 250 for (const trackInfo of currentSelection.tracks) { 251 if ( 252 trackInfo?.tags?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND && 253 trackInfo.tags?.utid === undefined 254 ) { 255 upids.push(assertExists(trackInfo.tags?.upid)); 256 } 257 } 258 return upids; 259} 260 261function getUtidsFromPerfSampleAreaSelection(currentSelection: AreaSelection) { 262 const utids = []; 263 for (const trackInfo of currentSelection.tracks) { 264 if ( 265 trackInfo?.tags?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND && 266 trackInfo.tags?.utid !== undefined 267 ) { 268 utids.push(trackInfo.tags?.utid); 269 } 270 } 271 return utids; 272} 273 274function computePerfSampleFlamegraph( 275 trace: Trace, 276 currentSelection: AreaSelection, 277) { 278 const upids = getUpidsFromPerfSampleAreaSelection(currentSelection); 279 const utids = getUtidsFromPerfSampleAreaSelection(currentSelection); 280 if (utids.length === 0 && upids.length === 0) { 281 return undefined; 282 } 283 const metrics = metricsFromTableOrSubquery( 284 ` 285 ( 286 select id, parent_id as parentId, name, self_count 287 from _callstacks_for_callsites!(( 288 select p.callsite_id 289 from perf_sample p 290 join thread t using (utid) 291 where p.ts >= ${currentSelection.start} 292 and p.ts <= ${currentSelection.end} 293 and ( 294 p.utid in (${utids.join(',')}) 295 or t.upid in (${upids.join(',')}) 296 ) 297 )) 298 ) 299 `, 300 [ 301 { 302 name: 'Perf Samples', 303 unit: '', 304 columnName: 'self_count', 305 }, 306 ], 307 'include perfetto module linux.perf.samples', 308 ); 309 return new QueryFlamegraph(trace, metrics, { 310 state: Flamegraph.createDefaultState(metrics), 311 }); 312} 313