зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1024774
- Part 9: Deserialize heap snapshots; r=jimb
This commit is contained in:
Родитель
0e6dcee572
Коммит
5689b01c3a
|
@ -21,6 +21,7 @@
|
||||||
#include "prtypes.h"
|
#include "prtypes.h"
|
||||||
|
|
||||||
#include "js/Debug.h"
|
#include "js/Debug.h"
|
||||||
|
#include "js/TypeDecls.h"
|
||||||
#include "js/UbiNodeTraverse.h"
|
#include "js/UbiNodeTraverse.h"
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
|
@ -170,8 +171,8 @@ EstablishBoundaries(JSContext *cx,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// A `CoreDumpWriter` that serializes nodes to protobufs and writes them to
|
// A `CoreDumpWriter` that serializes nodes to protobufs and writes them to the
|
||||||
// the given `CodedOutputStream`.
|
// given `ZeroCopyOutputStream`.
|
||||||
class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter
|
class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter
|
||||||
{
|
{
|
||||||
JSContext *cx;
|
JSContext *cx;
|
||||||
|
@ -382,5 +383,50 @@ ChromeUtils::SaveHeapSnapshot(GlobalObject &global,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* static */ already_AddRefed<HeapSnapshot>
|
||||||
|
ChromeUtils::ReadHeapSnapshot(GlobalObject &global,
|
||||||
|
JSContext *cx,
|
||||||
|
const nsAString &filePath,
|
||||||
|
ErrorResult &rv)
|
||||||
|
{
|
||||||
|
UniquePtr<char[]> path(ToNewCString(filePath));
|
||||||
|
if (!path) {
|
||||||
|
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PRFileInfo fileInfo;
|
||||||
|
if (PR_GetFileInfo(path.get(), &fileInfo) != PR_SUCCESS) {
|
||||||
|
rv.Throw(NS_ERROR_FILE_NOT_FOUND);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t size = fileInfo.size;
|
||||||
|
ScopedFreePtr<uint8_t> buffer(static_cast<uint8_t *>(malloc(size)));
|
||||||
|
if (!buffer) {
|
||||||
|
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PRFileDesc *fd = PR_Open(path.get(), PR_RDONLY, 0);
|
||||||
|
if (!fd) {
|
||||||
|
rv.Throw(NS_ERROR_UNEXPECTED);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t bytesRead = 0;
|
||||||
|
while (bytesRead < size) {
|
||||||
|
uint32_t bytesLeft = size - bytesRead;
|
||||||
|
int32_t bytesReadThisTime = PR_Read(fd, buffer.get() + bytesRead, bytesLeft);
|
||||||
|
if (bytesReadThisTime < 1) {
|
||||||
|
rv.Throw(NS_ERROR_UNEXPECTED);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
bytesRead += bytesReadThisTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HeapSnapshot::Create(cx, global, buffer.get(), size, rv);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
} // namespace devtools
|
||||||
|
} // namespace mozilla
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include "js/UbiNode.h"
|
#include "js/UbiNode.h"
|
||||||
#include "js/UbiNodeTraverse.h"
|
#include "js/UbiNodeTraverse.h"
|
||||||
|
#include "mozilla/AlreadyAddRefed.h"
|
||||||
#include "mozilla/ErrorResult.h"
|
#include "mozilla/ErrorResult.h"
|
||||||
#include "mozilla/dom/BindingDeclarations.h"
|
#include "mozilla/dom/BindingDeclarations.h"
|
||||||
#include "mozilla/dom/ChromeUtilsBinding.h"
|
#include "mozilla/dom/ChromeUtilsBinding.h"
|
||||||
|
@ -56,6 +57,9 @@ WriteHeapGraph(JSContext *cx,
|
||||||
JS::AutoCheckCannotGC &noGC);
|
JS::AutoCheckCannotGC &noGC);
|
||||||
|
|
||||||
|
|
||||||
|
class HeapSnapshot;
|
||||||
|
|
||||||
|
|
||||||
class ChromeUtils
|
class ChromeUtils
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -64,6 +68,11 @@ public:
|
||||||
const nsAString &filePath,
|
const nsAString &filePath,
|
||||||
const dom::HeapSnapshotBoundaries &boundaries,
|
const dom::HeapSnapshotBoundaries &boundaries,
|
||||||
ErrorResult &rv);
|
ErrorResult &rv);
|
||||||
|
|
||||||
|
static already_AddRefed<HeapSnapshot> ReadHeapSnapshot(dom::GlobalObject &global,
|
||||||
|
JSContext *cx,
|
||||||
|
const nsAString &filePath,
|
||||||
|
ErrorResult &rv);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace devtools
|
} // namespace devtools
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "mozilla/devtools/DeserializedNode.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace devtools {
|
||||||
|
|
||||||
|
DeserializedEdge::DeserializedEdge()
|
||||||
|
: referent(0)
|
||||||
|
, name(nullptr)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
DeserializedEdge::DeserializedEdge(DeserializedEdge &&rhs)
|
||||||
|
{
|
||||||
|
referent = rhs.referent;
|
||||||
|
name = rhs.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializedEdge &DeserializedEdge::operator=(DeserializedEdge &&rhs)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(&rhs != this);
|
||||||
|
this->~DeserializedEdge();
|
||||||
|
new(this) DeserializedEdge(Move(rhs));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
DeserializedEdge::init(const protobuf::Edge &edge, HeapSnapshot &owner)
|
||||||
|
{
|
||||||
|
// Although the referent property is optional in the protobuf format for
|
||||||
|
// future compatibility, we can't semantically have an edge to nowhere and
|
||||||
|
// require a referent here.
|
||||||
|
if (!edge.has_referent())
|
||||||
|
return false;
|
||||||
|
referent = edge.referent();
|
||||||
|
|
||||||
|
if (edge.has_name()) {
|
||||||
|
const char16_t* duplicateEdgeName = reinterpret_cast<const char16_t*>(edge.name().c_str());
|
||||||
|
name = owner.borrowUniqueString(duplicateEdgeName, edge.name().length() / sizeof(char16_t));
|
||||||
|
if (!name)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */ UniquePtr<DeserializedNode>
|
||||||
|
DeserializedNode::Create(const protobuf::Node &node, HeapSnapshot &owner)
|
||||||
|
{
|
||||||
|
if (!node.has_id())
|
||||||
|
return nullptr;
|
||||||
|
NodeId id = node.id();
|
||||||
|
|
||||||
|
if (!node.has_typename_())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
const char16_t* duplicatedTypeName = reinterpret_cast<const char16_t*>(node.typename_().c_str());
|
||||||
|
const char16_t* uniqueTypeName = owner.borrowUniqueString(duplicatedTypeName,
|
||||||
|
node.typename_().length() / sizeof(char16_t));
|
||||||
|
if (!uniqueTypeName)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto edgesLength = node.edges_size();
|
||||||
|
EdgeVector edges;
|
||||||
|
if (!edges.reserve(edgesLength))
|
||||||
|
return nullptr;
|
||||||
|
for (decltype(edgesLength) i = 0; i < edgesLength; i++) {
|
||||||
|
DeserializedEdge edge;
|
||||||
|
if (!edge.init(node.edges(i), owner))
|
||||||
|
return nullptr;
|
||||||
|
edges.infallibleAppend(Move(edge));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.has_size())
|
||||||
|
return nullptr;
|
||||||
|
uint64_t size = node.size();
|
||||||
|
|
||||||
|
return MakeUnique<DeserializedNode>(id,
|
||||||
|
uniqueTypeName,
|
||||||
|
size,
|
||||||
|
Move(edges),
|
||||||
|
owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializedNode::DeserializedNode(NodeId id,
|
||||||
|
const char16_t *typeName,
|
||||||
|
uint64_t size,
|
||||||
|
EdgeVector &&edges,
|
||||||
|
HeapSnapshot &owner)
|
||||||
|
: id(id)
|
||||||
|
, typeName(typeName)
|
||||||
|
, size(size)
|
||||||
|
, edges(Move(edges))
|
||||||
|
, owner(&owner)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
DeserializedNode &
|
||||||
|
DeserializedNode::getEdgeReferent(const DeserializedEdge &edge)
|
||||||
|
{
|
||||||
|
auto ptr = owner->nodes.lookup(edge.referent);
|
||||||
|
MOZ_ASSERT(ptr);
|
||||||
|
return *ptr->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace devtools
|
||||||
|
} // namespace mozilla
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef mozilla_devtools_DeserializedNode__
|
||||||
|
#define mozilla_devtools_DeserializedNode__
|
||||||
|
|
||||||
|
#include "mozilla/devtools/CoreDump.pb.h"
|
||||||
|
#include "mozilla/MaybeOneOf.h"
|
||||||
|
#include "mozilla/Move.h"
|
||||||
|
#include "mozilla/UniquePtr.h"
|
||||||
|
#include "mozilla/Vector.h"
|
||||||
|
|
||||||
|
// `Deserialized{Node,Edge}` translate protobuf messages from our core dump
|
||||||
|
// format into structures we can rely upon for implementing `JS::ubi::Node`
|
||||||
|
// specializations on top of. All of the properties of the protobuf messages are
|
||||||
|
// optional for future compatibility, and this is the layer where we validate
|
||||||
|
// that the properties that do actually exist in any given message fulfill our
|
||||||
|
// semantic requirements.
|
||||||
|
//
|
||||||
|
// Both `DeserializedNode` and `DeserializedEdge` are always owned by a
|
||||||
|
// `HeapSnapshot` instance, and their lifetimes must not extend after that of
|
||||||
|
// their owning `HeapSnapshot`.
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace devtools {
|
||||||
|
|
||||||
|
class HeapSnapshot;
|
||||||
|
|
||||||
|
using NodeId = uint64_t;
|
||||||
|
|
||||||
|
// A `DeserializedEdge` represents an edge in the heap graph pointing to the
|
||||||
|
// node with id equal to `DeserializedEdge::referent` that we deserialized from
|
||||||
|
// a core dump.
|
||||||
|
struct DeserializedEdge {
|
||||||
|
NodeId referent;
|
||||||
|
// A borrowed reference to a string owned by this node's owning HeapSnapshot.
|
||||||
|
const char16_t *name;
|
||||||
|
|
||||||
|
explicit DeserializedEdge();
|
||||||
|
DeserializedEdge(DeserializedEdge &&rhs);
|
||||||
|
DeserializedEdge &operator=(DeserializedEdge &&rhs);
|
||||||
|
|
||||||
|
// Initialize this `DeserializedEdge` from the given `protobuf::Edge` message.
|
||||||
|
bool init(const protobuf::Edge &edge, HeapSnapshot &owner);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DeserializedEdge(const DeserializedEdge &) = delete;
|
||||||
|
DeserializedEdge& operator=(const DeserializedEdge &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A `DeserializedNode` is a node in the heap graph that we deserialized from a
|
||||||
|
// core dump.
|
||||||
|
struct DeserializedNode {
|
||||||
|
using EdgeVector = Vector<DeserializedEdge>;
|
||||||
|
using UniqueStringPtr = UniquePtr<char16_t[]>;
|
||||||
|
|
||||||
|
NodeId id;
|
||||||
|
// A borrowed reference to a string owned by this node's owning HeapSnapshot.
|
||||||
|
const char16_t *typeName;
|
||||||
|
uint64_t size;
|
||||||
|
EdgeVector edges;
|
||||||
|
// A weak pointer to this node's owning `HeapSnapshot`. Safe without
|
||||||
|
// AddRef'ing because this node's lifetime is equal to that of its owner.
|
||||||
|
HeapSnapshot *owner;
|
||||||
|
|
||||||
|
// Create a new `DeserializedNode` from the given `protobuf::Node` message.
|
||||||
|
static UniquePtr<DeserializedNode> Create(const protobuf::Node &node,
|
||||||
|
HeapSnapshot &owner);
|
||||||
|
|
||||||
|
DeserializedNode(NodeId id,
|
||||||
|
const char16_t *typeName,
|
||||||
|
uint64_t size,
|
||||||
|
EdgeVector &&edges,
|
||||||
|
HeapSnapshot &owner);
|
||||||
|
virtual ~DeserializedNode() { }
|
||||||
|
|
||||||
|
// Get a borrowed reference to the given edge's referent. This method is
|
||||||
|
// virtual to provide a hook for gmock and gtest.
|
||||||
|
virtual DeserializedNode &getEdgeReferent(const DeserializedEdge &edge);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DeserializedNode(const DeserializedNode &) = delete;
|
||||||
|
DeserializedNode &operator=(const DeserializedNode &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace devtools
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif // mozilla_devtools_DeserializedNode__
|
|
@ -0,0 +1,194 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "HeapSnapshot.h"
|
||||||
|
|
||||||
|
#include <google/protobuf/io/coded_stream.h>
|
||||||
|
#include <google/protobuf/io/gzip_stream.h>
|
||||||
|
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
|
||||||
|
|
||||||
|
#include "mozilla/devtools/DeserializedNode.h"
|
||||||
|
#include "mozilla/dom/HeapSnapshotBinding.h"
|
||||||
|
|
||||||
|
#include "CoreDump.pb.h"
|
||||||
|
#include "nsCycleCollectionParticipant.h"
|
||||||
|
#include "nsCRTGlue.h"
|
||||||
|
#include "nsISupportsImpl.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace devtools {
|
||||||
|
|
||||||
|
using ::google::protobuf::io::ArrayInputStream;
|
||||||
|
using ::google::protobuf::io::CodedInputStream;
|
||||||
|
using ::google::protobuf::io::GzipInputStream;
|
||||||
|
using ::google::protobuf::io::ZeroCopyInputStream;
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_CLASS(HeapSnapshot)
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HeapSnapshot)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HeapSnapshot)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(HeapSnapshot)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(HeapSnapshot)
|
||||||
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(HeapSnapshot)
|
||||||
|
|
||||||
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HeapSnapshot)
|
||||||
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||||
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||||
|
NS_INTERFACE_MAP_END
|
||||||
|
|
||||||
|
/* virtual */ JSObject *
|
||||||
|
HeapSnapshot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||||
|
{
|
||||||
|
return dom::HeapSnapshotBinding::Wrap(aCx, this, aGivenProto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */ already_AddRefed<HeapSnapshot>
|
||||||
|
HeapSnapshot::Create(JSContext *cx,
|
||||||
|
dom::GlobalObject &global,
|
||||||
|
const uint8_t *buffer,
|
||||||
|
uint32_t size,
|
||||||
|
ErrorResult &rv)
|
||||||
|
{
|
||||||
|
nsRefPtr<HeapSnapshot> snapshot = new HeapSnapshot(cx, global.GetAsSupports());
|
||||||
|
if (!snapshot->init(buffer, size)) {
|
||||||
|
rv.Throw(NS_ERROR_UNEXPECTED);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return snapshot.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename MessageType>
|
||||||
|
static bool
|
||||||
|
parseMessage(ZeroCopyInputStream &stream, MessageType &message)
|
||||||
|
{
|
||||||
|
// We need to create a new `CodedInputStream` for each message so that the
|
||||||
|
// 64MB limit is applied per-message rather than to the whole stream.
|
||||||
|
CodedInputStream codedStream(&stream);
|
||||||
|
|
||||||
|
// Because protobuf messages aren't self-delimiting, we serialize each message
|
||||||
|
// preceeded by its size in bytes. When deserializing, we read this size and
|
||||||
|
// then limit reading from the stream to the given byte size. If we didn't,
|
||||||
|
// then the first message would consume the entire stream.
|
||||||
|
|
||||||
|
uint32_t size = 0;
|
||||||
|
if (NS_WARN_IF(!codedStream.ReadVarint32(&size)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto limit = codedStream.PushLimit(size);
|
||||||
|
if (NS_WARN_IF(!message.ParseFromCodedStream(&codedStream)) ||
|
||||||
|
NS_WARN_IF(!codedStream.ConsumedEntireMessage()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
codedStream.PopLimit(limit);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
HeapSnapshot::saveNode(const protobuf::Node &node)
|
||||||
|
{
|
||||||
|
UniquePtr<DeserializedNode> dn(DeserializedNode::Create(node, *this));
|
||||||
|
if (!dn)
|
||||||
|
return false;
|
||||||
|
return nodes.put(dn->id, Move(dn));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
StreamHasData(GzipInputStream &stream)
|
||||||
|
{
|
||||||
|
// Test for the end of the stream. The protobuf library gives no way to tell
|
||||||
|
// the difference between an underlying read error and the stream being
|
||||||
|
// done. All we can do is attempt to read data and extrapolate guestimations
|
||||||
|
// from the result of that operation.
|
||||||
|
|
||||||
|
const void *buf;
|
||||||
|
int size;
|
||||||
|
bool more = stream.Next(&buf, &size);
|
||||||
|
if (!more)
|
||||||
|
// Could not read any more data. We are optimistic and assume the stream is
|
||||||
|
// just exhausted and there is not an underlying IO error, since this
|
||||||
|
// function is only called at message boundaries.
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// There is more data still available in the stream. Return the data we read
|
||||||
|
// to the stream and let the parser get at it.
|
||||||
|
stream.BackUp(size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
HeapSnapshot::init(const uint8_t *buffer, uint32_t size)
|
||||||
|
{
|
||||||
|
if (!nodes.init() || !strings.init())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ArrayInputStream stream(buffer, size);
|
||||||
|
GzipInputStream gzipStream(&stream);
|
||||||
|
|
||||||
|
// First is the metadata.
|
||||||
|
|
||||||
|
protobuf::Metadata metadata;
|
||||||
|
if (!parseMessage(gzipStream, metadata))
|
||||||
|
return false;
|
||||||
|
if (metadata.has_timestamp())
|
||||||
|
timestamp.emplace(metadata.timestamp());
|
||||||
|
|
||||||
|
// Next is the root node.
|
||||||
|
|
||||||
|
protobuf::Node root;
|
||||||
|
if (!parseMessage(gzipStream, root))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Although the id is optional in the protobuf format for future proofing, we
|
||||||
|
// can't currently do anything without it.
|
||||||
|
if (NS_WARN_IF(!root.has_id()))
|
||||||
|
return false;
|
||||||
|
rootId = root.id();
|
||||||
|
|
||||||
|
if (NS_WARN_IF(!saveNode(root)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Finally, the rest of the nodes in the core dump.
|
||||||
|
|
||||||
|
while (StreamHasData(gzipStream)) {
|
||||||
|
protobuf::Node node;
|
||||||
|
if (!parseMessage(gzipStream, node))
|
||||||
|
return false;
|
||||||
|
if (NS_WARN_IF(!saveNode(node)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char16_t*
|
||||||
|
HeapSnapshot::borrowUniqueString(const char16_t* duplicateString, size_t length)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(duplicateString);
|
||||||
|
UniqueStringHashPolicy::Lookup lookup(duplicateString, length);
|
||||||
|
auto ptr = strings.lookupForAdd(lookup);
|
||||||
|
|
||||||
|
if (!ptr) {
|
||||||
|
UniqueString owned(NS_strndup(duplicateString, length));
|
||||||
|
if (!owned || !strings.add(ptr, Move(owned)))
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOZ_ASSERT(ptr->get() != duplicateString);
|
||||||
|
return ptr->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace devtools
|
||||||
|
} // namespace mozilla
|
|
@ -0,0 +1,133 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef mozilla_devtools_HeapSnapshot__
|
||||||
|
#define mozilla_devtools_HeapSnapshot__
|
||||||
|
|
||||||
|
#include "js/HashTable.h"
|
||||||
|
#include "mozilla/ErrorResult.h"
|
||||||
|
#include "mozilla/devtools/DeserializedNode.h"
|
||||||
|
#include "mozilla/dom/BindingDeclarations.h"
|
||||||
|
#include "mozilla/HashFunctions.h"
|
||||||
|
#include "mozilla/Maybe.h"
|
||||||
|
#include "mozilla/RefPtr.h"
|
||||||
|
#include "mozilla/UniquePtr.h"
|
||||||
|
|
||||||
|
#include "CoreDump.pb.h"
|
||||||
|
#include "nsCOMPtr.h"
|
||||||
|
#include "nsCRTGlue.h"
|
||||||
|
#include "nsCycleCollectionParticipant.h"
|
||||||
|
#include "nsISupports.h"
|
||||||
|
#include "nsWrapperCache.h"
|
||||||
|
#include "nsXPCOM.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace devtools {
|
||||||
|
|
||||||
|
struct NSFreePolicy {
|
||||||
|
void operator()(void* ptr) {
|
||||||
|
NS_Free(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using UniqueString = UniquePtr<char16_t[], NSFreePolicy>;
|
||||||
|
|
||||||
|
struct UniqueStringHashPolicy {
|
||||||
|
struct Lookup {
|
||||||
|
const char16_t* str;
|
||||||
|
size_t length;
|
||||||
|
|
||||||
|
Lookup(const char16_t* str, size_t length)
|
||||||
|
: str(str)
|
||||||
|
, length(length)
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static js::HashNumber hash(const Lookup& lookup) {
|
||||||
|
MOZ_ASSERT(lookup.str);
|
||||||
|
return HashString(lookup.str, lookup.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool match(const UniqueString& existing, const Lookup& lookup) {
|
||||||
|
MOZ_ASSERT(lookup.str);
|
||||||
|
return NS_strncmp(existing.get(), lookup.str, lookup.length) == 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeapSnapshot final : public nsISupports
|
||||||
|
, public nsWrapperCache
|
||||||
|
{
|
||||||
|
friend struct DeserializedNode;
|
||||||
|
|
||||||
|
explicit HeapSnapshot(JSContext *cx, nsISupports *aParent)
|
||||||
|
: timestamp(Nothing())
|
||||||
|
, rootId(0)
|
||||||
|
, nodes(cx)
|
||||||
|
, strings(cx)
|
||||||
|
, mParent(aParent)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(aParent);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize this HeapSnapshot from the given buffer that contains a
|
||||||
|
// serialized core dump. Do NOT take ownership of the buffer, only borrow it
|
||||||
|
// for the duration of the call. Return false on failure.
|
||||||
|
bool init(const uint8_t *buffer, uint32_t size);
|
||||||
|
|
||||||
|
// Save the given `protobuf::Node` message in this `HeapSnapshot` as a
|
||||||
|
// `DeserializedNode`.
|
||||||
|
bool saveNode(const protobuf::Node &node);
|
||||||
|
|
||||||
|
// If present, a timestamp in the same units that `PR_Now` gives.
|
||||||
|
Maybe<uint64_t> timestamp;
|
||||||
|
|
||||||
|
// The id of the root node for this deserialized heap graph.
|
||||||
|
NodeId rootId;
|
||||||
|
|
||||||
|
// The set of nodes in this deserialized heap graph, keyed by id.
|
||||||
|
using NodeMap = js::HashMap<NodeId, UniquePtr<DeserializedNode>>;
|
||||||
|
NodeMap nodes;
|
||||||
|
|
||||||
|
// Core dump files have many duplicate strings: type names are repeated for
|
||||||
|
// each node, and although in theory edge names are highly customizable for
|
||||||
|
// specific edges, in practice they are also highly duplicated. Rather than
|
||||||
|
// make each Deserialized{Node,Edge} malloc their own copy of their edge and
|
||||||
|
// type names, we de-duplicate the strings here and Deserialized{Node,Edge}
|
||||||
|
// get borrowed pointers into this set.
|
||||||
|
using UniqueStringSet = js::HashSet<UniqueString, UniqueStringHashPolicy>;
|
||||||
|
UniqueStringSet strings;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
nsCOMPtr<nsISupports> mParent;
|
||||||
|
|
||||||
|
virtual ~HeapSnapshot() { }
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Create a `HeapSnapshot` from the given buffer that contains a serialized
|
||||||
|
// core dump. Do NOT take ownership of the buffer, only borrow it for the
|
||||||
|
// duration of the call.
|
||||||
|
static already_AddRefed<HeapSnapshot> Create(JSContext *cx,
|
||||||
|
dom::GlobalObject &global,
|
||||||
|
const uint8_t *buffer,
|
||||||
|
uint32_t size,
|
||||||
|
ErrorResult &rv);
|
||||||
|
|
||||||
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||||
|
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(HeapSnapshot)
|
||||||
|
MOZ_DECLARE_REFCOUNTED_TYPENAME(HeapSnapshot)
|
||||||
|
|
||||||
|
nsISupports *GetParentObject() const { return mParent; }
|
||||||
|
|
||||||
|
virtual JSObject *WrapObject(JSContext* aCx,
|
||||||
|
JS::Handle<JSObject*> aGivenProto) override;
|
||||||
|
|
||||||
|
const char16_t* borrowUniqueString(const char16_t* duplicateString,
|
||||||
|
size_t length);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace devtools
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif // mozilla_devtools_HeapSnapshot__
|
|
@ -20,12 +20,16 @@ XPIDL_MODULE = 'jsinspector'
|
||||||
EXPORTS.mozilla.devtools += [
|
EXPORTS.mozilla.devtools += [
|
||||||
'ChromeUtils.h',
|
'ChromeUtils.h',
|
||||||
'CoreDump.pb.h',
|
'CoreDump.pb.h',
|
||||||
|
'DeserializedNode.h',
|
||||||
|
'HeapSnapshot.h',
|
||||||
'ZeroCopyNSIOutputStream.h',
|
'ZeroCopyNSIOutputStream.h',
|
||||||
]
|
]
|
||||||
|
|
||||||
SOURCES += [
|
SOURCES += [
|
||||||
'ChromeUtils.cpp',
|
'ChromeUtils.cpp',
|
||||||
'CoreDump.pb.cc',
|
'CoreDump.pb.cc',
|
||||||
|
'DeserializedNode.cpp',
|
||||||
|
'HeapSnapshot.cpp',
|
||||||
'nsJSInspector.cpp',
|
'nsJSInspector.cpp',
|
||||||
'ZeroCopyNSIOutputStream.cpp',
|
'ZeroCopyNSIOutputStream.cpp',
|
||||||
]
|
]
|
||||||
|
|
|
@ -76,6 +76,7 @@ NS_strtok(const char* aDelims, char** aStr)
|
||||||
uint32_t
|
uint32_t
|
||||||
NS_strlen(const char16_t* aString)
|
NS_strlen(const char16_t* aString)
|
||||||
{
|
{
|
||||||
|
MOZ_ASSERT(aString);
|
||||||
const char16_t* end;
|
const char16_t* end;
|
||||||
|
|
||||||
for (end = aString; *end; ++end) {
|
for (end = aString; *end; ++end) {
|
||||||
|
@ -101,6 +102,23 @@ NS_strcmp(const char16_t* aStrA, const char16_t* aStrB)
|
||||||
return *aStrA != '\0';
|
return *aStrA != '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
NS_strncmp(const char16_t* aStrA, const char16_t* aStrB, size_t aLen)
|
||||||
|
{
|
||||||
|
while (aLen && *aStrB) {
|
||||||
|
int r = *aStrA - *aStrB;
|
||||||
|
if (r) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
++aStrA;
|
||||||
|
++aStrB;
|
||||||
|
--aLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
return aLen ? *aStrA != '\0' : *aStrA - *aStrB;
|
||||||
|
}
|
||||||
|
|
||||||
char16_t*
|
char16_t*
|
||||||
NS_strdup(const char16_t* aString)
|
NS_strdup(const char16_t* aString)
|
||||||
{
|
{
|
||||||
|
|
|
@ -47,6 +47,11 @@ uint32_t NS_strlen(const char16_t* aString);
|
||||||
*/
|
*/
|
||||||
int NS_strcmp(const char16_t* aStrA, const char16_t* aStrB);
|
int NS_strcmp(const char16_t* aStrA, const char16_t* aStrB);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "strncmp" for char16_t strings
|
||||||
|
*/
|
||||||
|
int NS_strncmp(const char16_t* aStrA, const char16_t* aStrB, size_t aLen);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "strdup" for char16_t strings, uses the NS_Alloc allocator.
|
* "strdup" for char16_t strings, uses the NS_Alloc allocator.
|
||||||
*/
|
*/
|
||||||
|
|
Загрузка…
Ссылка в новой задаче