blob: 09092d409ed122a7bd715979fb2786da6cdcb3ea [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
15import {Engine} from '../common/engine';
16import {
17 ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
18 DEFAULT_VIEWING_OPTION,
19 expandCallsites,
20 findRootSize,
21 mergeCallsites,
22 OBJECTS_ALLOCATED_KEY,
23 OBJECTS_ALLOCATED_NOT_FREED_KEY,
24 SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY
25} from '../common/flamegraph_util';
26import {CallsiteInfo, HeapProfileFlamegraph} from '../common/state';
Neda Topoljanac17432412019-11-29 16:50:44 +000027import {fromNs} from '../common/time';
28import {HeapProfileDetails} from '../frontend/globals';
Neda Topoljanacb62621c2019-11-26 12:41:58 +000029
30import {Controller} from './controller';
31import {globals} from './globals';
32
33export interface HeapProfileControllerArgs {
34 engine: Engine;
35}
36const MIN_PIXEL_DISPLAYED = 1;
37
Hector Dearmanda988af2020-02-25 13:42:42 +000038class TablesCache {
39 private engine: Engine;
40 private cache: Map<string, string>;
41 private prefix: string;
42 private tableId: number;
43 private cacheSizeLimit: number;
44
45 constructor(engine: Engine, prefix: string) {
46 this.engine = engine;
47 this.cache = new Map<string, string>();
48 this.prefix = prefix;
49 this.tableId = 0;
50 this.cacheSizeLimit = 10;
51 }
52
53 async getTableName(query: string): Promise<string> {
54 let tableName = this.cache.get(query);
55 if (tableName === undefined) {
56 // TODO(hjd): This should be LRU.
57 if (this.cache.size > this.cacheSizeLimit) {
58 for (const name of this.cache.values()) {
59 await this.engine.query(`drop table ${name}`);
60 }
61 this.cache.clear();
62 }
63 tableName = `${this.prefix}_${this.tableId++}`;
64 await this.engine.query(
65 `create temp table if not exists ${tableName} as ${query}`);
66 this.cache.set(query, tableName);
67 }
68 return tableName;
69 }
70}
71
Neda Topoljanacb62621c2019-11-26 12:41:58 +000072export class HeapProfileController extends Controller<'main'> {
73 private flamegraphDatasets: Map<string, CallsiteInfo[]> = new Map();
74 private lastSelectedHeapProfile?: HeapProfileFlamegraph;
Neda Topoljanac17432412019-11-29 16:50:44 +000075 private requestingData = false;
76 private queuedRequest = false;
77 private heapProfileDetails: HeapProfileDetails = {};
Hector Dearmanda988af2020-02-25 13:42:42 +000078 private cache: TablesCache;
Neda Topoljanacb62621c2019-11-26 12:41:58 +000079
80 constructor(private args: HeapProfileControllerArgs) {
81 super('main');
Hector Dearmanda988af2020-02-25 13:42:42 +000082 this.cache = new TablesCache(args.engine, 'grouped_callsites');
Neda Topoljanacb62621c2019-11-26 12:41:58 +000083 }
84
85 run() {
86 const selection = globals.state.currentHeapProfileFlamegraph;
87
88 if (!selection) return;
89
Neda Topoljanac17432412019-11-29 16:50:44 +000090 if (this.shouldRequestData(selection)) {
91 if (this.requestingData) {
92 this.queuedRequest = true;
93 } else {
94 this.requestingData = true;
95 const selectedHeapProfile: HeapProfileFlamegraph =
96 this.copyHeapProfile(selection);
Neda Topoljanacb62621c2019-11-26 12:41:58 +000097
Neda Topoljanac17432412019-11-29 16:50:44 +000098 this.getHeapProfileMetadata(
Ioannis Ilkos4cf94e32020-03-26 11:22:44 +000099 selection.type,
100 selectedHeapProfile.ts,
101 selectedHeapProfile.upid)
Neda Topoljanac17432412019-11-29 16:50:44 +0000102 .then(result => {
103 if (result !== undefined) {
104 Object.assign(this.heapProfileDetails, result);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000105 }
Neda Topoljanac17432412019-11-29 16:50:44 +0000106
Hector Dearmanda988af2020-02-25 13:42:42 +0000107 // TODO(hjd): Clean this up.
108 if (this.lastSelectedHeapProfile &&
109 this.lastSelectedHeapProfile.focusRegex !==
110 selection.focusRegex) {
111 this.flamegraphDatasets.clear();
112 }
113
Neda Topoljanac17432412019-11-29 16:50:44 +0000114 this.lastSelectedHeapProfile = this.copyHeapProfile(selection);
115
116 const expandedId = selectedHeapProfile.expandedCallsite ?
117 selectedHeapProfile.expandedCallsite.id :
118 -1;
119 const rootSize =
120 selectedHeapProfile.expandedCallsite === undefined ?
121 undefined :
122 selectedHeapProfile.expandedCallsite.totalSize;
123
124 const key =
125 `${selectedHeapProfile.upid};${selectedHeapProfile.ts}`;
126
127 this.getFlamegraphData(
128 key,
129 selectedHeapProfile.viewingOption ?
130 selectedHeapProfile.viewingOption :
131 DEFAULT_VIEWING_OPTION,
132 selection.ts,
Florian Mayerc62855a2020-01-22 18:15:06 +0000133 selectedHeapProfile.upid,
Hector Dearmanda988af2020-02-25 13:42:42 +0000134 selectedHeapProfile.type,
135 selectedHeapProfile.focusRegex)
Neda Topoljanac17432412019-11-29 16:50:44 +0000136 .then(flamegraphData => {
137 if (flamegraphData !== undefined && selection &&
138 selection.kind === selectedHeapProfile.kind &&
139 selection.id === selectedHeapProfile.id &&
140 selection.ts === selectedHeapProfile.ts) {
141 const expandedFlamegraphData =
142 expandCallsites(flamegraphData, expandedId);
143 this.prepareAndMergeCallsites(
144 expandedFlamegraphData,
145 this.lastSelectedHeapProfile!.viewingOption,
146 rootSize,
147 this.lastSelectedHeapProfile!.expandedCallsite);
148 }
149 })
150 .finally(() => {
151 this.requestingData = false;
152 if (this.queuedRequest) {
153 this.queuedRequest = false;
154 this.run();
155 }
156 });
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000157 });
158 }
159 }
160 }
161
Neda Topoljanac17432412019-11-29 16:50:44 +0000162 private copyHeapProfile(heapProfile: HeapProfileFlamegraph):
163 HeapProfileFlamegraph {
164 return {
165 kind: heapProfile.kind,
166 id: heapProfile.id,
167 upid: heapProfile.upid,
168 ts: heapProfile.ts,
Florian Mayerc62855a2020-01-22 18:15:06 +0000169 type: heapProfile.type,
Neda Topoljanac17432412019-11-29 16:50:44 +0000170 expandedCallsite: heapProfile.expandedCallsite,
Hector Dearmanda988af2020-02-25 13:42:42 +0000171 viewingOption: heapProfile.viewingOption,
172 focusRegex: heapProfile.focusRegex,
Neda Topoljanac17432412019-11-29 16:50:44 +0000173 };
174 }
175
176 private shouldRequestData(selection: HeapProfileFlamegraph) {
177 return selection.kind === 'HEAP_PROFILE_FLAMEGRAPH' &&
178 (this.lastSelectedHeapProfile === undefined ||
179 (this.lastSelectedHeapProfile !== undefined &&
180 (this.lastSelectedHeapProfile.id !== selection.id ||
181 this.lastSelectedHeapProfile.ts !== selection.ts ||
Florian Mayerc62855a2020-01-22 18:15:06 +0000182 this.lastSelectedHeapProfile.type !== selection.type ||
Neda Topoljanac17432412019-11-29 16:50:44 +0000183 this.lastSelectedHeapProfile.upid !== selection.upid ||
184 this.lastSelectedHeapProfile.viewingOption !==
185 selection.viewingOption ||
Hector Dearmanda988af2020-02-25 13:42:42 +0000186 this.lastSelectedHeapProfile.focusRegex !== selection.focusRegex ||
Neda Topoljanac17432412019-11-29 16:50:44 +0000187 this.lastSelectedHeapProfile.expandedCallsite !==
188 selection.expandedCallsite)));
189 }
190
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000191 private prepareAndMergeCallsites(
192 flamegraphData: CallsiteInfo[],
193 viewingOption: string|undefined = DEFAULT_VIEWING_OPTION,
194 rootSize?: number, expandedCallsite?: CallsiteInfo) {
195 const mergedFlamegraphData = mergeCallsites(
196 flamegraphData, this.getMinSizeDisplayed(flamegraphData, rootSize));
Neda Topoljanac17432412019-11-29 16:50:44 +0000197 this.heapProfileDetails.flamegraph = mergedFlamegraphData;
198 this.heapProfileDetails.expandedCallsite = expandedCallsite;
199 this.heapProfileDetails.viewingOption = viewingOption;
200 globals.publish('HeapProfileDetails', this.heapProfileDetails);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000201 }
202
203
204 async getFlamegraphData(
Florian Mayerc62855a2020-01-22 18:15:06 +0000205 baseKey: string, viewingOption: string, ts: number, upid: number,
Hector Dearmanda988af2020-02-25 13:42:42 +0000206 type: string, focusRegex: string): Promise<CallsiteInfo[]> {
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000207 let currentData: CallsiteInfo[];
208 const key = `${baseKey}-${viewingOption}`;
209 if (this.flamegraphDatasets.has(key)) {
210 currentData = this.flamegraphDatasets.get(key)!;
211 } else {
Neda Topoljanaccbec1c62019-11-28 16:45:41 +0000212 // TODO(taylori): Show loading state.
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000213
214 // Collecting data for drawing flamegraph for selected heap profile.
215 // Data needs to be in following format:
216 // id, name, parent_id, depth, total_size
Hector Dearmanda988af2020-02-25 13:42:42 +0000217 const tableName =
218 await this.prepareViewsAndTables(ts, upid, type, focusRegex);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000219 currentData =
220 await this.getFlamegraphDataFromTables(tableName, viewingOption);
221 this.flamegraphDatasets.set(key, currentData);
222 }
223 return currentData;
224 }
225
226 async getFlamegraphDataFromTables(
227 tableName: string, viewingOption = DEFAULT_VIEWING_OPTION) {
228 let orderBy = '';
229 let sizeIndex = 4;
Florian Mayer187c4112020-01-31 14:08:18 +0000230 let selfIndex = 9;
Florian Mayer68845bf2020-01-23 13:29:58 +0000231 // TODO(fmayer): Improve performance so this is no longer necessary.
232 // Alternatively consider collapsing frames of the same label.
233 const maxDepth = 100;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000234 switch (viewingOption) {
235 case SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY:
Florian Mayer68845bf2020-01-23 13:29:58 +0000236 orderBy = `where cumulative_size > 0 and depth < ${
237 maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000238 cumulative_size desc, name`;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000239 sizeIndex = 4;
Florian Mayer187c4112020-01-31 14:08:18 +0000240 selfIndex = 9;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000241 break;
242 case ALLOC_SPACE_MEMORY_ALLOCATED_KEY:
Florian Mayer68845bf2020-01-23 13:29:58 +0000243 orderBy = `where cumulative_alloc_size > 0 and depth < ${
244 maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000245 cumulative_alloc_size desc, name`;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000246 sizeIndex = 5;
Florian Mayer187c4112020-01-31 14:08:18 +0000247 selfIndex = 9;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000248 break;
249 case OBJECTS_ALLOCATED_NOT_FREED_KEY:
Florian Mayer68845bf2020-01-23 13:29:58 +0000250 orderBy = `where cumulative_count > 0 and depth < ${
251 maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000252 cumulative_count desc, name`;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000253 sizeIndex = 6;
Florian Mayer187c4112020-01-31 14:08:18 +0000254 selfIndex = 10;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000255 break;
256 case OBJECTS_ALLOCATED_KEY:
Florian Mayer68845bf2020-01-23 13:29:58 +0000257 orderBy = `where cumulative_alloc_count > 0 and depth < ${
258 maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000259 cumulative_alloc_count desc, name`;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000260 sizeIndex = 7;
Florian Mayer187c4112020-01-31 14:08:18 +0000261 selfIndex = 10;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000262 break;
263 default:
264 break;
265 }
266
267 const callsites = await this.args.engine.query(
Florian Mayer34772c22020-01-23 15:26:50 +0000268 `SELECT id, IFNULL(DEMANGLE(name), name), IFNULL(parent_id, -1), depth,
269 cumulative_size, cumulative_alloc_size, cumulative_count,
Florian Mayer187c4112020-01-31 14:08:18 +0000270 cumulative_alloc_count, map_name, size, count from ${tableName} ${
271 orderBy}`);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000272
273 const flamegraphData: CallsiteInfo[] = new Array();
274 const hashToindex: Map<number, number> = new Map();
275 for (let i = 0; i < callsites.numRecords; i++) {
276 const hash = callsites.columns[0].longValues![i];
Florian Mayer68845bf2020-01-23 13:29:58 +0000277 let name = callsites.columns[1].stringValues![i];
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000278 const parentHash = callsites.columns[2].longValues![i];
279 const depth = +callsites.columns[3].longValues![i];
280 const totalSize = +callsites.columns[sizeIndex].longValues![i];
281 const mapping = callsites.columns[8].stringValues![i];
Florian Mayer187c4112020-01-31 14:08:18 +0000282 const selfSize = +callsites.columns[selfIndex].longValues![i];
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000283 const parentId =
284 hashToindex.has(+parentHash) ? hashToindex.get(+parentHash)! : -1;
Florian Mayer68845bf2020-01-23 13:29:58 +0000285 if (depth === maxDepth - 1) {
286 name += ' [tree truncated]';
287 }
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000288 hashToindex.set(+hash, i);
289 // Instead of hash, we will store index of callsite in this original array
290 // as an id of callsite. That way, we have quicker access to parent and it
291 // will stay unique.
Florian Mayer697e0c22020-01-30 17:01:10 +0000292 flamegraphData.push({
293 id: i,
294 totalSize,
295 depth,
296 parentId,
297 name,
298 selfSize,
299 mapping,
300 merged: false
301 });
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000302 }
303 return flamegraphData;
304 }
305
Hector Dearmanda988af2020-02-25 13:42:42 +0000306 private async prepareViewsAndTables(
307 ts: number, upid: number, type: string,
308 focusRegex: string): Promise<string> {
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000309 // Creating unique names for views so we can reuse and not delete them
310 // for each marker.
Hector Dearmanda988af2020-02-25 13:42:42 +0000311 let whereClause = '';
312 if (focusRegex !== '') {
313 whereClause = `where focus_str = '${focusRegex}'`;
314 }
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000315
Hector Dearmanda988af2020-02-25 13:42:42 +0000316 return this.cache.getTableName(
317 `select id, name, map_name, parent_id, depth, cumulative_size,
Florian Mayere13b68d2020-01-22 13:19:41 +0000318 cumulative_alloc_size, cumulative_count, cumulative_alloc_count,
319 size, alloc_size, count, alloc_count
Hector Dearmanda988af2020-02-25 13:42:42 +0000320 from experimental_flamegraph(${ts}, ${upid}, '${type}') ${
321 whereClause}`);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000322 }
323
324 getMinSizeDisplayed(flamegraphData: CallsiteInfo[], rootSize?: number):
325 number {
326 const timeState = globals.state.frontendLocalState.visibleState;
Hector Dearman77bf83f2020-09-09 15:54:07 +0100327 let width = (timeState.endSec - timeState.startSec) / timeState.resolution;
328 // TODO(168048193): Remove screen size hack:
329 width = Math.max(width, 800);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000330 if (rootSize === undefined) {
331 rootSize = findRootSize(flamegraphData);
332 }
333 return MIN_PIXEL_DISPLAYED * rootSize / width;
334 }
Neda Topoljanac17432412019-11-29 16:50:44 +0000335
Ioannis Ilkos4cf94e32020-03-26 11:22:44 +0000336 async getHeapProfileMetadata(type: string, ts: number, upid: number) {
Neda Topoljanac17432412019-11-29 16:50:44 +0000337 // Don't do anything if selection of the marker stayed the same.
338 if ((this.lastSelectedHeapProfile !== undefined &&
339 ((this.lastSelectedHeapProfile.ts === ts &&
340 this.lastSelectedHeapProfile.upid === upid)))) {
341 return undefined;
342 }
343
344 // Collecting data for more information about heap profile, such as:
345 // total memory allocated, memory that is allocated and not freed.
346 const pidValue = await this.args.engine.query(
347 `select pid from process where upid = ${upid}`);
348 const pid = pidValue.columns[0].longValues![0];
Neda Topoljanac17432412019-11-29 16:50:44 +0000349 const startTime = fromNs(ts) - globals.state.traceTime.startSec;
Ioannis Ilkos4cf94e32020-03-26 11:22:44 +0000350 return {ts: startTime, tsNs: ts, pid, upid, type};
Neda Topoljanac17432412019-11-29 16:50:44 +0000351 }
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000352}