|
1 /* |
|
2 * Copyright (C) 2010 Google Inc. All rights reserved. |
|
3 * |
|
4 * Redistribution and use in source and binary forms, with or without |
|
5 * modification, are permitted provided that the following conditions are |
|
6 * met: |
|
7 * |
|
8 * * Redistributions of source code must retain the above copyright |
|
9 * notice, this list of conditions and the following disclaimer. |
|
10 * * Redistributions in binary form must reproduce the above |
|
11 * copyright notice, this list of conditions and the following disclaimer |
|
12 * in the documentation and/or other materials provided with the |
|
13 * distribution. |
|
14 * * Neither the name of Google Inc. nor the names of its |
|
15 * contributors may be used to endorse or promote products derived from |
|
16 * this software without specific prior written permission. |
|
17 * |
|
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
29 */ |
|
30 |
|
31 /** |
|
32 * @fileoverview Heap profiler panel implementation. |
|
33 */ |
|
34 |
|
35 WebInspector.ProfilesPanel.prototype.addSnapshot = function(snapshot) { |
|
36 snapshot.title = WebInspector.UIString("Snapshot %d", snapshot.number); |
|
37 snapshot.typeId = WebInspector.HeapSnapshotProfileType.TypeId; |
|
38 |
|
39 var snapshots = WebInspector.HeapSnapshotProfileType.snapshots; |
|
40 snapshots.push(snapshot); |
|
41 |
|
42 snapshot.listIndex = snapshots.length - 1; |
|
43 |
|
44 if (WebInspector.CPUProfile) |
|
45 this.addProfileHeader(WebInspector.HeapSnapshotProfileType.TypeId, snapshot); |
|
46 else |
|
47 this.addProfileHeader(snapshot); |
|
48 |
|
49 this.dispatchEventToListeners("snapshot added"); |
|
50 } |
|
51 |
|
52 |
|
53 WebInspector.HeapSnapshotView = function(parent, profile) |
|
54 { |
|
55 WebInspector.View.call(this); |
|
56 |
|
57 this.element.addStyleClass("heap-snapshot-view"); |
|
58 |
|
59 this.parent = parent; |
|
60 this.parent.addEventListener("snapshot added", this._updateBaseOptions, this); |
|
61 |
|
62 this.showCountAsPercent = false; |
|
63 this.showSizeAsPercent = false; |
|
64 this.showCountDeltaAsPercent = false; |
|
65 this.showSizeDeltaAsPercent = false; |
|
66 |
|
67 this.categories = { |
|
68 code: new WebInspector.ResourceCategory("code", WebInspector.UIString("Code"), "rgb(255,121,0)"), |
|
69 data: new WebInspector.ResourceCategory("data", WebInspector.UIString("Objects"), "rgb(47,102,236)") |
|
70 }; |
|
71 |
|
72 var summaryContainer = document.createElement("div"); |
|
73 summaryContainer.id = "heap-snapshot-summary-container"; |
|
74 |
|
75 this.countsSummaryBar = new WebInspector.SummaryBar(this.categories); |
|
76 this.countsSummaryBar.element.className = "heap-snapshot-summary"; |
|
77 this.countsSummaryBar.calculator = new WebInspector.HeapSummaryCountCalculator(); |
|
78 var countsLabel = document.createElement("div"); |
|
79 countsLabel.className = "heap-snapshot-summary-label"; |
|
80 countsLabel.textContent = WebInspector.UIString("Count"); |
|
81 this.countsSummaryBar.element.appendChild(countsLabel); |
|
82 summaryContainer.appendChild(this.countsSummaryBar.element); |
|
83 |
|
84 this.sizesSummaryBar = new WebInspector.SummaryBar(this.categories); |
|
85 this.sizesSummaryBar.element.className = "heap-snapshot-summary"; |
|
86 this.sizesSummaryBar.calculator = new WebInspector.HeapSummarySizeCalculator(); |
|
87 var sizesLabel = document.createElement("label"); |
|
88 sizesLabel.className = "heap-snapshot-summary-label"; |
|
89 sizesLabel.textContent = WebInspector.UIString("Size"); |
|
90 this.sizesSummaryBar.element.appendChild(sizesLabel); |
|
91 summaryContainer.appendChild(this.sizesSummaryBar.element); |
|
92 |
|
93 this.element.appendChild(summaryContainer); |
|
94 |
|
95 var columns = { "cons": { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true }, |
|
96 "count": { title: WebInspector.UIString("Count"), width: "54px", sortable: true }, |
|
97 "size": { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true }, |
|
98 "countDelta": { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true }, |
|
99 "sizeDelta": { title: WebInspector.UIString("\xb1 Size"), width: "72px", sortable: true } }; |
|
100 |
|
101 this.dataGrid = new WebInspector.DataGrid(columns); |
|
102 this.dataGrid.addEventListener("sorting changed", this._sortData, this); |
|
103 this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true); |
|
104 this.element.appendChild(this.dataGrid.element); |
|
105 |
|
106 this.profile = profile; |
|
107 |
|
108 this.baseSelectElement = document.createElement("select"); |
|
109 this.baseSelectElement.className = "status-bar-item"; |
|
110 this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false); |
|
111 this._updateBaseOptions(); |
|
112 if (this.profile.listIndex > 0) |
|
113 this.baseSelectElement.selectedIndex = this.profile.listIndex - 1; |
|
114 else |
|
115 this.baseSelectElement.selectedIndex = this.profile.listIndex; |
|
116 this._resetDataGridList(); |
|
117 |
|
118 this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item"); |
|
119 this.percentButton.addEventListener("click", this._percentClicked.bind(this), false); |
|
120 |
|
121 this.refresh(); |
|
122 |
|
123 this._updatePercentButton(); |
|
124 }; |
|
125 |
|
126 WebInspector.HeapSnapshotView.prototype = { |
|
127 |
|
128 get statusBarItems() |
|
129 { |
|
130 return [this.baseSelectElement, this.percentButton.element]; |
|
131 }, |
|
132 |
|
133 get profile() |
|
134 { |
|
135 return this._profile; |
|
136 }, |
|
137 |
|
138 set profile(profile) |
|
139 { |
|
140 this._profile = profile; |
|
141 }, |
|
142 |
|
143 show: function(parentElement) |
|
144 { |
|
145 WebInspector.View.prototype.show.call(this, parentElement); |
|
146 this.dataGrid.updateWidths(); |
|
147 }, |
|
148 |
|
149 hide: function() |
|
150 { |
|
151 WebInspector.View.prototype.hide.call(this); |
|
152 this._currentSearchResultIndex = -1; |
|
153 }, |
|
154 |
|
155 resize: function() |
|
156 { |
|
157 if (this.dataGrid) |
|
158 this.dataGrid.updateWidths(); |
|
159 }, |
|
160 |
|
161 refresh: function() |
|
162 { |
|
163 this.dataGrid.removeChildren(); |
|
164 |
|
165 var children = this.snapshotDataGridList.children; |
|
166 var count = children.length; |
|
167 for (var index = 0; index < count; ++index) |
|
168 this.dataGrid.appendChild(children[index]); |
|
169 |
|
170 this._updateSummaryGraph(); |
|
171 }, |
|
172 |
|
173 refreshShowAsPercents: function() |
|
174 { |
|
175 this._updatePercentButton(); |
|
176 this.refreshVisibleData(); |
|
177 }, |
|
178 |
|
179 _deleteSearchMatchedFlags: function(node) |
|
180 { |
|
181 delete node._searchMatchedConsColumn; |
|
182 delete node._searchMatchedCountColumn; |
|
183 delete node._searchMatchedSizeColumn; |
|
184 delete node._searchMatchedCountDeltaColumn; |
|
185 delete node._searchMatchedSizeDeltaColumn; |
|
186 }, |
|
187 |
|
188 searchCanceled: function() |
|
189 { |
|
190 if (this._searchResults) { |
|
191 for (var i = 0; i < this._searchResults.length; ++i) { |
|
192 var profileNode = this._searchResults[i].profileNode; |
|
193 this._deleteSearchMatchedFlags(profileNode); |
|
194 profileNode.refresh(); |
|
195 } |
|
196 } |
|
197 |
|
198 delete this._searchFinishedCallback; |
|
199 this._currentSearchResultIndex = -1; |
|
200 this._searchResults = []; |
|
201 }, |
|
202 |
|
203 performSearch: function(query, finishedCallback) |
|
204 { |
|
205 // Call searchCanceled since it will reset everything we need before doing a new search. |
|
206 this.searchCanceled(); |
|
207 |
|
208 query = query.trim(); |
|
209 |
|
210 if (!query.length) |
|
211 return; |
|
212 |
|
213 this._searchFinishedCallback = finishedCallback; |
|
214 |
|
215 var helper = WebInspector.HeapSnapshotView.SearchHelper; |
|
216 |
|
217 var operationAndNumber = helper.parseOperationAndNumber(query); |
|
218 var operation = operationAndNumber[0]; |
|
219 var queryNumber = operationAndNumber[1]; |
|
220 |
|
221 var percentUnits = helper.percents.test(query); |
|
222 var megaBytesUnits = helper.megaBytes.test(query); |
|
223 var kiloBytesUnits = helper.kiloBytes.test(query); |
|
224 var bytesUnits = helper.bytes.test(query); |
|
225 |
|
226 var queryNumberBytes = (megaBytesUnits ? (queryNumber * 1024 * 1024) : (kiloBytesUnits ? (queryNumber * 1024) : queryNumber)); |
|
227 |
|
228 function matchesQuery(heapSnapshotDataGridNode) |
|
229 { |
|
230 WebInspector.HeapSnapshotView.prototype._deleteSearchMatchedFlags(heapSnapshotDataGridNode); |
|
231 |
|
232 if (percentUnits) { |
|
233 heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.countPercent, queryNumber); |
|
234 heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.sizePercent, queryNumber); |
|
235 heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDeltaPercent, queryNumber); |
|
236 heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDeltaPercent, queryNumber); |
|
237 } else if (megaBytesUnits || kiloBytesUnits || bytesUnits) { |
|
238 heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.size, queryNumberBytes); |
|
239 heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDelta, queryNumberBytes); |
|
240 } else { |
|
241 heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.count, queryNumber); |
|
242 heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDelta, queryNumber); |
|
243 } |
|
244 |
|
245 if (heapSnapshotDataGridNode.constructorName.hasSubstring(query, true)) |
|
246 heapSnapshotDataGridNode._searchMatchedConsColumn = true; |
|
247 |
|
248 if (heapSnapshotDataGridNode._searchMatchedConsColumn || |
|
249 heapSnapshotDataGridNode._searchMatchedCountColumn || |
|
250 heapSnapshotDataGridNode._searchMatchedSizeColumn || |
|
251 heapSnapshotDataGridNode._searchMatchedCountDeltaColumn || |
|
252 heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn) { |
|
253 heapSnapshotDataGridNode.refresh(); |
|
254 return true; |
|
255 } |
|
256 |
|
257 return false; |
|
258 } |
|
259 |
|
260 var current = this.snapshotDataGridList.children[0]; |
|
261 var depth = 0; |
|
262 var info = {}; |
|
263 |
|
264 // The second and subsequent levels of heap snapshot nodes represent retainers, |
|
265 // so recursive expansion will be infinite, since a graph is being traversed. |
|
266 // So default to a recursion cap of 2 levels. |
|
267 var maxDepth = 2; |
|
268 |
|
269 while (current) { |
|
270 if (matchesQuery(current)) |
|
271 this._searchResults.push({ profileNode: current }); |
|
272 current = current.traverseNextNode(false, null, (depth >= maxDepth), info); |
|
273 depth += info.depthChange; |
|
274 } |
|
275 |
|
276 finishedCallback(this, this._searchResults.length); |
|
277 }, |
|
278 |
|
279 jumpToFirstSearchResult: WebInspector.CPUProfileView.prototype.jumpToFirstSearchResult, |
|
280 jumpToLastSearchResult: WebInspector.CPUProfileView.prototype.jumpToLastSearchResult, |
|
281 jumpToNextSearchResult: WebInspector.CPUProfileView.prototype.jumpToNextSearchResult, |
|
282 jumpToPreviousSearchResult: WebInspector.CPUProfileView.prototype.jumpToPreviousSearchResult, |
|
283 showingFirstSearchResult: WebInspector.CPUProfileView.prototype.showingFirstSearchResult, |
|
284 showingLastSearchResult: WebInspector.CPUProfileView.prototype.showingLastSearchResult, |
|
285 _jumpToSearchResult: WebInspector.CPUProfileView.prototype._jumpToSearchResult, |
|
286 |
|
287 refreshVisibleData: function() |
|
288 { |
|
289 var child = this.dataGrid.children[0]; |
|
290 while (child) { |
|
291 child.refresh(); |
|
292 child = child.traverseNextNode(false, null, true); |
|
293 } |
|
294 this._updateSummaryGraph(); |
|
295 }, |
|
296 |
|
297 _changeBase: function() { |
|
298 if (this.baseSnapshot === WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex]) |
|
299 return; |
|
300 |
|
301 this._resetDataGridList(); |
|
302 this.refresh(); |
|
303 |
|
304 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) |
|
305 return; |
|
306 |
|
307 // The current search needs to be performed again. First negate out previous match |
|
308 // count by calling the search finished callback with a negative number of matches. |
|
309 // Then perform the search again with the same query and callback. |
|
310 this._searchFinishedCallback(this, -this._searchResults.length); |
|
311 this.performSearch(this.currentQuery, this._searchFinishedCallback); |
|
312 }, |
|
313 |
|
314 _createSnapshotDataGridList: function() |
|
315 { |
|
316 if (this._snapshotDataGridList) |
|
317 delete this._snapshotDataGridList; |
|
318 |
|
319 this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.profile.entries); |
|
320 return this._snapshotDataGridList; |
|
321 }, |
|
322 |
|
323 _mouseDownInDataGrid: function(event) |
|
324 { |
|
325 if (event.detail < 2) |
|
326 return; |
|
327 |
|
328 var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); |
|
329 if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column"))) |
|
330 return; |
|
331 |
|
332 if (cell.hasStyleClass("count-column")) |
|
333 this.showCountAsPercent = !this.showCountAsPercent; |
|
334 else if (cell.hasStyleClass("size-column")) |
|
335 this.showSizeAsPercent = !this.showSizeAsPercent; |
|
336 else if (cell.hasStyleClass("countDelta-column")) |
|
337 this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent; |
|
338 else if (cell.hasStyleClass("sizeDelta-column")) |
|
339 this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent; |
|
340 |
|
341 this.refreshShowAsPercents(); |
|
342 |
|
343 event.preventDefault(); |
|
344 event.stopPropagation(); |
|
345 }, |
|
346 |
|
347 get _isShowingAsPercent() |
|
348 { |
|
349 return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent; |
|
350 }, |
|
351 |
|
352 _percentClicked: function(event) |
|
353 { |
|
354 var currentState = this._isShowingAsPercent; |
|
355 this.showCountAsPercent = !currentState; |
|
356 this.showSizeAsPercent = !currentState; |
|
357 this.showCountDeltaAsPercent = !currentState; |
|
358 this.showSizeDeltaAsPercent = !currentState; |
|
359 this.refreshShowAsPercents(); |
|
360 }, |
|
361 |
|
362 _resetDataGridList: function() |
|
363 { |
|
364 this.baseSnapshot = WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex]; |
|
365 var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false); |
|
366 if (this.snapshotDataGridList) |
|
367 lastComparator = this.snapshotDataGridList.lastComparator; |
|
368 this.snapshotDataGridList = this._createSnapshotDataGridList(); |
|
369 this.snapshotDataGridList.sort(lastComparator, true); |
|
370 }, |
|
371 |
|
372 _sortData: function() |
|
373 { |
|
374 var sortAscending = this.dataGrid.sortOrder === "ascending"; |
|
375 var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; |
|
376 var sortProperty = { |
|
377 "cons": ["constructorName", null], |
|
378 "count": ["count", null], |
|
379 "size": ["size", "count"], |
|
380 "countDelta": this.showCountDeltaAsPercent ? ["countDeltaPercent", null] : ["countDelta", null], |
|
381 "sizeDelta": this.showSizeDeltaAsPercent ? ["sizeDeltaPercent", "countDeltaPercent"] : ["sizeDelta", "sizeDeltaPercent"] |
|
382 }[sortColumnIdentifier]; |
|
383 |
|
384 this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty[0], sortProperty[1], sortAscending)); |
|
385 |
|
386 this.refresh(); |
|
387 }, |
|
388 |
|
389 _updateBaseOptions: function() |
|
390 { |
|
391 var list = WebInspector.HeapSnapshotProfileType.snapshots; |
|
392 // We're assuming that snapshots can only be added. |
|
393 if (this.baseSelectElement.length === list.length) |
|
394 return; |
|
395 |
|
396 for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) { |
|
397 var baseOption = document.createElement("option"); |
|
398 baseOption.label = WebInspector.UIString("Compared to %s", list[i].title); |
|
399 this.baseSelectElement.appendChild(baseOption); |
|
400 } |
|
401 }, |
|
402 |
|
403 _updatePercentButton: function() |
|
404 { |
|
405 if (this._isShowingAsPercent) { |
|
406 this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes."); |
|
407 this.percentButton.toggled = true; |
|
408 } else { |
|
409 this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages."); |
|
410 this.percentButton.toggled = false; |
|
411 } |
|
412 }, |
|
413 |
|
414 _updateSummaryGraph: function() |
|
415 { |
|
416 this.countsSummaryBar.calculator.showAsPercent = this._isShowingAsPercent; |
|
417 this.countsSummaryBar.update(this.profile.lowlevels); |
|
418 |
|
419 this.sizesSummaryBar.calculator.showAsPercent = this._isShowingAsPercent; |
|
420 this.sizesSummaryBar.update(this.profile.lowlevels); |
|
421 } |
|
422 }; |
|
423 |
|
424 WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype; |
|
425 |
|
426 WebInspector.HeapSnapshotView.SearchHelper = { |
|
427 // In comparators, we assume that a value from a node is passed as the first parameter. |
|
428 operations: { LESS: function (a, b) { return a !== null && a < b; }, |
|
429 LESS_OR_EQUAL: function (a, b) { return a !== null && a <= b; }, |
|
430 EQUAL: function (a, b) { return a !== null && a === b; }, |
|
431 GREATER_OR_EQUAL: function (a, b) { return a !== null && a >= b; }, |
|
432 GREATER: function (a, b) { return a !== null && a > b; } }, |
|
433 |
|
434 operationParsers: { LESS: /^<(\d+)/, |
|
435 LESS_OR_EQUAL: /^<=(\d+)/, |
|
436 GREATER_OR_EQUAL: /^>=(\d+)/, |
|
437 GREATER: /^>(\d+)/ }, |
|
438 |
|
439 parseOperationAndNumber: function(query) |
|
440 { |
|
441 var operations = WebInspector.HeapSnapshotView.SearchHelper.operations; |
|
442 var parsers = WebInspector.HeapSnapshotView.SearchHelper.operationParsers; |
|
443 for (var operation in parsers) { |
|
444 var match = query.match(parsers[operation]); |
|
445 if (match !== null) |
|
446 return [operations[operation], parseFloat(match[1])]; |
|
447 } |
|
448 return [operations.EQUAL, parseFloat(query)]; |
|
449 }, |
|
450 |
|
451 percents: /%$/, |
|
452 |
|
453 megaBytes: /MB$/i, |
|
454 |
|
455 kiloBytes: /KB$/i, |
|
456 |
|
457 bytes: /B$/i |
|
458 } |
|
459 |
|
460 WebInspector.HeapSummaryCalculator = function(lowLevelField) |
|
461 { |
|
462 this.total = 1; |
|
463 this.lowLevelField = lowLevelField; |
|
464 } |
|
465 |
|
466 WebInspector.HeapSummaryCalculator.prototype = { |
|
467 computeSummaryValues: function(lowLevels) |
|
468 { |
|
469 var highLevels = {data: 0, code: 0}; |
|
470 this.total = 0; |
|
471 for (var item in lowLevels) { |
|
472 var highItem = this._highFromLow(item); |
|
473 if (highItem) { |
|
474 var value = lowLevels[item][this.lowLevelField]; |
|
475 highLevels[highItem] += value; |
|
476 this.total += value; |
|
477 } |
|
478 } |
|
479 var result = {categoryValues: highLevels}; |
|
480 if (!this.showAsPercent) |
|
481 result.total = this.total; |
|
482 return result; |
|
483 }, |
|
484 |
|
485 formatValue: function(value) |
|
486 { |
|
487 if (this.showAsPercent) |
|
488 return WebInspector.UIString("%.2f%%", value / this.total * 100.0); |
|
489 else |
|
490 return this._valueToString(value); |
|
491 }, |
|
492 |
|
493 get showAsPercent() |
|
494 { |
|
495 return this._showAsPercent; |
|
496 }, |
|
497 |
|
498 set showAsPercent(x) |
|
499 { |
|
500 this._showAsPercent = x; |
|
501 } |
|
502 } |
|
503 |
|
504 WebInspector.HeapSummaryCountCalculator = function() |
|
505 { |
|
506 WebInspector.HeapSummaryCalculator.call(this, "count"); |
|
507 } |
|
508 |
|
509 WebInspector.HeapSummaryCountCalculator.prototype = { |
|
510 _highFromLow: function(type) { |
|
511 if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code"; |
|
512 if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/)) return "data"; |
|
513 return null; |
|
514 }, |
|
515 |
|
516 _valueToString: function(value) { |
|
517 return value.toString(); |
|
518 } |
|
519 } |
|
520 |
|
521 WebInspector.HeapSummaryCountCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype; |
|
522 |
|
523 WebInspector.HeapSummarySizeCalculator = function() |
|
524 { |
|
525 WebInspector.HeapSummaryCalculator.call(this, "size"); |
|
526 } |
|
527 |
|
528 WebInspector.HeapSummarySizeCalculator.prototype = { |
|
529 _highFromLow: function(type) { |
|
530 if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code"; |
|
531 if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/)) return "data"; |
|
532 return null; |
|
533 }, |
|
534 |
|
535 _valueToString: Number.bytesToString |
|
536 } |
|
537 |
|
538 WebInspector.HeapSummarySizeCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype; |
|
539 |
|
540 WebInspector.HeapSnapshotSidebarTreeElement = function(snapshot) |
|
541 { |
|
542 this.profile = snapshot; |
|
543 |
|
544 WebInspector.SidebarTreeElement.call(this, "heap-snapshot-sidebar-tree-item", "", "", snapshot, false); |
|
545 |
|
546 this.refreshTitles(); |
|
547 }; |
|
548 |
|
549 WebInspector.HeapSnapshotSidebarTreeElement.prototype = { |
|
550 get mainTitle() |
|
551 { |
|
552 if (this._mainTitle) |
|
553 return this._mainTitle; |
|
554 return this.profile.title; |
|
555 }, |
|
556 |
|
557 set mainTitle(x) |
|
558 { |
|
559 this._mainTitle = x; |
|
560 this.refreshTitles(); |
|
561 } |
|
562 }; |
|
563 |
|
564 WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.ProfileSidebarTreeElement.prototype; |
|
565 |
|
566 WebInspector.HeapSnapshotDataGridNodeWithRetainers = function(owningTree) |
|
567 { |
|
568 this.tree = owningTree; |
|
569 |
|
570 WebInspector.DataGridNode.call(this, null, this._hasRetainers); |
|
571 |
|
572 this.addEventListener("populate", this._populate, this); |
|
573 }; |
|
574 |
|
575 WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype = { |
|
576 isEmptySet: function(set) |
|
577 { |
|
578 for (var x in set) |
|
579 return false; |
|
580 return true; |
|
581 }, |
|
582 |
|
583 get _hasRetainers() |
|
584 { |
|
585 return !this.isEmptySet(this.retainers); |
|
586 }, |
|
587 |
|
588 get _parent() |
|
589 { |
|
590 // For top-level nodes, return owning tree as a parent, not data grid. |
|
591 return this.parent !== this.dataGrid ? this.parent : this.tree; |
|
592 }, |
|
593 |
|
594 _populate: function(event) |
|
595 { |
|
596 var self = this; |
|
597 this.produceDiff(this.baseRetainers, this.retainers, function(baseItem, snapshotItem) { |
|
598 self.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(self.snapshotView, baseItem, snapshotItem, self.tree)); |
|
599 }); |
|
600 |
|
601 if (this._parent) { |
|
602 var currentComparator = this._parent.lastComparator; |
|
603 if (currentComparator) |
|
604 this.sort(currentComparator, true); |
|
605 } |
|
606 |
|
607 this.removeEventListener("populate", this._populate, this); |
|
608 }, |
|
609 |
|
610 produceDiff: function(baseEntries, currentEntries, callback) |
|
611 { |
|
612 for (var item in currentEntries) |
|
613 callback(baseEntries[item], currentEntries[item]); |
|
614 |
|
615 for (item in baseEntries) { |
|
616 if (!(item in currentEntries)) |
|
617 callback(baseEntries[item], null); |
|
618 } |
|
619 }, |
|
620 |
|
621 sort: function(comparator, force) { |
|
622 if (!force && this.lastComparator === comparator) |
|
623 return; |
|
624 |
|
625 this.children.sort(comparator); |
|
626 var childCount = this.children.length; |
|
627 for (var childIndex = 0; childIndex < childCount; ++childIndex) |
|
628 this.children[childIndex]._recalculateSiblings(childIndex); |
|
629 for (var i = 0; i < this.children.length; ++i) { |
|
630 var child = this.children[i]; |
|
631 if (!force && (!child.expanded || child.lastComparator === comparator)) |
|
632 continue; |
|
633 child.sort(comparator, force); |
|
634 } |
|
635 this.lastComparator = comparator; |
|
636 }, |
|
637 |
|
638 signForDelta: function(delta) { |
|
639 if (delta === 0) |
|
640 return ""; |
|
641 if (delta > 0) |
|
642 return "+"; |
|
643 else |
|
644 // Math minus sign, same width as plus. |
|
645 return "\u2212"; |
|
646 }, |
|
647 |
|
648 showDeltaAsPercent: function(value) { |
|
649 if (value === Number.POSITIVE_INFINITY) |
|
650 return WebInspector.UIString("new"); |
|
651 else if (value === Number.NEGATIVE_INFINITY) |
|
652 return WebInspector.UIString("deleted"); |
|
653 if (value > 1000.0) |
|
654 return WebInspector.UIString("%s >1000%%", this.signForDelta(value)); |
|
655 return WebInspector.UIString("%s%.2f%%", this.signForDelta(value), Math.abs(value)); |
|
656 }, |
|
657 |
|
658 getTotalCount: function() { |
|
659 if (!this._count) { |
|
660 this._count = 0; |
|
661 for (var i = 0, n = this.children.length; i < n; ++i) |
|
662 this._count += this.children[i].count; |
|
663 } |
|
664 return this._count; |
|
665 }, |
|
666 |
|
667 getTotalSize: function() { |
|
668 if (!this._size) { |
|
669 this._size = 0; |
|
670 for (var i = 0, n = this.children.length; i < n; ++i) |
|
671 this._size += this.children[i].size; |
|
672 } |
|
673 return this._size; |
|
674 }, |
|
675 |
|
676 get countPercent() |
|
677 { |
|
678 return this.count / this._parent.getTotalCount() * 100.0; |
|
679 }, |
|
680 |
|
681 get sizePercent() |
|
682 { |
|
683 return this.size / this._parent.getTotalSize() * 100.0; |
|
684 }, |
|
685 |
|
686 get countDeltaPercent() |
|
687 { |
|
688 if (this.baseCount > 0) { |
|
689 if (this.count > 0) |
|
690 return this.countDelta / this.baseCount * 100.0; |
|
691 else |
|
692 return Number.NEGATIVE_INFINITY; |
|
693 } else |
|
694 return Number.POSITIVE_INFINITY; |
|
695 }, |
|
696 |
|
697 get sizeDeltaPercent() |
|
698 { |
|
699 if (this.baseSize > 0) { |
|
700 if (this.size > 0) |
|
701 return this.sizeDelta / this.baseSize * 100.0; |
|
702 else |
|
703 return Number.NEGATIVE_INFINITY; |
|
704 } else |
|
705 return Number.POSITIVE_INFINITY; |
|
706 }, |
|
707 |
|
708 get data() |
|
709 { |
|
710 var data = {}; |
|
711 |
|
712 data["cons"] = this.constructorName; |
|
713 |
|
714 if (this.snapshotView.showCountAsPercent) |
|
715 data["count"] = WebInspector.UIString("%.2f%%", this.countPercent); |
|
716 else |
|
717 data["count"] = this.count; |
|
718 |
|
719 if (this.size !== null) { |
|
720 if (this.snapshotView.showSizeAsPercent) |
|
721 data["size"] = WebInspector.UIString("%.2f%%", this.sizePercent); |
|
722 else |
|
723 data["size"] = Number.bytesToString(this.size); |
|
724 } else |
|
725 data["size"] = ""; |
|
726 |
|
727 if (this.snapshotView.showCountDeltaAsPercent) |
|
728 data["countDelta"] = this.showDeltaAsPercent(this.countDeltaPercent); |
|
729 else |
|
730 data["countDelta"] = WebInspector.UIString("%s%d", this.signForDelta(this.countDelta), Math.abs(this.countDelta)); |
|
731 |
|
732 if (this.sizeDelta !== null) { |
|
733 if (this.snapshotView.showSizeDeltaAsPercent) |
|
734 data["sizeDelta"] = this.showDeltaAsPercent(this.sizeDeltaPercent); |
|
735 else |
|
736 data["sizeDelta"] = WebInspector.UIString("%s%s", this.signForDelta(this.sizeDelta), Number.bytesToString(Math.abs(this.sizeDelta))); |
|
737 } else |
|
738 data["sizeDelta"] = ""; |
|
739 |
|
740 return data; |
|
741 }, |
|
742 |
|
743 createCell: function(columnIdentifier) |
|
744 { |
|
745 var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); |
|
746 |
|
747 if ((columnIdentifier === "cons" && this._searchMatchedConsColumn) || |
|
748 (columnIdentifier === "count" && this._searchMatchedCountColumn) || |
|
749 (columnIdentifier === "size" && this._searchMatchedSizeColumn) || |
|
750 (columnIdentifier === "countDelta" && this._searchMatchedCountDeltaColumn) || |
|
751 (columnIdentifier === "sizeDelta" && this._searchMatchedSizeDeltaColumn)) |
|
752 cell.addStyleClass("highlight"); |
|
753 |
|
754 return cell; |
|
755 } |
|
756 }; |
|
757 |
|
758 WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.__proto__ = WebInspector.DataGridNode.prototype; |
|
759 |
|
760 WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningTree) |
|
761 { |
|
762 this.snapshotView = snapshotView; |
|
763 |
|
764 if (!snapshotEntry) |
|
765 snapshotEntry = { cons: baseEntry.cons, count: 0, size: 0, retainers: {} }; |
|
766 this.constructorName = snapshotEntry.cons; |
|
767 this.count = snapshotEntry.count; |
|
768 this.size = snapshotEntry.size; |
|
769 this.retainers = snapshotEntry.retainers; |
|
770 |
|
771 if (!baseEntry) |
|
772 baseEntry = { count: 0, size: 0, retainers: {} }; |
|
773 this.baseCount = baseEntry.count; |
|
774 this.countDelta = this.count - this.baseCount; |
|
775 this.baseSize = baseEntry.size; |
|
776 this.sizeDelta = this.size - this.baseSize; |
|
777 this.baseRetainers = baseEntry.retainers; |
|
778 |
|
779 WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree); |
|
780 }; |
|
781 |
|
782 WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype; |
|
783 |
|
784 WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries) |
|
785 { |
|
786 this.tree = this; |
|
787 this.snapshotView = snapshotView; |
|
788 this.children = []; |
|
789 this.lastComparator = null; |
|
790 this.populateChildren(baseEntries, snapshotEntries); |
|
791 }; |
|
792 |
|
793 WebInspector.HeapSnapshotDataGridList.prototype = { |
|
794 appendChild: function(child) |
|
795 { |
|
796 this.insertChild(child, this.children.length); |
|
797 }, |
|
798 |
|
799 insertChild: function(child, index) |
|
800 { |
|
801 this.children.splice(index, 0, child); |
|
802 }, |
|
803 |
|
804 removeChildren: function() |
|
805 { |
|
806 this.children = []; |
|
807 }, |
|
808 |
|
809 populateChildren: function(baseEntries, snapshotEntries) |
|
810 { |
|
811 var self = this; |
|
812 this.produceDiff(baseEntries, snapshotEntries, function(baseItem, snapshotItem) { |
|
813 self.appendChild(new WebInspector.HeapSnapshotDataGridNode(self.snapshotView, baseItem, snapshotItem, self)); |
|
814 }); |
|
815 }, |
|
816 |
|
817 produceDiff: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.produceDiff, |
|
818 sort: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.sort, |
|
819 getTotalCount: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalCount, |
|
820 getTotalSize: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalSize |
|
821 }; |
|
822 |
|
823 WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}]; |
|
824 |
|
825 WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, property2, isAscending) |
|
826 { |
|
827 var propertyHash = property + "#" + property2; |
|
828 var comparator = this.propertyComparators[(isAscending ? 1 : 0)][propertyHash]; |
|
829 if (!comparator) { |
|
830 comparator = function(lhs, rhs) { |
|
831 var l = lhs[property], r = rhs[property]; |
|
832 if ((l === null || r === null) && property2 !== null) |
|
833 l = lhs[property2], r = rhs[property2]; |
|
834 var result = l < r ? -1 : (l > r ? 1 : 0); |
|
835 return isAscending ? result : -result; |
|
836 }; |
|
837 this.propertyComparators[(isAscending ? 1 : 0)][propertyHash] = comparator; |
|
838 } |
|
839 return comparator; |
|
840 }; |
|
841 |
|
842 WebInspector.HeapSnapshotDataGridRetainerNode = function(snapshotView, baseEntry, snapshotEntry, owningTree) |
|
843 { |
|
844 this.snapshotView = snapshotView; |
|
845 |
|
846 if (!snapshotEntry) |
|
847 snapshotEntry = { cons: baseEntry.cons, count: 0, clusters: {} }; |
|
848 this.constructorName = snapshotEntry.cons; |
|
849 this.count = snapshotEntry.count; |
|
850 this.retainers = this._calculateRetainers(this.snapshotView.profile, snapshotEntry.clusters); |
|
851 |
|
852 if (!baseEntry) |
|
853 baseEntry = { count: 0, clusters: {} }; |
|
854 this.baseCount = baseEntry.count; |
|
855 this.countDelta = this.count - this.baseCount; |
|
856 this.baseRetainers = this._calculateRetainers(this.snapshotView.baseSnapshot, baseEntry.clusters); |
|
857 |
|
858 this.size = null; |
|
859 this.sizeDelta = null; |
|
860 |
|
861 WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree); |
|
862 } |
|
863 |
|
864 WebInspector.HeapSnapshotDataGridRetainerNode.prototype = { |
|
865 get sizePercent() |
|
866 { |
|
867 return null; |
|
868 }, |
|
869 |
|
870 get sizeDeltaPercent() |
|
871 { |
|
872 return null; |
|
873 }, |
|
874 |
|
875 _calculateRetainers: function(snapshot, clusters) { |
|
876 var retainers = {}; |
|
877 if (this.isEmptySet(clusters)) { |
|
878 if (this.constructorName in snapshot.entries) |
|
879 return snapshot.entries[this.constructorName].retainers; |
|
880 } else { |
|
881 // In case when an entry is retained by clusters, we need to gather up the list |
|
882 // of retainers by merging retainers of every cluster. |
|
883 // E.g. having such a tree: |
|
884 // A |
|
885 // Object:1 10 |
|
886 // X 3 |
|
887 // Y 4 |
|
888 // Object:2 5 |
|
889 // X 6 |
|
890 // |
|
891 // will result in a following retainers list: X 9, Y 4. |
|
892 for (var clusterName in clusters) { |
|
893 if (clusterName in snapshot.clusters) { |
|
894 var clusterRetainers = snapshot.clusters[clusterName].retainers; |
|
895 for (var clusterRetainer in clusterRetainers) { |
|
896 var clusterRetainerEntry = clusterRetainers[clusterRetainer]; |
|
897 if (!(clusterRetainer in retainers)) |
|
898 retainers[clusterRetainer] = { cons: clusterRetainerEntry.cons, count: 0, clusters: {} }; |
|
899 retainers[clusterRetainer].count += clusterRetainerEntry.count; |
|
900 for (var clusterRetainerCluster in clusterRetainerEntry.clusters) |
|
901 retainers[clusterRetainer].clusters[clusterRetainerCluster] = true; |
|
902 } |
|
903 } |
|
904 } |
|
905 } |
|
906 return retainers; |
|
907 } |
|
908 }; |
|
909 |
|
910 WebInspector.HeapSnapshotDataGridRetainerNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype; |
|
911 |
|
912 |
|
913 WebInspector.HeapSnapshotProfileType = function() |
|
914 { |
|
915 WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS")); |
|
916 } |
|
917 |
|
918 WebInspector.HeapSnapshotProfileType.TypeId = "HEAP"; |
|
919 |
|
920 WebInspector.HeapSnapshotProfileType.snapshots = []; |
|
921 |
|
922 WebInspector.HeapSnapshotProfileType.prototype = { |
|
923 get buttonTooltip() |
|
924 { |
|
925 return WebInspector.UIString("Take heap snapshot."); |
|
926 }, |
|
927 |
|
928 get buttonStyle() |
|
929 { |
|
930 return "heap-snapshot-status-bar-item status-bar-item"; |
|
931 }, |
|
932 |
|
933 buttonClicked: function() |
|
934 { |
|
935 devtools.tools.getProfilerAgent().startProfiling(devtools.ProfilerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT); |
|
936 }, |
|
937 |
|
938 get welcomeMessage() |
|
939 { |
|
940 return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar."); |
|
941 }, |
|
942 |
|
943 createSidebarTreeElementForProfile: function(profile) |
|
944 { |
|
945 var element = new WebInspector.HeapSnapshotSidebarTreeElement(profile); |
|
946 element.small = false; |
|
947 return element; |
|
948 }, |
|
949 |
|
950 createView: function(profile) |
|
951 { |
|
952 return new WebInspector.HeapSnapshotView(WebInspector.panels.profiles, profile); |
|
953 } |
|
954 } |
|
955 |
|
956 WebInspector.HeapSnapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype; |
|
957 |
|
958 |
|
959 (function() { |
|
960 var originalCreatePanels = WebInspector._createPanels; |
|
961 WebInspector._createPanels = function() { |
|
962 originalCreatePanels.apply(this, arguments); |
|
963 if (WebInspector.panels.profiles) |
|
964 WebInspector.panels.profiles.registerProfileType(new WebInspector.HeapSnapshotProfileType()); |
|
965 } |
|
966 })(); |