Bug 1024774 - Part 9: Deserialize heap snapshots; r=jimb

This commit is contained in:
Nick Fitzgerald 2015-04-22 11:09:54 -07:00
Родитель 0e6dcee572
Коммит 5689b01c3a
9 изменённых файлов: 612 добавлений и 3 удалений

Просмотреть файл

@ -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.
*/ */