Memory Tooling in Firefox Developer Tools in 2014
A big push for the Firefox Developer Tools team this year is performance tools. Jim Blandy and I are collaborating on the memory half of performance tooling. What follows is a broad overview of our plans.
Category: A key/value pair with which we can tag an individual
ubi::Node. Some categories are simple booleans, such as whether a DOM node is orphaned from its document. Others may have a value, for example an object may be categorized by its prototype and constructor.
ubi::Nodescan have to many categories!
Census: A semi-lightweight traversal of the heap that provides accurate category counts without saving the full heap state. It gives us totals, but not the specifics of individuals.
Snapshot: A heavyweight traversal of the heap. It saves the full heap state for later inspection by creating a core dump.
Core dump: A binary blob containing the full serialized heap state at a past instant in time.
A Recurring Theme
As we build the infrastructure and lay the foundation for the memory panel, we
will expose utility and testing functions developers can use now. Generally, the
console object will expose these functions.
The benefit of this approach is two-fold. First, it enables developers to cash in on our work quickly. Second, it gives us a larger testing population; helping us catch and fix bugs as soon as possible.
Graph Algorithms on the Heap
Depth First Search and Dominator Trees
y, then any path from the global window to
must pass through
x. We can use this information in two practical ways:
If you nullify all references to
ywill also become unreachable and will eventually be garbage collected.
We can calculate the retained size of
x. That is, the amount of memory that will be reclaimed if
x(and therefore also every
y) were to be garbage collected.
We can expose this information to developers with
Breadth First Search
By doing a BFS in the heap graph from the global window to an object, we find
the shortest retaining path for that object. We can use this path to construct
a developer-friendly label for that object. Often the label we provide will be a
"window.MyApp.WidgetView.element". Other times, we will be forced to display
labels that cannot be evaluated in the console:
"window.[[requestAnimationFrame renderLoop]].[[ closure environment ]].player.sprite".
This can be exposed to developers as a useful little pair of methods on
console. If you expect an object to be reclaimed by GC, you will be able to
tag it with
console.expectGarbageCollected(obj). Next, you would perform
whatever actions are supposed to trigger the clean up of that object. Finally,
you could call
console.logRetained() to log the retaining path of any objects
that you tagged via
console.expectGarbageCollected that have not been garbage
collected. I realize these aren't the greatest method names;
please tweet me your suggestions!
Tracking Allocation Sites
We will track the allocation site of every object in the heap. Allocation sites come into play in a few ways.
First, if you interact with one component of your app, and notice that an unrelated component is allocating or retaining objects, you most likely have an opportunity to reduce memory consumption. Perhaps that unrelated component can lazily delay any allocations it needs, thereby lowering your app's memory usage when that component isn't active.
Second, once developers know which objects are using their precious memory, the next info they need is where the objects were allocated. That leads to why they were allocated, and finally how to reduce those allocations. We can hack this workflow and group objects by allocation site then sort them for developers to effectively make the first step (which objects) redundant.
I'm not sure what the best way to expose this information to developers before
the complete memory panel is ready. Tracking allocations isn't lightweight; we
can't do it all the time, you have to turn the mode on. We could expose
console.stopTrackingAllocationSites(), and then allow calls to
obj was allocated while we were tracking
allocation sites. Or, we could expose
console.stopLoggingAllocationSites(), which could just dump every
allocation site to the console as it occurs. Tweet at me if you have
an opinion about the best API from which to expose this data.
Putting it all together
The memory panel will feature a live-updating graph. To construct this graph we will frequently poll the recent categorized allocations, and the total, non-granular heap size. This gives us a fuzzy, slightly inaccurate picture of the heap over time, but it should be efficient enough for us to do at a high frequency. At a less frequent interval, we will take a census. This will be a reality check of sorts that gives us precise numbers for each category of objects in the heap.
You will be able to click on the graph to get a shallow view into the heap at that past moment in time. Alternatively, you will be able to select a region of the graph to view the difference in memory consumption between the start and end points of your selection.
If you need to deep dive into the full heap state, you'll be able to take snapshots, which are too heavy for us to automatically collect on an interval. These can be compared with other snapshots down to each individual object, so you will be able to see exactly what has been allocated and reclaimed in the time between when each snapshot was taken. They will also be exportable and importable as core dumps, so you could attach them to bug tickets, send to other developers, etc.
Darrin Henein has created a beautiful mockup of the memory panel. Caveat: despite said beauty, the mockup is still very much a work in progress, it is far from complete, and what we ship might look very different!
You can follow along with our work by watching the bugs in this bugzilla dependency graph.
2014 will be an exciting year for memory tooling in Firefox Developer Tools!