1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
# gjs-heapgraph
A heap analyzer for Gjs based on https://github.com/amccreight/heapgraph to aid
in debugging and plugging memory leaks.
## Resource Usage
Be aware that parsing a heap can take a fair amount of RAM depending on the
heap size and time depending on the amount of target objects and path length.
Examples of approximate memory and time required to build DOT graphs on an
IvyBridge i7:
| Heap Size | RAM | Targets | Time |
|-----------|-------|---------|-------------|
| 5MB | 80MB | 1500 | 1.5 Minutes |
| 30MB | 425MB | 7700 | 40 Minutes |
## Basic Usage
### Getting a Heap Dump
The more convenient way to dump a heap is to send `SIGUSR1` to a GJS process
with the env variable `GJS_DEBUG_HEAP_OUTPUT` set:
```sh
$ GJS_DEBUG_HEAP_OUTPUT=myApp.heap gjs myApp.js &
$ kill -USR1 <gjs-pid>
```
It's also possible to dump a heap from within a script via the `System` import:
```js
const System = imports.system;
// Dumping the heap before the "leak" has happened
System.dumpHeap('/home/user/myApp1.heap.');
// Code presumably resulting in a leak...
// Running the garbage collector before dumping can avoid some false positives
System.gc();
// Dumping the heap after the "leak" has happened
System.dumpHeap('/home/user/myApp2.heap.');
```
### Output
The default output of `./heapgraph.py` is a tiered tree of paths from root to
rooted objects. If the output is being sent to a terminal (TTY) some minimal
ANSI styling is used to make the output more readable. Additionally, anything
that isn't part of the graph will be sent to `stderr` so the output can be
directed to a file as plain text. Below is a snippet:
```sh
$ ./heapgraph.py myApp2.heap Object > myApp2.tree
Parsing file.heap...done
Found 343 targets with type "Object"
$ cat file.tree
├─[vm_stack[1]]─➤ [Object jsobj@0x7fce60683440]
│
├─[vm_stack[1]]─➤ [Object jsobj@0x7fce606833c0]
│
├─[exact-Object]─➤ [Object jsobj@0x7fce60683380]
│
├─[exact-Object]─➤ [GjsGlobal jsobj@0x7fce60680060]
│ ├─[Debugger]─➤ [Function Debugger jsobj@0x7fce606a4540]
│ │ ╰─[Object]─➤ [Function Object jsobj@0x7fce606a9cc0]
│ │ ╰─[prototype]─➤ [Object (nil) jsobj@0x7fce60681160]
│ │
...and so on
```
`heapgraph.py` can also output DOT graphs that can be a useful way to visualize
the heap graph, especially if you don't know exactly what you're looking for.
Passing the `--dot-graph` option will output a DOT graph to `<input-file>.dot`
in the current working directory.
There are a few choices for viewing dot graphs, and many utilities for
converting them to other formats like PDF, Tex or GraphML. For Gnome desktops
[`xdot`](https://github.com/jrfonseca/xdot.py) is a nice lightweight
Python/Cairo viewer available on PyPi and in most distributions.
```sh
$ ./heapgraph.py --dot-graph /home/user/myApp2.heap Object
Parsing file.heap...done
Found 343 targets with type "Object"
$ xdot myApp2.heap.dot
```
### Excluding Nodes from the Graph
The exclusion switch you are most likely to use is `--diff-heap` which will
exclude all nodes in the graph common to that heap, allowing you to easily
see what's not being collected between two states.
```sh
$ ./heapgraph --diff-heap myApp1.heap myApp2.heap GObject
```
You can also exclude Gray Roots, WeakMaps, nodes with a heap address or nodes
with labels containing a string. Because GObject addresses are part of the node
label, these can be excluded with `--hide-node` as well.
By default the global object (GjsGlobal aka `globalThis`), imports (GjsModule,
GjsFileImporter), and namespaces (GIRepositoryNamespace) aren't shown in the
graph since these are less useful and can't be garbage collected anyways.
```sh
$ ./heapgraph.py --hide-addr 0x7f6ef022c060 \
--hide-node 'self-hosting-global' \
--no-gray-roots \
/home/user/myApp2.heap Object
$ ./heapgraph.py --hide-node 0x55e93cf5deb0 /home/user/myApp2.heap Object
```
### Labeling Nodes
It can be hard to see what some nodes mean, especially if all the nodes
you are interested in are labeled `GObject_Object`.
Luckily there is a way to label the nodes in your program so that they
are visible in the heap graph.
Add a property named `__heapgraph_name` with a simple string value to
your object:
```js
myObj.__heapgraph_name = 'My object';
```
Heapgraph will detect this and display the name as part of the node's
label, e.g. GObject_Object "My object".
### Command-Line Arguments
> **NOTE:** Command line arguments are subject to change; Check
> `./heapgraph.py --help` before running.
```
usage: heapgraph.py [-h] [--edge | --function | --string] [--count]
[--dot-graph] [--no-addr] [--diff-heap FILE]
[--no-gray-roots] [--no-weak-maps] [--show-global]
[--show-imports] [--hide-addr ADDR] [--hide-node LABEL]
[--hide-edge LABEL] FILE TARGET
Find what is rooting or preventing an object from being collected in a GJS
heap using a shortest-path breadth-first algorithm.
positional arguments:
FILE Garbage collector heap from System.dumpHeap()
TARGET Heap address (eg. 0x7fa814054d00) or type prefix (eg.
Array, Object, GObject, Function...)
optional arguments:
-h, --help show this help message and exit
--edge, -e Treat TARGET as a function name
--function, -f Treat TARGET as a function name
--string, -s Treat TARGET as a string literal or String()
Output Options:
--count, -c Only count the matches for TARGET
--dot-graph, -d Output a DOT graph to FILE.dot
--no-addr, -na Don't show addresses
Node/Root Filtering:
--diff-heap FILE, -dh FILE
Don't show roots common to the heap FILE
--no-gray-roots, -ng Don't show gray roots (marked to be collected)
--no-weak-maps, -nwm Don't show WeakMaps
--show-global, -g Show the global object (eg. globalThis/GjsGlobal)
--show-imports, -i Show import and module nodes (eg. imports.foo)
--hide-addr ADDR, -ha ADDR
Don't show roots with the heap address ADDR
--hide-node LABEL, -hn LABEL
Don't show nodes with labels containing LABEL
--hide-edge LABEL, -he LABEL
Don't show edges labelled LABEL
```
## See Also
Below are some links to information relevant to SpiderMonkey garbage collection
and heap parsing:
* [GC.cpp Comments](https://searchfox.org/mozilla-central/source/js/src/gc/GC.cpp)
* [How JavaScript Objects Are Implemented](https://www.infoq.com/presentations/javascript-objects-spidermonkey)
* [Tracing garbage collection](https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking) on Wikipedia
* [SpiderMonkey Memory](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/doc/SpiderMonkey_Memory.md) via GJS Repo
|