blob: 50afa4296b30ac62bf68408625d1540eca90a991 [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
38export class HeapProfileController extends Controller<'main'> {
39 private flamegraphDatasets: Map<string, CallsiteInfo[]> = new Map();
40 private lastSelectedHeapProfile?: HeapProfileFlamegraph;
Neda Topoljanac17432412019-11-29 16:50:44 +000041 private requestingData = false;
42 private queuedRequest = false;
43 private heapProfileDetails: HeapProfileDetails = {};
Neda Topoljanacb62621c2019-11-26 12:41:58 +000044
45 constructor(private args: HeapProfileControllerArgs) {
46 super('main');
47 }
48
49 run() {
50 const selection = globals.state.currentHeapProfileFlamegraph;
51
52 if (!selection) return;
53
Neda Topoljanac17432412019-11-29 16:50:44 +000054 if (this.shouldRequestData(selection)) {
55 if (this.requestingData) {
56 this.queuedRequest = true;
57 } else {
58 this.requestingData = true;
59 const selectedHeapProfile: HeapProfileFlamegraph =
60 this.copyHeapProfile(selection);
Neda Topoljanacb62621c2019-11-26 12:41:58 +000061
Neda Topoljanac17432412019-11-29 16:50:44 +000062 this.getHeapProfileMetadata(
63 selectedHeapProfile.ts, selectedHeapProfile.upid)
64 .then(result => {
65 if (result !== undefined) {
66 Object.assign(this.heapProfileDetails, result);
Neda Topoljanacb62621c2019-11-26 12:41:58 +000067 }
Neda Topoljanac17432412019-11-29 16:50:44 +000068
69 this.lastSelectedHeapProfile = this.copyHeapProfile(selection);
70
71 const expandedId = selectedHeapProfile.expandedCallsite ?
72 selectedHeapProfile.expandedCallsite.id :
73 -1;
74 const rootSize =
75 selectedHeapProfile.expandedCallsite === undefined ?
76 undefined :
77 selectedHeapProfile.expandedCallsite.totalSize;
78
79 const key =
80 `${selectedHeapProfile.upid};${selectedHeapProfile.ts}`;
81
82 this.getFlamegraphData(
83 key,
84 selectedHeapProfile.viewingOption ?
85 selectedHeapProfile.viewingOption :
86 DEFAULT_VIEWING_OPTION,
87 selection.ts,
Florian Mayerc62855a2020-01-22 18:15:06 +000088 selectedHeapProfile.upid,
89 selectedHeapProfile.type)
Neda Topoljanac17432412019-11-29 16:50:44 +000090 .then(flamegraphData => {
91 if (flamegraphData !== undefined && selection &&
92 selection.kind === selectedHeapProfile.kind &&
93 selection.id === selectedHeapProfile.id &&
94 selection.ts === selectedHeapProfile.ts) {
95 const expandedFlamegraphData =
96 expandCallsites(flamegraphData, expandedId);
97 this.prepareAndMergeCallsites(
98 expandedFlamegraphData,
99 this.lastSelectedHeapProfile!.viewingOption,
100 rootSize,
101 this.lastSelectedHeapProfile!.expandedCallsite);
102 }
103 })
104 .finally(() => {
105 this.requestingData = false;
106 if (this.queuedRequest) {
107 this.queuedRequest = false;
108 this.run();
109 }
110 });
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000111 });
112 }
113 }
114 }
115
Neda Topoljanac17432412019-11-29 16:50:44 +0000116 private copyHeapProfile(heapProfile: HeapProfileFlamegraph):
117 HeapProfileFlamegraph {
118 return {
119 kind: heapProfile.kind,
120 id: heapProfile.id,
121 upid: heapProfile.upid,
122 ts: heapProfile.ts,
Florian Mayerc62855a2020-01-22 18:15:06 +0000123 type: heapProfile.type,
Neda Topoljanac17432412019-11-29 16:50:44 +0000124 expandedCallsite: heapProfile.expandedCallsite,
125 viewingOption: heapProfile.viewingOption
126 };
127 }
128
129 private shouldRequestData(selection: HeapProfileFlamegraph) {
130 return selection.kind === 'HEAP_PROFILE_FLAMEGRAPH' &&
131 (this.lastSelectedHeapProfile === undefined ||
132 (this.lastSelectedHeapProfile !== undefined &&
133 (this.lastSelectedHeapProfile.id !== selection.id ||
134 this.lastSelectedHeapProfile.ts !== selection.ts ||
Florian Mayerc62855a2020-01-22 18:15:06 +0000135 this.lastSelectedHeapProfile.type !== selection.type ||
Neda Topoljanac17432412019-11-29 16:50:44 +0000136 this.lastSelectedHeapProfile.upid !== selection.upid ||
137 this.lastSelectedHeapProfile.viewingOption !==
138 selection.viewingOption ||
139 this.lastSelectedHeapProfile.expandedCallsite !==
140 selection.expandedCallsite)));
141 }
142
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000143 private prepareAndMergeCallsites(
144 flamegraphData: CallsiteInfo[],
145 viewingOption: string|undefined = DEFAULT_VIEWING_OPTION,
146 rootSize?: number, expandedCallsite?: CallsiteInfo) {
147 const mergedFlamegraphData = mergeCallsites(
148 flamegraphData, this.getMinSizeDisplayed(flamegraphData, rootSize));
Neda Topoljanac17432412019-11-29 16:50:44 +0000149 this.heapProfileDetails.flamegraph = mergedFlamegraphData;
150 this.heapProfileDetails.expandedCallsite = expandedCallsite;
151 this.heapProfileDetails.viewingOption = viewingOption;
152 globals.publish('HeapProfileDetails', this.heapProfileDetails);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000153 }
154
155
156 async getFlamegraphData(
Florian Mayerc62855a2020-01-22 18:15:06 +0000157 baseKey: string, viewingOption: string, ts: number, upid: number,
158 type: string): Promise<CallsiteInfo[]> {
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000159 let currentData: CallsiteInfo[];
160 const key = `${baseKey}-${viewingOption}`;
161 if (this.flamegraphDatasets.has(key)) {
162 currentData = this.flamegraphDatasets.get(key)!;
163 } else {
Neda Topoljanaccbec1c62019-11-28 16:45:41 +0000164 // TODO(taylori): Show loading state.
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000165
166 // Collecting data for drawing flamegraph for selected heap profile.
167 // Data needs to be in following format:
168 // id, name, parent_id, depth, total_size
Florian Mayerc62855a2020-01-22 18:15:06 +0000169 const tableName = await this.prepareViewsAndTables(ts, upid, type);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000170 currentData =
171 await this.getFlamegraphDataFromTables(tableName, viewingOption);
172 this.flamegraphDatasets.set(key, currentData);
173 }
174 return currentData;
175 }
176
177 async getFlamegraphDataFromTables(
178 tableName: string, viewingOption = DEFAULT_VIEWING_OPTION) {
179 let orderBy = '';
180 let sizeIndex = 4;
Florian Mayer187c4112020-01-31 14:08:18 +0000181 let selfIndex = 9;
Florian Mayer68845bf2020-01-23 13:29:58 +0000182 // TODO(fmayer): Improve performance so this is no longer necessary.
183 // Alternatively consider collapsing frames of the same label.
184 const maxDepth = 100;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000185 switch (viewingOption) {
186 case SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY:
Florian Mayer68845bf2020-01-23 13:29:58 +0000187 orderBy = `where cumulative_size > 0 and depth < ${
188 maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000189 cumulative_size desc, name`;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000190 sizeIndex = 4;
Florian Mayer187c4112020-01-31 14:08:18 +0000191 selfIndex = 9;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000192 break;
193 case ALLOC_SPACE_MEMORY_ALLOCATED_KEY:
Florian Mayer68845bf2020-01-23 13:29:58 +0000194 orderBy = `where cumulative_alloc_size > 0 and depth < ${
195 maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000196 cumulative_alloc_size desc, name`;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000197 sizeIndex = 5;
Florian Mayer187c4112020-01-31 14:08:18 +0000198 selfIndex = 9;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000199 break;
200 case OBJECTS_ALLOCATED_NOT_FREED_KEY:
Florian Mayer68845bf2020-01-23 13:29:58 +0000201 orderBy = `where cumulative_count > 0 and depth < ${
202 maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000203 cumulative_count desc, name`;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000204 sizeIndex = 6;
Florian Mayer187c4112020-01-31 14:08:18 +0000205 selfIndex = 10;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000206 break;
207 case OBJECTS_ALLOCATED_KEY:
Florian Mayer68845bf2020-01-23 13:29:58 +0000208 orderBy = `where cumulative_alloc_count > 0 and depth < ${
209 maxDepth} order by depth, parent_id,
Florian Mayere13b68d2020-01-22 13:19:41 +0000210 cumulative_alloc_count desc, name`;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000211 sizeIndex = 7;
Florian Mayer187c4112020-01-31 14:08:18 +0000212 selfIndex = 10;
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000213 break;
214 default:
215 break;
216 }
217
218 const callsites = await this.args.engine.query(
Florian Mayer34772c22020-01-23 15:26:50 +0000219 `SELECT id, IFNULL(DEMANGLE(name), name), IFNULL(parent_id, -1), depth,
220 cumulative_size, cumulative_alloc_size, cumulative_count,
Florian Mayer187c4112020-01-31 14:08:18 +0000221 cumulative_alloc_count, map_name, size, count from ${tableName} ${
222 orderBy}`);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000223
224 const flamegraphData: CallsiteInfo[] = new Array();
225 const hashToindex: Map<number, number> = new Map();
226 for (let i = 0; i < callsites.numRecords; i++) {
227 const hash = callsites.columns[0].longValues![i];
Florian Mayer68845bf2020-01-23 13:29:58 +0000228 let name = callsites.columns[1].stringValues![i];
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000229 const parentHash = callsites.columns[2].longValues![i];
230 const depth = +callsites.columns[3].longValues![i];
231 const totalSize = +callsites.columns[sizeIndex].longValues![i];
232 const mapping = callsites.columns[8].stringValues![i];
Florian Mayer187c4112020-01-31 14:08:18 +0000233 const selfSize = +callsites.columns[selfIndex].longValues![i];
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000234 const parentId =
235 hashToindex.has(+parentHash) ? hashToindex.get(+parentHash)! : -1;
Florian Mayer68845bf2020-01-23 13:29:58 +0000236 if (depth === maxDepth - 1) {
237 name += ' [tree truncated]';
238 }
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000239 hashToindex.set(+hash, i);
240 // Instead of hash, we will store index of callsite in this original array
241 // as an id of callsite. That way, we have quicker access to parent and it
242 // will stay unique.
Florian Mayer697e0c22020-01-30 17:01:10 +0000243 flamegraphData.push({
244 id: i,
245 totalSize,
246 depth,
247 parentId,
248 name,
249 selfSize,
250 mapping,
251 merged: false
252 });
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000253 }
254 return flamegraphData;
255 }
256
Florian Mayerc62855a2020-01-22 18:15:06 +0000257 private async prepareViewsAndTables(ts: number, upid: number, type: string):
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000258 Promise<string> {
259 // Creating unique names for views so we can reuse and not delete them
260 // for each marker.
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000261 const tableNameGroupedCallsitesForFlamegraph =
262 this.tableName(`grouped_callsites_for_flamegraph`);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000263
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000264 await this.args.engine.query(`create temp table if not exists ${
Florian Mayere13b68d2020-01-22 13:19:41 +0000265 tableNameGroupedCallsitesForFlamegraph} as
266 select id, name, map_name, parent_id, depth, cumulative_size,
267 cumulative_alloc_size, cumulative_count, cumulative_alloc_count,
268 size, alloc_size, count, alloc_count
Florian Mayerc62855a2020-01-22 18:15:06 +0000269 from experimental_flamegraph(${ts}, ${upid}, '${type}')`);
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000270 return tableNameGroupedCallsitesForFlamegraph;
271 }
272
273 tableName(name: string): string {
274 const selection = globals.state.currentHeapProfileFlamegraph;
275 if (!selection) return name;
276 return `${name}_${selection.upid}_${selection.ts}`;
277 }
278
279 getMinSizeDisplayed(flamegraphData: CallsiteInfo[], rootSize?: number):
280 number {
281 const timeState = globals.state.frontendLocalState.visibleState;
282 const width =
283 (timeState.endSec - timeState.startSec) / timeState.resolution;
284 if (rootSize === undefined) {
285 rootSize = findRootSize(flamegraphData);
286 }
287 return MIN_PIXEL_DISPLAYED * rootSize / width;
288 }
Neda Topoljanac17432412019-11-29 16:50:44 +0000289
290 async getHeapProfileMetadata(ts: number, upid: number) {
291 // Don't do anything if selection of the marker stayed the same.
292 if ((this.lastSelectedHeapProfile !== undefined &&
293 ((this.lastSelectedHeapProfile.ts === ts &&
294 this.lastSelectedHeapProfile.upid === upid)))) {
295 return undefined;
296 }
297
298 // Collecting data for more information about heap profile, such as:
299 // total memory allocated, memory that is allocated and not freed.
300 const pidValue = await this.args.engine.query(
301 `select pid from process where upid = ${upid}`);
302 const pid = pidValue.columns[0].longValues![0];
303 const allocatedMemory = await this.args.engine.query(
304 `select sum(size) from heap_profile_allocation where ts <= ${
305 ts} and size > 0 and upid = ${upid}`);
306 const allocated = allocatedMemory.columns[0].longValues![0];
307 const allocatedNotFreedMemory = await this.args.engine.query(
308 `select sum(size) from heap_profile_allocation where ts <= ${
309 ts} and upid = ${upid}`);
310 const allocatedNotFreed = allocatedNotFreedMemory.columns[0].longValues![0];
311 const startTime = fromNs(ts) - globals.state.traceTime.startSec;
312 return {ts: startTime, allocated, allocatedNotFreed, tsNs: ts, pid, upid};
313 }
Neda Topoljanacb62621c2019-11-26 12:41:58 +0000314}