blob: 22365ad812edda0c1d97305905aac718262a7c16 [file] [log] [blame]
Neda Topoljanacb62621c2019-11-26 12:41:58 +00001// Copyright (C) 2019 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
Ioannis Ilkose03ca502024-04-23 18:57:23 +010015import {Duration, time} from '../base/time';
Steve Golton3067f8a2023-12-14 17:09:26 +000016import {exists} from '../base/utils';
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +000017import {Actions} from '../common/actions';
Neda Topoljanacb62621c2019-11-26 12:41:58 +000018import {
Daniele Di Proietto88551232023-11-02 12:56:01 +000019 defaultViewingOption,
Neda Topoljanacb62621c2019-11-26 12:41:58 +000020 expandCallsites,
21 findRootSize,
22 mergeCallsites,
Neda Topoljanacb62621c2019-11-26 12:41:58 +000023} from '../common/flamegraph_util';
Daniele Di Proietto88551232023-11-02 12:56:01 +000024import {
25 CallsiteInfo,
26 FlamegraphState,
27 FlamegraphStateViewingOption,
Bingqian Liu7afd5452024-03-27 18:42:47 +000028 isHeapGraphDominatorTreeViewingOption,
Ioannis Ilkose03ca502024-04-23 18:57:23 +010029 ProfileType,
Daniele Di Proietto88551232023-11-02 12:56:01 +000030} from '../common/state';
Hector Dearman6fbcabd2023-02-10 12:26:59 +000031import {FlamegraphDetails, globals} from '../frontend/globals';
Tuchila Octavian52047582021-09-21 15:35:54 +010032import {publishFlamegraphDetails} from '../frontend/publish';
Hector Dearmanea4719d2023-10-31 09:11:09 +000033import {Engine} from '../trace_processor/engine';
34import {NUM, STR} from '../trace_processor/query_result';
Steve Golton729115f2023-10-17 14:22:24 +010035import {PERF_SAMPLES_PROFILE_TRACK_KIND} from '../tracks/perf_samples_profile';
Neda Topoljanacb62621c2019-11-26 12:41:58 +000036
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +000037import {AreaSelectionHandler} from './area_selection_handler';
Neda Topoljanacb62621c2019-11-26 12:41:58 +000038import {Controller} from './controller';
Neda Topoljanacb62621c2019-11-26 12:41:58 +000039
Ioannis Ilkos40cc9662022-07-15 15:35:09 +010040export function profileType(s: string): ProfileType {
41 if (isProfileType(s)) {
42 return s;
43 }
44 if (s.startsWith('heap_profile')) {
45 return ProfileType.HEAP_PROFILE;
46 }
47 throw new Error('Unknown type ${s}');
48}
49
50function isProfileType(s: string): s is ProfileType {
51 return Object.values(ProfileType).includes(s as ProfileType);
52}
53
54function getFlamegraphType(type: ProfileType) {
55 switch (type) {
Steve Golton7e63f422024-03-18 14:17:32 +000056 case ProfileType.HEAP_PROFILE:
57 case ProfileType.MIXED_HEAP_PROFILE:
58 case ProfileType.NATIVE_HEAP_PROFILE:
59 case ProfileType.JAVA_HEAP_SAMPLES:
60 return 'native';
61 case ProfileType.JAVA_HEAP_GRAPH:
62 return 'graph';
63 case ProfileType.PERF_SAMPLE:
64 return 'perf';
65 default:
66 const exhaustiveCheck: never = type;
67 throw new Error(`Unhandled case: ${exhaustiveCheck}`);
Ioannis Ilkos40cc9662022-07-15 15:35:09 +010068 }
69}
70
Tuchila Octavian52047582021-09-21 15:35:54 +010071export interface FlamegraphControllerArgs {
Neda Topoljanacb62621c2019-11-26 12:41:58 +000072 engine: Engine;
73}
74const MIN_PIXEL_DISPLAYED = 1;
75
Hector Dearmanda988af2020-02-25 13:42:42 +000076class TablesCache {
77 private engine: Engine;
78 private cache: Map<string, string>;
79 private prefix: string;
80 private tableId: number;
81 private cacheSizeLimit: number;
82
83 constructor(engine: Engine, prefix: string) {
84 this.engine = engine;
85 this.cache = new Map<string, string>();
86 this.prefix = prefix;
87 this.tableId = 0;
88 this.cacheSizeLimit = 10;
89 }
90
91 async getTableName(query: string): Promise<string> {
92 let tableName = this.cache.get(query);
93 if (tableName === undefined) {
94 // TODO(hjd): This should be LRU.
95 if (this.cache.size > this.cacheSizeLimit) {
96 for (const name of this.cache.values()) {
Hector Dearman7514f302021-08-19 18:21:52 +010097 await this.engine.query(`drop table ${name}`);
Hector Dearmanda988af2020-02-25 13:42:42 +000098 }
99 this.cache.clear();
100 }
101 tableName = `${this.prefix}_${this.tableId++}`;
Hector Dearman7514f302021-08-19 18:21:52 +0100102 await this.engine.query(
Steve Golton7e63f422024-03-18 14:17:32 +0000103 `create temp table if not exists ${tableName} as ${query}`,
104 );
Hector Dearmanda988af2020-02-25 13:42:42 +0000105 this.cache.set(query, tableName);
106 }
107 return tableName;
108 }
109}
110
Tuchila Octavian52047582021-09-21 15:35:54 +0100111export class FlamegraphController extends Controller<'main'> {
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000112 private flamegraphDatasets: Map<string, CallsiteInfo[]> = new Map();
Tuchila Octavian52047582021-09-21 15:35:54 +0100113 private lastSelectedFlamegraphState?: FlamegraphState;
Neda Topoljanac17432412019-11-29 16:50:44 +0000114 private requestingData = false;
115 private queuedRequest = false;
Tuchila Octavian52047582021-09-21 15:35:54 +0100116 private flamegraphDetails: FlamegraphDetails = {};
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000117 private areaSelectionHandler: AreaSelectionHandler;
Hector Dearmanda988af2020-02-25 13:42:42 +0000118 private cache: TablesCache;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000119
Tuchila Octavian52047582021-09-21 15:35:54 +0100120 constructor(private args: FlamegraphControllerArgs) {
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000121 super('main');
Hector Dearmanda988af2020-02-25 13:42:42 +0000122 this.cache = new TablesCache(args.engine, 'grouped_callsites');
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000123 this.areaSelectionHandler = new AreaSelectionHandler();
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000124 }
125
126 run() {
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000127 const [hasAreaChanged, area] = this.areaSelectionHandler.getAreaChange();
128 if (hasAreaChanged) {
129 const upids = [];
130 if (!area) {
Steve Golton7e63f422024-03-18 14:17:32 +0000131 this.checkCompletionAndPublishFlamegraph({
132 ...globals.flamegraphDetails,
133 isInAreaSelection: false,
134 });
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000135 return;
136 }
137 for (const trackId of area.tracks) {
Steve Golton729115f2023-10-17 14:22:24 +0100138 const track = globals.state.tracks[trackId];
139 if (track?.uri) {
Steve Golton1ea8b9f2024-01-25 16:03:37 +0000140 const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
Steve Golton729115f2023-10-17 14:22:24 +0100141 if (trackInfo?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND) {
Steve Golton3067f8a2023-12-14 17:09:26 +0000142 exists(trackInfo.upid) && upids.push(trackInfo.upid);
Steve Golton729115f2023-10-17 14:22:24 +0100143 }
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000144 }
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000145 }
146 if (upids.length === 0) {
Steve Golton7e63f422024-03-18 14:17:32 +0000147 this.checkCompletionAndPublishFlamegraph({
148 ...globals.flamegraphDetails,
149 isInAreaSelection: false,
150 });
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000151 return;
152 }
Steve Golton7e63f422024-03-18 14:17:32 +0000153 globals.dispatch(
154 Actions.openFlamegraph({
155 upids,
156 start: area.start,
157 end: area.end,
158 type: ProfileType.PERF_SAMPLE,
159 viewingOption: defaultViewingOption(ProfileType.PERF_SAMPLE),
160 }),
161 );
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000162 }
Hector Dearman6fbcabd2023-02-10 12:26:59 +0000163 const selection = globals.state.currentFlamegraphState;
Tuchila Octavian7eede882021-09-28 13:24:31 +0100164 if (!selection || !this.shouldRequestData(selection)) {
165 return;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000166 }
Tuchila Octavian7eede882021-09-28 13:24:31 +0100167 if (this.requestingData) {
168 this.queuedRequest = true;
169 return;
170 }
171 this.requestingData = true;
172
Alexander Timin86d2e8a2022-06-28 17:14:14 +0000173 this.assembleFlamegraphDetails(selection, area !== undefined);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000174 }
175
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000176 private async assembleFlamegraphDetails(
Steve Golton7e63f422024-03-18 14:17:32 +0000177 selection: FlamegraphState,
178 isInAreaSelection: boolean,
179 ) {
Tuchila Octavian7eede882021-09-28 13:24:31 +0100180 const selectedFlamegraphState = {...selection};
181 const flamegraphMetadata = await this.getFlamegraphMetadata(
Steve Golton5dbacf52024-01-25 09:20:51 +0000182 selection.type,
183 selectedFlamegraphState.start,
184 selectedFlamegraphState.end,
Steve Golton7e63f422024-03-18 14:17:32 +0000185 selectedFlamegraphState.upids,
186 );
Tuchila Octavian7eede882021-09-28 13:24:31 +0100187 if (flamegraphMetadata !== undefined) {
188 Object.assign(this.flamegraphDetails, flamegraphMetadata);
189 }
190
191 // TODO(hjd): Clean this up.
Steve Golton7e63f422024-03-18 14:17:32 +0000192 if (
193 this.lastSelectedFlamegraphState &&
194 this.lastSelectedFlamegraphState.focusRegex !== selection.focusRegex
195 ) {
Tuchila Octavian7eede882021-09-28 13:24:31 +0100196 this.flamegraphDatasets.clear();
197 }
198
199 this.lastSelectedFlamegraphState = {...selection};
200
Bingqian Liu7afd5452024-03-27 18:42:47 +0000201 const expandedCallsite =
202 selectedFlamegraphState.expandedCallsiteByViewingOption[
203 selectedFlamegraphState.viewingOption
204 ];
205 const expandedId = expandedCallsite ? expandedCallsite.id : -1;
206 const rootSize = expandedCallsite?.totalSize;
Tuchila Octavian7eede882021-09-28 13:24:31 +0100207
Steve Golton7e63f422024-03-18 14:17:32 +0000208 const key = `${selectedFlamegraphState.upids};${selectedFlamegraphState.start};${selectedFlamegraphState.end}`;
Tuchila Octavian7eede882021-09-28 13:24:31 +0100209
210 try {
211 const flamegraphData = await this.getFlamegraphData(
Steve Golton5dbacf52024-01-25 09:20:51 +0000212 key,
213 /* eslint-disable @typescript-eslint/strict-boolean-expressions */
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100214 selectedFlamegraphState.viewingOption /* eslint-enable */
215 ? selectedFlamegraphState.viewingOption
Steve Golton7e63f422024-03-18 14:17:32 +0000216 : defaultViewingOption(selectedFlamegraphState.type),
Steve Golton5dbacf52024-01-25 09:20:51 +0000217 selection.start,
218 selection.end,
219 selectedFlamegraphState.upids,
220 selectedFlamegraphState.type,
Steve Golton7e63f422024-03-18 14:17:32 +0000221 selectedFlamegraphState.focusRegex,
222 );
223 if (
224 flamegraphData !== undefined &&
225 // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
226 selection &&
227 selection.kind === selectedFlamegraphState.kind &&
228 selection.start === selectedFlamegraphState.start &&
229 selection.end === selectedFlamegraphState.end
230 ) {
231 const expandedFlamegraphData = expandCallsites(
232 flamegraphData,
233 expandedId,
234 );
Tuchila Octavian7eede882021-09-28 13:24:31 +0100235 this.prepareAndMergeCallsites(
Steve Golton5dbacf52024-01-25 09:20:51 +0000236 expandedFlamegraphData,
237 this.lastSelectedFlamegraphState.viewingOption,
238 isInAreaSelection,
239 rootSize,
Bingqian Liu7afd5452024-03-27 18:42:47 +0000240 expandedCallsite,
Steve Golton7e63f422024-03-18 14:17:32 +0000241 );
Tuchila Octavian7eede882021-09-28 13:24:31 +0100242 }
243 } finally {
244 this.requestingData = false;
245 if (this.queuedRequest) {
246 this.queuedRequest = false;
247 this.run();
248 }
249 }
Neda Topoljanac17432412019-11-29 16:50:44 +0000250 }
251
Tuchila Octavian52047582021-09-21 15:35:54 +0100252 private shouldRequestData(selection: FlamegraphState) {
Steve Golton7e63f422024-03-18 14:17:32 +0000253 return (
254 selection.kind === 'FLAMEGRAPH_STATE' &&
255 (this.lastSelectedFlamegraphState === undefined ||
256 this.lastSelectedFlamegraphState.start !== selection.start ||
257 this.lastSelectedFlamegraphState.end !== selection.end ||
258 this.lastSelectedFlamegraphState.type !== selection.type ||
259 !FlamegraphController.areArraysEqual(
260 this.lastSelectedFlamegraphState.upids,
261 selection.upids,
262 ) ||
263 this.lastSelectedFlamegraphState.viewingOption !==
264 selection.viewingOption ||
265 this.lastSelectedFlamegraphState.focusRegex !== selection.focusRegex ||
Bingqian Liu7afd5452024-03-27 18:42:47 +0000266 this.lastSelectedFlamegraphState.expandedCallsiteByViewingOption[
267 selection.viewingOption
268 ] !==
269 selection.expandedCallsiteByViewingOption[selection.viewingOption])
Steve Golton7e63f422024-03-18 14:17:32 +0000270 );
Neda Topoljanac17432412019-11-29 16:50:44 +0000271 }
272
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000273 private prepareAndMergeCallsites(
Steve Golton5dbacf52024-01-25 09:20:51 +0000274 flamegraphData: CallsiteInfo[],
Steve Golton7e63f422024-03-18 14:17:32 +0000275 viewingOption: FlamegraphStateViewingOption,
276 isInAreaSelection: boolean,
277 rootSize?: number,
278 expandedCallsite?: CallsiteInfo,
279 ) {
Tuchila Octavian7eede882021-09-28 13:24:31 +0100280 this.flamegraphDetails.flamegraph = mergeCallsites(
Steve Golton7e63f422024-03-18 14:17:32 +0000281 flamegraphData,
282 this.getMinSizeDisplayed(flamegraphData, rootSize),
283 );
Tuchila Octavian52047582021-09-21 15:35:54 +0100284 this.flamegraphDetails.expandedCallsite = expandedCallsite;
285 this.flamegraphDetails.viewingOption = viewingOption;
Alexander Timin86d2e8a2022-06-28 17:14:14 +0000286 this.flamegraphDetails.isInAreaSelection = isInAreaSelection;
Tuchila Octavian0fbb7a22022-03-24 11:36:58 +0000287 this.checkCompletionAndPublishFlamegraph(this.flamegraphDetails);
288 }
289
Steve Golton7e63f422024-03-18 14:17:32 +0000290 private async checkCompletionAndPublishFlamegraph(
291 flamegraphDetails: FlamegraphDetails,
292 ) {
Tuchila Octavian0fbb7a22022-03-24 11:36:58 +0000293 flamegraphDetails.graphIncomplete =
Steve Golton7e63f422024-03-18 14:17:32 +0000294 (
295 await this.args.engine.query(`select value from stats
296 where severity = 'error' and name = 'heap_graph_non_finalized_graph'`)
297 ).firstRow({value: NUM}).value > 0;
Bingqian Liud4de9c12024-04-09 14:33:58 +0100298 flamegraphDetails.graphLoading = false;
Tuchila Octavian0fbb7a22022-03-24 11:36:58 +0000299 publishFlamegraphDetails(flamegraphDetails);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000300 }
301
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000302 async getFlamegraphData(
Steve Golton7e63f422024-03-18 14:17:32 +0000303 baseKey: string,
304 viewingOption: FlamegraphStateViewingOption,
305 start: time,
306 end: time,
307 upids: number[],
308 type: ProfileType,
309 focusRegex: string,
310 ): Promise<CallsiteInfo[]> {
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000311 let currentData: CallsiteInfo[];
312 const key = `${baseKey}-${viewingOption}`;
313 if (this.flamegraphDatasets.has(key)) {
314 currentData = this.flamegraphDatasets.get(key)!;
315 } else {
Bingqian Liud4de9c12024-04-09 14:33:58 +0100316 publishFlamegraphDetails({
317 ...globals.flamegraphDetails,
318 graphLoading: true,
319 });
Tuchila Octavian52047582021-09-21 15:35:54 +0100320 // Collecting data for drawing flamegraph for selected profile.
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000321 // Data needs to be in following format:
322 // id, name, parent_id, depth, total_size
Steve Golton7e63f422024-03-18 14:17:32 +0000323 const tableName = await this.prepareViewsAndTables(
324 start,
325 end,
326 upids,
327 type,
328 focusRegex,
Bingqian Liu7afd5452024-03-27 18:42:47 +0000329 viewingOption,
Steve Golton7e63f422024-03-18 14:17:32 +0000330 );
Isabelle Taylor8ee25832020-10-28 16:25:27 +0000331 currentData = await this.getFlamegraphDataFromTables(
Steve Golton7e63f422024-03-18 14:17:32 +0000332 tableName,
333 viewingOption,
334 focusRegex,
335 );
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000336 this.flamegraphDatasets.set(key, currentData);
337 }
338 return currentData;
339 }
340
341 async getFlamegraphDataFromTables(
Steve Golton7e63f422024-03-18 14:17:32 +0000342 tableName: string,
343 viewingOption: FlamegraphStateViewingOption,
344 focusRegex: string,
345 ) {
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000346 let orderBy = '';
Steve Golton7e63f422024-03-18 14:17:32 +0000347 let totalColumnName:
348 | 'cumulativeSize'
349 | 'cumulativeAllocSize'
350 | 'cumulativeCount'
351 | 'cumulativeAllocCount' = 'cumulativeSize';
352 let selfColumnName: 'size' | 'count' = 'size';
Florian Mayer68845bf2020-01-23 13:29:58 +0000353 // TODO(fmayer): Improve performance so this is no longer necessary.
354 // Alternatively consider collapsing frames of the same label.
355 const maxDepth = 100;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000356 switch (viewingOption) {
Steve Golton7e63f422024-03-18 14:17:32 +0000357 case FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY:
358 orderBy = `where cumulative_alloc_size > 0 and depth < ${maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000359 cumulative_alloc_size desc, name`;
Steve Golton7e63f422024-03-18 14:17:32 +0000360 totalColumnName = 'cumulativeAllocSize';
361 selfColumnName = 'size';
362 break;
363 case FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY:
364 orderBy = `where cumulative_count > 0 and depth < ${maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000365 cumulative_count desc, name`;
Steve Golton7e63f422024-03-18 14:17:32 +0000366 totalColumnName = 'cumulativeCount';
367 selfColumnName = 'count';
368 break;
369 case FlamegraphStateViewingOption.OBJECTS_ALLOCATED_KEY:
370 orderBy = `where cumulative_alloc_count > 0 and depth < ${maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000371 cumulative_alloc_count desc, name`;
Steve Golton7e63f422024-03-18 14:17:32 +0000372 totalColumnName = 'cumulativeAllocCount';
373 selfColumnName = 'count';
374 break;
375 case FlamegraphStateViewingOption.PERF_SAMPLES_KEY:
376 case FlamegraphStateViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY:
377 orderBy = `where cumulative_size > 0 and depth < ${maxDepth} order by depth, parent_id,
Tuchila Octavian3b31ed62021-09-14 12:27:26 +0100378 cumulative_size desc, name`;
Steve Golton7e63f422024-03-18 14:17:32 +0000379 totalColumnName = 'cumulativeSize';
380 selfColumnName = 'size';
381 break;
Bingqian Liu7afd5452024-03-27 18:42:47 +0000382 case FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY:
383 orderBy = `where depth < ${maxDepth} order by depth,
384 cumulativeCount desc, name`;
385 totalColumnName = 'cumulativeCount';
386 selfColumnName = 'count';
387 break;
388 case FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY:
389 orderBy = `where depth < ${maxDepth} order by depth,
390 cumulativeSize desc, name`;
391 totalColumnName = 'cumulativeSize';
392 selfColumnName = 'size';
393 break;
Steve Golton7e63f422024-03-18 14:17:32 +0000394 default:
395 const exhaustiveCheck: never = viewingOption;
396 throw new Error(`Unhandled case: ${exhaustiveCheck}`);
397 break;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000398 }
399
Hector Dearman7514f302021-08-19 18:21:52 +0100400 const callsites = await this.args.engine.query(`
Bingqian Liu7afd5452024-03-27 18:42:47 +0000401 SELECT
402 id as hash,
403 IFNULL(IFNULL(DEMANGLE(name), name), '[NULL]') as name,
404 IFNULL(parent_id, -1) as parentHash,
405 depth,
406 cumulative_size as cumulativeSize,
407 cumulative_alloc_size as cumulativeAllocSize,
408 cumulative_count as cumulativeCount,
409 cumulative_alloc_count as cumulativeAllocCount,
410 map_name as mapping,
411 size,
412 count,
413 IFNULL(source_file, '') as sourceFile,
414 IFNULL(line_number, -1) as lineNumber
415 from ${tableName} ${orderBy}`);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000416
Tuchila Octavian3b31ed62021-09-14 12:27:26 +0100417 const flamegraphData: CallsiteInfo[] = [];
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000418 const hashToindex: Map<number, number> = new Map();
Hector Dearman7b864652021-07-16 10:44:13 +0100419 const it = callsites.iter({
420 hash: NUM,
421 name: STR,
422 parentHash: NUM,
423 depth: NUM,
424 cumulativeSize: NUM,
425 cumulativeAllocSize: NUM,
426 cumulativeCount: NUM,
427 cumulativeAllocCount: NUM,
428 mapping: STR,
429 sourceFile: STR,
430 lineNumber: NUM,
431 size: NUM,
432 count: NUM,
433 });
434 for (let i = 0; it.valid(); ++i, it.next()) {
435 const hash = it.hash;
436 let name = it.name;
437 const parentHash = it.parentHash;
438 const depth = it.depth;
439 const totalSize = it[totalColumnName];
440 const selfSize = it[selfColumnName];
441 const mapping = it.mapping;
Steve Golton7e63f422024-03-18 14:17:32 +0000442 const highlighted =
443 focusRegex !== '' &&
444 name.toLocaleLowerCase().includes(focusRegex.toLocaleLowerCase());
445 const parentId = hashToindex.has(+parentHash)
446 ? hashToindex.get(+parentHash)!
447 : -1;
Tuchila Octavian795f60b2021-05-24 16:39:38 +0100448
Steve Golton7e63f422024-03-18 14:17:32 +0000449 let location: string | undefined;
Hector Dearman7b864652021-07-16 10:44:13 +0100450 if (/[a-zA-Z]/i.test(it.sourceFile)) {
451 location = it.sourceFile;
452 if (it.lineNumber !== -1) {
453 location += `:${it.lineNumber}`;
Tuchila Octavian795f60b2021-05-24 16:39:38 +0100454 }
455 }
456
Florian Mayer68845bf2020-01-23 13:29:58 +0000457 if (depth === maxDepth - 1) {
458 name += ' [tree truncated]';
459 }
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000460 // Instead of hash, we will store index of callsite in this original array
461 // as an id of callsite. That way, we have quicker access to parent and it
Hector Dearman7b864652021-07-16 10:44:13 +0100462 // will stay unique:
463 hashToindex.set(hash, i);
464
Florian Mayer697e0c22020-01-30 17:01:10 +0000465 flamegraphData.push({
466 id: i,
467 totalSize,
468 depth,
469 parentId,
470 name,
471 selfSize,
472 mapping,
Isabelle Taylor8ee25832020-10-28 16:25:27 +0000473 merged: false,
Tuchila Octavian795f60b2021-05-24 16:39:38 +0100474 highlighted,
Hector Dearman23c078b2022-06-05 12:00:25 +0100475 location,
Florian Mayer697e0c22020-01-30 17:01:10 +0000476 });
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000477 }
478 return flamegraphData;
479 }
480
Hector Dearmanda988af2020-02-25 13:42:42 +0000481 private async prepareViewsAndTables(
Steve Golton7e63f422024-03-18 14:17:32 +0000482 start: time,
483 end: time,
484 upids: number[],
485 type: ProfileType,
486 focusRegex: string,
Bingqian Liu7afd5452024-03-27 18:42:47 +0000487 viewingOption: FlamegraphStateViewingOption,
Steve Golton7e63f422024-03-18 14:17:32 +0000488 ): Promise<string> {
Ioannis Ilkos40cc9662022-07-15 15:35:09 +0100489 const flamegraphType = getFlamegraphType(type);
Ioannis Ilkos40cc9662022-07-15 15:35:09 +0100490 if (type === ProfileType.PERF_SAMPLE) {
Lalit Magantic07333b2024-01-24 01:12:49 +0000491 let upid: string;
492 let upidGroup: string;
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000493 if (upids.length > 1) {
Lalit Magantic07333b2024-01-24 01:12:49 +0000494 upid = `NULL`;
495 upidGroup = `'${FlamegraphController.serializeUpidGroup(upids)}'`;
496 } else {
497 upid = `${upids[0]}`;
498 upidGroup = `NULL`;
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000499 }
Tuchila Octavianc1e0f4c2021-09-23 17:35:09 +0100500 return this.cache.getTableName(
Steve Golton5dbacf52024-01-25 09:20:51 +0000501 `select id, name, map_name, parent_id, depth, cumulative_size,
Tuchila Octavianc1e0f4c2021-09-23 17:35:09 +0100502 cumulative_alloc_size, cumulative_count, cumulative_alloc_count,
503 size, alloc_size, count, alloc_count, source_file, line_number
Lalit Magantic07333b2024-01-24 01:12:49 +0000504 from experimental_flamegraph(
505 '${flamegraphType}',
506 NULL,
507 '>=${start},<=${end}',
508 ${upid},
509 ${upidGroup},
510 '${focusRegex}'
Steve Golton7e63f422024-03-18 14:17:32 +0000511 )`,
512 );
Tuchila Octavianc1e0f4c2021-09-23 17:35:09 +0100513 }
Bingqian Liu7afd5452024-03-27 18:42:47 +0000514 if (
515 type === ProfileType.JAVA_HEAP_GRAPH &&
516 isHeapGraphDominatorTreeViewingOption(viewingOption)
517 ) {
518 return this.cache.getTableName(
519 await this.loadHeapGraphDominatorTreeQuery(upids[0], end),
520 );
521 }
Hector Dearmanda988af2020-02-25 13:42:42 +0000522 return this.cache.getTableName(
Steve Golton5dbacf52024-01-25 09:20:51 +0000523 `select id, name, map_name, parent_id, depth, cumulative_size,
Florian Mayere13b68d2020-01-22 13:19:41 +0000524 cumulative_alloc_size, cumulative_count, cumulative_alloc_count,
Tuchila Octavian795f60b2021-05-24 16:39:38 +0100525 size, alloc_size, count, alloc_count, source_file, line_number
Lalit Magantic07333b2024-01-24 01:12:49 +0000526 from experimental_flamegraph(
527 '${flamegraphType}',
528 ${end},
529 NULL,
530 ${upids[0]},
531 NULL,
532 '${focusRegex}'
Steve Golton7e63f422024-03-18 14:17:32 +0000533 )`,
534 );
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000535 }
536
Bingqian Liu7afd5452024-03-27 18:42:47 +0000537 private async loadHeapGraphDominatorTreeQuery(upid: number, timestamp: time) {
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100538 const outputTableName = `heap_graph_type_dominated_${upid}_${timestamp}`;
539
Bingqian Liu7afd5452024-03-27 18:42:47 +0000540 this.args.engine.query(`
541 INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree;
542
543 -- heap graph dominator tree with objects as nodes and all relavant
544 -- object self stats and dominated stats
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100545 CREATE PERFETTO TABLE _heap_graph_object_dominated AS
Bingqian Liu7afd5452024-03-27 18:42:47 +0000546 SELECT
547 node.id,
548 node.idom_id,
549 node.dominated_obj_count,
550 node.dominated_size_bytes + node.dominated_native_size_bytes AS dominated_size,
551 node.depth,
552 obj.type_id,
553 obj.root_type,
554 obj.self_size + obj.native_size AS self_size
555 FROM memory_heap_graph_dominator_tree node
556 JOIN heap_graph_object obj USING(id)
557 WHERE obj.upid = ${upid} AND obj.graph_sample_ts = ${timestamp}
558 -- required to accelerate the recursive cte below
559 ORDER BY idom_id;
560
561 -- calculate for each object node in the dominator tree the
562 -- HASH(path of type_id's from the super root to the object)
563 CREATE PERFETTO TABLE _dominator_tree_path_hash AS
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100564 WITH RECURSIVE _tree_visitor(id, path_hash) AS (
Bingqian Liu7afd5452024-03-27 18:42:47 +0000565 SELECT
566 id,
Bingqian Liu7afd5452024-03-27 18:42:47 +0000567 HASH(
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100568 CAST(type_id AS TEXT) || '-' || IFNULL(root_type, '')
Bingqian Liu7afd5452024-03-27 18:42:47 +0000569 ) AS path_hash
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100570 FROM _heap_graph_object_dominated
Bingqian Liu7afd5452024-03-27 18:42:47 +0000571 WHERE depth = 1
572 UNION ALL
573 SELECT
574 child.id,
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100575 HASH(CAST(parent.path_hash AS TEXT) || '/' || CAST(type_id AS TEXT)) AS path_hash
576 FROM _heap_graph_object_dominated child
Bingqian Liu7afd5452024-03-27 18:42:47 +0000577 JOIN _tree_visitor parent ON child.idom_id = parent.id
578 )
579 SELECT * from _tree_visitor
580 ORDER BY id;
581
582 -- merge object nodes with the same path into one "class type node", so the
583 -- end result is a tree where nodes are identified by their types and the
584 -- dominator relationships are preserved.
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100585 CREATE PERFETTO TABLE ${outputTableName} AS
Bingqian Liu7afd5452024-03-27 18:42:47 +0000586 SELECT
587 map.path_hash as id,
588 COALESCE(cls.deobfuscated_name, cls.name, '[NULL]') || IIF(
589 node.root_type IS NOT NULL,
590 ' [' || node.root_type || ']', ''
591 ) AS name,
592 IFNULL(parent_map.path_hash, -1) AS parent_id,
593 node.depth - 1 AS depth,
594 sum(dominated_size) AS cumulative_size,
595 -1 AS cumulative_alloc_size,
596 sum(dominated_obj_count) AS cumulative_count,
597 -1 AS cumulative_alloc_count,
598 '' as map_name,
599 '' as source_file,
600 -1 as line_number,
601 sum(self_size) AS size,
602 count(*) AS count
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100603 FROM _heap_graph_object_dominated node
Bingqian Liu7afd5452024-03-27 18:42:47 +0000604 JOIN _dominator_tree_path_hash map USING(id)
605 LEFT JOIN _dominator_tree_path_hash parent_map ON node.idom_id = parent_map.id
606 JOIN heap_graph_class cls ON node.type_id = cls.id
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100607 GROUP BY map.path_hash, name, parent_id, depth, map_name, source_file, line_number;
Bingqian Liu7afd5452024-03-27 18:42:47 +0000608
Ioannis Ilkose03ca502024-04-23 18:57:23 +0100609 -- These are intermediates and not needed
610 DROP TABLE _heap_graph_object_dominated;
611 DROP TABLE _dominator_tree_path_hash;
612 `);
613
614 return `SELECT * FROM ${outputTableName}`;
Bingqian Liu7afd5452024-03-27 18:42:47 +0000615 }
616
Steve Golton7e63f422024-03-18 14:17:32 +0000617 getMinSizeDisplayed(
618 flamegraphData: CallsiteInfo[],
619 rootSize?: number,
620 ): number {
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000621 const timeState = globals.state.frontendLocalState.visibleState;
Steve Goltonf3897e22023-05-11 14:18:30 +0100622 const dur = globals.stateVisibleTime().duration;
Steve Goltonb3a389d2023-07-10 11:03:17 +0100623 // TODO(stevegolton): Does this actually do what we want???
624 let width = Duration.toSeconds(dur / timeState.resolution);
Hector Dearman77bf83f2020-09-09 15:54:07 +0100625 // TODO(168048193): Remove screen size hack:
626 width = Math.max(width, 800);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000627 if (rootSize === undefined) {
628 rootSize = findRootSize(flamegraphData);
629 }
Steve Golton7e63f422024-03-18 14:17:32 +0000630 return (MIN_PIXEL_DISPLAYED * rootSize) / width;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000631 }
Neda Topoljanac17432412019-11-29 16:50:44 +0000632
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000633 async getFlamegraphMetadata(
Steve Golton7e63f422024-03-18 14:17:32 +0000634 type: ProfileType,
635 start: time,
636 end: time,
637 upids: number[],
638 ): Promise<FlamegraphDetails | undefined> {
Neda Topoljanac17432412019-11-29 16:50:44 +0000639 // Don't do anything if selection of the marker stayed the same.
Steve Golton7e63f422024-03-18 14:17:32 +0000640 if (
641 this.lastSelectedFlamegraphState !== undefined &&
642 this.lastSelectedFlamegraphState.start === start &&
643 this.lastSelectedFlamegraphState.end === end &&
644 FlamegraphController.areArraysEqual(
645 this.lastSelectedFlamegraphState.upids,
646 upids,
647 )
648 ) {
Neda Topoljanac17432412019-11-29 16:50:44 +0000649 return undefined;
650 }
651
Tuchila Octavian52047582021-09-21 15:35:54 +0100652 // Collecting data for more information about profile, such as:
Neda Topoljanac17432412019-11-29 16:50:44 +0000653 // total memory allocated, memory that is allocated and not freed.
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000654 const upidGroup = FlamegraphController.serializeUpidGroup(upids);
655
Hector Dearman7514f302021-08-19 18:21:52 +0100656 const result = await this.args.engine.query(
Steve Golton7e63f422024-03-18 14:17:32 +0000657 `select pid from process where upid in (${upidGroup})`,
658 );
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000659 const it = result.iter({pid: NUM});
660 const pids = [];
661 for (let i = 0; it.valid(); ++i, it.next()) {
662 pids.push(it.pid);
663 }
Steve Goltonf3897e22023-05-11 14:18:30 +0100664 return {start, dur: end - start, pids, upids, type};
Tuchila Octaviand54b3cd2021-11-15 10:38:26 +0000665 }
666
667 private static areArraysEqual(a: number[], b: number[]) {
668 if (a.length !== b.length) {
669 return false;
670 }
671 for (let i = 0; i < a.length; i++) {
672 if (a[i] !== b[i]) {
673 return false;
674 }
675 }
676 return true;
677 }
678
679 private static serializeUpidGroup(upids: number[]) {
680 return new Array(upids).join();
Neda Topoljanac17432412019-11-29 16:50:44 +0000681 }
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000682}