Heap Snapshot should include different Edge types and data (Property, Index, Variable)
[WebKit-https.git] / Source / JavaScriptCore / tests / heapProfiler / driver / driver.js
1 function assert(condition, reason) {
2     if (!condition)
3         throw new Error(reason);
4 }
5
6 // -----------------
7 // CheapHeapSnapshot
8 //
9 // Contains two large lists of all node data and all edge data.
10 // Lazily creates node and edge objects off of indexes into these lists.
11
12 // [<0:id>, <1:size>, <2:classNameTableIndex>, <3:internal>, <4:firstEdgeIndex>];
13 const nodeFieldCount = 5;
14 const nodeIdOffset = 0;
15 const nodeSizeOffset = 1;
16 const nodeClassNameOffset = 2;
17 const nodeInternalOffset = 3;
18 const nodeFirstEdgeOffset = 4;
19 const nodeNoEdgeValue = 0xffffffff; // UINT_MAX
20
21 // [<0:from-id>, <1:to-id>, <2:typeTableIndex>, <3:data>]
22 const edgeFieldCount = 4;
23 const edgeFromIdOffset = 0;
24 const edgeToIdOffset = 1;
25 const edgeTypeOffset = 2;
26 const edgeDataOffset = 3;
27
28 CheapHeapSnapshotNode = class CheapHeapSnapshotNode
29 {
30     constructor(snapshot, nodeIndex)
31     {
32         assert((nodeIndex % nodeFieldCount) === 0, "Bad Node Index: " + nodeIndex);
33
34         let nodes = snapshot.nodes;
35         this.id = nodes[nodeIndex + nodeIdOffset];
36         this.size = nodes[nodeIndex + nodeSizeOffset];
37         this.className = snapshot.classNameFromTableIndex(nodes[nodeIndex + nodeClassNameOffset]);
38         this.internal = nodes[nodeIndex + nodeInternalOffset] ? true : false;
39
40         this.outgoingEdges = [];
41         let firstEdgeIndex = nodes[nodeIndex + nodeFirstEdgeOffset];
42         if (firstEdgeIndex !== nodeNoEdgeValue) {
43             for (let i = firstEdgeIndex; i < snapshot.edges.length; i += edgeFieldCount) {
44                 if (snapshot.edges[i + edgeFromIdOffset] !== this.id)
45                     break;
46                 this.outgoingEdges.push(new CheapHeapSnapshotEdge(snapshot, i));
47             }
48         }
49     }
50 }
51
52 CheapHeapSnapshotEdge = class CheapHeapSnapshotEdge
53 {
54     constructor(snapshot, edgeIndex)
55     {
56         assert((edgeIndex % edgeFieldCount) === 0, "Bad Edge Index: " + edgeIndex);
57         this.snapshot = snapshot;
58
59         let edges = snapshot.edges;
60         this.fromId = edges[edgeIndex + edgeFromIdOffset];
61         this.toId = edges[edgeIndex + edgeToIdOffset];
62         this.type = snapshot.edgeTypeFromTableIndex(edges[edgeIndex + edgeTypeOffset]);
63         this.data = edges[edgeIndex + edgeDataOffset];
64     }
65
66     get from() { return this.snapshot.nodeWithIdentifier(this.fromId); }
67     get to() { return this.snapshot.nodeWithIdentifier(this.toId); }
68 }
69
70 CheapHeapSnapshot = class CheapHeapSnapshot
71 {
72     constructor(json)
73     {
74         let {nodes, nodeClassNames, edges, edgeTypes} = json;
75
76         this._nodes = new Uint32Array(nodes.length * nodeFieldCount);
77         this._edges = new Uint32Array(edges.length * edgeFieldCount);
78         this._nodeIdentifierToIndex = new Map; // <id> => index in _nodes
79
80         this._edgeTypesTable = edgeTypes;
81         this._nodeClassNamesTable = nodeClassNames;
82
83         let n = 0;
84         nodes.forEach((nodePayload) => {
85             let [id, size, classNameTableIndex, internal] = nodePayload;
86             this._nodeIdentifierToIndex.set(id, n);
87             this._nodes[n++] = id;
88             this._nodes[n++] = size;
89             this._nodes[n++] = classNameTableIndex;
90             this._nodes[n++] = internal;
91             this._nodes[n++] = nodeNoEdgeValue;
92         });
93
94         let e = 0;
95         let lastNodeIdentifier = -1;
96         edges.sort((a, b) => a[0] - b[0]).forEach((edgePayload) => {
97             let [fromIdentifier, toIdentifier, edgeTypeTableIndex, data] = edgePayload;
98             if (fromIdentifier !== lastNodeIdentifier) {
99                 let nodeIndex = this._nodeIdentifierToIndex.get(fromIdentifier);
100                 assert(this._nodes[nodeIndex + nodeIdOffset] === fromIdentifier, "Node lookup failed");
101                 this._nodes[nodeIndex + nodeFirstEdgeOffset] = e;
102                 lastNodeIdentifier = fromIdentifier;
103             }
104             this._edges[e++] = fromIdentifier;
105             this._edges[e++] = toIdentifier;
106             this._edges[e++] = edgeTypeTableIndex;
107             this._edges[e++] = data;
108         });
109     }
110
111     get nodes() { return this._nodes; }
112     get edges() { return this._edges; }
113
114     nodeWithIdentifier(id)
115     {
116         return new CheapHeapSnapshotNode(this, this._nodeIdentifierToIndex.get(id));
117     }
118
119     nodesWithClassName(className)
120     {
121         let result = [];
122         for (let i = 0; i < this._nodes.length; i += nodeFieldCount) {
123             let classNameTableIndex = this._nodes[i + nodeClassNameOffset];
124             if (this.classNameFromTableIndex(classNameTableIndex) === className)
125                 result.push(new CheapHeapSnapshotNode(this, i));
126         }
127         return result;
128     }
129
130     classNameFromTableIndex(tableIndex)
131     {
132         return this._nodeClassNamesTable[tableIndex];
133     }
134
135     edgeTypeFromTableIndex(tableIndex)
136     {
137         return this._edgeTypesTable[tableIndex];
138     }
139 }
140
141 function createCheapHeapSnapshot() {
142     let json = generateHeapSnapshot();
143
144     let {version, nodes, nodeClassNames, edges, edgeTypes} = json;
145     assert(version === 1, "Heap Snapshot payload should be version 1");
146     assert(nodes.length, "Heap Snapshot should have nodes");
147     assert(nodeClassNames.length, "Heap Snapshot should have nodeClassNames");
148     assert(edges.length, "Heap Snapshot should have edges");
149     assert(edgeTypes.length, "Heap Snapshot should have edgeTypes");
150
151     return new CheapHeapSnapshot(json);
152 }
153
154
155 // ------------
156 // HeapSnapshot
157 //
158 // This creates a lot of objects that make it easy to walk the entire node graph
159 // (incoming and outgoing edges). However when a test creates multiple snapshots
160 // with snapshots in scope this can quickly explode into a snapshot with a massive
161 // number of nodes/edges. For such cases create CheapHeapSnapshots, which create
162 // a very small number of objects per Heap Snapshot.
163
164 HeapSnapshotNode = class HeapSnapshotNode
165 {
166     constructor(id, className, size, internal)
167     {
168         this.id = id;
169         this.className = className;
170         this.size = size; 
171         this.internal = internal;
172         this.incomingEdges = [];
173         this.outgoingEdges = [];
174     }
175 }
176
177 HeapSnapshotEdge = class HeapSnapshotEdge
178 {
179     constructor(from, to, type, data)
180     {
181         this.from = from;
182         this.to = to;
183         this.type = type;
184         this.data = data;
185     }
186 }
187
188 HeapSnapshot = class HeapSnapshot
189 {
190     constructor(json)
191     {        
192         let {version, nodes, nodeClassNames, edges, edgeTypes} = json;
193
194         this.nodeMap = new Map;
195
196         this.nodes = nodes.map((nodePayload) => {
197             let [id, size, classNameIndex, internal] = nodePayload;
198             let node = new HeapSnapshotNode(id, nodeClassNames[classNameIndex], size, internal);
199             this.nodeMap.set(id, node);
200             return node;
201         });
202
203         edges.map((edgePayload) => {
204             let [fromIdentifier, toIdentifier, edgeTypeIndex, data] = edgePayload;
205             let from = this.nodeMap.get(fromIdentifier);
206             let to = this.nodeMap.get(toIdentifier);
207             assert(from, "Missing node for `from` part of edge");
208             assert(to, "Missing node for `to` part of edge");
209             let edge = new HeapSnapshotEdge(from, to, edgeTypes[edgeTypeIndex], data);
210             from.outgoingEdges.push(edge);
211             to.incomingEdges.push(edge);
212         });
213
214         this.rootNode = this.nodeMap.get(0);
215         assert(this.rootNode, "Missing <root> node with identifier 0");
216         assert(this.rootNode.outgoingEdges.length > 0, "<root> should have children");
217         assert(this.rootNode.incomingEdges.length === 0, "<root> should not have back references");
218     }
219
220     nodesWithClassName(className)
221     {
222         let result = [];
223         for (let node of this.nodes) {
224             if (node.className === className)
225                 result.push(node);
226         }
227         return result;
228     }
229 }
230
231 function createHeapSnapshot() {
232     let json = generateHeapSnapshot();
233
234     let {version, nodes, nodeClassNames, edges, edgeTypes} = json;
235     assert(version === 1, "Heap Snapshot payload should be version 1");
236     assert(nodes.length, "Heap Snapshot should have nodes");
237     assert(nodeClassNames.length, "Heap Snapshot should have nodeClassNames");
238     assert(edges.length, "Heap Snapshot should have edges");
239     assert(edgeTypes.length, "Heap Snapshot should have edgeTypes");
240
241     return new HeapSnapshot(json);
242 }
243
244 function followPath(node, path) {
245     let current = node;
246     for (let component of path) {
247         let edges = null;
248         if (component.edge)
249             edges = current.outgoingEdges.filter((e) => e.data === component.edge);
250         else if (component.node)
251             edges = current.outgoingEdges.filter((e) => e.to.className === component.node);
252         assert(edges.length === 1, "Ambiguous or bad path component: " + JSON.stringify(component));
253         current = edges[0].to;
254     }
255     return current;
256 }