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