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 */ 34WebInspector.AllocationProfile = function(profile, liveObjectStats) 35{ 36 this._strings = profile.strings; 37 this._liveObjectStats = liveObjectStats; 38 39 this._nextNodeId = 1; 40 this._functionInfos = [] 41 this._idToNode = {}; 42 this._idToTopDownNode = {}; 43 this._collapsedTopNodeIdToFunctionInfo = {}; 44 45 this._traceTops = null; 46 47 this._buildFunctionAllocationInfos(profile); 48 this._traceTree = this._buildAllocationTree(profile, liveObjectStats); 49} 50 51WebInspector.AllocationProfile.prototype = { 52 _buildFunctionAllocationInfos: function(profile) 53 { 54 var strings = this._strings; 55 56 var functionInfoFields = profile.snapshot.meta.trace_function_info_fields; 57 var functionIdOffset = functionInfoFields.indexOf("function_id"); 58 var functionNameOffset = functionInfoFields.indexOf("name"); 59 var scriptNameOffset = functionInfoFields.indexOf("script_name"); 60 var scriptIdOffset = functionInfoFields.indexOf("script_id"); 61 var lineOffset = functionInfoFields.indexOf("line"); 62 var columnOffset = functionInfoFields.indexOf("column"); 63 var functionInfoFieldCount = functionInfoFields.length; 64 65 var rawInfos = profile.trace_function_infos; 66 var infoLength = rawInfos.length; 67 var functionInfos = this._functionInfos = new Array(infoLength / functionInfoFieldCount); 68 var index = 0; 69 for (var i = 0; i < infoLength; i += functionInfoFieldCount) { 70 functionInfos[index++] = new WebInspector.FunctionAllocationInfo( 71 strings[rawInfos[i + functionNameOffset]], 72 strings[rawInfos[i + scriptNameOffset]], 73 rawInfos[i + scriptIdOffset], 74 rawInfos[i + lineOffset], 75 rawInfos[i + columnOffset]); 76 } 77 }, 78 79 _buildAllocationTree: function(profile, liveObjectStats) 80 { 81 var traceTreeRaw = profile.trace_tree; 82 var functionInfos = this._functionInfos; 83 var idToTopDownNode = this._idToTopDownNode; 84 85 var traceNodeFields = profile.snapshot.meta.trace_node_fields; 86 var nodeIdOffset = traceNodeFields.indexOf("id"); 87 var functionInfoIndexOffset = traceNodeFields.indexOf("function_info_index"); 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 = functionInfos[rawNodeArray[nodeOffset + functionInfoIndexOffset]]; 96 var id = rawNodeArray[nodeOffset + nodeIdOffset]; 97 var stats = liveObjectStats[id]; 98 var liveCount = stats ? stats.count : 0; 99 var liveSize = stats ? stats.size : 0; 100 var result = new WebInspector.TopDownAllocationNode( 101 id, 102 functionInfo, 103 rawNodeArray[nodeOffset + allocationCountOffset], 104 rawNodeArray[nodeOffset + allocationSizeOffset], 105 liveCount, 106 liveSize, 107 parent); 108 idToTopDownNode[id] = result; 109 functionInfo.addTraceTopNode(result); 110 111 var rawChildren = rawNodeArray[nodeOffset + childrenOffset]; 112 for (var i = 0; i < rawChildren.length; i += nodeFieldCount) { 113 result.children.push(traverseNode(rawChildren, i, result)); 114 } 115 return result; 116 } 117 118 return traverseNode(traceTreeRaw, 0, null); 119 }, 120 121 /** 122 * @return {!Array.<!WebInspector.HeapSnapshotCommon.SerializedAllocationNode>} 123 */ 124 serializeTraceTops: function() 125 { 126 if (this._traceTops) 127 return this._traceTops; 128 var result = this._traceTops = []; 129 var functionInfos = this._functionInfos; 130 for (var i = 0; i < functionInfos.length; i++) { 131 var info = functionInfos[i]; 132 if (info.totalCount === 0) 133 continue; 134 var nodeId = this._nextNodeId++; 135 var isRoot = i == 0; 136 result.push(this._serializeNode( 137 nodeId, 138 info, 139 info.totalCount, 140 info.totalSize, 141 info.totalLiveCount, 142 info.totalLiveSize, 143 !isRoot)); 144 this._collapsedTopNodeIdToFunctionInfo[nodeId] = info; 145 } 146 result.sort(function(a, b) { 147 return b.size - a.size; 148 }); 149 return result; 150 }, 151 152 /** 153 * @param {number} nodeId 154 * @return {!WebInspector.HeapSnapshotCommon.AllocationNodeCallers} 155 */ 156 serializeCallers: function(nodeId) 157 { 158 var node = this._ensureBottomUpNode(nodeId); 159 var nodesWithSingleCaller = []; 160 while (node.callers().length === 1) { 161 node = node.callers()[0]; 162 nodesWithSingleCaller.push(this._serializeCaller(node)); 163 } 164 165 var branchingCallers = []; 166 var callers = node.callers(); 167 for (var i = 0; i < callers.length; i++) { 168 branchingCallers.push(this._serializeCaller(callers[i])); 169 } 170 return new WebInspector.HeapSnapshotCommon.AllocationNodeCallers(nodesWithSingleCaller, branchingCallers); 171 }, 172 173 /** 174 * @param {number} traceNodeId 175 * @return {!Array.<!WebInspector.HeapSnapshotCommon.AllocationStackFrame>} 176 */ 177 serializeAllocationStack: function(traceNodeId) 178 { 179 var node = this._idToTopDownNode[traceNodeId]; 180 var result = []; 181 while (node) { 182 var functionInfo = node.functionInfo; 183 result.push(new WebInspector.HeapSnapshotCommon.AllocationStackFrame( 184 functionInfo.functionName, 185 functionInfo.scriptName, 186 functionInfo.scriptId, 187 functionInfo.line, 188 functionInfo.column 189 )); 190 node = node.parent; 191 } 192 return result; 193 }, 194 195 /** 196 * @param {number} allocationNodeId 197 * @return {!Array.<number>} 198 */ 199 traceIds: function(allocationNodeId) 200 { 201 return this._ensureBottomUpNode(allocationNodeId).traceTopIds; 202 }, 203 204 /** 205 * @param {number} nodeId 206 * @return {!WebInspector.BottomUpAllocationNode} 207 */ 208 _ensureBottomUpNode: function(nodeId) 209 { 210 var node = this._idToNode[nodeId]; 211 if (!node) { 212 var functionInfo = this._collapsedTopNodeIdToFunctionInfo[nodeId]; 213 node = functionInfo.bottomUpRoot(); 214 delete this._collapsedTopNodeIdToFunctionInfo[nodeId]; 215 this._idToNode[nodeId] = node; 216 } 217 return node; 218 }, 219 220 /** 221 * @param {!WebInspector.BottomUpAllocationNode} node 222 * @return {!WebInspector.HeapSnapshotCommon.SerializedAllocationNode} 223 */ 224 _serializeCaller: function(node) 225 { 226 var callerId = this._nextNodeId++; 227 this._idToNode[callerId] = node; 228 return this._serializeNode( 229 callerId, 230 node.functionInfo, 231 node.allocationCount, 232 node.allocationSize, 233 node.liveCount, 234 node.liveSize, 235 node.hasCallers()); 236 }, 237 238 /** 239 * @param {number} nodeId 240 * @param {!WebInspector.FunctionAllocationInfo} functionInfo 241 * @param {number} count 242 * @param {number} size 243 * @param {number} liveCount 244 * @param {number} liveSize 245 * @param {boolean} hasChildren 246 * @return {!WebInspector.HeapSnapshotCommon.SerializedAllocationNode} 247 */ 248 _serializeNode: function(nodeId, functionInfo, count, size, liveCount, liveSize, hasChildren) 249 { 250 return new WebInspector.HeapSnapshotCommon.SerializedAllocationNode( 251 nodeId, 252 functionInfo.functionName, 253 functionInfo.scriptName, 254 functionInfo.scriptId, 255 functionInfo.line, 256 functionInfo.column, 257 count, 258 size, 259 liveCount, 260 liveSize, 261 hasChildren 262 ); 263 } 264} 265 266 267/** 268 * @constructor 269 * @param {number} id 270 * @param {!WebInspector.FunctionAllocationInfo} functionInfo 271 * @param {number} count 272 * @param {number} size 273 * @param {number} liveCount 274 * @param {number} liveSize 275 * @param {?WebInspector.TopDownAllocationNode} parent 276 */ 277WebInspector.TopDownAllocationNode = function(id, functionInfo, count, size, liveCount, liveSize, parent) 278{ 279 this.id = id; 280 this.functionInfo = functionInfo; 281 this.allocationCount = count; 282 this.allocationSize = size; 283 this.liveCount = liveCount; 284 this.liveSize = liveSize; 285 this.parent = parent; 286 this.children = []; 287} 288 289 290/** 291 * @constructor 292 * @param {!WebInspector.FunctionAllocationInfo} functionInfo 293 */ 294WebInspector.BottomUpAllocationNode = function(functionInfo) 295{ 296 this.functionInfo = functionInfo; 297 this.allocationCount = 0; 298 this.allocationSize = 0; 299 this.liveCount = 0; 300 this.liveSize = 0; 301 this.traceTopIds = []; 302 this._callers = []; 303} 304 305 306WebInspector.BottomUpAllocationNode.prototype = { 307 /** 308 * @param {!WebInspector.TopDownAllocationNode} traceNode 309 * @return {!WebInspector.BottomUpAllocationNode} 310 */ 311 addCaller: function(traceNode) 312 { 313 var functionInfo = traceNode.functionInfo; 314 var result; 315 for (var i = 0; i < this._callers.length; i++) { 316 var caller = this._callers[i]; 317 if (caller.functionInfo === functionInfo) { 318 result = caller; 319 break; 320 } 321 } 322 if (!result) { 323 result = new WebInspector.BottomUpAllocationNode(functionInfo); 324 this._callers.push(result); 325 } 326 return result; 327 }, 328 329 /** 330 * @return {!Array.<!WebInspector.BottomUpAllocationNode>} 331 */ 332 callers: function() 333 { 334 return this._callers; 335 }, 336 337 /** 338 * @return {boolean} 339 */ 340 hasCallers: function() 341 { 342 return this._callers.length > 0; 343 } 344} 345 346 347/** 348 * @constructor 349 * @param {string} functionName 350 * @param {string} scriptName 351 * @param {number} scriptId 352 * @param {number} line 353 * @param {number} column 354 */ 355WebInspector.FunctionAllocationInfo = function(functionName, scriptName, scriptId, line, column) 356{ 357 this.functionName = functionName; 358 this.scriptName = scriptName; 359 this.scriptId = scriptId; 360 this.line = line; 361 this.column = column; 362 this.totalCount = 0; 363 this.totalSize = 0; 364 this.totalLiveCount = 0; 365 this.totalLiveSize = 0; 366 this._traceTops = []; 367} 368 369WebInspector.FunctionAllocationInfo.prototype = { 370 /** 371 * @param {!WebInspector.TopDownAllocationNode} node 372 */ 373 addTraceTopNode: function(node) 374 { 375 if (node.allocationCount === 0) 376 return; 377 this._traceTops.push(node); 378 this.totalCount += node.allocationCount; 379 this.totalSize += node.allocationSize; 380 this.totalLiveCount += node.liveCount; 381 this.totalLiveSize += node.liveSize; 382 }, 383 384 /** 385 * @return {?WebInspector.BottomUpAllocationNode} 386 */ 387 bottomUpRoot: function() 388 { 389 if (!this._traceTops.length) 390 return null; 391 if (!this._bottomUpTree) 392 this._buildAllocationTraceTree(); 393 return this._bottomUpTree; 394 }, 395 396 _buildAllocationTraceTree: function() 397 { 398 this._bottomUpTree = new WebInspector.BottomUpAllocationNode(this); 399 400 for (var i = 0; i < this._traceTops.length; i++) { 401 var node = this._traceTops[i]; 402 var bottomUpNode = this._bottomUpTree; 403 var count = node.allocationCount; 404 var size = node.allocationSize; 405 var liveCount = node.liveCount; 406 var liveSize = node.liveSize; 407 var traceId = node.id; 408 while (true) { 409 bottomUpNode.allocationCount += count; 410 bottomUpNode.allocationSize += size; 411 bottomUpNode.liveCount += liveCount; 412 bottomUpNode.liveSize += liveSize; 413 bottomUpNode.traceTopIds.push(traceId); 414 node = node.parent; 415 if (node === null) { 416 break; 417 } 418 bottomUpNode = bottomUpNode.addCaller(node); 419 } 420 } 421 } 422} 423