|  | /* | 
|  | * 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; | 
|  | } |