Files
sdk/runtime/vm/object_graph.h
T
Martin Kustermann e1c2192110 [vm] Make writeHeapSnapshotToFile() API public in dart:developer
This API can be used by app developers that want to diagnose high
memory consumption by dumping state of Dart VM heap at specific
points in their apps code and later analyzing those dumps offline
(e.g. using the tool in `<sdk>/runtime/tools/heapsnapshot`).

TEST=Existing test suite.

Change-Id: I04de6ba564f7b9eaf8b59d41fc4b3ec35ce8d2e5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279972
Reviewed-by: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Michael Thomsen <mit@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
Reviewed-by: Jacob Richman <jacobr@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
2023-04-03 12:25:55 +00:00

303 lines
9.1 KiB
C++

// Copyright (c) 2014, 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.
#ifndef RUNTIME_VM_OBJECT_GRAPH_H_
#define RUNTIME_VM_OBJECT_GRAPH_H_
#include <memory>
#include "vm/allocation.h"
#include "vm/dart_api_state.h"
#include "vm/thread_stack_resource.h"
namespace dart {
class Array;
class Object;
class CountingPage;
#if defined(DART_ENABLE_HEAP_SNAPSHOT_WRITER)
// Utility to traverse the object graph in an ordered fashion.
// Example uses:
// - find a retaining path from the isolate roots to a particular object, or
// - determine how much memory is retained by some particular object(s).
class ObjectGraph : public ThreadStackResource {
public:
class Stack;
// Allows climbing the search tree all the way to the root.
class StackIterator {
public:
// The object this iterator currently points to.
ObjectPtr Get() const;
// Returns false if there is no parent.
bool MoveToParent();
// Offset into parent for the pointer to current object. -1 if no parent.
intptr_t OffsetFromParent() const;
private:
StackIterator(const Stack* stack, intptr_t index)
: stack_(stack), index_(index) {}
const Stack* stack_;
intptr_t index_;
friend class ObjectGraph::Stack;
DISALLOW_IMPLICIT_CONSTRUCTORS(StackIterator);
};
class Visitor {
public:
// Directs how the search should continue after visiting an object.
enum Direction {
kProceed, // Recurse on this object's pointers.
kBacktrack, // Ignore this object's pointers.
kAbort, // Terminate the entire search immediately.
};
virtual ~Visitor() {}
// Visits the object pointed to by *it. The iterator is only valid
// during this call. This method must not allocate from the heap or
// trigger GC in any way.
virtual Direction VisitObject(StackIterator* it) = 0;
virtual bool visit_weak_persistent_handles() const { return false; }
const char* gc_root_type = NULL;
bool is_traversing = false;
};
typedef struct {
intptr_t length;
const char* gc_root_type;
} RetainingPathResult;
explicit ObjectGraph(Thread* thread);
~ObjectGraph();
// Visits all strongly reachable objects in the isolate's heap, in a
// pre-order, depth first traversal.
void IterateObjects(Visitor* visitor);
void IterateUserObjects(Visitor* visitor);
// Like 'IterateObjects', but restricted to objects reachable from 'root'
// (including 'root' itself).
void IterateObjectsFrom(const Object& root, Visitor* visitor);
void IterateObjectsFrom(intptr_t class_id,
HeapIterationScope* iteration,
Visitor* visitor);
// The number of bytes retained by 'obj'.
intptr_t SizeRetainedByInstance(const Object& obj);
intptr_t SizeReachableByInstance(const Object& obj);
// The number of bytes retained by the set of all objects of the given class.
intptr_t SizeRetainedByClass(intptr_t class_id);
intptr_t SizeReachableByClass(intptr_t class_id);
// Finds some retaining path from the isolate roots to 'obj'. Populates the
// provided array with pairs of (object, offset from parent in words),
// starting with 'obj' itself, as far as there is room. Returns the number
// of objects on the full path. A null input array behaves like a zero-length
// input array. The 'offset' of a root is -1.
//
// To break the trivial path, the handle 'obj' is temporarily cleared during
// the search, but restored before returning. If no path is found (i.e., the
// provided handle was the only way to reach the object), zero is returned.
RetainingPathResult RetainingPath(Object* obj, const Array& path);
// Find the objects that reference 'obj'. Populates the provided array with
// pairs of (object pointing to 'obj', offset of pointer in words), as far as
// there is room. Returns the number of objects found.
//
// An object for which this function answers no inbound references might still
// be live due to references from the stack or embedder handles.
intptr_t InboundReferences(Object* obj, const Array& references);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ObjectGraph);
};
class ChunkedWriter : public ThreadStackResource {
public:
explicit ChunkedWriter(Thread* thread) : ThreadStackResource(thread) {}
virtual intptr_t ReserveChunkPrefixSize() { return 0; }
// Takes ownership of [buffer], must be freed with [malloc].
virtual void WriteChunk(uint8_t* buffer, intptr_t size, bool last) = 0;
};
class FileHeapSnapshotWriter : public ChunkedWriter {
public:
FileHeapSnapshotWriter(Thread* thread,
const char* filename,
bool* success = nullptr);
~FileHeapSnapshotWriter();
virtual void WriteChunk(uint8_t* buffer, intptr_t size, bool last);
private:
void* file_ = nullptr;
bool* success_;
};
class CallbackHeapSnapshotWriter : public ChunkedWriter {
public:
CallbackHeapSnapshotWriter(Thread* thread,
Dart_HeapSnapshotWriteChunkCallback callback,
void* context);
~CallbackHeapSnapshotWriter();
virtual void WriteChunk(uint8_t* buffer, intptr_t size, bool last);
private:
Dart_HeapSnapshotWriteChunkCallback callback_;
void* context_;
};
class VmServiceHeapSnapshotChunkedWriter : public ChunkedWriter {
public:
explicit VmServiceHeapSnapshotChunkedWriter(Thread* thread)
: ChunkedWriter(thread) {}
virtual intptr_t ReserveChunkPrefixSize() { return kMetadataReservation; }
virtual void WriteChunk(uint8_t* buffer, intptr_t size, bool last);
private:
static const intptr_t kMetadataReservation = 512;
};
// Generates a dump of the heap, whose format is described in
// runtime/vm/service/heap_snapshot.md.
class HeapSnapshotWriter : public ThreadStackResource {
public:
HeapSnapshotWriter(Thread* thread, ChunkedWriter* writer)
: ThreadStackResource(thread), writer_(writer) {}
void WriteSigned(int64_t value) {
EnsureAvailable((sizeof(value) * kBitsPerByte) / 7 + 1);
bool is_last_part = false;
while (!is_last_part) {
uint8_t part = value & 0x7F;
value >>= 7;
if ((value == 0 && (part & 0x40) == 0) ||
(value == static_cast<intptr_t>(-1) && (part & 0x40) != 0)) {
is_last_part = true;
} else {
part |= 0x80;
}
buffer_[size_++] = part;
}
}
void WriteUnsigned(uintptr_t value) {
EnsureAvailable((sizeof(value) * kBitsPerByte) / 7 + 1);
bool is_last_part = false;
while (!is_last_part) {
uint8_t part = value & 0x7F;
value >>= 7;
if (value == 0) {
is_last_part = true;
} else {
part |= 0x80;
}
buffer_[size_++] = part;
}
}
void WriteBytes(const void* bytes, intptr_t len) {
EnsureAvailable(len);
memmove(&buffer_[size_], bytes, len);
size_ += len;
}
void ScrubAndWriteUtf8(char* value) {
intptr_t len = strlen(value);
for (intptr_t i = len - 1; i >= 0; i--) {
if (value[i] == '@') {
value[i] = '\0';
}
}
WriteUtf8(value);
}
void WriteUtf8(const char* value) {
intptr_t len = strlen(value);
WriteUnsigned(len);
WriteBytes(value, len);
}
void AssignObjectId(ObjectPtr obj);
intptr_t GetObjectId(ObjectPtr obj) const;
void ClearObjectIds();
void CountReferences(intptr_t count);
void CountExternalProperty();
void AddSmi(SmiPtr smi);
void Write();
static uint32_t GetHeapSnapshotIdentityHash(Thread* thread, ObjectPtr obj);
private:
static uint32_t GetHashHelper(Thread* thread, ObjectPtr obj);
static const intptr_t kPreferredChunkSize = MB;
void SetupCountingPages();
bool OnImagePage(ObjectPtr obj) const;
CountingPage* FindCountingPage(ObjectPtr obj) const;
void EnsureAvailable(intptr_t needed);
void Flush(bool last = false);
ChunkedWriter* writer_ = nullptr;
uint8_t* buffer_ = nullptr;
intptr_t size_ = 0;
intptr_t capacity_ = 0;
intptr_t class_count_ = 0;
intptr_t object_count_ = 0;
intptr_t reference_count_ = 0;
intptr_t external_property_count_ = 0;
struct ImagePageRange {
uword base;
uword size;
};
// There are up to 4 images to consider:
// {instructions, data} x {vm isolate, current isolate}
static const intptr_t kMaxImagePages = 4;
ImagePageRange image_page_ranges_[kMaxImagePages];
MallocGrowableArray<SmiPtr> smis_;
DISALLOW_COPY_AND_ASSIGN(HeapSnapshotWriter);
};
class CountObjectsVisitor : public ObjectVisitor, public HandleVisitor {
public:
CountObjectsVisitor(Thread* thread, intptr_t class_count);
~CountObjectsVisitor() {}
void VisitObject(ObjectPtr obj);
void VisitHandle(uword addr);
std::unique_ptr<intptr_t[]> new_count_;
std::unique_ptr<intptr_t[]> new_size_;
std::unique_ptr<intptr_t[]> new_external_size_;
std::unique_ptr<intptr_t[]> old_count_;
std::unique_ptr<intptr_t[]> old_size_;
std::unique_ptr<intptr_t[]> old_external_size_;
DISALLOW_COPY_AND_ASSIGN(CountObjectsVisitor);
};
#endif // !defined(DART_ENABLE_HEAP_SNAPSHOT_WRITER)
} // namespace dart
#endif // RUNTIME_VM_OBJECT_GRAPH_H_