String HeapSnapshotBuilder::json(std::function<bool (const HeapSnapshotNode&)> allowNodeCallback) { VM& vm = m_profiler.vm(); DeferGCForAWhile deferGC(vm.heap); // Build a node to identifier map of allowed nodes to use when serializing edges. HashMap<JSCell*, unsigned> allowedNodeIdentifiers; // Build a list of used class names. HashMap<const char*, unsigned> classNameIndexes; classNameIndexes.set("<root>", 0); unsigned nextClassNameIndex = 1; // Build a list of used edge names. HashMap<UniquedStringImpl*, unsigned> edgeNameIndexes; unsigned nextEdgeNameIndex = 0; StringBuilder json; auto appendNodeJSON = [&] (const HeapSnapshotNode& node) { // Let the client decide if they want to allow or disallow certain nodes. if (!allowNodeCallback(node)) return; allowedNodeIdentifiers.set(node.cell, node.identifier); auto result = classNameIndexes.add(node.cell->classInfo()->className, nextClassNameIndex); if (result.isNewEntry) nextClassNameIndex++; unsigned classNameIndex = result.iterator->value; bool isInternal = false; if (!node.cell->isString()) { Structure* structure = node.cell->structure(vm); isInternal = !structure || !structure->globalObject(); } // <nodeId>, <sizeInBytes>, <className>, <optionalInternalBoolean> json.append(','); json.appendNumber(node.identifier); json.append(','); json.appendNumber(node.cell->estimatedSizeInBytes()); json.append(','); json.appendNumber(classNameIndex); json.append(','); json.append(isInternal ? '1' : '0'); }; bool firstEdge = true; auto appendEdgeJSON = [&] (const HeapSnapshotEdge& edge) { if (!firstEdge) json.append(','); firstEdge = false; // <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData> json.appendNumber(edge.from.identifier); json.append(','); json.appendNumber(edge.to.identifier); json.append(','); json.appendNumber(edgeTypeToNumber(edge.type)); json.append(','); switch (edge.type) { case EdgeType::Property: case EdgeType::Variable: { auto result = edgeNameIndexes.add(edge.u.name, nextEdgeNameIndex); if (result.isNewEntry) nextEdgeNameIndex++; unsigned edgeNameIndex = result.iterator->value; json.appendNumber(edgeNameIndex); break; } case EdgeType::Index: json.appendNumber(edge.u.index); break; default: // No data for this edge type. json.append('0'); break; } }; json.append('{'); // version json.appendLiteral("\"version\":1"); // nodes json.append(','); json.appendLiteral("\"nodes\":"); json.append('['); json.appendLiteral("0,0,0,0"); // <root> for (HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot(); snapshot; snapshot = snapshot->previous()) { for (auto& node : snapshot->m_nodes) appendNodeJSON(node); } json.append(']'); // node class names json.append(','); json.appendLiteral("\"nodeClassNames\":"); json.append('['); Vector<const char *> orderedClassNames(classNameIndexes.size()); for (auto& entry : classNameIndexes) orderedClassNames[entry.value] = entry.key; classNameIndexes.clear(); bool firstClassName = true; for (auto& className : orderedClassNames) { if (!firstClassName) json.append(','); firstClassName = false; json.appendQuotedJSONString(className); } orderedClassNames.clear(); json.append(']'); // Process edges. // Replace pointers with identifiers. // Remove any edges that we won't need. m_edges.removeAllMatching([&] (HeapSnapshotEdge& edge) { // If the from cell is null, this means a <root> edge. if (!edge.from.cell) edge.from.identifier = 0; else { auto fromLookup = allowedNodeIdentifiers.find(edge.from.cell); if (fromLookup == allowedNodeIdentifiers.end()) return true; edge.from.identifier = fromLookup->value; } if (!edge.to.cell) edge.to.identifier = 0; else { auto toLookup = allowedNodeIdentifiers.find(edge.to.cell); if (toLookup == allowedNodeIdentifiers.end()) return true; edge.to.identifier = toLookup->value; } return false; }); allowedNodeIdentifiers.clear(); m_edges.shrinkToFit(); // Sort edges based on from identifier. std::sort(m_edges.begin(), m_edges.end(), [&] (const HeapSnapshotEdge& a, const HeapSnapshotEdge& b) { return a.from.identifier < b.from.identifier; }); // edges json.append(','); json.appendLiteral("\"edges\":"); json.append('['); for (auto& edge : m_edges) appendEdgeJSON(edge); json.append(']'); // edge types json.append(','); json.appendLiteral("\"edgeTypes\":"); json.append('['); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Internal)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Property)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Index)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Variable)); json.append(']'); // edge names json.append(','); json.appendLiteral("\"edgeNames\":"); json.append('['); Vector<UniquedStringImpl*> orderedEdgeNames(edgeNameIndexes.size()); for (auto& entry : edgeNameIndexes) orderedEdgeNames[entry.value] = entry.key; edgeNameIndexes.clear(); bool firstEdgeName = true; for (auto& edgeName : orderedEdgeNames) { if (!firstEdgeName) json.append(','); firstEdgeName = false; json.appendQuotedJSONString(edgeName); } orderedEdgeNames.clear(); json.append(']'); json.append('}'); return json.toString(); }
String HeapSnapshotBuilder::json(Function<bool (const HeapSnapshotNode&)> allowNodeCallback) { VM& vm = m_profiler.vm(); DeferGCForAWhile deferGC(vm.heap); // Build a node to identifier map of allowed nodes to use when serializing edges. HashMap<JSCell*, NodeIdentifier> allowedNodeIdentifiers; // Build a list of used class names. HashMap<String, unsigned> classNameIndexes; classNameIndexes.set("<root>"_s, 0); unsigned nextClassNameIndex = 1; // Build a list of labels (this is just a string table). HashMap<String, unsigned> labelIndexes; labelIndexes.set(emptyString(), 0); unsigned nextLabelIndex = 1; // Build a list of used edge names. HashMap<UniquedStringImpl*, unsigned> edgeNameIndexes; unsigned nextEdgeNameIndex = 0; StringBuilder json; auto appendNodeJSON = [&] (const HeapSnapshotNode& node) { // Let the client decide if they want to allow or disallow certain nodes. if (!allowNodeCallback(node)) return; unsigned flags = 0; allowedNodeIdentifiers.set(node.cell, node.identifier); String className = node.cell->classInfo(vm)->className; if (node.cell->isObject() && className == JSObject::info()->className) { flags |= static_cast<unsigned>(NodeFlags::ObjectSubtype); // Skip calculating a class name if this object has a `constructor` own property. // These cases are typically F.prototype objects and we want to treat these as // "Object" in snapshots and not get the name of the prototype's parent. JSObject* object = asObject(node.cell); if (JSGlobalObject* globalObject = object->globalObject(vm)) { ExecState* exec = globalObject->globalExec(); PropertySlot slot(object, PropertySlot::InternalMethodType::VMInquiry); if (!object->getOwnPropertySlot(object, exec, vm.propertyNames->constructor, slot)) className = JSObject::calculatedClassName(object); } } auto result = classNameIndexes.add(className, nextClassNameIndex); if (result.isNewEntry) nextClassNameIndex++; unsigned classNameIndex = result.iterator->value; void* wrappedAddress = 0; unsigned labelIndex = 0; if (!node.cell->isString()) { Structure* structure = node.cell->structure(vm); if (!structure || !structure->globalObject()) flags |= static_cast<unsigned>(NodeFlags::Internal); if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { String nodeLabel; auto it = m_cellLabels.find(node.cell); if (it != m_cellLabels.end()) nodeLabel = it->value; if (nodeLabel.isEmpty()) { if (auto* object = jsDynamicCast<JSObject*>(vm, node.cell)) { if (auto* function = jsDynamicCast<JSFunction*>(vm, object)) nodeLabel = function->calculatedDisplayName(vm); } } String description = descriptionForCell(node.cell); if (description.length()) { if (nodeLabel.length()) nodeLabel.append(' '); nodeLabel.append(description); } if (!nodeLabel.isEmpty() && m_snapshotType == SnapshotType::GCDebuggingSnapshot) { auto result = labelIndexes.add(nodeLabel, nextLabelIndex); if (result.isNewEntry) nextLabelIndex++; labelIndex = result.iterator->value; } wrappedAddress = m_wrappedObjectPointers.get(node.cell); } } // <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags>, [<labelIndex>, <cellEddress>, <wrappedAddress>] json.append(','); json.appendNumber(node.identifier); json.append(','); json.appendNumber(node.cell->estimatedSizeInBytes(vm)); json.append(','); json.appendNumber(classNameIndex); json.append(','); json.appendNumber(flags); if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { json.append(','); json.appendNumber(labelIndex); json.appendLiteral(",\"0x"); appendUnsignedAsHex(reinterpret_cast<uintptr_t>(node.cell), json, Lowercase); json.appendLiteral("\",\"0x"); appendUnsignedAsHex(reinterpret_cast<uintptr_t>(wrappedAddress), json, Lowercase); json.append('"'); } }; bool firstEdge = true; auto appendEdgeJSON = [&] (const HeapSnapshotEdge& edge) { if (!firstEdge) json.append(','); firstEdge = false; // <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData> json.appendNumber(edge.from.identifier); json.append(','); json.appendNumber(edge.to.identifier); json.append(','); json.appendNumber(edgeTypeToNumber(edge.type)); json.append(','); switch (edge.type) { case EdgeType::Property: case EdgeType::Variable: { auto result = edgeNameIndexes.add(edge.u.name, nextEdgeNameIndex); if (result.isNewEntry) nextEdgeNameIndex++; unsigned edgeNameIndex = result.iterator->value; json.appendNumber(edgeNameIndex); break; } case EdgeType::Index: json.appendNumber(edge.u.index); break; default: // No data for this edge type. json.append('0'); break; } }; json.append('{'); // version json.appendLiteral("\"version\":2"); // type json.append(','); json.appendLiteral("\"type\":"); json.appendQuotedJSONString(snapshotTypeToString(m_snapshotType)); // nodes json.append(','); json.appendLiteral("\"nodes\":"); json.append('['); // <root> if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) json.appendLiteral("0,0,0,0,0,\"0x0\",\"0x0\""); else json.appendLiteral("0,0,0,0"); for (HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot(); snapshot; snapshot = snapshot->previous()) { for (auto& node : snapshot->m_nodes) appendNodeJSON(node); } json.append(']'); // node class names json.append(','); json.appendLiteral("\"nodeClassNames\":"); json.append('['); Vector<String> orderedClassNames(classNameIndexes.size()); for (auto& entry : classNameIndexes) orderedClassNames[entry.value] = entry.key; classNameIndexes.clear(); bool firstClassName = true; for (auto& className : orderedClassNames) { if (!firstClassName) json.append(','); firstClassName = false; json.appendQuotedJSONString(className); } orderedClassNames.clear(); json.append(']'); // Process edges. // Replace pointers with identifiers. // Remove any edges that we won't need. m_edges.removeAllMatching([&] (HeapSnapshotEdge& edge) { // If the from cell is null, this means a <root> edge. if (!edge.from.cell) edge.from.identifier = 0; else { auto fromLookup = allowedNodeIdentifiers.find(edge.from.cell); if (fromLookup == allowedNodeIdentifiers.end()) { if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) WTFLogAlways("Failed to find node for from-edge cell %p", edge.from.cell); return true; } edge.from.identifier = fromLookup->value; } if (!edge.to.cell) edge.to.identifier = 0; else { auto toLookup = allowedNodeIdentifiers.find(edge.to.cell); if (toLookup == allowedNodeIdentifiers.end()) { if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) WTFLogAlways("Failed to find node for to-edge cell %p", edge.to.cell); return true; } edge.to.identifier = toLookup->value; } return false; }); allowedNodeIdentifiers.clear(); m_edges.shrinkToFit(); // Sort edges based on from identifier. std::sort(m_edges.begin(), m_edges.end(), [&] (const HeapSnapshotEdge& a, const HeapSnapshotEdge& b) { return a.from.identifier < b.from.identifier; }); // edges json.append(','); json.appendLiteral("\"edges\":"); json.append('['); for (auto& edge : m_edges) appendEdgeJSON(edge); json.append(']'); // edge types json.append(','); json.appendLiteral("\"edgeTypes\":"); json.append('['); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Internal)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Property)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Index)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Variable)); json.append(']'); // edge names json.append(','); json.appendLiteral("\"edgeNames\":"); json.append('['); Vector<UniquedStringImpl*> orderedEdgeNames(edgeNameIndexes.size()); for (auto& entry : edgeNameIndexes) orderedEdgeNames[entry.value] = entry.key; edgeNameIndexes.clear(); bool firstEdgeName = true; for (auto& edgeName : orderedEdgeNames) { if (!firstEdgeName) json.append(','); firstEdgeName = false; json.appendQuotedJSONString(edgeName); } orderedEdgeNames.clear(); json.append(']'); if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { json.append(','); json.appendLiteral("\"roots\":"); json.append('['); HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot(); bool firstNode = true; for (auto it : m_rootData) { auto snapshotNode = snapshot->nodeForCell(it.key); if (!snapshotNode) { WTFLogAlways("Failed to find snapshot node for cell %p", it.key); continue; } if (!firstNode) json.append(','); firstNode = false; json.appendNumber(snapshotNode.value().identifier); // Maybe we should just always encode the root names. const char* rootName = rootTypeToString(it.value.markReason); auto result = labelIndexes.add(rootName, nextLabelIndex); if (result.isNewEntry) nextLabelIndex++; unsigned labelIndex = result.iterator->value; json.append(','); json.appendNumber(labelIndex); unsigned reachabilityReasonIndex = 0; if (it.value.reachabilityFromOpaqueRootReasons) { auto result = labelIndexes.add(it.value.reachabilityFromOpaqueRootReasons, nextLabelIndex); if (result.isNewEntry) nextLabelIndex++; reachabilityReasonIndex = result.iterator->value; } json.append(','); json.appendNumber(reachabilityReasonIndex); } json.append(']'); } if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { // internal node descriptions json.append(','); json.appendLiteral("\"labels\":"); json.append('['); Vector<String> orderedLabels(labelIndexes.size()); for (auto& entry : labelIndexes) orderedLabels[entry.value] = entry.key; labelIndexes.clear(); bool firstLabel = true; for (auto& label : orderedLabels) { if (!firstLabel) json.append(','); firstLabel = false; json.appendQuotedJSONString(label); } orderedLabels.clear(); json.append(']'); } json.append('}'); return json.toString(); }