Web Inspector: HeapSnapshots are slow and use too much memory
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / HeapSnapshotClassDataGridNode.js
1 /*
2 * Copyright (C) 2016 Apple 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
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 WebInspector.HeapSnapshotClassDataGridNode = class HeapSnapshotClassDataGridNode extends WebInspector.DataGridNode
27 {
28     constructor(data, tree)
29     {
30         super(data, true);
31
32         this._data = data;
33         this._tree = tree;
34
35         this._batched = false;
36         this._instances = null;
37
38         this.addEventListener("populate", this._populate, this);
39     }
40
41     // Protected
42
43     get data() { return this._data; }
44
45     createCellContent(columnIdentifier)
46     {
47         if (columnIdentifier === "size") {
48             let {size, percent} = this._data;
49             let fragment = document.createDocumentFragment();
50             let timeElement = fragment.appendChild(document.createElement("span"));
51             timeElement.classList.add("size");
52             timeElement.textContent = Number.bytesToString(size);
53             let percentElement = fragment.appendChild(document.createElement("span"));
54             percentElement.classList.add("percentage");
55             percentElement.textContent = Number.percentageString(percent);
56             return fragment;
57         }
58
59         if (columnIdentifier === "className") {
60             let {className, allInternal} = this._data;
61             let fragment = document.createDocumentFragment();
62             let iconElement = fragment.appendChild(document.createElement("img"));
63             iconElement.classList.add("icon", WebInspector.HeapSnapshotClusterContentView.iconStyleClassNameForClassName(className, allInternal));
64             let nameElement = fragment.appendChild(document.createElement("span"));
65             nameElement.classList.add("class-name");
66             nameElement.textContent = className;
67             return fragment;
68         }
69
70         return super.createCellContent(columnIdentifier);
71     }
72
73     sort()
74     {
75         if (this._batched) {
76             this._removeFetchMoreDataGridNode();
77             this._updateBatchedSort();
78             this._updateBatchedChildren();
79             this._appendFetchMoreDataGridNode();
80             return;
81         }
82
83         let children = this.children;
84         children.sort(this._tree._sortComparator);
85
86         for (let i = 0; i < children.length; ++i) {
87             children[i]._recalculateSiblings(i);
88             children[i].sort();
89         }
90     }
91
92     // Private
93
94     _populate()
95     {
96         this.removeEventListener("populate", this._populate, this);
97
98         this._tree.heapSnapshot.instancesWithClassName(this._data.className, (instances) => {
99             // Batch.
100             if (instances.length > WebInspector.HeapSnapshotClassDataGridNode.ChildrenBatchLimit) {
101                 // FIXME: This should respect the this._tree.includeInternalObjects setting.
102                 this._instances = instances;
103                 this._batched = true;
104                 this._updateBatchedSort();
105                 this._fetchBatch(WebInspector.HeapSnapshotClassDataGridNode.ChildrenBatchLimit);
106                 return;
107             }
108
109             for (let instance of instances) {
110                 if (instance.internal && !this._tree.includeInternalObjects)
111                     continue;
112                 this.appendChild(new WebInspector.HeapSnapshotInstanceDataGridNode(instance, this._tree));
113             }
114
115             this.sort();
116         });
117     }
118
119     _fetchBatch(batchSize)
120     {
121         if (this._batched && this.children.length)
122             this._removeFetchMoreDataGridNode();
123
124         let oldCount = this.children.length;
125         let newCount = oldCount + batchSize;
126
127         if (newCount >= this._instances.length) {
128             newCount = this._instances.length - 1;
129             this._batched = false;
130         }
131
132         let count = newCount - oldCount;
133         for (let i = 0; i <= count; ++i) {
134             let instance = this._instances[oldCount + i];
135             this.appendChild(new WebInspector.HeapSnapshotInstanceDataGridNode(instance, this._tree));
136         }
137
138         if (this._batched)
139             this._appendFetchMoreDataGridNode();
140     }
141
142     _updateBatchedSort()
143     {
144         this._instances.sort((a, b) => {
145             let fakeDataGridNodeA = {data: a};
146             let fakeDataGridNodeB = {data: b};
147             return this._tree._sortComparator(fakeDataGridNodeA, fakeDataGridNodeB);
148         });
149     }
150
151     _updateBatchedChildren()
152     {
153         let count = this.children.length;
154
155         this.removeChildren();
156
157         for (let i = 0; i < count; ++i)
158             this.appendChild(new WebInspector.HeapSnapshotInstanceDataGridNode(this._instances[i], this._tree));
159     }
160
161     _removeFetchMoreDataGridNode()
162     {
163         console.assert(this.children[this.children.length - 1] instanceof WebInspector.HeapSnapshotInstanceFetchMoreDataGridNode);
164
165         this.removeChild(this.children[this.children.length - 1]);
166     }
167
168     _appendFetchMoreDataGridNode()
169     {
170         console.assert(!(this.children[this.children.length - 1] instanceof WebInspector.HeapSnapshotInstanceFetchMoreDataGridNode));
171
172         let count = this.children.length;
173         let totalCount = this._instances.length;
174         let remainingCount = totalCount - count;
175         let batchSize = remainingCount >= WebInspector.HeapSnapshotClassDataGridNode.ChildrenBatchLimit ? WebInspector.HeapSnapshotClassDataGridNode.ChildrenBatchLimit : 0;
176
177         this.appendChild(new WebInspector.HeapSnapshotInstanceFetchMoreDataGridNode(this._tree, batchSize, remainingCount, this._fetchBatch.bind(this)));
178     }
179 };
180
181 WebInspector.HeapSnapshotClassDataGridNode.ChildrenBatchLimit = 100;