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