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