/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; function flamegraphInit() { let flamegraph = document.getElementById('flamegraph_id'); let svgs = flamegraph.getElementsByTagName('svg'); for (let i = 0; i < svgs.length; ++i) { createZoomHistoryStack(svgs[i]); adjust_text_size(svgs[i]); } function throttle(callback) { let running = false; return function() { if (!running) { running = true; window.requestAnimationFrame(function () { callback(); running = false; }); } }; } window.addEventListener('resize', throttle(function() { let flamegraph = document.getElementById('flamegraph_id'); let svgs = flamegraph.getElementsByTagName('svg'); for (let i = 0; i < svgs.length; ++i) { adjust_text_size(svgs[i]); } })); } // Create a stack add the root svg element in it. function createZoomHistoryStack(svgElement) { svgElement.zoomStack = [svgElement.getElementById(svgElement.attributes['rootid'].value)]; } function adjust_node_text_size(x, svgWidth) { let title = x.getElementsByTagName('title')[0]; let text = x.getElementsByTagName('text')[0]; let rect = x.getElementsByTagName('rect')[0]; let width = parseFloat(rect.attributes['width'].value) * svgWidth * 0.01; // Don't even bother trying to find a best fit. The area is too small. if (width < 28) { text.textContent = ''; return; } // Remove dso and #samples which are here only for mouseover purposes. let methodName = title.textContent.split(' | ')[0]; let numCharacters; for (numCharacters = methodName.length; numCharacters > 4; numCharacters--) { // Avoid reflow by using hard-coded estimate instead of // text.getSubStringLength(0, numCharacters). if (numCharacters * 7.5 <= width) { break; } } if (numCharacters == methodName.length) { text.textContent = methodName; return; } text.textContent = methodName.substring(0, numCharacters-2) + '..'; } function adjust_text_size(svgElement) { let svgWidth = window.innerWidth; let x = svgElement.getElementsByTagName('g'); for (let i = 0; i < x.length; i++) { adjust_node_text_size(x[i], svgWidth); } } function zoom(e) { let svgElement = e.ownerSVGElement; let zoomStack = svgElement.zoomStack; zoomStack.push(e); displaySVGElement(svgElement); select(e); // Show zoom out button. svgElement.getElementById('zoom_rect').style.display = 'block'; svgElement.getElementById('zoom_text').style.display = 'block'; } function displaySVGElement(svgElement) { let zoomStack = svgElement.zoomStack; let e = zoomStack[zoomStack.length - 1]; let clicked_rect = e.getElementsByTagName('rect')[0]; let clicked_origin_x; let clicked_origin_y = clicked_rect.attributes['oy'].value; let clicked_origin_width; if (zoomStack.length == 1) { // Show all nodes when zoomStack only contains the root node. // This is needed to show flamegraph containing more than one node at the root level. clicked_origin_x = 0; clicked_origin_width = 100; } else { clicked_origin_x = clicked_rect.attributes['ox'].value; clicked_origin_width = clicked_rect.attributes['owidth'].value; } let svgBox = svgElement.getBoundingClientRect(); let svgBoxHeight = svgBox.height; let svgBoxWidth = 100; let scaleFactor = svgBoxWidth / clicked_origin_width; let callsites = svgElement.getElementsByTagName('g'); for (let i = 0; i < callsites.length; i++) { let text = callsites[i].getElementsByTagName('text')[0]; let rect = callsites[i].getElementsByTagName('rect')[0]; let rect_o_x = parseFloat(rect.attributes['ox'].value); let rect_o_y = parseFloat(rect.attributes['oy'].value); // Avoid multiple forced reflow by hiding nodes. if (rect_o_y > clicked_origin_y) { rect.style.display = 'none'; text.style.display = 'none'; continue; } rect.style.display = 'block'; text.style.display = 'block'; let newrec_x = rect.attributes['x'].value = (rect_o_x - clicked_origin_x) * scaleFactor + '%'; let newrec_y = rect.attributes['y'].value = rect_o_y + (svgBoxHeight - clicked_origin_y - 17 - 2); text.attributes['y'].value = newrec_y + 12; text.attributes['x'].value = newrec_x; rect.attributes['width'].value = (rect.attributes['owidth'].value * scaleFactor) + '%'; } adjust_text_size(svgElement); } function unzoom(e) { let svgOwner = e.ownerSVGElement; let stack = svgOwner.zoomStack; // Unhighlight whatever was selected. if (selected) { selected.classList.remove('s'); } // Stack management: Never remove the last element which is the flamegraph root. if (stack.length > 1) { let previouslySelected = stack.pop(); select(previouslySelected); } // Hide zoom out button. if (stack.length == 1) { svgOwner.getElementById('zoom_rect').style.display = 'none'; svgOwner.getElementById('zoom_text').style.display = 'none'; } displaySVGElement(svgOwner); } function search(e) { let term = prompt('Search for:', ''); let callsites = e.ownerSVGElement.getElementsByTagName('g'); if (!term) { for (let i = 0; i < callsites.length; i++) { let rect = callsites[i].getElementsByTagName('rect')[0]; rect.attributes['fill'].value = rect.attributes['ofill'].value; } return; } for (let i = 0; i < callsites.length; i++) { let title = callsites[i].getElementsByTagName('title')[0]; let rect = callsites[i].getElementsByTagName('rect')[0]; if (title.textContent.indexOf(term) != -1) { rect.attributes['fill'].value = 'rgb(230,100,230)'; } else { rect.attributes['fill'].value = rect.attributes['ofill'].value; } } } let selected; document.addEventListener('keydown', (e) => { if (!selected) { return false; } let nav = selected.attributes['nav'].value.split(','); let navigation_index; switch (e.keyCode) { // case 38: // ARROW UP case 87: navigation_index = 0; break; // W // case 32 : // ARROW LEFT case 65: navigation_index = 1; break; // A // case 43: // ARROW DOWN case 68: navigation_index = 3; break; // S // case 39: // ARROW RIGHT case 83: navigation_index = 2; break; // D case 32: zoom(selected); return false; // SPACE case 8: // BACKSPACE unzoom(selected); return false; default: return true; } if (nav[navigation_index] == '0') { return false; } let target_element = selected.ownerSVGElement.getElementById(nav[navigation_index]); select(target_element); return false; }); function select(e) { if (selected) { selected.classList.remove('s'); } selected = e; selected.classList.add('s'); // Update info bar let titleElement = selected.getElementsByTagName('title')[0]; let text = titleElement.textContent; // Parse title let method_and_info = text.split(' | '); let methodName = method_and_info[0]; let info = method_and_info[1]; // Parse info // '/system/lib64/libhwbinder.so (4 events: 0.28%)' let regexp = /(.*) \((.*)\)/g; let match = regexp.exec(info); if (match.length > 2) { let percentage = match[2]; // Write percentage let percentageTextElement = selected.ownerSVGElement.getElementById('percent_text'); percentageTextElement.textContent = percentage; // console.log("'" + percentage + "'") } // Set fields let barTextElement = selected.ownerSVGElement.getElementById('info_text'); barTextElement.textContent = methodName; }