1 /* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 */ 34 WebInspector.AllocationProfile = function(profile) 35 { 36 this._strings = profile.strings; 37 38 this._nextNodeId = 1; 39 this._idToFunctionInfo = {}; 40 this._idToNode = {}; 41 this._collapsedTopNodeIdToFunctionInfo = {}; 42 43 this._traceTops = null; 44 45 this._buildAllocationFunctionInfos(profile); 46 this._traceTree = this._buildInvertedAllocationTree(profile); 47 } 48 49 WebInspector.AllocationProfile.prototype = { 50 _buildAllocationFunctionInfos: function(profile) 51 { 52 var strings = this._strings; 53 54 var functionInfoFields = profile.snapshot.meta.trace_function_info_fields; 55 var functionIdOffset = functionInfoFields.indexOf("function_id"); 56 var functionNameOffset = functionInfoFields.indexOf("name"); 57 var scriptNameOffset = functionInfoFields.indexOf("script_name"); 58 var scriptIdOffset = functionInfoFields.indexOf("script_id"); 59 var lineOffset = functionInfoFields.indexOf("line"); 60 var columnOffset = functionInfoFields.indexOf("column"); 61 var functionInfoFieldCount = functionInfoFields.length; 62 63 var map = this._idToFunctionInfo; 64 65 // Special case for the root node. 66 map[0] = new WebInspector.FunctionAllocationInfo("(root)", "<unknown>", 0, -1, -1); 67 68 var rawInfos = profile.trace_function_infos; 69 var infoLength = rawInfos.length; 70 for (var i = 0; i < infoLength; i += functionInfoFieldCount) { 71 map[rawInfos[i + functionIdOffset]] = new WebInspector.FunctionAllocationInfo( 72 strings[rawInfos[i + functionNameOffset]], 73 strings[rawInfos[i + scriptNameOffset]], 74 rawInfos[i + scriptIdOffset], 75 rawInfos[i + lineOffset], 76 rawInfos[i + columnOffset]); 77 } 78 }, 79 80 _buildInvertedAllocationTree: function(profile) 81 { 82 var traceTreeRaw = profile.trace_tree; 83 var idToFunctionInfo = this._idToFunctionInfo; 84 85 var traceNodeFields = profile.snapshot.meta.trace_node_fields; 86 var nodeIdOffset = traceNodeFields.indexOf("id"); 87 var functionIdOffset = traceNodeFields.indexOf("function_id"); 88 var allocationCountOffset = traceNodeFields.indexOf("count"); 89 var allocationSizeOffset = traceNodeFields.indexOf("size"); 90 var childrenOffset = traceNodeFields.indexOf("children"); 91 var nodeFieldCount = traceNodeFields.length; 92 93 function traverseNode(rawNodeArray, nodeOffset, parent) 94 { 95 var functionInfo = idToFunctionInfo[rawNodeArray[nodeOffset + functionIdOffset]]; 96 var result = new WebInspector.AllocationTraceNode( 97 rawNodeArray[nodeOffset + nodeIdOffset], 98 functionInfo, 99 rawNodeArray[nodeOffset + allocationCountOffset], 100 rawNodeArray[nodeOffset + allocationSizeOffset], 101 parent); 102 functionInfo.addTraceTopNode(result); 103 104 var rawChildren = rawNodeArray[nodeOffset + childrenOffset]; 105 for (var i = 0; i < rawChildren.length; i += nodeFieldCount) { 106 result.children.push(traverseNode(rawChildren, i, result)); 107 } 108 return result; 109 } 110 111 return traverseNode(traceTreeRaw, 0, null); 112 }, 113 114 serializeTraceTops: function() 115 { 116 if (this._traceTops) 117 return this._traceTops; 118 var result = this._traceTops = []; 119 var idToFunctionInfo = this._idToFunctionInfo; 120 for (var id in idToFunctionInfo) { 121 var info = idToFunctionInfo[id]; 122 if (info.totalCount === 0) 123 continue; 124 var nodeId = this._nextNodeId++; 125 result.push(this._serializeNode( 126 nodeId, 127 info, 128 info.totalCount, 129 info.totalSize, 130 true)); 131 this._collapsedTopNodeIdToFunctionInfo[nodeId] = info; 132 } 133 result.sort(function(a, b) { 134 return b.size - a.size; 135 }); 136 return result; 137 }, 138 139 serializeCallers: function(nodeId) 140 { 141 var node = this._idToNode[nodeId]; 142 if (!node) { 143 var functionInfo = this._collapsedTopNodeIdToFunctionInfo[nodeId]; 144 node = functionInfo.tracesWithThisTop(); 145 delete this._collapsedTopNodeIdToFunctionInfo[nodeId]; 146 this._idToNode[nodeId] = node; 147 } 148 149 var nodesWithSingleCaller = []; 150 while (node.callers().length === 1) { 151 node = node.callers()[0]; 152 nodesWithSingleCaller.push(this._serializeCaller(node)); 153 } 154 155 var branchingCallers = []; 156 var callers = node.callers(); 157 for (var i = 0; i < callers.length; i++) { 158 branchingCallers.push(this._serializeCaller(callers[i])); 159 } 160 return { 161 nodesWithSingleCaller: nodesWithSingleCaller, 162 branchingCallers: branchingCallers 163 }; 164 }, 165 166 _serializeCaller: function(node) 167 { 168 var callerId = this._nextNodeId++; 169 this._idToNode[callerId] = node; 170 return this._serializeNode( 171 callerId, 172 node.functionInfo, 173 node.allocationCount, 174 node.allocationSize, 175 node.hasCallers()); 176 }, 177 178 _serializeNode: function(nodeId, functionInfo, count, size, hasChildren) 179 { 180 return { 181 id: nodeId, 182 name: functionInfo.functionName, 183 scriptName: functionInfo.scriptName, 184 line: functionInfo.line, 185 column: functionInfo.column, 186 count: count, 187 size: size, 188 hasChildren: hasChildren 189 }; 190 } 191 } 192 193 194 /** 195 * @constructor 196 */ 197 WebInspector.AllocationTraceNode = function(id, functionInfo, count, size, parent) 198 { 199 this.id = id; 200 this.functionInfo = functionInfo; 201 this.allocationCount = count; 202 this.allocationSize = size; 203 this.parent = parent; 204 this.children = []; 205 } 206 207 208 /** 209 * @constructor 210 * @param {!WebInspector.FunctionAllocationInfo} functionInfo 211 */ 212 WebInspector.AllocationBackTraceNode = function(functionInfo) 213 { 214 this.functionInfo = functionInfo; 215 this.allocationCount = 0; 216 this.allocationSize = 0; 217 this._callers = []; 218 } 219 220 221 WebInspector.AllocationBackTraceNode.prototype = { 222 /** 223 * @param {!WebInspector.AllocationTraceNode} traceNode 224 * @return {!WebInspector.AllocationTraceNode} 225 */ 226 addCaller: function(traceNode) 227 { 228 var functionInfo = traceNode.functionInfo; 229 var result; 230 for (var i = 0; i < this._callers.length; i++) { 231 var caller = this._callers[i]; 232 if (caller.functionInfo === functionInfo) { 233 result = caller; 234 break; 235 } 236 } 237 if (!result) { 238 result = new WebInspector.AllocationBackTraceNode(functionInfo); 239 this._callers.push(result); 240 } 241 return result; 242 }, 243 244 callers: function() 245 { 246 return this._callers; 247 }, 248 249 hasCallers: function() 250 { 251 return this._callers.length > 0; 252 } 253 } 254 255 256 /** 257 * @constructor 258 */ 259 WebInspector.FunctionAllocationInfo = function(functionName, scriptName, scriptId, line, column) 260 { 261 this.functionName = functionName; 262 this.scriptName = scriptName; 263 this.scriptId = scriptId; 264 this.line = line; 265 this.column = column; 266 this.totalCount = 0; 267 this.totalSize = 0; 268 this._traceTops = []; 269 } 270 271 WebInspector.FunctionAllocationInfo.prototype = { 272 addTraceTopNode: function(node) 273 { 274 if (node.allocationCount === 0) 275 return; 276 this._traceTops.push(node); 277 this.totalCount += node.allocationCount; 278 this.totalSize += node.allocationSize; 279 }, 280 281 tracesWithThisTop: function() 282 { 283 if (!this._traceTops.length) 284 return null; 285 if (!this._backTraceTree) 286 this._buildAllocationTraceTree(); 287 return this._backTraceTree; 288 }, 289 290 _buildAllocationTraceTree: function() 291 { 292 this._backTraceTree = new WebInspector.AllocationBackTraceNode(this._traceTops[0].functionInfo); 293 294 for (var i = 0; i < this._traceTops.length; i++) { 295 var node = this._traceTops[i]; 296 var backTraceNode = this._backTraceTree; 297 var count = node.allocationCount; 298 var size = node.allocationSize; 299 while (true) { 300 backTraceNode.allocationCount += count; 301 backTraceNode.allocationSize += size; 302 node = node.parent; 303 if (node === null) { 304 break; 305 } 306 backTraceNode = backTraceNode.addCaller(node); 307 } 308 } 309 } 310 } 311