blob: 2aaf1cb967049f1d8fce99b1996ea85aeef78c54 [file] [log] [blame] [edit]
// 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),
nodes_(zone_),
node_types_(zone_),
edge_types_(zone_),
strings_(zone_),
roots_(zone_) {
intptr_t idx = edge_types_.Add("context");
ASSERT_EQUAL(idx, static_cast<intptr_t>(Edge::Type::kContext));
idx = edge_types_.Add("element");
ASSERT_EQUAL(idx, static_cast<intptr_t>(Edge::Type::kElement));
idx = edge_types_.Add("property");
ASSERT_EQUAL(idx, static_cast<intptr_t>(Edge::Type::kProperty));
idx = edge_types_.Add("internal");
ASSERT_EQUAL(idx, static_cast<intptr_t>(Edge::Type::kInternal));
SetObjectTypeAndName(kArtificialRootId, "ArtificialRoot",
"<artificial root>");
}
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(this, 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(
V8SnapshotProfileWriter* profile_writer,
Zone* zone) const {
JSONWriter writer;
WriteDebug(profile_writer, &writer);
return OS::SCreate(zone, "%s", writer.buffer()->buffer());
}
void V8SnapshotProfileWriter::NodeInfo::Write(
V8SnapshotProfileWriter* profile_writer,
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(
V8SnapshotProfileWriter* profile_writer,
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(profile_writer, 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";
default:
UNREACHABLE();
}
}
const char* V8SnapshotProfileWriter::EdgeMap::ToCString(
V8SnapshotProfileWriter* profile_writer,
Zone* zone) const {
JSONWriter writer;
WriteDebug(profile_writer, &writer);
return OS::SCreate(zone, "%s", writer.buffer()->buffer());
}
void V8SnapshotProfileWriter::EdgeMap::WriteDebug(
V8SnapshotProfileWriter* profile_writer,
JSONWriter* writer,
const char* property) const {
writer->OpenArray(property);
auto edge_it = GetIterator();
while (auto const pair = edge_it.Next()) {
pair->edge.WriteDebug(profile_writer, writer, pair->target);
}
writer->CloseArray();
}
void V8SnapshotProfileWriter::Edge::Write(
V8SnapshotProfileWriter* profile_writer,
JSONWriter* writer,
const ObjectId& target_id) const {
ASSERT(type != Type::kInvalid);
writer->PrintValue64(static_cast<intptr_t>(type));
writer->PrintValue64(name_or_offset);
auto const target = profile_writer->nodes_.LookupValue(target_id);
writer->PrintValue64(target.offset());
}
void V8SnapshotProfileWriter::Edge::WriteDebug(
V8SnapshotProfileWriter* profile_writer,
JSONWriter* writer,
const ObjectId& target_id) const {
writer->OpenObject();
if (type != Type::kInvalid) {
writer->PrintProperty(
"type", profile_writer->edge_types_.At(static_cast<intptr_t>(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);
va_end(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(this, 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(this, 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(this, 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("warning: Could not access file callbacks.");
return;
}
auto file = file_open(filename, /*write=*/true);
if (file == nullptr) {
OS::PrintErr("warning: Failed to write snapshot profile: %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