зеркало из 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 "js/Debug.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "js/UbiNodeTraverse.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -170,8 +171,8 @@ EstablishBoundaries(JSContext *cx,
|
|||
}
|
||||
|
||||
|
||||
// A `CoreDumpWriter` that serializes nodes to protobufs and writes them to
|
||||
// the given `CodedOutputStream`.
|
||||
// A `CoreDumpWriter` that serializes nodes to protobufs and writes them to the
|
||||
// given `ZeroCopyOutputStream`.
|
||||
class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter
|
||||
{
|
||||
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/UbiNodeTraverse.h"
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/ChromeUtilsBinding.h"
|
||||
|
@ -56,6 +57,9 @@ WriteHeapGraph(JSContext *cx,
|
|||
JS::AutoCheckCannotGC &noGC);
|
||||
|
||||
|
||||
class HeapSnapshot;
|
||||
|
||||
|
||||
class ChromeUtils
|
||||
{
|
||||
public:
|
||||
|
@ -64,6 +68,11 @@ public:
|
|||
const nsAString &filePath,
|
||||
const dom::HeapSnapshotBoundaries &boundaries,
|
||||
ErrorResult &rv);
|
||||
|
||||
static already_AddRefed<HeapSnapshot> ReadHeapSnapshot(dom::GlobalObject &global,
|
||||
JSContext *cx,
|
||||
const nsAString &filePath,
|
||||
ErrorResult &rv);
|
||||
};
|
||||
|
||||
} // 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 += [
|
||||
'ChromeUtils.h',
|
||||
'CoreDump.pb.h',
|
||||
'DeserializedNode.h',
|
||||
'HeapSnapshot.h',
|
||||
'ZeroCopyNSIOutputStream.h',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'ChromeUtils.cpp',
|
||||
'CoreDump.pb.cc',
|
||||
'DeserializedNode.cpp',
|
||||
'HeapSnapshot.cpp',
|
||||
'nsJSInspector.cpp',
|
||||
'ZeroCopyNSIOutputStream.cpp',
|
||||
]
|
||||
|
|
|
@ -76,6 +76,7 @@ NS_strtok(const char* aDelims, char** aStr)
|
|||
uint32_t
|
||||
NS_strlen(const char16_t* aString)
|
||||
{
|
||||
MOZ_ASSERT(aString);
|
||||
const char16_t* end;
|
||||
|
||||
for (end = aString; *end; ++end) {
|
||||
|
@ -101,6 +102,23 @@ NS_strcmp(const char16_t* aStrA, const char16_t* aStrB)
|
|||
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*
|
||||
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);
|
||||
|
||||
/**
|
||||
* "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.
|
||||
*/
|
||||
|
|
Загрузка…
Ссылка в новой задаче