mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
![Ryan Macnak](/assets/img/avatar_default.png)
This replaces _requestHeapSnapshot and _getObjectByAddresses, which relied on the invalid assumption that objects don't move and the heap hasn't mutated since the initial snapshot. Updates Observatory to the new format. Adds treemap visualization of the dominator tree. Adds the ability to save and load heap snapshots to files. Removes analysis grouping references by source and target class. Removes ability to navigate from a pixel in the fragmentation view to inspector. Removes ability to query the top retaining instance from an ordinary class browser. Change-Id: Ia7781c05d43bf3ec149f8b4ecab803b37c3ee981 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/112181 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Ben Konyi <bkonyi@google.com>
200 lines
6.8 KiB
C++
200 lines
6.8 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.
|
||
|
||
#include "vm/object_graph.h"
|
||
#include "platform/assert.h"
|
||
#include "vm/unit_test.h"
|
||
|
||
namespace dart {
|
||
|
||
#if !defined(PRODUCT)
|
||
|
||
class CounterVisitor : public ObjectGraph::Visitor {
|
||
public:
|
||
// Records the number of objects and total size visited, excluding 'skip'
|
||
// and any objects only reachable through 'skip'.
|
||
CounterVisitor(RawObject* skip, RawObject* expected_parent)
|
||
: count_(0), size_(0), skip_(skip), expected_parent_(expected_parent) {}
|
||
|
||
virtual Direction VisitObject(ObjectGraph::StackIterator* it) {
|
||
RawObject* obj = it->Get();
|
||
if (obj == skip_) {
|
||
EXPECT(it->MoveToParent());
|
||
EXPECT_EQ(expected_parent_, it->Get());
|
||
return kBacktrack;
|
||
}
|
||
++count_;
|
||
size_ += obj->HeapSize();
|
||
return kProceed;
|
||
}
|
||
|
||
int count() const { return count_; }
|
||
int size() const { return size_; }
|
||
|
||
private:
|
||
int count_;
|
||
intptr_t size_;
|
||
RawObject* skip_;
|
||
RawObject* expected_parent_;
|
||
};
|
||
|
||
ISOLATE_UNIT_TEST_CASE(ObjectGraph) {
|
||
Isolate* isolate = thread->isolate();
|
||
// Create a simple object graph with objects a, b, c, d:
|
||
// a+->b+->c
|
||
// + +
|
||
// | v
|
||
// +-->d
|
||
Array& a = Array::Handle(Array::New(12, Heap::kNew));
|
||
Array& b = Array::Handle(Array::New(2, Heap::kOld));
|
||
Array& c = Array::Handle(Array::New(0, Heap::kOld));
|
||
Array& d = Array::Handle(Array::New(0, Heap::kOld));
|
||
a.SetAt(10, b);
|
||
b.SetAt(0, c);
|
||
b.SetAt(1, d);
|
||
a.SetAt(11, d);
|
||
intptr_t a_size = a.raw()->HeapSize();
|
||
intptr_t b_size = b.raw()->HeapSize();
|
||
intptr_t c_size = c.raw()->HeapSize();
|
||
intptr_t d_size = d.raw()->HeapSize();
|
||
{
|
||
// No more allocation; raw pointers ahead.
|
||
SafepointOperationScope safepoint(thread);
|
||
RawObject* b_raw = b.raw();
|
||
// Clear handles to cut unintended retained paths.
|
||
b = Array::null();
|
||
c = Array::null();
|
||
d = Array::null();
|
||
ObjectGraph graph(thread);
|
||
{
|
||
HeapIterationScope iteration_scope(thread, true);
|
||
{
|
||
// Compare count and size when 'b' is/isn't skipped.
|
||
CounterVisitor with(Object::null(), Object::null());
|
||
graph.IterateObjectsFrom(a, &with);
|
||
CounterVisitor without(b_raw, a.raw());
|
||
graph.IterateObjectsFrom(a, &without);
|
||
// Only 'b' and 'c' were cut off.
|
||
EXPECT_EQ(2, with.count() - without.count());
|
||
EXPECT_EQ(b_size + c_size, with.size() - without.size());
|
||
}
|
||
{
|
||
// Like above, but iterate over the entire isolate. The counts and sizes
|
||
// are thus larger, but the difference should still be just 'b' and 'c'.
|
||
CounterVisitor with(Object::null(), Object::null());
|
||
graph.IterateObjects(&with);
|
||
CounterVisitor without(b_raw, a.raw());
|
||
graph.IterateObjects(&without);
|
||
EXPECT_EQ(2, with.count() - without.count());
|
||
EXPECT_EQ(b_size + c_size, with.size() - without.size());
|
||
}
|
||
}
|
||
EXPECT_EQ(a_size + b_size + c_size + d_size,
|
||
graph.SizeRetainedByInstance(a));
|
||
}
|
||
{
|
||
// Get hold of c again.
|
||
b ^= a.At(10);
|
||
c ^= b.At(0);
|
||
b = Array::null();
|
||
ObjectGraph graph(thread);
|
||
// A retaining path should end like this: c <- b <- a <- ...
|
||
{
|
||
HANDLESCOPE(thread);
|
||
// Test null, empty, and length 1 array.
|
||
intptr_t null_length =
|
||
graph.RetainingPath(&c, Object::null_array()).length;
|
||
intptr_t empty_length =
|
||
graph.RetainingPath(&c, Object::empty_array()).length;
|
||
Array& path = Array::Handle(Array::New(1, Heap::kNew));
|
||
intptr_t one_length = graph.RetainingPath(&c, path).length;
|
||
EXPECT_EQ(null_length, empty_length);
|
||
EXPECT_EQ(null_length, one_length);
|
||
EXPECT_LE(3, null_length);
|
||
}
|
||
{
|
||
HANDLESCOPE(thread);
|
||
Array& path = Array::Handle(Array::New(6, Heap::kNew));
|
||
// Trigger a full GC to increase probability of concurrent tasks.
|
||
isolate->heap()->CollectAllGarbage();
|
||
intptr_t length = graph.RetainingPath(&c, path).length;
|
||
EXPECT_LE(3, length);
|
||
Array& expected_c = Array::Handle();
|
||
expected_c ^= path.At(0);
|
||
// c is the first element in b.
|
||
Smi& offset_from_parent = Smi::Handle();
|
||
offset_from_parent ^= path.At(1);
|
||
EXPECT_EQ(Array::element_offset(0),
|
||
offset_from_parent.Value() * kWordSize);
|
||
Array& expected_b = Array::Handle();
|
||
expected_b ^= path.At(2);
|
||
// b is the element with index 10 in a.
|
||
offset_from_parent ^= path.At(3);
|
||
EXPECT_EQ(Array::element_offset(10),
|
||
offset_from_parent.Value() * kWordSize);
|
||
Array& expected_a = Array::Handle();
|
||
expected_a ^= path.At(4);
|
||
EXPECT(expected_c.raw() == c.raw());
|
||
EXPECT(expected_b.raw() == a.At(10));
|
||
EXPECT(expected_a.raw() == a.raw());
|
||
}
|
||
}
|
||
}
|
||
|
||
static void WeakHandleFinalizer(void* isolate_callback_data,
|
||
Dart_WeakPersistentHandle handle,
|
||
void* peer) {}
|
||
|
||
ISOLATE_UNIT_TEST_CASE(RetainingPathGCRoot) {
|
||
Dart_PersistentHandle persistent_handle;
|
||
Dart_WeakPersistentHandle weak_persistent_handle;
|
||
Array& path = Array::Handle(Array::New(1, Heap::kNew));
|
||
ObjectGraph graph(thread);
|
||
Dart_Handle handle = Api::NewHandle(thread, path.raw());
|
||
|
||
// GC root should be a local handle
|
||
auto result = graph.RetainingPath(&path, path);
|
||
EXPECT_STREQ(result.gc_root_type, "local handle");
|
||
|
||
// GC root should now be a weak persistent handle
|
||
{
|
||
TransitionVMToNative transition(thread);
|
||
weak_persistent_handle = Dart_NewWeakPersistentHandle(
|
||
handle, reinterpret_cast<void*>(0xdeadbeef), 128, WeakHandleFinalizer);
|
||
}
|
||
result = graph.RetainingPath(&path, path);
|
||
EXPECT_STREQ(result.gc_root_type, "weak persistent handle");
|
||
|
||
// GC root should now be a persistent handle
|
||
{
|
||
TransitionVMToNative transition(thread);
|
||
persistent_handle = Dart_NewPersistentHandle(handle);
|
||
}
|
||
result = graph.RetainingPath(&path, path);
|
||
EXPECT_STREQ(result.gc_root_type, "persistent handle");
|
||
|
||
// Delete the persistent handle. GC root should now be weak persistent handle
|
||
{
|
||
TransitionVMToNative transition(thread);
|
||
Dart_DeletePersistentHandle(persistent_handle);
|
||
persistent_handle = NULL;
|
||
}
|
||
result = graph.RetainingPath(&path, path);
|
||
EXPECT_STREQ(result.gc_root_type, "weak persistent handle");
|
||
|
||
// Delete the weak persistent handle. GC root should now be local handle.
|
||
{
|
||
TransitionVMToNative transition(thread);
|
||
Dart_DeleteWeakPersistentHandle(Api::CastIsolate(thread->isolate()),
|
||
weak_persistent_handle);
|
||
weak_persistent_handle = NULL;
|
||
}
|
||
result = graph.RetainingPath(&path, path);
|
||
EXPECT_STREQ(result.gc_root_type, "local handle");
|
||
}
|
||
|
||
#endif // !defined(PRODUCT)
|
||
|
||
} // namespace dart
|