| 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 | |
| 5 /** | 4 /** |
| 6 * @constructor | 5 * @unrestricted |
| 7 * @extends {WebInspector.ProfileNode} | |
| 8 * @param {!ProfilerAgent.ProfileNode} node | |
| 9 * @param {number} sampleTime | |
| 10 */ | 6 */ |
| 11 WebInspector.CPUProfileNode = function(node, sampleTime) | 7 WebInspector.CPUProfileNode = class extends WebInspector.ProfileNode { |
| 12 { | 8 /** |
| 9 * @param {!ProfilerAgent.ProfileNode} node |
| 10 * @param {number} sampleTime |
| 11 */ |
| 12 constructor(node, sampleTime) { |
| 13 var callFrame = node.callFrame || /** @type {!RuntimeAgent.CallFrame} */ ({ | 13 var callFrame = node.callFrame || /** @type {!RuntimeAgent.CallFrame} */ ({ |
| 14 // Backward compatibility for old SamplingHeapProfileNode format. | 14 // Backward compatibility for old SamplingHeapProfileNode
format. |
| 15 functionName: node["functionName"], | 15 functionName: node['functionName'], |
| 16 scriptId: node["scriptId"], | 16 scriptId: node['scriptId'], |
| 17 url: node["url"], | 17 url: node['url'], |
| 18 lineNumber: node["lineNumber"] - 1, | 18 lineNumber: node['lineNumber'] - 1, |
| 19 columnNumber: node["columnNumber"] - 1 | 19 columnNumber: node['columnNumber'] - 1 |
| 20 }); | 20 }); |
| 21 WebInspector.ProfileNode.call(this, callFrame); | 21 super(callFrame); |
| 22 this.id = node.id; | 22 this.id = node.id; |
| 23 this.self = node.hitCount * sampleTime; | 23 this.self = node.hitCount * sampleTime; |
| 24 this.positionTicks = node.positionTicks; | 24 this.positionTicks = node.positionTicks; |
| 25 // Compatibility: legacy backends could provide "no reason" for optimized fu
nctions. | 25 // Compatibility: legacy backends could provide "no reason" for optimized fu
nctions. |
| 26 this.deoptReason = node.deoptReason && node.deoptReason !== "no reason" ? no
de.deoptReason : null; | 26 this.deoptReason = node.deoptReason && node.deoptReason !== 'no reason' ? no
de.deoptReason : null; |
| 27 }; | 27 } |
| 28 | |
| 29 WebInspector.CPUProfileNode.prototype = { | |
| 30 __proto__: WebInspector.ProfileNode.prototype | |
| 31 }; | 28 }; |
| 32 | 29 |
| 33 /** | 30 /** |
| 34 * @constructor | 31 * @unrestricted |
| 35 * @extends {WebInspector.ProfileTreeModel} | |
| 36 * @param {!ProfilerAgent.Profile} profile | |
| 37 */ | 32 */ |
| 38 WebInspector.CPUProfileDataModel = function(profile) | 33 WebInspector.CPUProfileDataModel = class extends WebInspector.ProfileTreeModel { |
| 39 { | 34 /** |
| 40 WebInspector.ProfileTreeModel.call(this); | 35 * @param {!ProfilerAgent.Profile} profile |
| 41 var isLegacyFormat = !!profile["head"]; | 36 */ |
| 37 constructor(profile) { |
| 38 super(); |
| 39 var isLegacyFormat = !!profile['head']; |
| 42 if (isLegacyFormat) { | 40 if (isLegacyFormat) { |
| 43 // Legacy format contains raw timestamps and start/stop times are in sec
onds. | 41 // Legacy format contains raw timestamps and start/stop times are in secon
ds. |
| 44 this.profileStartTime = profile.startTime * 1000; | 42 this.profileStartTime = profile.startTime * 1000; |
| 45 this.profileEndTime = profile.endTime * 1000; | 43 this.profileEndTime = profile.endTime * 1000; |
| 46 this.timestamps = profile.timestamps; | 44 this.timestamps = profile.timestamps; |
| 47 this._compatibilityConversionHeadToNodes(profile); | 45 this._compatibilityConversionHeadToNodes(profile); |
| 48 } else { | 46 } else { |
| 49 // Current format encodes timestamps as deltas. Start/stop times are in
microseconds. | 47 // Current format encodes timestamps as deltas. Start/stop times are in mi
croseconds. |
| 50 this.profileStartTime = profile.startTime / 1000; | 48 this.profileStartTime = profile.startTime / 1000; |
| 51 this.profileEndTime = profile.endTime / 1000; | 49 this.profileEndTime = profile.endTime / 1000; |
| 52 this.timestamps = this._convertTimeDeltas(profile); | 50 this.timestamps = this._convertTimeDeltas(profile); |
| 53 } | 51 } |
| 54 this.samples = profile.samples; | 52 this.samples = profile.samples; |
| 55 this.totalHitCount = 0; | 53 this.totalHitCount = 0; |
| 56 this.profileHead = this._translateProfileTree(profile.nodes); | 54 this.profileHead = this._translateProfileTree(profile.nodes); |
| 57 this.initialize(this.profileHead); | 55 this.initialize(this.profileHead); |
| 58 this._extractMetaNodes(); | 56 this._extractMetaNodes(); |
| 59 if (this.samples) { | 57 if (this.samples) { |
| 60 this._buildIdToNodeMap(); | 58 this._buildIdToNodeMap(); |
| 61 this._sortSamples(); | 59 this._sortSamples(); |
| 62 this._normalizeTimestamps(); | 60 this._normalizeTimestamps(); |
| 63 } | 61 } |
| 64 }; | 62 } |
| 65 | 63 |
| 66 WebInspector.CPUProfileDataModel.prototype = { | 64 /** |
| 65 * @param {!ProfilerAgent.Profile} profile |
| 66 */ |
| 67 _compatibilityConversionHeadToNodes(profile) { |
| 68 if (!profile.head || profile.nodes) |
| 69 return; |
| 70 /** @type {!Array<!ProfilerAgent.ProfileNode>} */ |
| 71 var nodes = []; |
| 72 convertNodesTree(profile.head); |
| 73 profile.nodes = nodes; |
| 74 delete profile.head; |
| 67 /** | 75 /** |
| 68 * @param {!ProfilerAgent.Profile} profile | 76 * @param {!ProfilerAgent.ProfileNode} node |
| 77 * @return {number} |
| 69 */ | 78 */ |
| 70 _compatibilityConversionHeadToNodes: function(profile) | 79 function convertNodesTree(node) { |
| 71 { | 80 nodes.push(node); |
| 72 if (!profile.head || profile.nodes) | 81 node.children = (/** @type {!Array<!ProfilerAgent.ProfileNode>} */ (node.c
hildren)).map(convertNodesTree); |
| 73 return; | 82 return node.id; |
| 74 /** @type {!Array<!ProfilerAgent.ProfileNode>} */ | 83 } |
| 75 var nodes = []; | 84 } |
| 76 convertNodesTree(profile.head); | 85 |
| 77 profile.nodes = nodes; | 86 /** |
| 78 delete profile.head; | 87 * @param {!ProfilerAgent.Profile} profile |
| 79 /** | 88 * @return {?Array<number>} |
| 80 * @param {!ProfilerAgent.ProfileNode} node | 89 */ |
| 81 * @return {number} | 90 _convertTimeDeltas(profile) { |
| 82 */ | 91 if (!profile.timeDeltas) |
| 83 function convertNodesTree(node) | 92 return null; |
| 84 { | 93 var lastTimeUsec = profile.startTime; |
| 85 nodes.push(node); | 94 var timestamps = new Array(profile.timeDeltas.length); |
| 86 node.children = (/** @type {!Array<!ProfilerAgent.ProfileNode>} */(n
ode.children)).map(convertNodesTree); | 95 for (var i = 0; i < timestamps.length; ++i) { |
| 87 return node.id; | 96 lastTimeUsec += profile.timeDeltas[i]; |
| 88 } | 97 timestamps[i] = lastTimeUsec; |
| 89 }, | 98 } |
| 90 | 99 return timestamps; |
| 100 } |
| 101 |
| 102 /** |
| 103 * @param {!Array<!ProfilerAgent.ProfileNode>} nodes |
| 104 * @return {!WebInspector.CPUProfileNode} |
| 105 */ |
| 106 _translateProfileTree(nodes) { |
| 91 /** | 107 /** |
| 92 * @param {!ProfilerAgent.Profile} profile | 108 * @param {!ProfilerAgent.ProfileNode} node |
| 93 * @return {?Array<number>} | 109 * @return {boolean} |
| 94 */ | 110 */ |
| 95 _convertTimeDeltas: function(profile) | 111 function isNativeNode(node) { |
| 96 { | 112 if (node.callFrame) |
| 97 if (!profile.timeDeltas) | 113 return !!node.callFrame.url && node.callFrame.url.startsWith('native '); |
| 98 return null; | 114 return !!node.url && node.url.startsWith('native '); |
| 99 var lastTimeUsec = profile.startTime; | 115 } |
| 100 var timestamps = new Array(profile.timeDeltas.length); | |
| 101 for (var i = 0; i < timestamps.length; ++i) { | |
| 102 lastTimeUsec += profile.timeDeltas[i]; | |
| 103 timestamps[i] = lastTimeUsec; | |
| 104 } | |
| 105 return timestamps; | |
| 106 }, | |
| 107 | |
| 108 /** | 116 /** |
| 109 * @param {!Array<!ProfilerAgent.ProfileNode>} nodes | 117 * @param {!Array<!ProfilerAgent.ProfileNode>} nodes |
| 110 * @return {!WebInspector.CPUProfileNode} | |
| 111 */ | 118 */ |
| 112 _translateProfileTree: function(nodes) | 119 function buildChildrenFromParents(nodes) { |
| 113 { | 120 if (nodes[0].children) |
| 114 /** | 121 return; |
| 115 * @param {!ProfilerAgent.ProfileNode} node | 122 nodes[0].children = []; |
| 116 * @return {boolean} | 123 for (var i = 1; i < nodes.length; ++i) { |
| 117 */ | 124 var node = nodes[i]; |
| 118 function isNativeNode(node) | 125 var parentNode = nodeByIdMap.get(node.parent); |
| 119 { | 126 if (parentNode.children) |
| 120 if (node.callFrame) | 127 parentNode.children.push(node.id); |
| 121 return !!node.callFrame.url && node.callFrame.url.startsWith("na
tive "); | 128 else |
| 122 return !!node.url && node.url.startsWith("native "); | 129 parentNode.children = [node.id]; |
| 130 } |
| 131 } |
| 132 /** @type {!Map<number, !ProfilerAgent.ProfileNode>} */ |
| 133 var nodeByIdMap = new Map(); |
| 134 for (var i = 0; i < nodes.length; ++i) { |
| 135 var node = nodes[i]; |
| 136 nodeByIdMap.set(node.id, node); |
| 137 } |
| 138 buildChildrenFromParents(nodes); |
| 139 this.totalHitCount = nodes.reduce((acc, node) => acc + node.hitCount, 0); |
| 140 var sampleTime = (this.profileEndTime - this.profileStartTime) / this.totalH
itCount; |
| 141 var keepNatives = !!WebInspector.moduleSetting('showNativeFunctionsInJSProfi
le').get(); |
| 142 var root = nodes[0]; |
| 143 /** @type {!Map<number, number>} */ |
| 144 var idMap = new Map([[root.id, root.id]]); |
| 145 var resultRoot = new WebInspector.CPUProfileNode(root, sampleTime); |
| 146 var parentNodeStack = root.children.map(() => resultRoot); |
| 147 var sourceNodeStack = root.children.map(id => nodeByIdMap.get(id)); |
| 148 while (sourceNodeStack.length) { |
| 149 var parentNode = parentNodeStack.pop(); |
| 150 var sourceNode = sourceNodeStack.pop(); |
| 151 if (!sourceNode.children) |
| 152 sourceNode.children = []; |
| 153 var targetNode = new WebInspector.CPUProfileNode(sourceNode, sampleTime); |
| 154 if (keepNatives || !isNativeNode(sourceNode)) { |
| 155 parentNode.children.push(targetNode); |
| 156 parentNode = targetNode; |
| 157 } else { |
| 158 parentNode.self += targetNode.self; |
| 159 } |
| 160 idMap.set(sourceNode.id, parentNode.id); |
| 161 parentNodeStack.push.apply(parentNodeStack, sourceNode.children.map(() =>
parentNode)); |
| 162 sourceNodeStack.push.apply(sourceNodeStack, sourceNode.children.map(id =>
nodeByIdMap.get(id))); |
| 163 } |
| 164 if (this.samples) |
| 165 this.samples = this.samples.map(id => idMap.get(id)); |
| 166 return resultRoot; |
| 167 } |
| 168 |
| 169 _sortSamples() { |
| 170 var timestamps = this.timestamps; |
| 171 if (!timestamps) |
| 172 return; |
| 173 var samples = this.samples; |
| 174 var indices = timestamps.map((x, index) => index); |
| 175 indices.sort((a, b) => timestamps[a] - timestamps[b]); |
| 176 for (var i = 0; i < timestamps.length; ++i) { |
| 177 var index = indices[i]; |
| 178 if (index === i) |
| 179 continue; |
| 180 // Move items in a cycle. |
| 181 var savedTimestamp = timestamps[i]; |
| 182 var savedSample = samples[i]; |
| 183 var currentIndex = i; |
| 184 while (index !== i) { |
| 185 samples[currentIndex] = samples[index]; |
| 186 timestamps[currentIndex] = timestamps[index]; |
| 187 currentIndex = index; |
| 188 index = indices[index]; |
| 189 indices[currentIndex] = currentIndex; |
| 190 } |
| 191 samples[currentIndex] = savedSample; |
| 192 timestamps[currentIndex] = savedTimestamp; |
| 193 } |
| 194 } |
| 195 |
| 196 _normalizeTimestamps() { |
| 197 var timestamps = this.timestamps; |
| 198 if (!timestamps) { |
| 199 // Support loading old CPU profiles that are missing timestamps. |
| 200 // Derive timestamps from profile start and stop times. |
| 201 var profileStartTime = this.profileStartTime; |
| 202 var interval = (this.profileEndTime - profileStartTime) / this.samples.len
gth; |
| 203 timestamps = new Float64Array(this.samples.length + 1); |
| 204 for (var i = 0; i < timestamps.length; ++i) |
| 205 timestamps[i] = profileStartTime + i * interval; |
| 206 this.timestamps = timestamps; |
| 207 return; |
| 208 } |
| 209 |
| 210 // Convert samples from usec to msec |
| 211 for (var i = 0; i < timestamps.length; ++i) |
| 212 timestamps[i] /= 1000; |
| 213 var averageSample = (timestamps.peekLast() - timestamps[0]) / (timestamps.le
ngth - 1); |
| 214 // Add an extra timestamp used to calculate the last sample duration. |
| 215 this.timestamps.push(timestamps.peekLast() + averageSample); |
| 216 this.profileStartTime = timestamps[0]; |
| 217 this.profileEndTime = timestamps.peekLast(); |
| 218 } |
| 219 |
| 220 _buildIdToNodeMap() { |
| 221 /** @type {!Map<number, !WebInspector.CPUProfileNode>} */ |
| 222 this._idToNode = new Map(); |
| 223 var idToNode = this._idToNode; |
| 224 var stack = [this.profileHead]; |
| 225 while (stack.length) { |
| 226 var node = stack.pop(); |
| 227 idToNode.set(node.id, node); |
| 228 stack.push.apply(stack, node.children); |
| 229 } |
| 230 } |
| 231 |
| 232 _extractMetaNodes() { |
| 233 var topLevelNodes = this.profileHead.children; |
| 234 for (var i = 0; i < topLevelNodes.length && !(this.gcNode && this.programNod
e && this.idleNode); i++) { |
| 235 var node = topLevelNodes[i]; |
| 236 if (node.functionName === '(garbage collector)') |
| 237 this.gcNode = node; |
| 238 else if (node.functionName === '(program)') |
| 239 this.programNode = node; |
| 240 else if (node.functionName === '(idle)') |
| 241 this.idleNode = node; |
| 242 } |
| 243 } |
| 244 |
| 245 /** |
| 246 * @param {function(number, !WebInspector.CPUProfileNode, number)} openFrameCa
llback |
| 247 * @param {function(number, !WebInspector.CPUProfileNode, number, number, numb
er)} closeFrameCallback |
| 248 * @param {number=} startTime |
| 249 * @param {number=} stopTime |
| 250 */ |
| 251 forEachFrame(openFrameCallback, closeFrameCallback, startTime, stopTime) { |
| 252 if (!this.profileHead || !this.samples) |
| 253 return; |
| 254 |
| 255 startTime = startTime || 0; |
| 256 stopTime = stopTime || Infinity; |
| 257 var samples = this.samples; |
| 258 var timestamps = this.timestamps; |
| 259 var idToNode = this._idToNode; |
| 260 var gcNode = this.gcNode; |
| 261 var samplesCount = samples.length; |
| 262 var startIndex = timestamps.lowerBound(startTime); |
| 263 var stackTop = 0; |
| 264 var stackNodes = []; |
| 265 var prevId = this.profileHead.id; |
| 266 var sampleTime = timestamps[samplesCount]; |
| 267 var gcParentNode = null; |
| 268 |
| 269 if (!this._stackStartTimes) |
| 270 this._stackStartTimes = new Float64Array(this.maxDepth + 2); |
| 271 var stackStartTimes = this._stackStartTimes; |
| 272 if (!this._stackChildrenDuration) |
| 273 this._stackChildrenDuration = new Float64Array(this.maxDepth + 2); |
| 274 var stackChildrenDuration = this._stackChildrenDuration; |
| 275 |
| 276 for (var sampleIndex = startIndex; sampleIndex < samplesCount; sampleIndex++
) { |
| 277 sampleTime = timestamps[sampleIndex]; |
| 278 if (sampleTime >= stopTime) |
| 279 break; |
| 280 var id = samples[sampleIndex]; |
| 281 if (id === prevId) |
| 282 continue; |
| 283 var node = idToNode.get(id); |
| 284 var prevNode = idToNode.get(prevId); |
| 285 |
| 286 if (node === gcNode) { |
| 287 // GC samples have no stack, so we just put GC node on top of the last r
ecorded sample. |
| 288 gcParentNode = prevNode; |
| 289 openFrameCallback(gcParentNode.depth + 1, gcNode, sampleTime); |
| 290 stackStartTimes[++stackTop] = sampleTime; |
| 291 stackChildrenDuration[stackTop] = 0; |
| 292 prevId = id; |
| 293 continue; |
| 294 } |
| 295 if (prevNode === gcNode) { |
| 296 // end of GC frame |
| 297 var start = stackStartTimes[stackTop]; |
| 298 var duration = sampleTime - start; |
| 299 stackChildrenDuration[stackTop - 1] += duration; |
| 300 closeFrameCallback(gcParentNode.depth + 1, gcNode, start, duration, dura
tion - stackChildrenDuration[stackTop]); |
| 301 --stackTop; |
| 302 prevNode = gcParentNode; |
| 303 prevId = prevNode.id; |
| 304 gcParentNode = null; |
| 305 } |
| 306 |
| 307 while (node.depth > prevNode.depth) { |
| 308 stackNodes.push(node); |
| 309 node = node.parent; |
| 310 } |
| 311 |
| 312 // Go down to the LCA and close current intervals. |
| 313 while (prevNode !== node) { |
| 314 var start = stackStartTimes[stackTop]; |
| 315 var duration = sampleTime - start; |
| 316 stackChildrenDuration[stackTop - 1] += duration; |
| 317 closeFrameCallback( |
| 318 prevNode.depth, /** @type {!WebInspector.CPUProfileNode} */ (prevNod
e), start, duration, |
| 319 duration - stackChildrenDuration[stackTop]); |
| 320 --stackTop; |
| 321 if (node.depth === prevNode.depth) { |
| 322 stackNodes.push(node); |
| 323 node = node.parent; |
| 123 } | 324 } |
| 124 /** | 325 prevNode = prevNode.parent; |
| 125 * @param {!Array<!ProfilerAgent.ProfileNode>} nodes | 326 } |
| 126 */ | 327 |
| 127 function buildChildrenFromParents(nodes) | 328 // Go up the nodes stack and open new intervals. |
| 128 { | 329 while (stackNodes.length) { |
| 129 if (nodes[0].children) | 330 node = stackNodes.pop(); |
| 130 return; | 331 openFrameCallback(node.depth, node, sampleTime); |
| 131 nodes[0].children = []; | 332 stackStartTimes[++stackTop] = sampleTime; |
| 132 for (var i = 1; i < nodes.length; ++i) { | 333 stackChildrenDuration[stackTop] = 0; |
| 133 var node = nodes[i]; | 334 } |
| 134 var parentNode = nodeByIdMap.get(node.parent); | 335 |
| 135 if (parentNode.children) | 336 prevId = id; |
| 136 parentNode.children.push(node.id); | 337 } |
| 137 else | 338 |
| 138 parentNode.children = [node.id]; | 339 if (idToNode.get(prevId) === gcNode) { |
| 139 } | 340 var start = stackStartTimes[stackTop]; |
| 140 } | 341 var duration = sampleTime - start; |
| 141 /** @type {!Map<number, !ProfilerAgent.ProfileNode>} */ | 342 stackChildrenDuration[stackTop - 1] += duration; |
| 142 var nodeByIdMap = new Map(); | 343 closeFrameCallback(gcParentNode.depth + 1, node, start, duration, duration
- stackChildrenDuration[stackTop]); |
| 143 for (var i = 0; i < nodes.length; ++i) { | 344 --stackTop; |
| 144 var node = nodes[i]; | 345 } |
| 145 nodeByIdMap.set(node.id, node); | 346 |
| 146 } | 347 for (var node = idToNode.get(prevId); node.parent; node = node.parent) { |
| 147 buildChildrenFromParents(nodes); | 348 var start = stackStartTimes[stackTop]; |
| 148 this.totalHitCount = nodes.reduce((acc, node) => acc + node.hitCount, 0)
; | 349 var duration = sampleTime - start; |
| 149 var sampleTime = (this.profileEndTime - this.profileStartTime) / this.to
talHitCount; | 350 stackChildrenDuration[stackTop - 1] += duration; |
| 150 var keepNatives = !!WebInspector.moduleSetting("showNativeFunctionsInJSP
rofile").get(); | 351 closeFrameCallback( |
| 151 var root = nodes[0]; | 352 node.depth, /** @type {!WebInspector.CPUProfileNode} */ (node), start,
duration, |
| 152 /** @type {!Map<number, number>} */ | 353 duration - stackChildrenDuration[stackTop]); |
| 153 var idMap = new Map([[root.id, root.id]]); | 354 --stackTop; |
| 154 var resultRoot = new WebInspector.CPUProfileNode(root, sampleTime); | 355 } |
| 155 var parentNodeStack = root.children.map(() => resultRoot); | 356 } |
| 156 var sourceNodeStack = root.children.map(id => nodeByIdMap.get(id)); | 357 |
| 157 while (sourceNodeStack.length) { | 358 /** |
| 158 var parentNode = parentNodeStack.pop(); | 359 * @param {number} index |
| 159 var sourceNode = sourceNodeStack.pop(); | 360 * @return {?WebInspector.CPUProfileNode} |
| 160 if (!sourceNode.children) | 361 */ |
| 161 sourceNode.children = []; | 362 nodeByIndex(index) { |
| 162 var targetNode = new WebInspector.CPUProfileNode(sourceNode, sampleT
ime); | 363 return this._idToNode.get(this.samples[index]) || null; |
| 163 if (keepNatives || !isNativeNode(sourceNode)) { | 364 } |
| 164 parentNode.children.push(targetNode); | |
| 165 parentNode = targetNode; | |
| 166 } else { | |
| 167 parentNode.self += targetNode.self; | |
| 168 } | |
| 169 idMap.set(sourceNode.id, parentNode.id); | |
| 170 parentNodeStack.push.apply(parentNodeStack, sourceNode.children.map(
() => parentNode)); | |
| 171 sourceNodeStack.push.apply(sourceNodeStack, sourceNode.children.map(
id => nodeByIdMap.get(id))); | |
| 172 } | |
| 173 if (this.samples) | |
| 174 this.samples = this.samples.map(id => idMap.get(id)); | |
| 175 return resultRoot; | |
| 176 }, | |
| 177 | |
| 178 _sortSamples: function() | |
| 179 { | |
| 180 var timestamps = this.timestamps; | |
| 181 if (!timestamps) | |
| 182 return; | |
| 183 var samples = this.samples; | |
| 184 var indices = timestamps.map((x, index) => index); | |
| 185 indices.sort((a, b) => timestamps[a] - timestamps[b]); | |
| 186 for (var i = 0; i < timestamps.length; ++i) { | |
| 187 var index = indices[i]; | |
| 188 if (index === i) | |
| 189 continue; | |
| 190 // Move items in a cycle. | |
| 191 var savedTimestamp = timestamps[i]; | |
| 192 var savedSample = samples[i]; | |
| 193 var currentIndex = i; | |
| 194 while (index !== i) { | |
| 195 samples[currentIndex] = samples[index]; | |
| 196 timestamps[currentIndex] = timestamps[index]; | |
| 197 currentIndex = index; | |
| 198 index = indices[index]; | |
| 199 indices[currentIndex] = currentIndex; | |
| 200 } | |
| 201 samples[currentIndex] = savedSample; | |
| 202 timestamps[currentIndex] = savedTimestamp; | |
| 203 } | |
| 204 }, | |
| 205 | |
| 206 _normalizeTimestamps: function() | |
| 207 { | |
| 208 var timestamps = this.timestamps; | |
| 209 if (!timestamps) { | |
| 210 // Support loading old CPU profiles that are missing timestamps. | |
| 211 // Derive timestamps from profile start and stop times. | |
| 212 var profileStartTime = this.profileStartTime; | |
| 213 var interval = (this.profileEndTime - profileStartTime) / this.sampl
es.length; | |
| 214 timestamps = new Float64Array(this.samples.length + 1); | |
| 215 for (var i = 0; i < timestamps.length; ++i) | |
| 216 timestamps[i] = profileStartTime + i * interval; | |
| 217 this.timestamps = timestamps; | |
| 218 return; | |
| 219 } | |
| 220 | |
| 221 // Convert samples from usec to msec | |
| 222 for (var i = 0; i < timestamps.length; ++i) | |
| 223 timestamps[i] /= 1000; | |
| 224 var averageSample = (timestamps.peekLast() - timestamps[0]) / (timestamp
s.length - 1); | |
| 225 // Add an extra timestamp used to calculate the last sample duration. | |
| 226 this.timestamps.push(timestamps.peekLast() + averageSample); | |
| 227 this.profileStartTime = timestamps[0]; | |
| 228 this.profileEndTime = timestamps.peekLast(); | |
| 229 }, | |
| 230 | |
| 231 _buildIdToNodeMap: function() | |
| 232 { | |
| 233 /** @type {!Map<number, !WebInspector.CPUProfileNode>} */ | |
| 234 this._idToNode = new Map(); | |
| 235 var idToNode = this._idToNode; | |
| 236 var stack = [this.profileHead]; | |
| 237 while (stack.length) { | |
| 238 var node = stack.pop(); | |
| 239 idToNode.set(node.id, node); | |
| 240 stack.push.apply(stack, node.children); | |
| 241 } | |
| 242 }, | |
| 243 | |
| 244 _extractMetaNodes: function() | |
| 245 { | |
| 246 var topLevelNodes = this.profileHead.children; | |
| 247 for (var i = 0; i < topLevelNodes.length && !(this.gcNode && this.progra
mNode && this.idleNode); i++) { | |
| 248 var node = topLevelNodes[i]; | |
| 249 if (node.functionName === "(garbage collector)") | |
| 250 this.gcNode = node; | |
| 251 else if (node.functionName === "(program)") | |
| 252 this.programNode = node; | |
| 253 else if (node.functionName === "(idle)") | |
| 254 this.idleNode = node; | |
| 255 } | |
| 256 }, | |
| 257 | |
| 258 /** | |
| 259 * @param {function(number, !WebInspector.CPUProfileNode, number)} openFrame
Callback | |
| 260 * @param {function(number, !WebInspector.CPUProfileNode, number, number, nu
mber)} closeFrameCallback | |
| 261 * @param {number=} startTime | |
| 262 * @param {number=} stopTime | |
| 263 */ | |
| 264 forEachFrame: function(openFrameCallback, closeFrameCallback, startTime, sto
pTime) | |
| 265 { | |
| 266 if (!this.profileHead || !this.samples) | |
| 267 return; | |
| 268 | |
| 269 startTime = startTime || 0; | |
| 270 stopTime = stopTime || Infinity; | |
| 271 var samples = this.samples; | |
| 272 var timestamps = this.timestamps; | |
| 273 var idToNode = this._idToNode; | |
| 274 var gcNode = this.gcNode; | |
| 275 var samplesCount = samples.length; | |
| 276 var startIndex = timestamps.lowerBound(startTime); | |
| 277 var stackTop = 0; | |
| 278 var stackNodes = []; | |
| 279 var prevId = this.profileHead.id; | |
| 280 var sampleTime = timestamps[samplesCount]; | |
| 281 var gcParentNode = null; | |
| 282 | |
| 283 if (!this._stackStartTimes) | |
| 284 this._stackStartTimes = new Float64Array(this.maxDepth + 2); | |
| 285 var stackStartTimes = this._stackStartTimes; | |
| 286 if (!this._stackChildrenDuration) | |
| 287 this._stackChildrenDuration = new Float64Array(this.maxDepth + 2); | |
| 288 var stackChildrenDuration = this._stackChildrenDuration; | |
| 289 | |
| 290 for (var sampleIndex = startIndex; sampleIndex < samplesCount; sampleInd
ex++) { | |
| 291 sampleTime = timestamps[sampleIndex]; | |
| 292 if (sampleTime >= stopTime) | |
| 293 break; | |
| 294 var id = samples[sampleIndex]; | |
| 295 if (id === prevId) | |
| 296 continue; | |
| 297 var node = idToNode.get(id); | |
| 298 var prevNode = idToNode.get(prevId); | |
| 299 | |
| 300 if (node === gcNode) { | |
| 301 // GC samples have no stack, so we just put GC node on top of th
e last recorded sample. | |
| 302 gcParentNode = prevNode; | |
| 303 openFrameCallback(gcParentNode.depth + 1, gcNode, sampleTime); | |
| 304 stackStartTimes[++stackTop] = sampleTime; | |
| 305 stackChildrenDuration[stackTop] = 0; | |
| 306 prevId = id; | |
| 307 continue; | |
| 308 } | |
| 309 if (prevNode === gcNode) { | |
| 310 // end of GC frame | |
| 311 var start = stackStartTimes[stackTop]; | |
| 312 var duration = sampleTime - start; | |
| 313 stackChildrenDuration[stackTop - 1] += duration; | |
| 314 closeFrameCallback(gcParentNode.depth + 1, gcNode, start, durati
on, duration - stackChildrenDuration[stackTop]); | |
| 315 --stackTop; | |
| 316 prevNode = gcParentNode; | |
| 317 prevId = prevNode.id; | |
| 318 gcParentNode = null; | |
| 319 } | |
| 320 | |
| 321 while (node.depth > prevNode.depth) { | |
| 322 stackNodes.push(node); | |
| 323 node = node.parent; | |
| 324 } | |
| 325 | |
| 326 // Go down to the LCA and close current intervals. | |
| 327 while (prevNode !== node) { | |
| 328 var start = stackStartTimes[stackTop]; | |
| 329 var duration = sampleTime - start; | |
| 330 stackChildrenDuration[stackTop - 1] += duration; | |
| 331 closeFrameCallback(prevNode.depth, /** @type {!WebInspector.CPUP
rofileNode} */(prevNode), start, duration, duration - stackChildrenDuration[stac
kTop]); | |
| 332 --stackTop; | |
| 333 if (node.depth === prevNode.depth) { | |
| 334 stackNodes.push(node); | |
| 335 node = node.parent; | |
| 336 } | |
| 337 prevNode = prevNode.parent; | |
| 338 } | |
| 339 | |
| 340 // Go up the nodes stack and open new intervals. | |
| 341 while (stackNodes.length) { | |
| 342 node = stackNodes.pop(); | |
| 343 openFrameCallback(node.depth, node, sampleTime); | |
| 344 stackStartTimes[++stackTop] = sampleTime; | |
| 345 stackChildrenDuration[stackTop] = 0; | |
| 346 } | |
| 347 | |
| 348 prevId = id; | |
| 349 } | |
| 350 | |
| 351 if (idToNode.get(prevId) === gcNode) { | |
| 352 var start = stackStartTimes[stackTop]; | |
| 353 var duration = sampleTime - start; | |
| 354 stackChildrenDuration[stackTop - 1] += duration; | |
| 355 closeFrameCallback(gcParentNode.depth + 1, node, start, duration, du
ration - stackChildrenDuration[stackTop]); | |
| 356 --stackTop; | |
| 357 } | |
| 358 | |
| 359 for (var node = idToNode.get(prevId); node.parent; node = node.parent) { | |
| 360 var start = stackStartTimes[stackTop]; | |
| 361 var duration = sampleTime - start; | |
| 362 stackChildrenDuration[stackTop - 1] += duration; | |
| 363 closeFrameCallback(node.depth, /** @type {!WebInspector.CPUProfileNo
de} */(node), start, duration, duration - stackChildrenDuration[stackTop]); | |
| 364 --stackTop; | |
| 365 } | |
| 366 }, | |
| 367 | |
| 368 /** | |
| 369 * @param {number} index | |
| 370 * @return {?WebInspector.CPUProfileNode} | |
| 371 */ | |
| 372 nodeByIndex: function(index) | |
| 373 { | |
| 374 return this._idToNode.get(this.samples[index]) || null; | |
| 375 }, | |
| 376 | |
| 377 __proto__: WebInspector.ProfileTreeModel.prototype | |
| 378 }; | 365 }; |
| OLD | NEW |