 Chromium Code Reviews
 Chromium Code Reviews| OLD | NEW | 
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 /** | |
| 6 * @constructor | |
| 7 * @extends {WebInspector.ProfileNode} | |
| 8 * @param {!ProfilerAgent.CPUProfileNode} sourceNode | |
| 9 * @param {number} sampleTime | |
| 10 */ | |
| 11 WebInspector.CPUProfileNode = function(sourceNode, sampleTime) | |
| 12 { | |
| 13 WebInspector.ProfileNode.call(this); | |
| 14 this.functionName = sourceNode.functionName; | |
| 15 this.scriptId = sourceNode.scriptId; | |
| 16 this.url = sourceNode.url; | |
| 17 this.lineNumber = sourceNode.lineNumber; | |
| 18 this.columnNumber = sourceNode.columnNumber; | |
| 19 this.id = sourceNode.id; | |
| 20 this.self = sourceNode.hitCount * sampleTime; | |
| 21 this.callUID = sourceNode.callUID; | |
| 22 this.positionTicks = sourceNode.positionTicks; | |
| 23 this.deoptReason = sourceNode.deoptReason; | |
| 
caseq
2016/04/13 17:21:12
Why do we have to do this? Having to copy all fiel
 
alph
2016/04/13 19:05:07
This is done just once for the tree. I want to hav
 | |
| 24 // TODO: Remove the following field in favor of this.self | |
| 25 this.selfTime = this.self; | |
| 26 } | |
| 27 | |
| 28 WebInspector.CPUProfileNode.prototype = { | |
| 29 __proto__: WebInspector.ProfileNode.prototype | |
| 30 } | |
| 5 | 31 | 
| 6 /** | 32 /** | 
| 7 * @constructor | 33 * @constructor | 
| 34 * @extends {WebInspector.ProfileTreeModel} | |
| 8 * @param {!ProfilerAgent.CPUProfile} profile | 35 * @param {!ProfilerAgent.CPUProfile} profile | 
| 9 */ | 36 */ | 
| 10 WebInspector.CPUProfileDataModel = function(profile) | 37 WebInspector.CPUProfileDataModel = function(profile) | 
| 11 { | 38 { | 
| 12 this.profileHead = profile.head; | |
| 13 this.samples = profile.samples; | 39 this.samples = profile.samples; | 
| 14 this.timestamps = profile.timestamps; | 40 this.timestamps = profile.timestamps; | 
| 41 // Convert times from sec to msec. | |
| 15 this.profileStartTime = profile.startTime * 1000; | 42 this.profileStartTime = profile.startTime * 1000; | 
| 16 this.profileEndTime = profile.endTime * 1000; | 43 this.profileEndTime = profile.endTime * 1000; | 
| 17 this._assignParentsInProfile(); | 44 if (!WebInspector.moduleSetting("showNativeFunctionsInJSProfile").get()) | 
| 45 this._filterNativeFrames(profile.head); | |
| 46 this.profileHead = this._translateProfileTree(profile.head); | |
| 47 WebInspector.ProfileTreeModel.call(this, this.profileHead, this.profileStart Time, this.profileEndTime); | |
| 48 this._extractMetaNodes(); | |
| 18 if (this.samples) { | 49 if (this.samples) { | 
| 50 this._buildIdToNodeMap(); | |
| 19 this._sortSamples(); | 51 this._sortSamples(); | 
| 20 this._normalizeTimestamps(); | 52 this._normalizeTimestamps(); | 
| 21 this._buildIdToNodeMap(); | |
| 22 this._fixMissingSamples(); | 53 this._fixMissingSamples(); | 
| 23 } | 54 } | 
| 24 if (!WebInspector.moduleSetting("showNativeFunctionsInJSProfile").get()) | 55 this._assignTotalTimes(this.profileHead); | 
| 25 this._filterNativeFrames(); | |
| 26 this._assignDepthsInProfile(); | |
| 27 this._calculateTimes(profile); | |
| 28 } | 56 } | 
| 29 | 57 | 
| 30 WebInspector.CPUProfileDataModel.prototype = { | 58 WebInspector.CPUProfileDataModel.prototype = { | 
| 31 /** | 59 /** | 
| 32 * @param {!ProfilerAgent.CPUProfile} profile | 60 * @param {!ProfilerAgent.CPUProfileNode} head | 
| 33 */ | 61 */ | 
| 34 _calculateTimes: function(profile) | 62 _filterNativeFrames: function(head) | 
| 35 { | |
| 36 function totalHitCount(node) { | |
| 37 var result = node.hitCount; | |
| 38 for (var i = 0; i < node.children.length; i++) | |
| 39 result += totalHitCount(node.children[i]); | |
| 40 return result; | |
| 41 } | |
| 42 profile.totalHitCount = totalHitCount(profile.head); | |
| 43 this.totalHitCount = profile.totalHitCount; | |
| 44 | |
| 45 var duration = this.profileEndTime - this.profileStartTime; | |
| 46 var samplingInterval = duration / profile.totalHitCount; | |
| 47 this.samplingInterval = samplingInterval; | |
| 48 | |
| 49 function calculateTimesForNode(node) { | |
| 50 node.selfTime = node.hitCount * samplingInterval; | |
| 51 var totalHitCount = node.hitCount; | |
| 52 for (var i = 0; i < node.children.length; i++) | |
| 53 totalHitCount += calculateTimesForNode(node.children[i]); | |
| 54 node.totalTime = totalHitCount * samplingInterval; | |
| 55 return totalHitCount; | |
| 56 } | |
| 57 calculateTimesForNode(profile.head); | |
| 58 }, | |
| 59 | |
| 60 _filterNativeFrames: function() | |
| 61 { | 63 { | 
| 62 if (this.samples) { | 64 if (this.samples) { | 
| 65 var idToNode = {}; | |
| 
caseq
2016/04/13 17:21:12
use Map()?
 
alph
2016/04/13 19:05:07
Done.
 | |
| 66 var stack = [head]; | |
| 67 while (stack.length) { | |
| 
caseq
2016/04/13 17:21:12
can this be combined with tree traversal we perfor
 
alph
2016/04/13 19:05:07
Yes, building the tree along with filtering should
 | |
| 68 var node = stack.pop(); | |
| 69 idToNode[node.id] = node; | |
| 70 for (var i = 0; i < node.children.length; i++) { | |
| 71 node.children[i].parent = node; | |
| 72 stack.push(node.children[i]); | |
| 73 } | |
| 74 } | |
| 63 for (var i = 0; i < this.samples.length; ++i) { | 75 for (var i = 0; i < this.samples.length; ++i) { | 
| 64 var node = this.nodeByIndex(i); | 76 var node = idToNode[this.samples[i]]; | 
| 65 while (isNativeNode(node)) | 77 while (isNativeNode(node)) | 
| 66 node = node.parent; | 78 node = node.parent; | 
| 
caseq
2016/04/13 17:21:12
so we actually care to know nearest non-native par
 
alph
2016/04/13 19:05:07
I'm going to get rid of this function later. Added
 | |
| 67 this.samples[i] = node.id; | 79 this.samples[i] = node.id; | 
| 68 } | 80 } | 
| 69 } | 81 } | 
| 70 processSubtree(this.profileHead); | 82 processSubtree(head); | 
| 71 | 83 | 
| 72 /** | 84 /** | 
| 73 * @param {!ProfilerAgent.CPUProfileNode} node | 85 * @param {!ProfilerAgent.CPUProfileNode} node | 
| 74 * @return {boolean} | 86 * @return {boolean} | 
| 75 */ | 87 */ | 
| 76 function isNativeNode(node) | 88 function isNativeNode(node) | 
| 77 { | 89 { | 
| 78 return !!node.url && node.url.startsWith("native "); | 90 return !!node.url && node.url.startsWith("native "); | 
| 79 } | 91 } | 
| 80 | 92 | 
| (...skipping 30 matching lines...) Expand all Loading... | |
| 111 mergeChildren(node, child); | 123 mergeChildren(node, child); | 
| 112 } else { | 124 } else { | 
| 113 node.children.push(child); | 125 node.children.push(child); | 
| 114 child.parent = node; | 126 child.parent = node; | 
| 115 processSubtree(child); | 127 processSubtree(child); | 
| 116 } | 128 } | 
| 117 } | 129 } | 
| 118 } | 130 } | 
| 119 }, | 131 }, | 
| 120 | 132 | 
| 121 _assignParentsInProfile: function() | 133 /** | 
| 134 * @param {!ProfilerAgent.CPUProfileNode} head | |
| 135 * @return {!WebInspector.CPUProfileNode} | |
| 136 */ | |
| 137 _translateProfileTree: function(head) | |
| 122 { | 138 { | 
| 123 var head = this.profileHead; | 139 /** | 
| 124 head.parent = null; | 140 * @param {!ProfilerAgent.CPUProfileNode} node | 
| 125 var nodesToTraverse = [ head ]; | 141 * @return {number} | 
| 142 */ | |
| 143 function treeHitCount(node) | |
| 
caseq
2016/04/13 17:21:12
nit: computeHitCountForSubtree()
 
alph
2016/04/13 19:05:07
Done.
 | |
| 144 { | |
| 145 return node.children.reduce((acc, node) => acc + treeHitCount(node), node.hitCount); | |
| 146 } | |
| 147 this.totalHitCount = treeHitCount(head); | |
| 148 var sampleTime = (this.profileEndTime - this.profileStartTime) / this.to talHitCount; | |
| 149 var root = new WebInspector.CPUProfileNode(head, sampleTime); | |
| 150 // The stack contains pairs: (parentNode, sourceNode.children) | |
| 
caseq
2016/04/13 17:21:12
Please make it an array of objects, this way it's
 
alph
2016/04/13 19:05:07
Done.
 | |
| 151 var nodesToTraverse = [ root, head.children ]; | |
| 126 while (nodesToTraverse.length) { | 152 while (nodesToTraverse.length) { | 
| 127 var parent = nodesToTraverse.pop(); | 153 var children = nodesToTraverse.pop(); | 
| 128 var children = parent.children; | 154 var parentNode = nodesToTraverse.pop(); | 
| 129 var length = children.length; | 155 for (var i = 0; i < children.length; ++i) { | 
| 130 for (var i = 0; i < length; ++i) { | 156 var sourceNode = children[i]; | 
| 131 var child = children[i]; | 157 var node = new WebInspector.CPUProfileNode(sourceNode, sampleTim e); | 
| 132 child.parent = parent; | 158 parentNode.children.push(node); | 
| 133 if (child.children.length) | 159 if (sourceNode.children.length) | 
| 134 nodesToTraverse.push(child); | 160 nodesToTraverse.push(node, sourceNode.children); | 
| 135 } | 161 } | 
| 136 } | 162 } | 
| 163 return root; | |
| 137 }, | 164 }, | 
| 138 | 165 | 
| 139 _assignDepthsInProfile: function() | 166 /** | 
| 167 * @param {!WebInspector.ProfileNode} node | |
| 168 */ | |
| 169 _assignTotalTimes: function(node) | |
| 140 { | 170 { | 
| 141 var head = this.profileHead; | 171 // TODO: get rid of this field in favor of this.total | 
| 142 head.depth = -1; | 172 node.totalTime = node.total; | 
| 143 this.maxDepth = 0; | 173 node.children.forEach(this._assignTotalTimes, this); | 
| 144 var nodesToTraverse = [ head ]; | |
| 145 while (nodesToTraverse.length) { | |
| 146 var parent = nodesToTraverse.pop(); | |
| 147 var depth = parent.depth + 1; | |
| 148 if (depth > this.maxDepth) | |
| 149 this.maxDepth = depth; | |
| 150 var children = parent.children; | |
| 151 var length = children.length; | |
| 152 for (var i = 0; i < length; ++i) { | |
| 153 var child = children[i]; | |
| 154 child.depth = depth; | |
| 155 if (child.children.length) | |
| 156 nodesToTraverse.push(child); | |
| 157 } | |
| 158 } | |
| 159 }, | 174 }, | 
| 160 | 175 | 
| 161 _sortSamples: function() | 176 _sortSamples: function() | 
| 162 { | 177 { | 
| 163 var timestamps = this.timestamps; | 178 var timestamps = this.timestamps; | 
| 164 if (!timestamps) | 179 if (!timestamps) | 
| 165 return; | 180 return; | 
| 166 var samples = this.samples; | 181 var samples = this.samples; | 
| 167 var indices = timestamps.map((x, index) => index); | 182 var indices = timestamps.map((x, index) => index); | 
| 168 indices.sort((a, b) => timestamps[a] - timestamps[b]); | 183 indices.sort((a, b) => timestamps[a] - timestamps[b]); | 
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 206 timestamps[i] /= 1000; | 221 timestamps[i] /= 1000; | 
| 207 var averageSample = (timestamps.peekLast() - timestamps[0]) / (timestamp s.length - 1); | 222 var averageSample = (timestamps.peekLast() - timestamps[0]) / (timestamp s.length - 1); | 
| 208 // Add an extra timestamp used to calculate the last sample duration. | 223 // Add an extra timestamp used to calculate the last sample duration. | 
| 209 this.timestamps.push(timestamps.peekLast() + averageSample); | 224 this.timestamps.push(timestamps.peekLast() + averageSample); | 
| 210 this.profileStartTime = timestamps[0]; | 225 this.profileStartTime = timestamps[0]; | 
| 211 this.profileEndTime = timestamps.peekLast(); | 226 this.profileEndTime = timestamps.peekLast(); | 
| 212 }, | 227 }, | 
| 213 | 228 | 
| 214 _buildIdToNodeMap: function() | 229 _buildIdToNodeMap: function() | 
| 215 { | 230 { | 
| 216 /** @type {!Object.<number, !ProfilerAgent.CPUProfileNode>} */ | 231 /** @type {!Object<number, !WebInspector.CPUProfileNode>} */ | 
| 217 this._idToNode = {}; | 232 this._idToNode = {}; | 
| 218 var idToNode = this._idToNode; | 233 var idToNode = this._idToNode; | 
| 219 var stack = [this.profileHead]; | 234 var stack = [this.profileHead]; | 
| 220 while (stack.length) { | 235 while (stack.length) { | 
| 221 var node = stack.pop(); | 236 var node = stack.pop(); | 
| 222 idToNode[node.id] = node; | 237 idToNode[node.id] = node; | 
| 223 for (var i = 0; i < node.children.length; i++) | 238 for (var i = 0; i < node.children.length; i++) | 
| 224 stack.push(node.children[i]); | 239 stack.push(node.children[i]); | 
| 225 } | 240 } | 
| 241 }, | |
| 226 | 242 | 
| 243 _extractMetaNodes: function() | |
| 244 { | |
| 227 var topLevelNodes = this.profileHead.children; | 245 var topLevelNodes = this.profileHead.children; | 
| 228 for (var i = 0; i < topLevelNodes.length && !(this.gcNode && this.progra mNode && this.idleNode); i++) { | 246 for (var i = 0; i < topLevelNodes.length && !(this.gcNode && this.progra mNode && this.idleNode); i++) { | 
| 229 var node = topLevelNodes[i]; | 247 var node = topLevelNodes[i]; | 
| 230 if (node.functionName === "(garbage collector)") | 248 if (node.functionName === "(garbage collector)") | 
| 231 this.gcNode = node; | 249 this.gcNode = node; | 
| 232 else if (node.functionName === "(program)") | 250 else if (node.functionName === "(program)") | 
| 233 this.programNode = node; | 251 this.programNode = node; | 
| 234 else if (node.functionName === "(idle)") | 252 else if (node.functionName === "(idle)") | 
| 235 this.idleNode = node; | 253 this.idleNode = node; | 
| 236 } | 254 } | 
| (...skipping 21 matching lines...) Expand all Loading... | |
| 258 var nextNodeId = samples[sampleIndex + 1]; | 276 var nextNodeId = samples[sampleIndex + 1]; | 
| 259 if (nodeId === programNodeId && !isSystemNode(prevNodeId) && !isSyst emNode(nextNodeId) | 277 if (nodeId === programNodeId && !isSystemNode(prevNodeId) && !isSyst emNode(nextNodeId) | 
| 260 && bottomNode(idToNode[prevNodeId]) === bottomNode(idToNode[next NodeId])) { | 278 && bottomNode(idToNode[prevNodeId]) === bottomNode(idToNode[next NodeId])) { | 
| 261 samples[sampleIndex] = prevNodeId; | 279 samples[sampleIndex] = prevNodeId; | 
| 262 } | 280 } | 
| 263 prevNodeId = nodeId; | 281 prevNodeId = nodeId; | 
| 264 nodeId = nextNodeId; | 282 nodeId = nextNodeId; | 
| 265 } | 283 } | 
| 266 | 284 | 
| 267 /** | 285 /** | 
| 268 * @param {!ProfilerAgent.CPUProfileNode} node | 286 * @param {!WebInspector.ProfileNode} node | 
| 269 * @return {!ProfilerAgent.CPUProfileNode} | 287 * @return {!WebInspector.ProfileNode} | 
| 270 */ | 288 */ | 
| 271 function bottomNode(node) | 289 function bottomNode(node) | 
| 272 { | 290 { | 
| 273 while (node.parent.parent) | 291 while (node.parent.parent) | 
| 274 node = node.parent; | 292 node = node.parent; | 
| 275 return node; | 293 return node; | 
| 276 } | 294 } | 
| 277 | 295 | 
| 278 /** | 296 /** | 
| 279 * @param {number} nodeId | 297 * @param {number} nodeId | 
| 280 * @return {boolean} | 298 * @return {boolean} | 
| 281 */ | 299 */ | 
| 282 function isSystemNode(nodeId) | 300 function isSystemNode(nodeId) | 
| 283 { | 301 { | 
| 284 return nodeId === programNodeId || nodeId === gcNodeId || nodeId === idleNodeId; | 302 return nodeId === programNodeId || nodeId === gcNodeId || nodeId === idleNodeId; | 
| 285 } | 303 } | 
| 286 }, | 304 }, | 
| 287 | 305 | 
| 288 /** | 306 /** | 
| 289 * @param {function(number, !ProfilerAgent.CPUProfileNode, number)} openFram eCallback | 307 * @param {function(number, !WebInspector.CPUProfileNode, number)} openFrame Callback | 
| 290 * @param {function(number, !ProfilerAgent.CPUProfileNode, number, number, n umber)} closeFrameCallback | 308 * @param {function(number, !WebInspector.CPUProfileNode, number, number, nu mber)} closeFrameCallback | 
| 291 * @param {number=} startTime | 309 * @param {number=} startTime | 
| 292 * @param {number=} stopTime | 310 * @param {number=} stopTime | 
| 293 */ | 311 */ | 
| 294 forEachFrame: function(openFrameCallback, closeFrameCallback, startTime, sto pTime) | 312 forEachFrame: function(openFrameCallback, closeFrameCallback, startTime, sto pTime) | 
| 295 { | 313 { | 
| 296 if (!this.profileHead) | 314 if (!this.profileHead) | 
| 297 return; | 315 return; | 
| 298 | 316 | 
| 299 startTime = startTime || 0; | 317 startTime = startTime || 0; | 
| 300 stopTime = stopTime || Infinity; | 318 stopTime = stopTime || Infinity; | 
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 351 while (node.depth > prevNode.depth) { | 369 while (node.depth > prevNode.depth) { | 
| 352 stackNodes.push(node); | 370 stackNodes.push(node); | 
| 353 node = node.parent; | 371 node = node.parent; | 
| 354 } | 372 } | 
| 355 | 373 | 
| 356 // Go down to the LCA and close current intervals. | 374 // Go down to the LCA and close current intervals. | 
| 357 while (prevNode !== node) { | 375 while (prevNode !== node) { | 
| 358 var start = stackStartTimes[stackTop]; | 376 var start = stackStartTimes[stackTop]; | 
| 359 var duration = sampleTime - start; | 377 var duration = sampleTime - start; | 
| 360 stackChildrenDuration[stackTop - 1] += duration; | 378 stackChildrenDuration[stackTop - 1] += duration; | 
| 361 closeFrameCallback(prevNode.depth, prevNode, start, duration, du ration - stackChildrenDuration[stackTop]); | 379 closeFrameCallback(prevNode.depth, /** @type {!WebInspector.CPUP rofileNode} */(prevNode), start, duration, duration - stackChildrenDuration[stac kTop]); | 
| 362 --stackTop; | 380 --stackTop; | 
| 363 if (node.depth === prevNode.depth) { | 381 if (node.depth === prevNode.depth) { | 
| 364 stackNodes.push(node); | 382 stackNodes.push(node); | 
| 365 node = node.parent; | 383 node = node.parent; | 
| 366 } | 384 } | 
| 367 prevNode = prevNode.parent; | 385 prevNode = prevNode.parent; | 
| 368 } | 386 } | 
| 369 | 387 | 
| 370 // Go up the nodes stack and open new intervals. | 388 // Go up the nodes stack and open new intervals. | 
| 371 while (stackNodes.length) { | 389 while (stackNodes.length) { | 
| (...skipping 11 matching lines...) Expand all Loading... | |
| 383 var duration = sampleTime - start; | 401 var duration = sampleTime - start; | 
| 384 stackChildrenDuration[stackTop - 1] += duration; | 402 stackChildrenDuration[stackTop - 1] += duration; | 
| 385 closeFrameCallback(gcParentNode.depth + 1, node, start, duration, du ration - stackChildrenDuration[stackTop]); | 403 closeFrameCallback(gcParentNode.depth + 1, node, start, duration, du ration - stackChildrenDuration[stackTop]); | 
| 386 --stackTop; | 404 --stackTop; | 
| 387 } | 405 } | 
| 388 | 406 | 
| 389 for (var node = idToNode[prevId]; node.parent; node = node.parent) { | 407 for (var node = idToNode[prevId]; node.parent; node = node.parent) { | 
| 390 var start = stackStartTimes[stackTop]; | 408 var start = stackStartTimes[stackTop]; | 
| 391 var duration = sampleTime - start; | 409 var duration = sampleTime - start; | 
| 392 stackChildrenDuration[stackTop - 1] += duration; | 410 stackChildrenDuration[stackTop - 1] += duration; | 
| 393 closeFrameCallback(node.depth, node, start, duration, duration - sta ckChildrenDuration[stackTop]); | 411 closeFrameCallback(node.depth, /** @type {!WebInspector.CPUProfileNo de} */(node), start, duration, duration - stackChildrenDuration[stackTop]); | 
| 394 --stackTop; | 412 --stackTop; | 
| 395 } | 413 } | 
| 396 }, | 414 }, | 
| 397 | 415 | 
| 398 /** | 416 /** | 
| 399 * @param {number} index | 417 * @param {number} index | 
| 400 * @return {!ProfilerAgent.CPUProfileNode} | 418 * @return {!WebInspector.CPUProfileNode} | 
| 401 */ | 419 */ | 
| 402 nodeByIndex: function(index) | 420 nodeByIndex: function(index) | 
| 403 { | 421 { | 
| 404 return this._idToNode[this.samples[index]]; | 422 return this._idToNode[this.samples[index]]; | 
| 405 } | 423 }, | 
| 406 | 424 | 
| 425 __proto__: WebInspector.ProfileTreeModel.prototype | |
| 407 } | 426 } | 
| OLD | NEW |