Add RPC and UI for reachable size of an object / all instances of a class.

BUG=http://dartbug.com/24945
R=johnmccutchan@google.com

Review URL: https://codereview.chromium.org/1453983002 .
This commit is contained in:
Ryan Macnak
2015-11-20 10:02:16 -08:00
parent 8b6fb86397
commit 75cd019a9c
11 changed files with 230 additions and 5 deletions
@@ -15,6 +15,7 @@ import 'package:polymer/polymer.dart';
class ClassViewElement extends ObservatoryElement {
@published Class cls;
@observable ServiceMap instances;
@observable int reachableBytes;
@observable int retainedBytes;
@observable ObservableList mostRetained;
SampleBufferControlElement sampleBufferControlElement;
@@ -44,6 +45,12 @@ class ClassViewElement extends ObservatoryElement {
}
// TODO(koda): Add no-arg "calculate-link" instead of reusing "eval-link".
Future<ServiceObject> reachableSize(var dummy) {
return cls.isolate.getReachableSize(cls).then((Instance obj) {
reachableBytes = int.parse(obj.valueAsString);
});
}
Future<ServiceObject> retainedSize(var dummy) {
return cls.isolate.getRetainedSize(cls).then((Instance obj) {
retainedBytes = int.parse(obj.valueAsString);
@@ -180,6 +180,19 @@
</template>
</div>
</div>
<div class="memberItem">
<div class="memberName">total reachable memory size</div>
<div class="memberValue">
<template if="{{ reachableBytes == null }}">
<eval-link callback="{{ reachableSize }}"
label="[calculate]">
</eval-link>
</template>
<template if="{{ reachableBytes != null }}">
{{ reachableBytes | formatSize }}
</template>
</div>
</div>
<div class="memberItem">
<div class="memberName">total retained memory size</div>
<div class="memberValue">
@@ -15,10 +15,18 @@ class ObjectCommonElement extends ObservatoryElement {
@published ServiceMap path;
@published ServiceMap inboundReferences;
@observable int retainedBytes = null;
@observable int reachableBytes = null;
ObjectCommonElement.created() : super.created();
// TODO(koda): Add no-arg "calculate-link" instead of reusing "eval-link".
Future<ServiceObject> reachableSize(var dummy) {
return object.isolate.getReachableSize(object).then((Instance obj) {
// TODO(turnidge): Handle collected/expired objects gracefully.
reachableBytes = int.parse(obj.valueAsString);
});
}
Future<ServiceObject> retainedSize(var dummy) {
return object.isolate.getRetainedSize(object).then((Instance obj) {
// TODO(turnidge): Handle collected/expired objects gracefully.
@@ -26,6 +26,20 @@
<div class="memberValue">{{ object.size | formatSize }}</div>
</div>
<div class="memberItem" title="Space reachable from this object, excluding class references">
<div class="memberName">reachable size</div>
<div class="memberValue">
<template if="{{ reachableBytes == null }}">
<eval-link callback="{{ reachableSize }}"
label="[calculate]">
</eval-link>
</template>
<template if="{{ reachableBytes != null }}">
{{ reachableBytes | formatSize }}
</template>
</div>
</div>
<div class="memberItem" title="Space that would be reclaimed if references to this object were replaced with null">
<div class="memberName">retained size</div>
<div class="memberValue">
@@ -1688,6 +1688,13 @@ class Isolate extends ServiceObjectOwner with Coverage {
return invokeRpc('evaluateInFrame', params);
}
Future<ServiceObject> getReachableSize(ServiceObject target) {
Map params = {
'targetId': target.id,
};
return invokeRpc('_getReachableSize', params);
}
Future<ServiceObject> getRetainedSize(ServiceObject target) {
Map params = {
'targetId': target.id,
@@ -0,0 +1,71 @@
// Copyright (c) 2015, 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.
// VMOptions=--error_on_bad_type --error_on_bad_override
import 'package:observatory/service_io.dart';
import 'package:unittest/unittest.dart';
import 'test_helper.dart';
class Pair {
var x, y;
}
var p1;
var p2;
buildGraph() {
p1 = new Pair();
p2 = new Pair();
// Adds to both reachable and retained size.
p1.x = new List();
p2.x = new List();
// Adds to reachable size only.
p1.y = p2.y = new List();
}
getReachableSize(ServiceObject obj) {
return obj.isolate.getReachableSize(obj).then((Instance obj) {
return int.parse(obj.valueAsString);
});
}
getRetainedSize(ServiceObject obj) {
return obj.isolate.getRetainedSize(obj).then((Instance obj) {
return int.parse(obj.valueAsString);
});
}
var tests = [
(Isolate isolate) async {
Instance p1 = await rootLibraryFieldValue(isolate, "p1");
Instance p2 = await rootLibraryFieldValue(isolate, "p2");
// In general, shallow <= retained <= reachable. In this program,
// 0 < shallow < retained < reachable.
int p1_shallow = p1.size;
int p1_retained = await getRetainedSize(p1);
int p1_reachable = await getReachableSize(p1);
expect(0, lessThan(p1_shallow));
expect(p1_shallow, lessThan(p1_retained));
expect(p1_retained, lessThan(p1_reachable));
int p2_shallow = p2.size;
int p2_retained = await getRetainedSize(p2);
int p2_reachable = await getReachableSize(p2);
expect(0, lessThan(p2_shallow));
expect(p2_shallow, lessThan(p2_retained));
expect(p2_retained, lessThan(p2_reachable));
expect(p1_shallow, equals(p2_shallow));
expect(p1_retained, equals(p2_retained));
expect(p1_reachable, equals(p2_reachable));
},
];
main(args) => runIsolateTests(args, tests, testeeBefore: buildGraph);
@@ -319,6 +319,17 @@ Future<Class> getClassFromRootLib(Isolate isolate, String className) async {
}
Future<Instance> rootLibraryFieldValue(Isolate isolate,
String fieldName) async {
Library rootLib = await isolate.rootLibrary.load();
Field field = rootLib.variables.singleWhere((v) => v.name == fieldName);
await field.load();
Instance value = field.staticValue;
await value.load();
return value;
}
/// Runs [tests] in sequence, each of which should take an [Isolate] and
/// return a [Future]. Code for setting up state can run before and/or
/// concurrently with the tests. Uses [mainArgs] to determine whether
+49 -1
View File
@@ -186,7 +186,41 @@ void ObjectGraph::IterateObjectsFrom(const Object& root,
RawObject* root_raw = root.raw();
stack.VisitPointer(&root_raw);
stack.TraverseGraph(visitor);
// TODO(koda): Optimize if we only visited a small subgraph.
Unmarker::UnmarkAll(isolate());
}
class InstanceAccumulator : public ObjectVisitor {
public:
explicit InstanceAccumulator(ObjectGraph::Stack* stack,
intptr_t class_id,
Isolate* isolate)
: ObjectVisitor(isolate), stack_(stack), class_id_(class_id) { }
void VisitObject(RawObject* obj) {
if (obj->GetClassId() == class_id_) {
RawObject* rawobj = obj;
stack_->VisitPointer(&rawobj);
}
}
private:
ObjectGraph::Stack* stack_;
const intptr_t class_id_;
DISALLOW_COPY_AND_ASSIGN(InstanceAccumulator);
};
void ObjectGraph::IterateObjectsFrom(intptr_t class_id,
ObjectGraph::Visitor* visitor) {
NoSafepointScope no_safepoint_scope_;
Stack stack(isolate());
InstanceAccumulator accumulator(&stack, class_id, isolate());
isolate()->heap()->IterateObjects(&accumulator);
stack.TraverseGraph(visitor);
Unmarker::UnmarkAll(isolate());
}
@@ -240,6 +274,13 @@ intptr_t ObjectGraph::SizeRetainedByInstance(const Object& obj) {
}
intptr_t ObjectGraph::SizeReachableByInstance(const Object& obj) {
SizeVisitor total;
IterateObjectsFrom(obj, &total);
return total.size();
}
intptr_t ObjectGraph::SizeRetainedByClass(intptr_t class_id) {
SizeVisitor total;
IterateObjects(&total);
@@ -251,6 +292,13 @@ intptr_t ObjectGraph::SizeRetainedByClass(intptr_t class_id) {
}
intptr_t ObjectGraph::SizeReachableByClass(intptr_t class_id) {
SizeVisitor total;
IterateObjectsFrom(class_id, &total);
return total.size();
}
class RetainingPathVisitor : public ObjectGraph::Visitor {
public:
// We cannot use a GrowableObjectArray, since we must not trigger GC.
+3
View File
@@ -63,12 +63,15 @@ class ObjectGraph : public StackResource {
// 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, 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),
+1
View File
@@ -614,6 +614,7 @@ class RawObject {
friend class Scavenger;
friend class ScavengerVisitor;
friend class SizeExcludingClassVisitor; // GetClassId
friend class InstanceAccumulator; // GetClassId
friend class RetainingPathVisitor; // GetClassId
friend class SkippedCodeFunctions; // StorePointer
friend class InstructionsReader; // tags_ check
+46 -4
View File
@@ -1764,16 +1764,14 @@ static bool GetRetainingPath(Thread* thread, JSONStream* js) {
static const MethodParameter* get_retained_size_params[] = {
ISOLATE_PARAMETER,
new IdParameter("targetId", true),
NULL,
};
static bool GetRetainedSize(Thread* thread, JSONStream* js) {
const char* target_id = js->LookupParam("targetId");
if (target_id == NULL) {
PrintMissingParamError(js, "targetId");
return true;
}
ASSERT(target_id != NULL);
ObjectIdRing::LookupResult lookup_result;
Object& obj = Object::Handle(LookupHeapObject(thread, target_id,
&lookup_result));
@@ -1806,6 +1804,48 @@ static bool GetRetainedSize(Thread* thread, JSONStream* js) {
}
static const MethodParameter* get_reachable_size_params[] = {
ISOLATE_PARAMETER,
new IdParameter("targetId", true),
NULL,
};
static bool GetReachableSize(Thread* thread, JSONStream* js) {
const char* target_id = js->LookupParam("targetId");
ASSERT(target_id != NULL);
ObjectIdRing::LookupResult lookup_result;
Object& obj = Object::Handle(LookupHeapObject(thread, target_id,
&lookup_result));
if (obj.raw() == Object::sentinel().raw()) {
if (lookup_result == ObjectIdRing::kCollected) {
PrintSentinel(js, kCollectedSentinel);
} else if (lookup_result == ObjectIdRing::kExpired) {
PrintSentinel(js, kExpiredSentinel);
} else {
PrintInvalidParamError(js, "targetId");
}
return true;
}
// TODO(rmacnak): There is no way to get the size retained by a class object.
// SizeRetainedByClass should be a separate RPC.
if (obj.IsClass()) {
const Class& cls = Class::Cast(obj);
ObjectGraph graph(thread);
intptr_t retained_size = graph.SizeReachableByClass(cls.id());
const Object& result = Object::Handle(Integer::New(retained_size));
result.PrintJSON(js, true);
return true;
}
ObjectGraph graph(thread);
intptr_t retained_size = graph.SizeReachableByInstance(obj);
const Object& result = Object::Handle(Integer::New(retained_size));
result.PrintJSON(js, true);
return true;
}
static const MethodParameter* evaluate_params[] = {
ISOLATE_PARAMETER,
NULL,
@@ -3455,6 +3495,8 @@ static const ServiceMethodDescriptor service_methods_[] = {
get_object_by_address_params },
{ "_getPorts", GetPorts,
get_ports_params },
{ "_getReachableSize", GetReachableSize,
get_reachable_size_params },
{ "_getRetainedSize", GetRetainedSize,
get_retained_size_params },
{ "_getRetainingPath", GetRetainingPath,