Home | History | Annotate | Download | only in sdk
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 
      6 /**
      7  * @constructor
      8  * @param {!ProfilerAgent.CPUProfile} profile
      9  */
     10 WebInspector.CPUProfileDataModel = function(profile)
     11 {
     12     this.profileHead = profile.head;
     13     this.samples = profile.samples;
     14     this.timestamps = profile.timestamps;
     15     this.profileStartTime = profile.startTime * 1000;
     16     this.profileEndTime = profile.endTime * 1000;
     17     this._assignParentsInProfile();
     18     if (this.samples) {
     19         this._normalizeTimestamps();
     20         this._buildIdToNodeMap();
     21         this._fixMissingSamples();
     22     }
     23     this._calculateTimes(profile);
     24 }
     25 
     26 /**
     27  * @param {string} name
     28  * @return {string}
     29  */
     30 WebInspector.CPUProfileDataModel.beautifyFunctionName = function(name)
     31 {
     32     return name || WebInspector.UIString("(anonymous function)");
     33 }
     34 
     35 WebInspector.CPUProfileDataModel.prototype = {
     36     /**
     37      * @param {!ProfilerAgent.CPUProfile} profile
     38      */
     39     _calculateTimes: function(profile)
     40     {
     41         function totalHitCount(node) {
     42             var result = node.hitCount;
     43             for (var i = 0; i < node.children.length; i++)
     44                 result += totalHitCount(node.children[i]);
     45             return result;
     46         }
     47         profile.totalHitCount = totalHitCount(profile.head);
     48 
     49         var duration = this.profileEndTime - this.profileStartTime;
     50         var samplingInterval = duration / profile.totalHitCount;
     51         this.samplingInterval = samplingInterval;
     52 
     53         function calculateTimesForNode(node) {
     54             node.selfTime = node.hitCount * samplingInterval;
     55             var totalHitCount = node.hitCount;
     56             for (var i = 0; i < node.children.length; i++)
     57                 totalHitCount += calculateTimesForNode(node.children[i]);
     58             node.totalTime = totalHitCount * samplingInterval;
     59             return totalHitCount;
     60         }
     61         calculateTimesForNode(profile.head);
     62     },
     63 
     64     _assignParentsInProfile: function()
     65     {
     66         var head = this.profileHead;
     67         head.parent = null;
     68         head.depth = -1;
     69         this.maxDepth = 0;
     70         var nodesToTraverse = [ head ];
     71         while (nodesToTraverse.length) {
     72             var parent = nodesToTraverse.pop();
     73             var depth = parent.depth + 1;
     74             if (depth > this.maxDepth)
     75                 this.maxDepth = depth;
     76             var children = parent.children;
     77             var length = children.length;
     78             for (var i = 0; i < length; ++i) {
     79                 var child = children[i];
     80                 child.parent = parent;
     81                 child.depth = depth;
     82                 if (child.children.length)
     83                     nodesToTraverse.push(child);
     84             }
     85         }
     86     },
     87 
     88     _normalizeTimestamps: function()
     89     {
     90         var timestamps = this.timestamps;
     91         if (!timestamps) {
     92             // Support loading old CPU profiles that are missing timestamps.
     93             // Derive timestamps from profile start and stop times.
     94             var profileStartTime = this.profileStartTime;
     95             var interval = (this.profileEndTime - profileStartTime) / this.samples.length;
     96             timestamps = new Float64Array(this.samples.length + 1);
     97             for (var i = 0; i < timestamps.length; ++i)
     98                 timestamps[i] = profileStartTime + i * interval;
     99             this.timestamps = timestamps;
    100             return;
    101         }
    102 
    103         // Convert samples from usec to msec
    104         for (var i = 0; i < timestamps.length; ++i)
    105             timestamps[i] /= 1000;
    106         var averageSample = (timestamps.peekLast() - timestamps[0]) / (timestamps.length - 1);
    107         // Add an extra timestamp used to calculate the last sample duration.
    108         this.timestamps.push(timestamps.peekLast() + averageSample);
    109         this.profileStartTime = timestamps[0];
    110         this.profileEndTime = timestamps.peekLast();
    111     },
    112 
    113     _buildIdToNodeMap: function()
    114     {
    115         /** @type {!Object.<number, !ProfilerAgent.CPUProfileNode>} */
    116         this._idToNode = {};
    117         var idToNode = this._idToNode;
    118         var stack = [this.profileHead];
    119         while (stack.length) {
    120             var node = stack.pop();
    121             idToNode[node.id] = node;
    122             for (var i = 0; i < node.children.length; i++)
    123                 stack.push(node.children[i]);
    124         }
    125 
    126         var topLevelNodes = this.profileHead.children;
    127         for (var i = 0; i < topLevelNodes.length && !(this.gcNode && this.programNode && this.idleNode); i++) {
    128             var node = topLevelNodes[i];
    129             if (node.functionName === "(garbage collector)")
    130                 this.gcNode = node;
    131             else if (node.functionName === "(program)")
    132                 this.programNode = node;
    133             else if (node.functionName === "(idle)")
    134                 this.idleNode = node;
    135         }
    136     },
    137 
    138     _fixMissingSamples: function()
    139     {
    140         // Sometimes sampler is not able to parse the JS stack and returns
    141         // a (program) sample instead. The issue leads to call frames belong
    142         // to the same function invocation being split apart.
    143         // Here's a workaround for that. When there's a single (program) sample
    144         // between two call stacks sharing the same bottom node, it is replaced
    145         // with the preceeding sample.
    146         var samples = this.samples;
    147         var samplesCount = samples.length;
    148         if (!this.programNode || samplesCount < 3)
    149             return;
    150         var idToNode = this._idToNode;
    151         var programNodeId = this.programNode.id;
    152         var gcNodeId = this.gcNode ? this.gcNode.id : -1;
    153         var idleNodeId = this.idleNode ? this.idleNode.id : -1;
    154         var prevNodeId = samples[0];
    155         var nodeId = samples[1];
    156         for (var sampleIndex = 1; sampleIndex < samplesCount - 1; sampleIndex++) {
    157             var nextNodeId = samples[sampleIndex + 1];
    158             if (nodeId === programNodeId && !isSystemNode(prevNodeId) && !isSystemNode(nextNodeId)
    159                 && bottomNode(idToNode[prevNodeId]) === bottomNode(idToNode[nextNodeId])) {
    160                 samples[sampleIndex] = prevNodeId;
    161             }
    162             prevNodeId = nodeId;
    163             nodeId = nextNodeId;
    164         }
    165 
    166         /**
    167          * @param {!ProfilerAgent.CPUProfileNode} node
    168          * @return {!ProfilerAgent.CPUProfileNode}
    169          */
    170         function bottomNode(node)
    171         {
    172             while (node.parent)
    173                 node = node.parent;
    174             return node;
    175         }
    176 
    177         /**
    178          * @param {number} nodeId
    179          * @return {boolean}
    180          */
    181         function isSystemNode(nodeId)
    182         {
    183             return nodeId === programNodeId || nodeId === gcNodeId || nodeId === idleNodeId;
    184         }
    185     },
    186 
    187     /**
    188      * @param {function(number, !ProfilerAgent.CPUProfileNode, number)} openFrameCallback
    189      * @param {function(number, !ProfilerAgent.CPUProfileNode, number, number, number)} closeFrameCallback
    190      * @param {number=} startTime
    191      * @param {number=} stopTime
    192      */
    193     forEachFrame: function(openFrameCallback, closeFrameCallback, startTime, stopTime)
    194     {
    195         if (!this.profileHead)
    196             return;
    197 
    198         startTime = startTime || 0;
    199         stopTime = stopTime || Infinity;
    200         var samples = this.samples;
    201         var timestamps = this.timestamps;
    202         var idToNode = this._idToNode;
    203         var gcNode = this.gcNode;
    204         var samplesCount = samples.length;
    205         var startIndex = timestamps.lowerBound(startTime);
    206         var stackTop = 0;
    207         var stackNodes = [];
    208         var prevId = this.profileHead.id;
    209         var prevHeight = this.profileHead.depth;
    210         var sampleTime = timestamps[samplesCount];
    211         var gcParentNode = null;
    212 
    213         if (!this._stackStartTimes)
    214             this._stackStartTimes = new Float64Array(this.maxDepth + 2);
    215         var stackStartTimes = this._stackStartTimes;
    216         if (!this._stackChildrenDuration)
    217             this._stackChildrenDuration = new Float64Array(this.maxDepth + 2);
    218         var stackChildrenDuration = this._stackChildrenDuration;
    219 
    220         for (var sampleIndex = startIndex; sampleIndex < samplesCount; sampleIndex++) {
    221             sampleTime = timestamps[sampleIndex];
    222             if (sampleTime >= stopTime)
    223                 break;
    224             var id = samples[sampleIndex];
    225             if (id === prevId)
    226                 continue;
    227             var node = idToNode[id];
    228             var prevNode = idToNode[prevId];
    229 
    230             if (node === gcNode) {
    231                 // GC samples have no stack, so we just put GC node on top of the last recorded sample.
    232                 gcParentNode = prevNode;
    233                 openFrameCallback(gcParentNode.depth + 1, gcNode, sampleTime);
    234                 stackStartTimes[++stackTop] = sampleTime;
    235                 stackChildrenDuration[stackTop] = 0;
    236                 prevId = id;
    237                 continue;
    238             }
    239             if (prevNode === gcNode) {
    240                 // end of GC frame
    241                 var start = stackStartTimes[stackTop];
    242                 var duration = sampleTime - start;
    243                 stackChildrenDuration[stackTop - 1] += duration;
    244                 closeFrameCallback(gcParentNode.depth + 1, gcNode, start, duration, duration - stackChildrenDuration[stackTop]);
    245                 --stackTop;
    246                 prevNode = gcParentNode;
    247                 prevId = prevNode.id;
    248                 gcParentNode = null;
    249             }
    250 
    251             while (node.depth > prevNode.depth) {
    252                 stackNodes.push(node);
    253                 node = node.parent;
    254             }
    255 
    256             // Go down to the LCA and close current intervals.
    257             while (prevNode !== node) {
    258                 var start = stackStartTimes[stackTop];
    259                 var duration = sampleTime - start;
    260                 stackChildrenDuration[stackTop - 1] += duration;
    261                 closeFrameCallback(prevNode.depth, prevNode, start, duration, duration - stackChildrenDuration[stackTop]);
    262                 --stackTop;
    263                 if (node.depth === prevNode.depth) {
    264                     stackNodes.push(node);
    265                     node = node.parent;
    266                 }
    267                 prevNode = prevNode.parent;
    268             }
    269 
    270             // Go up the nodes stack and open new intervals.
    271             while (stackNodes.length) {
    272                 node = stackNodes.pop();
    273                 openFrameCallback(node.depth, node, sampleTime);
    274                 stackStartTimes[++stackTop] = sampleTime;
    275                 stackChildrenDuration[stackTop] = 0;
    276             }
    277 
    278             prevId = id;
    279         }
    280 
    281         if (idToNode[prevId] === gcNode) {
    282             var start = stackStartTimes[stackTop];
    283             var duration = sampleTime - start;
    284             stackChildrenDuration[stackTop - 1] += duration;
    285             closeFrameCallback(gcParentNode.depth + 1, node, start, duration, duration - stackChildrenDuration[stackTop]);
    286             --stackTop;
    287         }
    288 
    289         for (var node = idToNode[prevId]; node.parent; node = node.parent) {
    290             var start = stackStartTimes[stackTop];
    291             var duration = sampleTime - start;
    292             stackChildrenDuration[stackTop - 1] += duration;
    293             closeFrameCallback(node.depth, node, start, duration, duration - stackChildrenDuration[stackTop]);
    294             --stackTop;
    295         }
    296     },
    297 
    298     /**
    299      * @param {number} index
    300      * @return {!ProfilerAgent.CPUProfileNode}
    301      */
    302     nodeByIndex: function(index)
    303     {
    304         return this._idToNode[this.samples[index]];
    305     }
    306 
    307 }
    308