Bug 1238048 - Add an architectural overview of the memory tool DONTBUILD; r=tromey,vporof,jsantell,jimb
authorNick Fitzgerald <fitzgen@gmail.com>
Tue, 12 Jan 2016 11:29:00 +0100
changeset 321225 2e6226f35e263f63189858c506cbc65c589ead8f
parent 321224 a87a27864bb88946c005815272f225cb722f83e4
child 321226 154482173e4021b606157b8b4e085e01dc75673f
push id9351
push userjdescottes@mozilla.com
push dateWed, 13 Jan 2016 08:24:50 +0000
reviewerstromey, vporof, jsantell, jimb
bugs1238048
milestone46.0a1
Bug 1238048 - Add an architectural overview of the memory tool DONTBUILD; r=tromey,vporof,jsantell,jimb
devtools/docs/memory-panel.md
new file mode 100644
--- /dev/null
+++ b/devtools/docs/memory-panel.md
@@ -0,0 +1,201 @@
+# Memory Tool Architecture
+
+The memory tool is built of three main elements:
+
+1. `JS::ubi::Node` provides an interface to either the live heap graph, or a
+   serialized, offline snapshot of some heap graph from a previous moment in
+   time. Our various heap analyses (census, dominator trees, shortest paths,
+   etc) run on top of `JS::ubi::Node` graphs.
+
+2. The `HeapAnalysesWorker` runs in a worker thread, performing analyses on
+   snapshots and translating the results into something the frontend can render
+   simply and quickly.
+
+3. Finally, the last element is the frontend that renders data received from the
+   `HeapAnalysesClient` to the DOM and translates user input into requests for
+   new data with the `HeapAnalysesClient`.
+
+Unlike other tools (such as the JavaScript debugger), the memory tool makes very
+little use of the Remote Debugger Server and the actors that reside in it. Use
+of the [`MemoryActor`](devtools/server/actors/memory.js) is limited to toggling
+allocation stack recording on and off, and transferring heap snapshots from the
+debuggee (which is on the server) to the `HeapAnalysesWorker` (which is on the
+client). A nice benefit that naturally emerges, is that supporting "legacy"
+servers (eg, using Firefox Developer Edition as a client to remote debug a
+release Firefox for Android server) is a no-op. As we add new analyses, we can
+run them on snapshots taken on old servers no problem. The only requirement is
+that changes to the snapshot format itself remain backwards compatible.
+
+## `JS::ubi::Node`
+
+`JS::ubi::Node` itself is very well documented in the `js/public/UbiNode.h`
+header. I suggest you at least skim that documentation before continuing.
+
+A "heap snapshot" is a representation of the heap graph at some particular past
+instance in time.
+
+A "heap analysis" is an algorithm that runs on a `JS::ubi::Node` heap
+graph. That's it. Generally, analyses can run on either the live heap graph or a
+deserialized snapshot. Example analyses include "census", which aggregates and
+counts nodes into various user-specified buckets; "dominator trees", which
+compute the "dominates" relation and retained size for all nodes in the heap
+graph; and "shortest paths" which finds the shortest paths from the GC roots to
+some subset of nodes.
+
+### Saving Heap Snapshots
+
+Saving a heap snapshot has a few requirements:
+
+1. The binary format must remain backwards compatible and future extensible.
+
+2. The live heap graph must not mutate while we are in the process of
+   serializing it.
+
+3. The act of saving a heap snapshot should impose as little memory overhead as
+   possible. If we are taking a snapshot to debug frequent out-of-memory errors,
+   we don't want to trigger an OOM ourselves!
+
+To solve (1), we use the protobuf message format. The message definitions
+themselves are in `devtools/shared/heapsnapshot/CoreDump.proto`. We always use
+`optional` fields so we can change our mind about what fields are required
+sometime in the future. Deserialization checks the semantic integrity of
+deserialized protobuf messages.
+
+For (2), we rely on SpiderMonkey's GC rooting hazard static analysis and the
+`AutoCheckCannotGC` dynamic analysis to ensure that neither JS nor GC runs and
+modifies objects or moves them from one address in memory to another. There is
+no equivalent suppression and static analysis technique for the
+[cycle collector](https://developer.mozilla.org/en/docs/Interfacing_with_the_XPCOM_cycle_collector),
+so care must be taken not to invoke methods that could start cycle collection or
+mutate the heap graph from the cycle collector's perspective. At the time of
+writing, we don't yet support saving the cycle collector's portion of the heap
+graph in snapshots, but that work is deemed Very Important and Very High
+Priority.
+
+Finally, (3) imposes upon us that we do not build the serialized heap snapshot
+binary blob in memory, but instead stream it out to disk while generating it.
+
+Once all of that is accounted for, saving snapshots becomes pretty straight
+forward. We traverse the live heap graph with `JS::ubi::Node` and
+`JS::ubi::BreadthFirst`, create a protobuf message for each node and each node's
+edges, and write these messages to disk before continuing the traversal to the
+next node.
+
+This functionality is exposed to chrome JavaScript as the
+`[ThreadSafe]ChromeUtils.saveHeapSnapshot` function. See
+`dom/webidl/ThreadSafeChromeUtils.webidl` for API documentation.
+
+### Reading Heap Snapshots
+
+Reading heap snapshots has less restrictions than saving heap snapshots. The
+protobuf messages that make up the core dump are deserialized one by one, stored
+as a set of `DeserializedNode`s and a set of `DeserializedEdge`s, and the result
+is a `HeapSnapshot` instance.
+
+The `DeserializedNode` and `DeserializedEdge` classes implement the
+`JS::ubi::Node` interface. Analyses running on offline heap snapshots rather
+than the live heap graph operate on these classes (unknowingly, of course).
+
+For more details, see the
+[`mozilla::devtools::HeapSnapshot`](devtools/shared/heapsnapshot/HeapSnapshot.cpp)
+and
+[`mozilla::devtools::Deserialized{Node,Edge}`](devtools/shared/heapsnapshot/DeserializedNode.h)
+classes.
+
+### Heap Analyses
+
+Heap analyses operate on `JS::ubi::Node` graphs without knowledge of whether
+that graph is backed by the live heap graph or an offline heap snapshot. They
+must make sure never to allocate GC things or modify the live heap graph.
+
+In general, analyses are implemented in their own `js/public/UbiFooBar.h` header
+(eg `js/public/UbiCensus.h`), and are exposed to chrome JavaScript code via a
+method on the [`HeapSnapshot`](dom/webidl/HeapSnapshot.webidl) webidl
+interface.
+
+For each analysis we expose to chrome JavaScript on the `HeapSnapshot` webidl
+interface, there is a small amount of glue code in Gecko. The
+[`mozilla::devtools::HeapSnapshot`](devtools/shared/heapsnapshot/HeapSnapshot.h)
+C++ class implements the webidl interface. The analyses methods (eg
+`ComputeDominatorTree`) take the deserialized nodes and edges from the heap
+snapshot, create `JS::ubi::Node`s from them, call the analyses from
+`js/public/Ubi*.h`, and wrap the results in something that can be represented in
+JavaScript.
+
+For API documentation on running specific analyses, see the
+[`HeapSnapshot`](dom/webidl/HeapSnapshot.webidl) webidl interface.
+
+### Testing `JS::ubi::Node`, Snapshots, and Analyses
+
+The majority of the tests reside within `devtools/shared/heapsnapshot/tests/**`.
+For reading and saving heap snapshots, most tests are gtests. The gtests can be
+run with the `mach gtest DevTools.*` command. The rest are integration sanity
+tests to make sure we can read and save snapshots in various environments, such
+as xpcshell or workers. These can be run with the usual `mach test $PATH`
+commands.
+
+There are also `JS::ubi::Node` related unit tests in
+`js/src/jit-test/tests/heap-analysis/*`, `js/src/jit-test/tests/debug/Memory-*`,
+and `js/src/jsapi-tests/testUbiNode.cpp`. See
+https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Running_Automated_JavaScript_Tests#Running_jit-tests
+for running the JIT tests.
+
+## `HeapAnalysesWorker`
+
+The `HeapAnalysesWorker` orchestrates running specific analyses on snapshots and
+transforming the results into something that can simply and quickly be rendered
+by the frontend. The analyses can take some time to run (sometimes on the order
+of seconds), so doing them in a worker thread allows the interface to stay
+responsive. The `HeapAnalysisClient` provides the main thread's interface to the
+worker.
+
+The `HeapAnalysesWorker` doesn't actually do much itself; mostly just shuffling
+data and transforming it from one representation to another or calling utility
+functions that do those things. Most of these are implemented as traversals of
+the resulting census or dominator trees.
+
+See the
+`devtools/shared/heapsnapshot/{CensusUtils,CensusTreeNode,DominatorTreeNode}.js`
+files for details on the various data transformations and shuffling that the
+`HeapAnalysesWorker` delegates to.
+
+### Testing the `HeapAnalysesWorker` and `HeapAnalysesClient`
+
+Tests for the `HeapAnalysesWorker` and `HeapAnalysesClient` reside in
+`devtools/shared/heapsnapshot/tests/**` and can be run with the usual `mach test
+$PATH` command.
+
+## Frontend
+
+The frontend of the memory tool is built with React and Redux.
+
+[React has thorough documentation.](https://facebook.github.io/react/)
+
+[Redux has thorough documentation.](http://rackt.org/redux/index.html)
+
+We have React components in `devtools/client/memory/components/*`.
+
+We have Redux reducers in `devtools/client/memory/reducers/*`.
+
+We have Redux actions and action-creating tasks in
+`devtools/client/memory/actions/*`.
+
+React components should be pure functions from their props to the rendered
+(virtual) DOM. Redux reducers should also be observably pure.
+
+Impurity within the frontend is confined to the tasks that are creating and
+dispatching actions. All communication with the outside world (such as the
+`HeapAnalysesWorker`, the Remote Debugger Server, or the file system) is
+restricted to within these tasks.
+
+### Testing the Frontend
+
+Unit tests for React components are in `devtools/client/memory/test/chrome/*`.
+
+Unit tests for actions, reducers, and state changes are in
+`devtools/client/memory/test/unit/*`.
+
+Holistic integration tests for the frontend and the whole memory tool are in
+`devtools/client/memory/test/browser/*`.
+
+All tests can be run with the usual `mach test $PATH` command.