1 | /* |
2 | * Copyright (C) 2016-2019 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 "JSCast.h" |
35 | #include "PreventCollectionScope.h" |
36 | #include "VM.h" |
37 | #include <wtf/HexNumber.h> |
38 | #include <wtf/text/StringBuilder.h> |
39 | |
40 | namespace JSC { |
41 | |
42 | static const char* rootTypeToString(SlotVisitor::RootMarkReason); |
43 | |
44 | NodeIdentifier HeapSnapshotBuilder::nextAvailableObjectIdentifier = 1; |
45 | NodeIdentifier HeapSnapshotBuilder::getNextObjectIdentifier() { return nextAvailableObjectIdentifier++; } |
46 | void HeapSnapshotBuilder::resetNextAvailableObjectIdentifier() { HeapSnapshotBuilder::nextAvailableObjectIdentifier = 1; } |
47 | |
48 | HeapSnapshotBuilder::HeapSnapshotBuilder(HeapProfiler& profiler, SnapshotType type) |
49 | : HeapAnalyzer() |
50 | , m_profiler(profiler) |
51 | , m_snapshotType(type) |
52 | { |
53 | } |
54 | |
55 | HeapSnapshotBuilder::~HeapSnapshotBuilder() |
56 | { |
57 | if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) |
58 | m_profiler.clearSnapshots(); |
59 | } |
60 | |
61 | void HeapSnapshotBuilder::buildSnapshot() |
62 | { |
63 | // GCDebuggingSnapshot are always full snapshots, so clear any existing snapshots. |
64 | if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) |
65 | m_profiler.clearSnapshots(); |
66 | |
67 | PreventCollectionScope preventCollectionScope(m_profiler.vm().heap); |
68 | |
69 | m_snapshot = makeUnique<HeapSnapshot>(m_profiler.mostRecentSnapshot()); |
70 | { |
71 | ASSERT(!m_profiler.activeHeapAnalyzer()); |
72 | m_profiler.setActiveHeapAnalyzer(this); |
73 | m_profiler.vm().heap.collectNow(Sync, CollectionScope::Full); |
74 | m_profiler.setActiveHeapAnalyzer(nullptr); |
75 | } |
76 | m_snapshot->finalize(); |
77 | |
78 | m_profiler.appendSnapshot(WTFMove(m_snapshot)); |
79 | } |
80 | |
81 | void HeapSnapshotBuilder::analyzeNode(JSCell* cell) |
82 | { |
83 | ASSERT(m_profiler.activeHeapAnalyzer() == this); |
84 | |
85 | ASSERT(m_profiler.vm().heap.isMarked(cell)); |
86 | |
87 | NodeIdentifier identifier; |
88 | if (previousSnapshotHasNodeForCell(cell, identifier)) |
89 | return; |
90 | |
91 | std::lock_guard<Lock> lock(m_buildingNodeMutex); |
92 | m_snapshot->appendNode(HeapSnapshotNode(cell, getNextObjectIdentifier())); |
93 | } |
94 | |
95 | void HeapSnapshotBuilder::analyzeEdge(JSCell* from, JSCell* to, SlotVisitor::RootMarkReason rootMarkReason) |
96 | { |
97 | ASSERT(m_profiler.activeHeapAnalyzer() == this); |
98 | ASSERT(to); |
99 | |
100 | // Avoid trivial edges. |
101 | if (from == to) |
102 | return; |
103 | |
104 | std::lock_guard<Lock> lock(m_buildingEdgeMutex); |
105 | |
106 | if (m_snapshotType == SnapshotType::GCDebuggingSnapshot && !from) { |
107 | if (rootMarkReason == SlotVisitor::RootMarkReason::None && m_snapshotType == SnapshotType::GCDebuggingSnapshot) |
108 | WTFLogAlways("Cell %p is a root but no root marking reason was supplied" , to); |
109 | |
110 | m_rootData.ensure(to, [] () -> RootData { |
111 | return { }; |
112 | }).iterator->value.markReason = rootMarkReason; |
113 | } |
114 | |
115 | m_edges.append(HeapSnapshotEdge(from, to)); |
116 | } |
117 | |
118 | void HeapSnapshotBuilder::analyzePropertyNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* propertyName) |
119 | { |
120 | ASSERT(m_profiler.activeHeapAnalyzer() == this); |
121 | ASSERT(to); |
122 | |
123 | std::lock_guard<Lock> lock(m_buildingEdgeMutex); |
124 | |
125 | m_edges.append(HeapSnapshotEdge(from, to, EdgeType::Property, propertyName)); |
126 | } |
127 | |
128 | void HeapSnapshotBuilder::analyzeVariableNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* variableName) |
129 | { |
130 | ASSERT(m_profiler.activeHeapAnalyzer() == this); |
131 | ASSERT(to); |
132 | |
133 | std::lock_guard<Lock> lock(m_buildingEdgeMutex); |
134 | |
135 | m_edges.append(HeapSnapshotEdge(from, to, EdgeType::Variable, variableName)); |
136 | } |
137 | |
138 | void HeapSnapshotBuilder::analyzeIndexEdge(JSCell* from, JSCell* to, uint32_t index) |
139 | { |
140 | ASSERT(m_profiler.activeHeapAnalyzer() == this); |
141 | ASSERT(to); |
142 | |
143 | std::lock_guard<Lock> lock(m_buildingEdgeMutex); |
144 | |
145 | m_edges.append(HeapSnapshotEdge(from, to, index)); |
146 | } |
147 | |
148 | void HeapSnapshotBuilder::setOpaqueRootReachabilityReasonForCell(JSCell* cell, const char* reason) |
149 | { |
150 | if (!reason || !*reason || m_snapshotType != SnapshotType::GCDebuggingSnapshot) |
151 | return; |
152 | |
153 | std::lock_guard<Lock> lock(m_buildingEdgeMutex); |
154 | |
155 | m_rootData.ensure(cell, [] () -> RootData { |
156 | return { }; |
157 | }).iterator->value.reachabilityFromOpaqueRootReasons = reason; |
158 | } |
159 | |
160 | void HeapSnapshotBuilder::setWrappedObjectForCell(JSCell* cell, void* wrappedPtr) |
161 | { |
162 | m_wrappedObjectPointers.set(cell, wrappedPtr); |
163 | } |
164 | |
165 | bool HeapSnapshotBuilder::previousSnapshotHasNodeForCell(JSCell* cell, NodeIdentifier& identifier) |
166 | { |
167 | if (!m_snapshot->previous()) |
168 | return false; |
169 | |
170 | auto existingNode = m_snapshot->previous()->nodeForCell(cell); |
171 | if (existingNode) { |
172 | identifier = existingNode.value().identifier; |
173 | return true; |
174 | } |
175 | |
176 | return false; |
177 | } |
178 | |
179 | // Heap Snapshot JSON Format: |
180 | // |
181 | // Inspector snapshots: |
182 | // |
183 | // { |
184 | // "version": 2, |
185 | // "type": "Inspector", |
186 | // // [<address>, <labelIndex>, <wrappedAddress>] only present in GCDebuggingSnapshot-type snapshots |
187 | // "nodes": [ |
188 | // <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags> |
189 | // <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags> |
190 | // ... |
191 | // ], |
192 | // "nodeClassNames": [ |
193 | // "string", "Structure", "Object", ... |
194 | // ], |
195 | // "edges": [ |
196 | // <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData>, |
197 | // <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData>, |
198 | // ... |
199 | // ], |
200 | // "edgeTypes": [ |
201 | // "Internal", "Property", "Index", "Variable" |
202 | // ], |
203 | // "edgeNames": [ |
204 | // "propertyName", "variableName", ... |
205 | // ] |
206 | // } |
207 | // |
208 | // GC heap debugger snapshots: |
209 | // |
210 | // { |
211 | // "version": 2, |
212 | // "type": "GCDebugging", |
213 | // "nodes": [ |
214 | // <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags>, <labelIndex>, <cellEddress>, <wrappedAddress>, |
215 | // <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags>, <labelIndex>, <cellEddress>, <wrappedAddress>, |
216 | // ... |
217 | // ], |
218 | // "nodeClassNames": [ |
219 | // "string", "Structure", "Object", ... |
220 | // ], |
221 | // "edges": [ |
222 | // <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData>, |
223 | // <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData>, |
224 | // ... |
225 | // ], |
226 | // "edgeTypes": [ |
227 | // "Internal", "Property", "Index", "Variable" |
228 | // ], |
229 | // "edgeNames": [ |
230 | // "propertyName", "variableName", ... |
231 | // ], |
232 | // "roots" : [ |
233 | // <nodeId>, <rootReasonIndex>, <reachabilityReasonIndex>, |
234 | // <nodeId>, <rootReasonIndex>, <reachabilityReasonIndex>, |
235 | // ... // <nodeId> may be repeated |
236 | // ], |
237 | // "labels" : [ |
238 | // "foo", "bar", ... |
239 | // ] |
240 | // } |
241 | // |
242 | // Notes: |
243 | // |
244 | // <nodeClassNameIndex> |
245 | // - index into the "nodeClassNames" list. |
246 | // |
247 | // <flags> |
248 | // - 0b0000 - no flags |
249 | // - 0b0001 - internal instance |
250 | // - 0b0010 - Object subclassification |
251 | // |
252 | // <edgeTypeIndex> |
253 | // - index into the "edgeTypes" list. |
254 | // |
255 | // <edgeExtraData> |
256 | // - for Internal edges this should be ignored (0). |
257 | // - for Index edges this is the index value. |
258 | // - for Property or Variable edges this is an index into the "edgeNames" list. |
259 | // |
260 | // <rootReasonIndex> |
261 | // - index into the "labels" list. |
262 | |
263 | enum class NodeFlags { |
264 | Internal = 1 << 0, |
265 | ObjectSubtype = 1 << 1, |
266 | }; |
267 | |
268 | static uint8_t edgeTypeToNumber(EdgeType type) |
269 | { |
270 | return static_cast<uint8_t>(type); |
271 | } |
272 | |
273 | static const char* edgeTypeToString(EdgeType type) |
274 | { |
275 | switch (type) { |
276 | case EdgeType::Internal: |
277 | return "Internal" ; |
278 | case EdgeType::Property: |
279 | return "Property" ; |
280 | case EdgeType::Index: |
281 | return "Index" ; |
282 | case EdgeType::Variable: |
283 | return "Variable" ; |
284 | } |
285 | ASSERT_NOT_REACHED(); |
286 | return "Internal" ; |
287 | } |
288 | |
289 | static const char* snapshotTypeToString(HeapSnapshotBuilder::SnapshotType type) |
290 | { |
291 | switch (type) { |
292 | case HeapSnapshotBuilder::SnapshotType::InspectorSnapshot: |
293 | return "Inspector" ; |
294 | case HeapSnapshotBuilder::SnapshotType::GCDebuggingSnapshot: |
295 | return "GCDebugging" ; |
296 | } |
297 | ASSERT_NOT_REACHED(); |
298 | return "Inspector" ; |
299 | } |
300 | |
301 | static const char* rootTypeToString(SlotVisitor::RootMarkReason type) |
302 | { |
303 | switch (type) { |
304 | case SlotVisitor::RootMarkReason::None: |
305 | return "None" ; |
306 | case SlotVisitor::RootMarkReason::ConservativeScan: |
307 | return "Conservative scan" ; |
308 | case SlotVisitor::RootMarkReason::StrongReferences: |
309 | return "Strong references" ; |
310 | case SlotVisitor::RootMarkReason::ProtectedValues: |
311 | return "Protected values" ; |
312 | case SlotVisitor::RootMarkReason::MarkListSet: |
313 | return "Mark list set" ; |
314 | case SlotVisitor::RootMarkReason::VMExceptions: |
315 | return "VM exceptions" ; |
316 | case SlotVisitor::RootMarkReason::StrongHandles: |
317 | return "Strong handles" ; |
318 | case SlotVisitor::RootMarkReason::Debugger: |
319 | return "Debugger" ; |
320 | case SlotVisitor::RootMarkReason::JITStubRoutines: |
321 | return "JIT stub routines" ; |
322 | case SlotVisitor::RootMarkReason::WeakSets: |
323 | return "Weak sets" ; |
324 | case SlotVisitor::RootMarkReason::Output: |
325 | return "Output" ; |
326 | case SlotVisitor::RootMarkReason::DFGWorkLists: |
327 | return "DFG work lists" ; |
328 | case SlotVisitor::RootMarkReason::CodeBlocks: |
329 | return "Code blocks" ; |
330 | case SlotVisitor::RootMarkReason::DOMGCOutput: |
331 | return "DOM GC output" ; |
332 | } |
333 | ASSERT_NOT_REACHED(); |
334 | return "None" ; |
335 | } |
336 | |
337 | String HeapSnapshotBuilder::json() |
338 | { |
339 | return json([] (const HeapSnapshotNode&) { return true; }); |
340 | } |
341 | |
342 | void HeapSnapshotBuilder::setLabelForCell(JSCell* cell, const String& label) |
343 | { |
344 | m_cellLabels.set(cell, label); |
345 | } |
346 | |
347 | String HeapSnapshotBuilder::descriptionForCell(JSCell *cell) const |
348 | { |
349 | if (cell->isString()) |
350 | return emptyString(); // FIXME: get part of string. |
351 | |
352 | VM& vm = m_profiler.vm(); |
353 | Structure* structure = cell->structure(vm); |
354 | |
355 | if (structure->classInfo()->isSubClassOf(Structure::info())) { |
356 | Structure* cellAsStructure = jsCast<Structure*>(cell); |
357 | return cellAsStructure->classInfo()->className; |
358 | } |
359 | |
360 | return emptyString(); |
361 | } |
362 | |
363 | String HeapSnapshotBuilder::json(Function<bool (const HeapSnapshotNode&)> allowNodeCallback) |
364 | { |
365 | VM& vm = m_profiler.vm(); |
366 | DeferGCForAWhile deferGC(vm.heap); |
367 | |
368 | // Build a node to identifier map of allowed nodes to use when serializing edges. |
369 | HashMap<JSCell*, NodeIdentifier> allowedNodeIdentifiers; |
370 | |
371 | // Build a list of used class names. |
372 | HashMap<String, unsigned> classNameIndexes; |
373 | classNameIndexes.set("<root>"_s , 0); |
374 | unsigned nextClassNameIndex = 1; |
375 | |
376 | // Build a list of labels (this is just a string table). |
377 | HashMap<String, unsigned> labelIndexes; |
378 | labelIndexes.set(emptyString(), 0); |
379 | unsigned nextLabelIndex = 1; |
380 | |
381 | // Build a list of used edge names. |
382 | HashMap<UniquedStringImpl*, unsigned> edgeNameIndexes; |
383 | unsigned nextEdgeNameIndex = 0; |
384 | |
385 | StringBuilder json; |
386 | |
387 | auto appendNodeJSON = [&] (const HeapSnapshotNode& node) { |
388 | // Let the client decide if they want to allow or disallow certain nodes. |
389 | if (!allowNodeCallback(node)) |
390 | return; |
391 | |
392 | unsigned flags = 0; |
393 | |
394 | allowedNodeIdentifiers.set(node.cell, node.identifier); |
395 | |
396 | String className = node.cell->classInfo(vm)->className; |
397 | if (node.cell->isObject() && className == JSObject::info()->className) { |
398 | flags |= static_cast<unsigned>(NodeFlags::ObjectSubtype); |
399 | |
400 | // Skip calculating a class name if this object has a `constructor` own property. |
401 | // These cases are typically F.prototype objects and we want to treat these as |
402 | // "Object" in snapshots and not get the name of the prototype's parent. |
403 | JSObject* object = asObject(node.cell); |
404 | if (JSGlobalObject* globalObject = object->globalObject(vm)) { |
405 | PropertySlot slot(object, PropertySlot::InternalMethodType::VMInquiry); |
406 | if (!object->getOwnPropertySlot(object, globalObject, vm.propertyNames->constructor, slot)) |
407 | className = JSObject::calculatedClassName(object); |
408 | } |
409 | } |
410 | |
411 | auto result = classNameIndexes.add(className, nextClassNameIndex); |
412 | if (result.isNewEntry) |
413 | nextClassNameIndex++; |
414 | unsigned classNameIndex = result.iterator->value; |
415 | |
416 | void* wrappedAddress = 0; |
417 | unsigned labelIndex = 0; |
418 | if (!node.cell->isString() && !node.cell->isBigInt()) { |
419 | Structure* structure = node.cell->structure(vm); |
420 | if (!structure || !structure->globalObject()) |
421 | flags |= static_cast<unsigned>(NodeFlags::Internal); |
422 | |
423 | if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { |
424 | String nodeLabel; |
425 | auto it = m_cellLabels.find(node.cell); |
426 | if (it != m_cellLabels.end()) |
427 | nodeLabel = it->value; |
428 | |
429 | if (nodeLabel.isEmpty()) { |
430 | if (auto* object = jsDynamicCast<JSObject*>(vm, node.cell)) { |
431 | if (auto* function = jsDynamicCast<JSFunction*>(vm, object)) |
432 | nodeLabel = function->calculatedDisplayName(vm); |
433 | } |
434 | } |
435 | |
436 | String description = descriptionForCell(node.cell); |
437 | if (description.length()) { |
438 | if (nodeLabel.length()) |
439 | nodeLabel.append(' '); |
440 | nodeLabel.append(description); |
441 | } |
442 | |
443 | if (!nodeLabel.isEmpty() && m_snapshotType == SnapshotType::GCDebuggingSnapshot) { |
444 | auto result = labelIndexes.add(nodeLabel, nextLabelIndex); |
445 | if (result.isNewEntry) |
446 | nextLabelIndex++; |
447 | labelIndex = result.iterator->value; |
448 | } |
449 | |
450 | wrappedAddress = m_wrappedObjectPointers.get(node.cell); |
451 | } |
452 | } |
453 | |
454 | // <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags>, [<labelIndex>, <cellEddress>, <wrappedAddress>] |
455 | json.append(','); |
456 | json.appendNumber(node.identifier); |
457 | json.append(','); |
458 | json.appendNumber(node.cell->estimatedSizeInBytes(vm)); |
459 | json.append(','); |
460 | json.appendNumber(classNameIndex); |
461 | json.append(','); |
462 | json.appendNumber(flags); |
463 | if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { |
464 | json.append(','); |
465 | json.appendNumber(labelIndex); |
466 | json.appendLiteral(",\"0x" ); |
467 | appendUnsignedAsHex(reinterpret_cast<uintptr_t>(node.cell), json, Lowercase); |
468 | json.appendLiteral("\",\"0x" ); |
469 | appendUnsignedAsHex(reinterpret_cast<uintptr_t>(wrappedAddress), json, Lowercase); |
470 | json.append('"'); |
471 | } |
472 | }; |
473 | |
474 | bool firstEdge = true; |
475 | auto appendEdgeJSON = [&] (const HeapSnapshotEdge& edge) { |
476 | if (!firstEdge) |
477 | json.append(','); |
478 | firstEdge = false; |
479 | |
480 | // <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData> |
481 | json.appendNumber(edge.from.identifier); |
482 | json.append(','); |
483 | json.appendNumber(edge.to.identifier); |
484 | json.append(','); |
485 | json.appendNumber(edgeTypeToNumber(edge.type)); |
486 | json.append(','); |
487 | switch (edge.type) { |
488 | case EdgeType::Property: |
489 | case EdgeType::Variable: { |
490 | auto result = edgeNameIndexes.add(edge.u.name, nextEdgeNameIndex); |
491 | if (result.isNewEntry) |
492 | nextEdgeNameIndex++; |
493 | unsigned edgeNameIndex = result.iterator->value; |
494 | json.appendNumber(edgeNameIndex); |
495 | break; |
496 | } |
497 | case EdgeType::Index: |
498 | json.appendNumber(edge.u.index); |
499 | break; |
500 | default: |
501 | // No data for this edge type. |
502 | json.append('0'); |
503 | break; |
504 | } |
505 | }; |
506 | |
507 | json.append('{'); |
508 | |
509 | // version |
510 | json.appendLiteral("\"version\":2" ); |
511 | |
512 | // type |
513 | json.append(','); |
514 | json.appendLiteral("\"type\":" ); |
515 | json.appendQuotedJSONString(snapshotTypeToString(m_snapshotType)); |
516 | |
517 | // nodes |
518 | json.append(','); |
519 | json.appendLiteral("\"nodes\":" ); |
520 | json.append('['); |
521 | // <root> |
522 | if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) |
523 | json.appendLiteral("0,0,0,0,0,\"0x0\",\"0x0\"" ); |
524 | else |
525 | json.appendLiteral("0,0,0,0" ); |
526 | |
527 | for (HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot(); snapshot; snapshot = snapshot->previous()) { |
528 | for (auto& node : snapshot->m_nodes) |
529 | appendNodeJSON(node); |
530 | } |
531 | json.append(']'); |
532 | |
533 | // node class names |
534 | json.append(','); |
535 | json.appendLiteral("\"nodeClassNames\":" ); |
536 | json.append('['); |
537 | Vector<String> orderedClassNames(classNameIndexes.size()); |
538 | for (auto& entry : classNameIndexes) |
539 | orderedClassNames[entry.value] = entry.key; |
540 | classNameIndexes.clear(); |
541 | bool firstClassName = true; |
542 | for (auto& className : orderedClassNames) { |
543 | if (!firstClassName) |
544 | json.append(','); |
545 | firstClassName = false; |
546 | json.appendQuotedJSONString(className); |
547 | } |
548 | orderedClassNames.clear(); |
549 | json.append(']'); |
550 | |
551 | // Process edges. |
552 | // Replace pointers with identifiers. |
553 | // Remove any edges that we won't need. |
554 | m_edges.removeAllMatching([&] (HeapSnapshotEdge& edge) { |
555 | // If the from cell is null, this means a <root> edge. |
556 | if (!edge.from.cell) |
557 | edge.from.identifier = 0; |
558 | else { |
559 | auto fromLookup = allowedNodeIdentifiers.find(edge.from.cell); |
560 | if (fromLookup == allowedNodeIdentifiers.end()) { |
561 | if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) |
562 | WTFLogAlways("Failed to find node for from-edge cell %p" , edge.from.cell); |
563 | return true; |
564 | } |
565 | edge.from.identifier = fromLookup->value; |
566 | } |
567 | |
568 | if (!edge.to.cell) |
569 | edge.to.identifier = 0; |
570 | else { |
571 | auto toLookup = allowedNodeIdentifiers.find(edge.to.cell); |
572 | if (toLookup == allowedNodeIdentifiers.end()) { |
573 | if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) |
574 | WTFLogAlways("Failed to find node for to-edge cell %p" , edge.to.cell); |
575 | return true; |
576 | } |
577 | edge.to.identifier = toLookup->value; |
578 | } |
579 | |
580 | return false; |
581 | }); |
582 | |
583 | allowedNodeIdentifiers.clear(); |
584 | m_edges.shrinkToFit(); |
585 | |
586 | // Sort edges based on from identifier. |
587 | std::sort(m_edges.begin(), m_edges.end(), [&] (const HeapSnapshotEdge& a, const HeapSnapshotEdge& b) { |
588 | return a.from.identifier < b.from.identifier; |
589 | }); |
590 | |
591 | // edges |
592 | json.append(','); |
593 | json.appendLiteral("\"edges\":" ); |
594 | json.append('['); |
595 | for (auto& edge : m_edges) |
596 | appendEdgeJSON(edge); |
597 | json.append(']'); |
598 | |
599 | // edge types |
600 | json.append(','); |
601 | json.appendLiteral("\"edgeTypes\":" ); |
602 | json.append('['); |
603 | json.appendQuotedJSONString(edgeTypeToString(EdgeType::Internal)); |
604 | json.append(','); |
605 | json.appendQuotedJSONString(edgeTypeToString(EdgeType::Property)); |
606 | json.append(','); |
607 | json.appendQuotedJSONString(edgeTypeToString(EdgeType::Index)); |
608 | json.append(','); |
609 | json.appendQuotedJSONString(edgeTypeToString(EdgeType::Variable)); |
610 | json.append(']'); |
611 | |
612 | // edge names |
613 | json.append(','); |
614 | json.appendLiteral("\"edgeNames\":" ); |
615 | json.append('['); |
616 | Vector<UniquedStringImpl*> orderedEdgeNames(edgeNameIndexes.size()); |
617 | for (auto& entry : edgeNameIndexes) |
618 | orderedEdgeNames[entry.value] = entry.key; |
619 | edgeNameIndexes.clear(); |
620 | bool firstEdgeName = true; |
621 | for (auto& edgeName : orderedEdgeNames) { |
622 | if (!firstEdgeName) |
623 | json.append(','); |
624 | firstEdgeName = false; |
625 | json.appendQuotedJSONString(edgeName); |
626 | } |
627 | orderedEdgeNames.clear(); |
628 | json.append(']'); |
629 | |
630 | if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { |
631 | json.append(','); |
632 | json.appendLiteral("\"roots\":" ); |
633 | json.append('['); |
634 | |
635 | HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot(); |
636 | |
637 | bool firstNode = true; |
638 | for (auto it : m_rootData) { |
639 | auto snapshotNode = snapshot->nodeForCell(it.key); |
640 | if (!snapshotNode) { |
641 | WTFLogAlways("Failed to find snapshot node for cell %p" , it.key); |
642 | continue; |
643 | } |
644 | |
645 | if (!firstNode) |
646 | json.append(','); |
647 | |
648 | firstNode = false; |
649 | json.appendNumber(snapshotNode.value().identifier); |
650 | |
651 | // Maybe we should just always encode the root names. |
652 | const char* rootName = rootTypeToString(it.value.markReason); |
653 | auto result = labelIndexes.add(rootName, nextLabelIndex); |
654 | if (result.isNewEntry) |
655 | nextLabelIndex++; |
656 | unsigned labelIndex = result.iterator->value; |
657 | json.append(','); |
658 | json.appendNumber(labelIndex); |
659 | |
660 | unsigned reachabilityReasonIndex = 0; |
661 | if (it.value.reachabilityFromOpaqueRootReasons) { |
662 | auto result = labelIndexes.add(it.value.reachabilityFromOpaqueRootReasons, nextLabelIndex); |
663 | if (result.isNewEntry) |
664 | nextLabelIndex++; |
665 | reachabilityReasonIndex = result.iterator->value; |
666 | } |
667 | json.append(','); |
668 | json.appendNumber(reachabilityReasonIndex); |
669 | } |
670 | |
671 | json.append(']'); |
672 | } |
673 | |
674 | if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { |
675 | // internal node descriptions |
676 | json.append(','); |
677 | json.appendLiteral("\"labels\":" ); |
678 | json.append('['); |
679 | |
680 | Vector<String> orderedLabels(labelIndexes.size()); |
681 | for (auto& entry : labelIndexes) |
682 | orderedLabels[entry.value] = entry.key; |
683 | labelIndexes.clear(); |
684 | bool firstLabel = true; |
685 | for (auto& label : orderedLabels) { |
686 | if (!firstLabel) |
687 | json.append(','); |
688 | |
689 | firstLabel = false; |
690 | json.appendQuotedJSONString(label); |
691 | } |
692 | orderedLabels.clear(); |
693 | |
694 | json.append(']'); |
695 | } |
696 | |
697 | json.append('}'); |
698 | return json.toString(); |
699 | } |
700 | |
701 | } // namespace JSC |
702 | |