Home | History | Annotate | Download | only in front_end
      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