You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					274 lines
				
				8.6 KiB
			
		
		
			
		
	
	
					274 lines
				
				8.6 KiB
			| 
											6 years ago
										 | /*
 | ||
|  |  * 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;
 | ||
|  | }
 |