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 m from 'mithril'; 16import {NUM} from '../../trace_processor/query_result'; 17import {Slice} from '../../public/track'; 18import { 19 BaseSliceTrack, 20 OnSliceClickArgs, 21} from '../../components/tracks/base_slice_track'; 22import {NAMED_ROW, NamedRow} from '../../components/tracks/named_slice_track'; 23import {getColorForSample} from '../../components/colorizer'; 24import { 25 ProfileType, 26 TrackEventDetails, 27 TrackEventSelection, 28} from '../../public/selection'; 29import {assertExists} from '../../base/logging'; 30import { 31 metricsFromTableOrSubquery, 32 QueryFlamegraph, 33} from '../../components/query_flamegraph'; 34import {DetailsShell} from '../../widgets/details_shell'; 35import {Timestamp} from '../../components/widgets/timestamp'; 36import {time} from '../../base/time'; 37import {TrackEventDetailsPanel} from '../../public/details_panel'; 38import {Flamegraph, FLAMEGRAPH_STATE_SCHEMA} from '../../widgets/flamegraph'; 39import {Trace} from '../../public/trace'; 40 41interface PerfSampleRow extends NamedRow { 42 callsiteId: number; 43} 44 45abstract class BasePerfSamplesProfileTrack extends BaseSliceTrack< 46 Slice, 47 PerfSampleRow 48> { 49 constructor(trace: Trace, uri: string) { 50 super(trace, uri); 51 } 52 53 protected getRowSpec(): PerfSampleRow { 54 return {...NAMED_ROW, callsiteId: NUM}; 55 } 56 57 protected rowToSlice(row: PerfSampleRow): Slice { 58 const baseSlice = super.rowToSliceBase(row); 59 const name = assertExists(row.name); 60 const colorScheme = getColorForSample(row.callsiteId); 61 return {...baseSlice, title: name, colorScheme}; 62 } 63 64 onUpdatedSlices(slices: Slice[]) { 65 for (const slice of slices) { 66 slice.isHighlighted = slice === this.hoveredSlice; 67 } 68 } 69 70 onSliceClick(args: OnSliceClickArgs<Slice>): void { 71 // TODO(stevegolton): Perhaps we could just move this to BaseSliceTrack? 72 this.trace.selection.selectTrackEvent(this.uri, args.slice.id); 73 } 74} 75 76export class ProcessPerfSamplesProfileTrack extends BasePerfSamplesProfileTrack { 77 constructor( 78 trace: Trace, 79 uri: string, 80 private readonly upid: number, 81 ) { 82 super(trace, uri); 83 } 84 85 getSqlSource(): string { 86 return ` 87 select 88 p.id, 89 ts, 90 0 as dur, 91 0 as depth, 92 'Perf Sample' as name, 93 callsite_id as callsiteId 94 from perf_sample p 95 join thread using (utid) 96 where upid = ${this.upid} and callsite_id is not null 97 order by ts 98 `; 99 } 100 101 async getSelectionDetails( 102 id: number, 103 ): Promise<TrackEventDetails | undefined> { 104 const details = await super.getSelectionDetails(id); 105 if (details === undefined) return undefined; 106 return { 107 ...details, 108 upid: this.upid, 109 profileType: ProfileType.PERF_SAMPLE, 110 }; 111 } 112 113 detailsPanel(sel: TrackEventSelection) { 114 const upid = assertExists(sel.upid); 115 const ts = sel.ts; 116 117 const metrics = metricsFromTableOrSubquery( 118 ` 119 ( 120 select 121 id, 122 parent_id as parentId, 123 name, 124 mapping_name, 125 source_file, 126 cast(line_number AS text) as line_number, 127 self_count 128 from _callstacks_for_callsites!(( 129 select p.callsite_id 130 from perf_sample p 131 join thread t using (utid) 132 where p.ts >= ${ts} 133 and p.ts <= ${ts} 134 and t.upid = ${upid} 135 )) 136 ) 137 `, 138 [ 139 { 140 name: 'Perf Samples', 141 unit: '', 142 columnName: 'self_count', 143 }, 144 ], 145 'include perfetto module linux.perf.samples', 146 [{name: 'mapping_name', displayName: 'Mapping'}], 147 [ 148 { 149 name: 'source_file', 150 displayName: 'Source File', 151 mergeAggregation: 'ONE_OR_NULL', 152 }, 153 { 154 name: 'line_number', 155 displayName: 'Line Number', 156 mergeAggregation: 'ONE_OR_NULL', 157 }, 158 ], 159 ); 160 const serialization = { 161 schema: FLAMEGRAPH_STATE_SCHEMA, 162 state: Flamegraph.createDefaultState(metrics), 163 }; 164 const flamegraph = new QueryFlamegraph(this.trace, metrics, serialization); 165 return { 166 render: () => renderDetailsPanel(flamegraph, ts), 167 serialization, 168 }; 169 } 170} 171 172export class ThreadPerfSamplesProfileTrack extends BasePerfSamplesProfileTrack { 173 constructor( 174 trace: Trace, 175 uri: string, 176 private readonly utid: number, 177 ) { 178 super(trace, uri); 179 } 180 181 getSqlSource(): string { 182 return ` 183 select 184 p.id, 185 ts, 186 0 as dur, 187 0 as depth, 188 'Perf Sample' as name, 189 callsite_id as callsiteId 190 from perf_sample p 191 where utid = ${this.utid} and callsite_id is not null 192 order by ts 193 `; 194 } 195 196 async getSelectionDetails( 197 id: number, 198 ): Promise<TrackEventDetails | undefined> { 199 const details = await super.getSelectionDetails(id); 200 if (details === undefined) return undefined; 201 return { 202 ...details, 203 utid: this.utid, 204 profileType: ProfileType.PERF_SAMPLE, 205 }; 206 } 207 208 detailsPanel(sel: TrackEventSelection): TrackEventDetailsPanel { 209 const utid = assertExists(sel.utid); 210 const ts = sel.ts; 211 212 const metrics = metricsFromTableOrSubquery( 213 ` 214 ( 215 select 216 id, 217 parent_id as parentId, 218 name, 219 mapping_name, 220 source_file, 221 cast(line_number AS text) as line_number, 222 self_count 223 from _callstacks_for_callsites!(( 224 select p.callsite_id 225 from perf_sample p 226 where p.ts >= ${ts} 227 and p.ts <= ${ts} 228 and p.utid = ${utid} 229 )) 230 ) 231 `, 232 [ 233 { 234 name: 'Perf Samples', 235 unit: '', 236 columnName: 'self_count', 237 }, 238 ], 239 'include perfetto module linux.perf.samples', 240 [{name: 'mapping_name', displayName: 'Mapping'}], 241 [ 242 { 243 name: 'source_file', 244 displayName: 'Source File', 245 mergeAggregation: 'ONE_OR_NULL', 246 }, 247 { 248 name: 'line_number', 249 displayName: 'Line Number', 250 mergeAggregation: 'ONE_OR_NULL', 251 }, 252 ], 253 ); 254 const serialization = { 255 schema: FLAMEGRAPH_STATE_SCHEMA, 256 state: Flamegraph.createDefaultState(metrics), 257 }; 258 const flamegraph = new QueryFlamegraph(this.trace, metrics, serialization); 259 return { 260 render: () => renderDetailsPanel(flamegraph, ts), 261 serialization, 262 }; 263 } 264} 265 266function renderDetailsPanel(flamegraph: QueryFlamegraph, ts: time) { 267 return m( 268 '.flamegraph-profile', 269 m( 270 DetailsShell, 271 { 272 fillParent: true, 273 title: m('.title', 'Perf Samples'), 274 description: [], 275 buttons: [ 276 m( 277 'div.time', 278 `First timestamp: `, 279 m(Timestamp, { 280 ts, 281 }), 282 ), 283 m( 284 'div.time', 285 `Last timestamp: `, 286 m(Timestamp, { 287 ts, 288 }), 289 ), 290 ], 291 }, 292 flamegraph.render(), 293 ), 294 ); 295} 296