blob: e8a998d3b722d56d4383b6757dd6a360a08f8089 [file] [log] [blame]
Andrea Falcone1c4977f2020-07-23 10:58:25 -04001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16'use strict';
17
18function flamegraphInit() {
19 let flamegraph = document.getElementById('flamegraph_id');
20 let svgs = flamegraph.getElementsByTagName('svg');
21 for (let i = 0; i < svgs.length; ++i) {
22 createZoomHistoryStack(svgs[i]);
23 adjust_text_size(svgs[i]);
24 }
25
26 function throttle(callback) {
27 let running = false;
28 return function() {
29 if (!running) {
30 running = true;
31 window.requestAnimationFrame(function () {
32 callback();
33 running = false;
34 });
35 }
36 };
37 }
38 window.addEventListener('resize', throttle(function() {
39 let flamegraph = document.getElementById('flamegraph_id');
40 let svgs = flamegraph.getElementsByTagName('svg');
41 for (let i = 0; i < svgs.length; ++i) {
42 adjust_text_size(svgs[i]);
43 }
44 }));
45}
46
47// Create a stack add the root svg element in it.
48function createZoomHistoryStack(svgElement) {
49 svgElement.zoomStack = [svgElement.getElementById(svgElement.attributes['rootid'].value)];
50}
51
52function adjust_node_text_size(x, svgWidth) {
53 let title = x.getElementsByTagName('title')[0];
54 let text = x.getElementsByTagName('text')[0];
55 let rect = x.getElementsByTagName('rect')[0];
56
57 let width = parseFloat(rect.attributes['width'].value) * svgWidth * 0.01;
58
59 // Don't even bother trying to find a best fit. The area is too small.
60 if (width < 28) {
61 text.textContent = '';
62 return;
63 }
64 // Remove dso and #samples which are here only for mouseover purposes.
65 let methodName = title.textContent.split(' | ')[0];
66
67 let numCharacters;
68 for (numCharacters = methodName.length; numCharacters > 4; numCharacters--) {
69 // Avoid reflow by using hard-coded estimate instead of
70 // text.getSubStringLength(0, numCharacters).
71 if (numCharacters * 7.5 <= width) {
72 break;
73 }
74 }
75
76 if (numCharacters == methodName.length) {
77 text.textContent = methodName;
78 return;
79 }
80
81 text.textContent = methodName.substring(0, numCharacters-2) + '..';
82}
83
84function adjust_text_size(svgElement) {
85 let svgWidth = window.innerWidth;
86 let x = svgElement.getElementsByTagName('g');
87 for (let i = 0; i < x.length; i++) {
88 adjust_node_text_size(x[i], svgWidth);
89 }
90}
91
92function zoom(e) {
93 let svgElement = e.ownerSVGElement;
94 let zoomStack = svgElement.zoomStack;
95 zoomStack.push(e);
96 displaySVGElement(svgElement);
97 select(e);
98
99 // Show zoom out button.
100 svgElement.getElementById('zoom_rect').style.display = 'block';
101 svgElement.getElementById('zoom_text').style.display = 'block';
102}
103
104function displaySVGElement(svgElement) {
105 let zoomStack = svgElement.zoomStack;
106 let e = zoomStack[zoomStack.length - 1];
107 let clicked_rect = e.getElementsByTagName('rect')[0];
108 let clicked_origin_x;
109 let clicked_origin_y = clicked_rect.attributes['oy'].value;
110 let clicked_origin_width;
111
112 if (zoomStack.length == 1) {
113 // Show all nodes when zoomStack only contains the root node.
114 // This is needed to show flamegraph containing more than one node at the root level.
115 clicked_origin_x = 0;
116 clicked_origin_width = 100;
117 } else {
118 clicked_origin_x = clicked_rect.attributes['ox'].value;
119 clicked_origin_width = clicked_rect.attributes['owidth'].value;
120 }
121
122
123 let svgBox = svgElement.getBoundingClientRect();
124 let svgBoxHeight = svgBox.height;
125 let svgBoxWidth = 100;
126 let scaleFactor = svgBoxWidth / clicked_origin_width;
127
128 let callsites = svgElement.getElementsByTagName('g');
129 for (let i = 0; i < callsites.length; i++) {
130 let text = callsites[i].getElementsByTagName('text')[0];
131 let rect = callsites[i].getElementsByTagName('rect')[0];
132
133 let rect_o_x = parseFloat(rect.attributes['ox'].value);
134 let rect_o_y = parseFloat(rect.attributes['oy'].value);
135
136 // Avoid multiple forced reflow by hiding nodes.
137 if (rect_o_y > clicked_origin_y) {
138 rect.style.display = 'none';
139 text.style.display = 'none';
140 continue;
141 }
142 rect.style.display = 'block';
143 text.style.display = 'block';
144
145 let newrec_x = rect.attributes['x'].value = (rect_o_x - clicked_origin_x) * scaleFactor +
146 '%';
147 let newrec_y = rect.attributes['y'].value = rect_o_y + (svgBoxHeight - clicked_origin_y
148 - 17 - 2);
149
150 text.attributes['y'].value = newrec_y + 12;
151 text.attributes['x'].value = newrec_x;
152
153 rect.attributes['width'].value = (rect.attributes['owidth'].value * scaleFactor) + '%';
154 }
155
156 adjust_text_size(svgElement);
157}
158
159function unzoom(e) {
160 let svgOwner = e.ownerSVGElement;
161 let stack = svgOwner.zoomStack;
162
163 // Unhighlight whatever was selected.
164 if (selected) {
165 selected.classList.remove('s');
166 }
167
168 // Stack management: Never remove the last element which is the flamegraph root.
169 if (stack.length > 1) {
170 let previouslySelected = stack.pop();
171 select(previouslySelected);
172 }
173
174 // Hide zoom out button.
175 if (stack.length == 1) {
176 svgOwner.getElementById('zoom_rect').style.display = 'none';
177 svgOwner.getElementById('zoom_text').style.display = 'none';
178 }
179
180 displaySVGElement(svgOwner);
181}
182
183function search(e) {
184 let term = prompt('Search for:', '');
185 let callsites = e.ownerSVGElement.getElementsByTagName('g');
186
187 if (!term) {
188 for (let i = 0; i < callsites.length; i++) {
189 let rect = callsites[i].getElementsByTagName('rect')[0];
190 rect.attributes['fill'].value = rect.attributes['ofill'].value;
191 }
192 return;
193 }
194
195 for (let i = 0; i < callsites.length; i++) {
196 let title = callsites[i].getElementsByTagName('title')[0];
197 let rect = callsites[i].getElementsByTagName('rect')[0];
198 if (title.textContent.indexOf(term) != -1) {
199 rect.attributes['fill'].value = 'rgb(230,100,230)';
200 } else {
201 rect.attributes['fill'].value = rect.attributes['ofill'].value;
202 }
203 }
204}
205
206let selected;
207document.addEventListener('keydown', (e) => {
208 if (!selected) {
209 return false;
210 }
211
212 let nav = selected.attributes['nav'].value.split(',');
213 let navigation_index;
214 switch (e.keyCode) {
215 // case 38: // ARROW UP
216 case 87: navigation_index = 0; break; // W
217
218 // case 32 : // ARROW LEFT
219 case 65: navigation_index = 1; break; // A
220
221 // case 43: // ARROW DOWN
222 case 68: navigation_index = 3; break; // S
223
224 // case 39: // ARROW RIGHT
225 case 83: navigation_index = 2; break; // D
226
227 case 32: zoom(selected); return false; // SPACE
228
229 case 8: // BACKSPACE
230 unzoom(selected); return false;
231 default: return true;
232 }
233
234 if (nav[navigation_index] == '0') {
235 return false;
236 }
237
238 let target_element = selected.ownerSVGElement.getElementById(nav[navigation_index]);
239 select(target_element);
240 return false;
241});
242
243function select(e) {
244 if (selected) {
245 selected.classList.remove('s');
246 }
247 selected = e;
248 selected.classList.add('s');
249
250 // Update info bar
251 let titleElement = selected.getElementsByTagName('title')[0];
252 let text = titleElement.textContent;
253
254 // Parse title
255 let method_and_info = text.split(' | ');
256 let methodName = method_and_info[0];
257 let info = method_and_info[1];
258
259 // Parse info
260 // '/system/lib64/libhwbinder.so (4 events: 0.28%)'
261 let regexp = /(.*) \((.*)\)/g;
262 let match = regexp.exec(info);
263 if (match.length > 2) {
264 let percentage = match[2];
265 // Write percentage
266 let percentageTextElement = selected.ownerSVGElement.getElementById('percent_text');
267 percentageTextElement.textContent = percentage;
268 // console.log("'" + percentage + "'")
269 }
270
271 // Set fields
272 let barTextElement = selected.ownerSVGElement.getElementById('info_text');
273 barTextElement.textContent = methodName;
274}