// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/v8_snapshot_writer.h" #include "vm/dart.h" #include "vm/os.h" namespace dart { const V8SnapshotProfileWriter::ObjectId V8SnapshotProfileWriter::kArtificialRootId{IdSpace::kArtificial, 0}; #if defined(DART_PRECOMPILER) V8SnapshotProfileWriter::V8SnapshotProfileWriter(Zone* zone) : zone_(zone), node_types_(zone_), edge_types_(zone_), strings_(zone_), roots_(zone_) { intptr_t idx = edge_types_.Add("context"); ASSERT_EQUAL(idx, static_cast(Edge::Type::kContext)); idx = edge_types_.Add("element"); ASSERT_EQUAL(idx, static_cast(Edge::Type::kElement)); idx = edge_types_.Add("property"); ASSERT_EQUAL(idx, static_cast(Edge::Type::kProperty)); idx = edge_types_.Add("internal"); ASSERT_EQUAL(idx, static_cast(Edge::Type::kInternal)); SetObjectTypeAndName(kArtificialRootId, "ArtificialRoot", ""); } void V8SnapshotProfileWriter::SetObjectTypeAndName(const ObjectId& object_id, const char* type, const char* name) { ASSERT(type != nullptr); NodeInfo* info = EnsureId(object_id); const intptr_t type_index = node_types_.Add(type); if (info->type != kInvalidString && info->type != type_index) { FATAL("Attempting to assign mismatching type %s to node %s", type, info->ToCString(zone_)); } info->type = type_index; // Don't overwrite any existing name. if (info->name == kInvalidString) { info->name = strings_.Add(name); } } void V8SnapshotProfileWriter::AttributeBytesTo(const ObjectId& object_id, size_t num_bytes) { EnsureId(object_id)->self_size += num_bytes; } void V8SnapshotProfileWriter::AttributeReferenceTo( const ObjectId& from_object_id, const Reference& reference, const ObjectId& to_object_id) { ASSERT(reference.IsElement() ? reference.offset >= 0 : reference.name != nullptr); EnsureId(to_object_id); const Edge edge(this, reference); EnsureId(from_object_id)->AddEdge(edge, to_object_id); } void V8SnapshotProfileWriter::AttributeDroppedReferenceTo( const ObjectId& from_object_id, const Reference& reference, const ObjectId& to_object_id, const ObjectId& replacement_object_id) { ASSERT(to_object_id.IsArtificial()); ASSERT(!replacement_object_id.IsArtificial()); ASSERT(reference.IsElement() ? reference.offset >= 0 : reference.name != nullptr); // The target node is added normally. AttributeReferenceTo(from_object_id, reference, to_object_id); EnsureId(replacement_object_id); // Put the replacement node at an invalid offset or name that can still be // associated with the real one. For offsets, this is the negative offset. // For names, it's the name prefixed with ":replacement_". Reference replacement_reference = reference.IsElement() ? Reference::Element(-reference.offset) : Reference::Property(OS::SCreate( zone_, ":replacement_%s", reference.name)); const Edge replacement_edge(this, replacement_reference); EnsureId(from_object_id)->AddEdge(replacement_edge, replacement_object_id); } bool V8SnapshotProfileWriter::HasId(const ObjectId& object_id) { return nodes_.HasKey(object_id); } V8SnapshotProfileWriter::NodeInfo* V8SnapshotProfileWriter::EnsureId( const ObjectId& object_id) { if (!HasId(object_id)) { nodes_.Insert(NodeInfo(this, object_id)); } return nodes_.Lookup(object_id); } const char* V8SnapshotProfileWriter::NodeInfo::ToCString(Zone* zone) const { JSONWriter writer; WriteDebug(&writer); return OS::SCreate(zone, "%s", writer.buffer()->buffer()); } void V8SnapshotProfileWriter::NodeInfo::Write(JSONWriter* writer) const { ASSERT(id.space() != IdSpace::kInvalid); if (type == kInvalidString) { FATAL("No type given for node %s", id.ToCString(profile_writer_->zone_)); } writer->PrintValue(type); if (name != kInvalidString) { writer->PrintValue(name); } else { ASSERT(profile_writer_ != nullptr); // If we don't already have a name for the node, we lazily create a default // one. This is safe since the strings table is written out after the nodes. const intptr_t name = profile_writer_->strings_.AddFormatted( "Unnamed [%s] (nil)", profile_writer_->node_types_.At(type)); writer->PrintValue(name); } id.Write(writer); writer->PrintValue(self_size); writer->PrintValue64(edges->Length()); } void V8SnapshotProfileWriter::NodeInfo::WriteDebug(JSONWriter* writer) const { writer->OpenObject(); if (type != kInvalidString) { writer->PrintProperty("type", profile_writer_->node_types_.At(type)); } if (name != kInvalidString) { writer->PrintProperty("name", profile_writer_->strings_.At(name)); } id.WriteDebug(writer, "id"); writer->PrintProperty("self_size", self_size); edges->WriteDebug(writer, "edges"); writer->CloseObject(); } const char* V8SnapshotProfileWriter::ObjectId::ToCString(Zone* zone) const { JSONWriter writer; WriteDebug(&writer); return OS::SCreate(zone, "%s", writer.buffer()->buffer()); } void V8SnapshotProfileWriter::ObjectId::Write(JSONWriter* writer, const char* property) const { if (property != nullptr) { writer->PrintProperty64(property, encoded_); } else { writer->PrintValue64(encoded_); } } void V8SnapshotProfileWriter::ObjectId::WriteDebug(JSONWriter* writer, const char* property) const { writer->OpenObject(property); writer->PrintProperty("space", IdSpaceToCString(space())); writer->PrintProperty64("nonce", nonce()); writer->CloseObject(); } const char* V8SnapshotProfileWriter::ObjectId::IdSpaceToCString(IdSpace space) { switch (space) { case IdSpace::kInvalid: return "Invalid"; case IdSpace::kSnapshot: return "Snapshot"; case IdSpace::kVmText: return "VmText"; case IdSpace::kIsolateText: return "IsolateText"; case IdSpace::kVmData: return "VmData"; case IdSpace::kIsolateData: return "IsolateData"; case IdSpace::kArtificial: return "Artificial"; } } const char* V8SnapshotProfileWriter::EdgeMap::ToCString(Zone* zone) const { JSONWriter writer; WriteDebug(&writer); return OS::SCreate(zone, "%s", writer.buffer()->buffer()); } void V8SnapshotProfileWriter::EdgeMap::WriteDebug(JSONWriter* writer, const char* property) const { writer->OpenArray(property); auto edge_it = GetIterator(); while (auto const pair = edge_it.Next()) { pair->edge.WriteDebug(writer, pair->target); } writer->CloseArray(); } void V8SnapshotProfileWriter::Edge::Write(JSONWriter* writer, const ObjectId& target_id) const { ASSERT(type != Type::kInvalid); writer->PrintValue64(static_cast(type)); writer->PrintValue64(name_or_offset); auto const target = profile_writer_->nodes_.LookupValue(target_id); writer->PrintValue64(target.offset()); } void V8SnapshotProfileWriter::Edge::WriteDebug( JSONWriter* writer, const ObjectId& target_id) const { writer->OpenObject(); if (type != Type::kInvalid) { writer->PrintProperty( "type", profile_writer_->edge_types_.At(static_cast(type))); } if (type == Type::kProperty) { writer->PrintProperty("name", profile_writer_->strings_.At(name_or_offset)); } else { writer->PrintProperty64("offset", name_or_offset); } auto const target = profile_writer_->nodes_.LookupValue(target_id); target.id.WriteDebug(writer, "target"); writer->CloseObject(); } void V8SnapshotProfileWriter::AddRoot(const ObjectId& object_id, const char* name) { // HeapSnapshotWorker.HeapSnapshot.calculateDistances (from HeapSnapshot.js) // assumes that the root does not have more than one edge to any other node // (most likely an oversight). if (roots_.HasKey(object_id)) return; roots_.Insert(object_id); auto const str_index = strings_.Add(name); auto const root = nodes_.Lookup(kArtificialRootId); ASSERT(root != nullptr); root->AddEdge(str_index != kInvalidString ? Edge(this, Edge::Type::kProperty, str_index) : Edge(this, Edge::Type::kInternal, root->edges->Length()), object_id); } intptr_t V8SnapshotProfileWriter::StringsTable::Add(const char* str) { if (str == nullptr) return kInvalidString; if (auto const kv = index_map_.Lookup(str)) { return kv->value; } const char* new_str = OS::SCreate(zone_, "%s", str); const intptr_t index = strings_.length(); strings_.Add(new_str); index_map_.Insert({new_str, index}); return index; } intptr_t V8SnapshotProfileWriter::StringsTable::AddFormatted(const char* fmt, ...) { va_list args; va_start(args, fmt); const char* str = OS::VSCreate(zone_, fmt, args); if (auto const kv = index_map_.Lookup(str)) { return kv->value; } const intptr_t index = strings_.length(); strings_.Add(str); index_map_.Insert({str, index}); return index; } const char* V8SnapshotProfileWriter::StringsTable::At(intptr_t index) const { if (index > strings_.length()) return nullptr; return strings_[index]; } void V8SnapshotProfileWriter::StringsTable::Write(JSONWriter* writer, const char* property) const { writer->OpenArray(property); for (auto const str : strings_) { writer->PrintValue(str); writer->PrintNewline(); } writer->CloseArray(); } void V8SnapshotProfileWriter::Write(JSONWriter* writer) { writer->OpenObject(); writer->OpenObject("snapshot"); { writer->OpenObject("meta"); { writer->OpenArray("node_fields"); writer->PrintValue("type"); writer->PrintValue("name"); writer->PrintValue("id"); writer->PrintValue("self_size"); writer->PrintValue("edge_count"); writer->CloseArray(); } { writer->OpenArray("node_types"); node_types_.Write(writer); writer->CloseArray(); } { writer->OpenArray("edge_fields"); writer->PrintValue("type"); writer->PrintValue("name_or_index"); writer->PrintValue("to_node"); writer->CloseArray(); } { writer->OpenArray("edge_types"); edge_types_.Write(writer); writer->CloseArray(); } writer->CloseObject(); writer->PrintProperty64("node_count", nodes_.Size()); { intptr_t edge_count = 0; auto nodes_it = nodes_.GetIterator(); while (auto const info = nodes_it.Next()) { // All nodes should have an edge map, though it may be empty. ASSERT(info->edges != nullptr); edge_count += info->edges->Length(); } writer->PrintProperty64("edge_count", edge_count); } } writer->CloseObject(); { writer->OpenArray("nodes"); // Always write the information for the artificial root first. auto const root = nodes_.Lookup(kArtificialRootId); ASSERT(root != nullptr); intptr_t offset = 0; root->set_offset(offset); root->Write(writer); offset += kNumNodeFields; auto nodes_it = nodes_.GetIterator(); for (auto entry = nodes_it.Next(); entry != nullptr; entry = nodes_it.Next()) { if (entry->id == kArtificialRootId) continue; entry->set_offset(offset); entry->Write(writer); offset += kNumNodeFields; } writer->CloseArray(); } { auto write_edges = [&](const NodeInfo& info) { auto edges_it = info.edges->GetIterator(); while (auto const pair = edges_it.Next()) { pair->edge.Write(writer, pair->target); } }; writer->OpenArray("edges"); // Always write the information for the artificial root first. auto const root = nodes_.Lookup(kArtificialRootId); ASSERT(root != nullptr); write_edges(*root); auto nodes_it = nodes_.GetIterator(); while (auto const entry = nodes_it.Next()) { if (entry->id == kArtificialRootId) continue; write_edges(*entry); } writer->CloseArray(); } // Must happen after any calls to WriteNodeInfo, as those calls may add more // strings. strings_.Write(writer, "strings"); writer->CloseObject(); } void V8SnapshotProfileWriter::Write(const char* filename) { JSONWriter json; Write(&json); auto file_open = Dart::file_open_callback(); auto file_write = Dart::file_write_callback(); auto file_close = Dart::file_close_callback(); if ((file_open == nullptr) || (file_write == nullptr) || (file_close == nullptr)) { OS::PrintErr("Could not access file callbacks to write snapshot profile."); return; } auto file = file_open(filename, /*write=*/true); if (file == nullptr) { OS::PrintErr("Failed to open file %s\n", filename); } else { char* output = nullptr; intptr_t output_length = 0; json.Steal(&output, &output_length); file_write(output, output_length, file); free(output); file_close(file); } } #endif } // namespace dart