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 |