Heap Snapshot should include different Edge types and data (Property, Index, Variable)
[WebKit-https.git] / Source / JavaScriptCore / heap / HeapSnapshotBuilder.cpp
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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "HeapSnapshotBuilder.h"
28
29 #include "DeferGC.h"
30 #include "Heap.h"
31 #include "HeapProfiler.h"
32 #include "HeapSnapshot.h"
33 #include "JSCInlines.h"
34 #include "JSCell.h"
35 #include "VM.h"
36 #include <wtf/text/StringBuilder.h>
37
38 namespace JSC {
39     
40 unsigned HeapSnapshotBuilder::nextAvailableObjectIdentifier = 1;
41 unsigned HeapSnapshotBuilder::getNextObjectIdentifier() { return nextAvailableObjectIdentifier++; }
42
43 HeapSnapshotBuilder::HeapSnapshotBuilder(HeapProfiler& profiler)
44     : m_profiler(profiler)
45 {
46 }
47
48 HeapSnapshotBuilder::~HeapSnapshotBuilder()
49 {
50 }
51
52 void HeapSnapshotBuilder::buildSnapshot()
53 {
54     m_snapshot = std::make_unique<HeapSnapshot>(m_profiler.mostRecentSnapshot());
55     {
56         m_profiler.setActiveSnapshotBuilder(this);
57         m_profiler.vm().heap.collectAllGarbage();
58         m_profiler.setActiveSnapshotBuilder(nullptr);
59     }
60     m_snapshot->finalize();
61
62     m_profiler.appendSnapshot(WTFMove(m_snapshot));
63 }
64
65 void HeapSnapshotBuilder::appendNode(JSCell* cell)
66 {
67     ASSERT(m_profiler.activeSnapshotBuilder() == this);
68     ASSERT(Heap::isMarked(cell));
69
70     if (hasExistingNodeForCell(cell))
71         return;
72
73     std::lock_guard<Lock> lock(m_buildingNodeMutex);
74
75     m_snapshot->appendNode(HeapSnapshotNode(cell, getNextObjectIdentifier()));
76 }
77
78 void HeapSnapshotBuilder::appendEdge(JSCell* from, JSCell* to)
79 {
80     ASSERT(m_profiler.activeSnapshotBuilder() == this);
81     ASSERT(to);
82
83     // Avoid trivial edges.
84     if (from == to)
85         return;
86
87     std::lock_guard<Lock> lock(m_buildingEdgeMutex);
88
89     m_edges.append(HeapSnapshotEdge(from, to));
90 }
91
92 void HeapSnapshotBuilder::appendPropertyNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* propertyName)
93 {
94     ASSERT(m_profiler.activeSnapshotBuilder() == this);
95     ASSERT(to);
96
97     std::lock_guard<Lock> lock(m_buildingEdgeMutex);
98
99     m_edges.append(HeapSnapshotEdge(from, to, EdgeType::Property, propertyName));
100 }
101
102 void HeapSnapshotBuilder::appendVariableNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* variableName)
103 {
104     ASSERT(m_profiler.activeSnapshotBuilder() == this);
105     ASSERT(to);
106
107     std::lock_guard<Lock> lock(m_buildingEdgeMutex);
108
109     m_edges.append(HeapSnapshotEdge(from, to, EdgeType::Variable, variableName));
110 }
111
112 void HeapSnapshotBuilder::appendIndexEdge(JSCell* from, JSCell* to, uint32_t index)
113 {
114     ASSERT(m_profiler.activeSnapshotBuilder() == this);
115     ASSERT(to);
116
117     std::lock_guard<Lock> lock(m_buildingEdgeMutex);
118
119     m_edges.append(HeapSnapshotEdge(from, to, index));
120 }
121
122 bool HeapSnapshotBuilder::hasExistingNodeForCell(JSCell* cell)
123 {
124     if (!m_snapshot->previous())
125         return false;
126
127     return !!m_snapshot->previous()->nodeForCell(cell);
128 }
129
130
131 // Heap Snapshot JSON Format:
132 //
133 //   {
134 //      "version": 1.0,
135 //      "nodes": [
136 //          [<nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <optionalInternal>], ...
137 //      ],
138 //      "nodeClassNames": [
139 //          "string", "Structure", "Object", ...
140 //      ],
141 //      "edges": [
142 //          [<fromNodeId>, <toNodeId>, <edgeTypeIndex>, <optionalEdgeExtraData>], ...
143 //      ],
144 //      "edgeTypes": [
145 //          "Internal", "Property", "Index", "Variable"
146 //      ]
147 //   }
148 //
149 // FIXME: Possible compaction improvements:
150 //   - eliminate inner array groups and just have a single list with fixed group sizes (meta data section).
151 //   - eliminate duplicate edge extra data strings, have an index into a de-duplicated like edgeTypes / nodeClassNames.
152
153 static uint8_t edgeTypeToNumber(EdgeType type)
154 {
155     return static_cast<uint8_t>(type);
156 }
157
158 static const char* edgeTypeToString(EdgeType type)
159 {
160     switch (type) {
161     case EdgeType::Internal:
162         return "Internal";
163     case EdgeType::Property:
164         return "Property";
165     case EdgeType::Index:
166         return "Index";
167     case EdgeType::Variable:
168         return "Variable";
169     }
170     ASSERT_NOT_REACHED();
171     return "Internal";
172 }
173
174 String HeapSnapshotBuilder::json()
175 {
176     return json([] (const HeapSnapshotNode&) { return true; });
177 }
178
179 String HeapSnapshotBuilder::json(std::function<bool (const HeapSnapshotNode&)> allowNodeCallback)
180 {
181     VM& vm = m_profiler.vm();
182     DeferGCForAWhile deferGC(vm.heap);
183
184     // Build a node to identifier map of allowed nodes to use when serializing edges.
185     HashMap<JSCell*, unsigned> allowedNodeIdentifiers;
186
187     // Build a list of used class names.
188     HashMap<const char*, unsigned> classNameIndexes;
189     unsigned nextClassNameIndex = 0;
190
191     StringBuilder json;
192
193     auto appendNodeJSON = [&] (const HeapSnapshotNode& node) {
194         // Let the client decide if they want to allow or disallow certain nodes.
195         if (!allowNodeCallback(node))
196             return;
197
198         allowedNodeIdentifiers.set(node.cell, node.identifier);
199
200         auto result = classNameIndexes.add(node.cell->classInfo()->className, nextClassNameIndex);
201         if (result.isNewEntry)
202             nextClassNameIndex++;
203         unsigned classNameIndex = result.iterator->value;
204
205         bool isInternal = false;
206         if (!node.cell->isString()) {
207             Structure* structure = node.cell->structure(vm);
208             isInternal = !structure || !structure->globalObject();
209         }
210
211         // [<nodeId>, <sizeInBytes>, <className>, <optionalInternalBoolean>]
212         json.append(',');
213         json.append('[');
214         json.appendNumber(node.identifier);
215         json.append(',');
216         json.appendNumber(node.cell->estimatedSizeInBytes());
217         json.append(',');
218         json.appendNumber(classNameIndex);
219         if (isInternal)
220             json.appendLiteral(",1");
221         json.append(']');
222     };
223
224     bool firstEdge = true;
225     auto appendEdgeJSON = [&] (const HeapSnapshotEdge& edge) {
226         // If the from cell is null, this means a root edge.
227         unsigned fromIdentifier = 0;
228         if (edge.from) {
229             auto fromLookup = allowedNodeIdentifiers.find(edge.from);
230             if (fromLookup == allowedNodeIdentifiers.end())
231                 return;
232             fromIdentifier = fromLookup->value;
233         }
234
235         unsigned toIdentifier = 0;
236         if (edge.to) {
237             auto toLookup = allowedNodeIdentifiers.find(edge.to);
238             if (toLookup == allowedNodeIdentifiers.end())
239                 return;
240             toIdentifier = toLookup->value;
241         }
242
243         if (!firstEdge)
244             json.append(',');
245         firstEdge = false;
246
247         // [<fromNodeId>, <toNodeId>, <edgeTypeIndex>, <optionalEdgeExtraData>],
248         json.append('[');
249         json.appendNumber(fromIdentifier);
250         json.append(',');
251         json.appendNumber(toIdentifier);
252         json.append(',');
253         json.appendNumber(edgeTypeToNumber(edge.type));
254         switch (edge.type) {
255         case EdgeType::Property:
256         case EdgeType::Variable:
257             json.append(',');
258             json.appendQuotedJSONString(edge.u.name);
259             break;
260         case EdgeType::Index:
261             json.append(',');
262             json.appendNumber(edge.u.index);
263             break;
264         default:
265             // No data for this edge type.
266             break;
267         }
268         json.append(']');
269     };
270
271     json.append('{');
272
273     // version
274     json.appendLiteral("\"version\":1");
275
276     // nodes
277     json.append(',');
278     json.appendLiteral("\"nodes\":");
279     json.append('[');
280     json.appendLiteral("[0,0,\"<root>\"]");
281     for (HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot(); snapshot; snapshot = snapshot->previous()) {
282         for (auto& node : snapshot->m_nodes)
283             appendNodeJSON(node);
284     }
285     json.append(']');
286
287     // node class names
288     json.append(',');
289     json.appendLiteral("\"nodeClassNames\":");
290     json.append('[');
291     Vector<const char *> orderedClassNames(classNameIndexes.size());
292     for (auto& entry : classNameIndexes)
293         orderedClassNames[entry.value] = entry.key;
294     bool firstClassName = true;
295     for (auto& className : orderedClassNames) {
296         if (!firstClassName)
297             json.append(',');
298         firstClassName = false;
299         json.appendQuotedJSONString(className);
300     }
301     json.append(']');
302
303     // edges
304     json.append(',');
305     json.appendLiteral("\"edges\":");
306     json.append('[');
307     for (auto& edge : m_edges)
308         appendEdgeJSON(edge);
309     json.append(']');
310
311     // edge types
312     json.append(',');
313     json.appendLiteral("\"edgeTypes\":");
314     json.append('[');
315     json.appendQuotedJSONString(edgeTypeToString(EdgeType::Internal));
316     json.append(',');
317     json.appendQuotedJSONString(edgeTypeToString(EdgeType::Property));
318     json.append(',');
319     json.appendQuotedJSONString(edgeTypeToString(EdgeType::Index));
320     json.append(',');
321     json.appendQuotedJSONString(edgeTypeToString(EdgeType::Variable));
322     json.append(']');
323
324     json.append('}');
325     return json.toString();
326 }
327
328 } // namespace JSC