Closed
Bug 961323
Opened 11 years ago
Closed 9 years ago
[ubi::Node] should be able to find paths by which some objects refer to others
Categories
(Core :: JavaScript Engine, defect)
Core
JavaScript Engine
Tracking
()
RESOLVED
FIXED
mozilla47
Tracking | Status | |
---|---|---|
firefox47 | --- | fixed |
People
(Reporter: jimb, Assigned: fitzgen)
References
(Blocks 2 open bugs, )
Details
Attachments
(1 file, 2 obsolete files)
(deleted),
patch
|
fitzgen
:
review+
|
Details | Diff | Splinter Review |
Debugger should be able to take a set of objects for which we'd like to find names, and then for each object report how it can be reached from a given set of globals.
Here are notes copied from: https://etherpad.mozilla.org/memory-tool
# Path Finding (BFS)
Can pass in multiple objects and share work (alternatively, can we just maintain some state for each a snapshot so we don't need to pass in multiple at once to share work, but just share work across all calls to a specific snapshot by default/automatically?)
On second iteration, we can add a cost to edges so that any non-JS edges are much more costly than plain JS references. This allows us to get JS expressions as the retained path in most cases.
As a JS function, it would take a set of roots and an object (or set of objects?) and give us a representation of the path from the root to the given object.
## Path representation
An array of path descriptors starting from a global and ending at this object. Each path descriptor describes the type of node and its outgoing edge. Path descriptors have the following form:
{
type: NodeType,
edge: {
type: EdgeType,
value: EdgeValue
}
}
Where:
* NodeType describes the type of the node. It is one of:
* "global"
* "object"
* "environment"
* EdgeType describes the type of the outgoing edge to the next node. It is one of:
* "property": JS property or index access.
* "variable": A reference to a variable.
* "timeout": A reference to a function passed to setTimeout.
* "interval": A reference to a function passed to setInterval.
* EdgeValue is the property name, index value, variable name, or ms interval/timeout.
Comment 1•11 years ago
|
||
FWIW, I have some scripts that do a similar thing for paths in CC and GC heaps logs (a fairly low-level view of memory), that we've used to fix a number of Gecko leaks, as well as a few Gaia ones.
https://github.com/amccreight/heapgraph/blob/master/cc/find_roots.py
https://github.com/amccreight/heapgraph/blob/master/g/find_roots.py
This is based on some earlier scripts by (I think) peterv, and it just does the simplest possible thing, and inverts the graph, then floods out from the rooting object, reporting paths as it goes.
Reporter | ||
Updated•11 years ago
|
Blocks: memory-platform
Reporter | ||
Comment 2•11 years ago
|
||
(In reply to Andrew McCreight [:mccr8] from comment #1)
> FWIW, I have some scripts that do a similar thing for paths in CC and GC
> heaps logs (a fairly low-level view of memory), that we've used to fix a
> number of Gecko leaks, as well as a few Gaia ones.
> https://github.com/amccreight/heapgraph/blob/master/cc/find_roots.py
> https://github.com/amccreight/heapgraph/blob/master/g/find_roots.py
> This is based on some earlier scripts by (I think) peterv, and it just does
> the simplest possible thing, and inverts the graph, then floods out from the
> rooting object, reporting paths as it goes.
Yes, these will be similar results, but with effort put into providing paths comprehensible to JS developers. We also want to be able to use this code to generate "names" for the objects in the "top N by retained size" lists, and similar enumerations.
Comment 3•11 years ago
|
||
Probably a lot of us have written this a couple times by now. :)
Assignee | ||
Updated•10 years ago
|
URL: devtools-memory-apis
Assignee | ||
Updated•9 years ago
|
Assignee: nobody → nfitzgerald
Status: NEW → ASSIGNED
Assignee | ||
Comment 5•9 years ago
|
||
This commit adds `JS::ubi::ShortestPaths` which can find the N shortest
retaining paths starting from some root for any number of target nodes.
Attachment #8718078 -
Flags: review?(jimb)
Assignee | ||
Comment 6•9 years ago
|
||
Assignee | ||
Comment 7•9 years ago
|
||
This iteration halts the traversal if we have found every path we will ever
record for every target.
Attachment #8718083 -
Flags: review?(jimb)
Assignee | ||
Updated•9 years ago
|
Attachment #8718078 -
Attachment is obsolete: true
Attachment #8718078 -
Flags: review?(jimb)
Assignee | ||
Comment 8•9 years ago
|
||
Assignee | ||
Updated•9 years ago
|
Summary: [jsdbg2] Debugger should be able to find paths by which some objects refer to others → [ubi::Node] should be able to find paths by which some objects refer to others
Reporter | ||
Comment 9•9 years ago
|
||
Comment on attachment 8718083 [details] [diff] [review]
Add a method for finding shortest retaining paths of `JS::ubi::Node` heap graphs
Review of attachment 8718083 [details] [diff] [review]:
-----------------------------------------------------------------
Still reviewing, just publishing as I go...
::: js/public/UbiNodeShortestPaths.h
@@ +79,5 @@
> +{
> + private:
> + // Types, type aliases, and data members.
> +
> + friend struct Handler;
Is this needed? I thought member types had access to everything in the parent types, even without being friended.
@@ +118,5 @@
> + if (first && !back->init(origin, edge))
> + return false;
> +
> +
> + if (!shortestPaths.targets_.has(edge.referent))
Why two blank lines above this?
@@ +121,5 @@
> +
> + if (!shortestPaths.targets_.has(edge.referent))
> + return true;
> +
> + auto ptr = shortestPaths.paths_.lookupForAdd(edge.referent);
At this point, either `edge` or `back` has the name we must use, depending on whether `first` is true or not. Using the wrong one would leave null UniquePtrs in surprising places. So I think it'd be worth commenting briefly that our source of names must always take `first` into account.
@@ +235,5 @@
> + * responsibility to handle and report the OOM.
> + */
> + static mozilla::Maybe<ShortestPaths>
> + Create(JSRuntime* rt, AutoCheckCannotGC& noGC, uint32_t maxNumPaths, const Node& root, NodeSet&& targets) {
> + MOZ_ASSERT(targets.count() > 0);
You probably want to also assert that the number of paths requested per target is greater than zero.
Reporter | ||
Comment 10•9 years ago
|
||
Comment on attachment 8718083 [details] [diff] [review]
Add a method for finding shortest retaining paths of `JS::ubi::Node` heap graphs
Review of attachment 8718083 [details] [diff] [review]:
-----------------------------------------------------------------
More in-progress comments...
::: js/public/UbiNodeShortestPaths.h
@@ +152,5 @@
> + }
> +
> + };
> +
> + using Traversal = BreadthFirst<Handler>;
This declaration is identical to the one in Handler. Would it work to drop the one in Handler, and just let it see this one? (Perhaps put it up above, for clarity?)
::: js/src/builtin/TestingFunctions.cpp
@@ +2569,5 @@
> + RootedNativeObject objs(cx, &args[1].toObject().as<ArrayObject>());
> + if (objs->getDenseInitializedLength() == 0) {
> + ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
> + JSDVG_SEARCH_STACK, args[1], nullptr,
> + "not a dense array object with one more elements", nullptr);
"one or more"
@@ +2597,5 @@
> +
> + Rooted<GCVector<GCVector<GCVector<Value>>>> values(cx, GCVector<GCVector<GCVector<Value>>>(cx));
> + Vector<Vector<Vector<JS::ubi::EdgeName>>> names(cx);
> + {
> + JS::AutoCheckCannotGC noGC(cx->runtime());
It might be worth a comment to mention why we have to copy everything out into a GC-stable form, within the scope of an AutoCheckCannotGC. Simply citing ShortestPaths' requirements is enough.
@@ +2607,5 @@
> + }
> +
> + for (size_t i = 0; i < length; i++) {
> + RootedValue val(cx, objs->getDenseElement(i));
> + JS::ubi::Node node(val);
I think there's actually no need for this: there's a deliberate implicit conversion between HandleValue and JS::ubi::Node.
@@ +2614,5 @@
> + return false;
> + }
> + }
> +
> + JS::ubi::Node root(args[0]);
Same here.
@@ +2617,5 @@
> +
> + JS::ubi::Node root(args[0]);
> + auto maybeShortestPaths = JS::ubi::ShortestPaths::Create(cx->runtime(), noGC, maxNumPaths,
> + root, mozilla::Move(targets));
> + if (maybeShortestPaths.isNothing())
ShortestPaths::Create doesn't report OOMs, right? So don't we need to `ReportOutOfMemory` here?
@@ +2629,5 @@
> + return false;
> + }
> +
> + RootedValue val(cx, objs->getDenseElement(i));
> + JS::ubi::Node target(val);
Another unneeded explicit conversion.
@@ +2639,5 @@
> + for (auto& part : path) {
> + if (!pathVals.append(part->predecessor().exposeToJS()) ||
> + !pathNames.append(mozilla::Move(part->name())))
> + {
> + return false;
Just checking: both `GCVector` and the `Vector` we're using here automatically report OOM on the context, right?
Reporter | ||
Comment 11•9 years ago
|
||
Comment on attachment 8718083 [details] [diff] [review]
Add a method for finding shortest retaining paths of `JS::ubi::Node` heap graphs
Review of attachment 8718083 [details] [diff] [review]:
-----------------------------------------------------------------
Looks great. r=me with comments addressed.
Attachment #8718083 -
Flags: review?(jimb) → review+
Assignee | ||
Comment 12•9 years ago
|
||
Thanks for the speedy review!
(In reply to Jim Blandy :jimb from comment #10)
> ::: js/public/UbiNodeShortestPaths.h
> @@ +152,5 @@
> > + }
> > +
> > + };
> > +
> > + using Traversal = BreadthFirst<Handler>;
>
> This declaration is identical to the one in Handler. Would it work to drop
> the one in Handler, and just let it see this one? (Perhaps put it up above,
> for clarity?)
I thought that it might complain about Handler only being forward declared at that point in time, but I didn't actually try it out. I will see if I can do that.
> @@ +2607,5 @@
> > + }
> > +
> > + for (size_t i = 0; i < length; i++) {
> > + RootedValue val(cx, objs->getDenseElement(i));
> > + JS::ubi::Node node(val);
>
> I think there's actually no need for this: there's a deliberate implicit
> conversion between HandleValue and JS::ubi::Node.
Unfortunately, there is no conversion for non-Handle, raw Value, and getDenseElement returns a raw Value, so the rooting is necessary. Blech.
> @@ +2617,5 @@
> > +
> > + JS::ubi::Node root(args[0]);
> > + auto maybeShortestPaths = JS::ubi::ShortestPaths::Create(cx->runtime(), noGC, maxNumPaths,
> > + root, mozilla::Move(targets));
> > + if (maybeShortestPaths.isNothing())
>
> ShortestPaths::Create doesn't report OOMs, right? So don't we need to
> `ReportOutOfMemory` here?
Good catch!
> @@ +2639,5 @@
> > + for (auto& part : path) {
> > + if (!pathVals.append(part->predecessor().exposeToJS()) ||
> > + !pathNames.append(mozilla::Move(part->name())))
> > + {
> > + return false;
>
> Just checking: both `GCVector` and the `Vector` we're using here
> automatically report OOM on the context, right?
Yes, the default alloc policy takes a cx on construction, which they report OOMs on.
Assignee | ||
Comment 13•9 years ago
|
||
Updated per review.
New try push: https://treeherder.mozilla.org/#/jobs?repo=try&revision=4831d2ffa20c
Attachment #8718430 -
Flags: review+
Assignee | ||
Updated•9 years ago
|
Attachment #8718083 -
Attachment is obsolete: true
Assignee | ||
Updated•9 years ago
|
Keywords: checkin-needed
Comment 14•9 years ago
|
||
Keywords: checkin-needed
Comment 15•9 years ago
|
||
backed out for bustage like https://treeherder.mozilla.org/logviewer.html#?job_id=21615859&repo=mozilla-inbound
Flags: needinfo?(nfitzgerald)
Comment 16•9 years ago
|
||
Assignee | ||
Comment 17•9 years ago
|
||
(In reply to Carsten Book [:Tomcat] from comment #15)
> backed out for bustage like
> https://treeherder.mozilla.org/logviewer.html#?job_id=21615859&repo=mozilla-
> inbound
This is because the dependent bugs which were also marked checkin-needed were not landed first. Should I not mark multiple bugs checkin-needed when there are ordering dependencies between them?
Flags: needinfo?(nfitzgerald) → needinfo?(cbook)
Comment 18•9 years ago
|
||
(In reply to Nick Fitzgerald [:fitzgen] [⏰PST; UTC-8] from comment #17)
> (In reply to Carsten Book [:Tomcat] from comment #15)
> > backed out for bustage like
> > https://treeherder.mozilla.org/logviewer.html#?job_id=21615859&repo=mozilla-
> > inbound
>
> This is because the dependent bugs which were also marked checkin-needed
> were not landed first. Should I not mark multiple bugs checkin-needed when
> there are ordering dependencies between them?
oh a note like checkin bug x first (or a order in which they need checked in would be awesome).
Flags: needinfo?(cbook)
Assignee | ||
Comment 19•9 years ago
|
||
Ah ok, I thought that the "depends on" field already described that, but I suppose it is good to highlight.
Well, the dependencies have been checked into m-i now, so re-adding checkin-needed.
Keywords: checkin-needed
Comment 20•9 years ago
|
||
Keywords: checkin-needed
Comment 21•9 years ago
|
||
bugherder |
Status: ASSIGNED → RESOLVED
Closed: 9 years ago
status-firefox47:
--- → fixed
Resolution: --- → FIXED
Target Milestone: --- → mozilla47
You need to log in
before you can comment on or make changes to this bug.
Description
•