# Eclipse MAT — Headless Runbook (and pitfalls)

Eclipse Memory Analyzer (MAT) is the gold standard for `.hprof` analysis: it builds a
dominator tree and produces a **Leak Suspects** report. This skill uses it only as
**cross-validation** for the pure-Python scripts — but when it runs, it is authoritative.
This runbook captures the exact invocation and the traps that make it fail *silently*.

## Install

macOS (Apple Silicon or Intel), via Homebrew:

```bash
brew install --cask mat          # cask is named "mat" (Eclipse Memory Analyzer)
# installs to /Applications/MemoryAnalyzer.app
```

Other platforms: download the **Standalone / RCP** build from
<https://eclipse.dev/mat/download/> (needs a JDK 17+ on PATH).

## ⚠️ Pitfall 1 — the macOS `.app` launcher exits 14, silently

Running `…/MemoryAnalyzer.app/Contents/Eclipse/ParseHeapDump.sh` (which calls the
`Contents/MacOS/MemoryAnalyzer` binary) from a **headless shell** (no GUI/WindowServer
session) fails with **exit code 14, zero output, no index files**. The Cocoa launcher
forces `-XstartOnFirstThread` and cannot reach the window server.

**Do not fight `ParseHeapDump.sh`.** Invoke the Equinox launcher jar directly with your own
JDK — fully headless, no GUI bootstrap:

```bash
ECL=/Applications/MemoryAnalyzer.app/Contents/Eclipse
LAUNCHER=$(ls "$ECL"/plugins/org.eclipse.equinox.launcher_*.jar)

java -Xmx12g \
  --add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED \
  -Djava.awt.headless=true \
  -Dosgi.install.area="file:$ECL/" \
  -Dosgi.configuration.area=file:/tmp/mat_cfg/ \
  -jar "$LAUNCHER" \
  -consoleLog -nosplash -data /tmp/mat_ws \
  -application org.eclipse.mat.api.parse \
  '/path/to/dump_clean.hprof' \
  org.eclipse.mat.api:suspects org.eclipse.mat.api:top_components
```

Notes:

- `-Dosgi.install.area` / `-Dosgi.configuration.area` are **required** — without them the
  launcher loads no MAT bundle and the application is a no-op (the same silent exit 14).
  Point `configuration.area` at a writable dir (the install `configuration/` is read-only).
- `-Xmx`: give MAT roughly **the dump size or more** (12g for a 2.6 GB dump is comfortable;
  MAT's indexing is efficient but needs headroom). If you instead use `ParseHeapDump.sh` on
  Linux/CI, raise `-Xmx` by editing `MemoryAnalyzer.ini`'s `-vmargs` block.
- Report ids: `org.eclipse.mat.api:suspects` (Leak Suspects),
  `org.eclipse.mat.api:top_components`, `org.eclipse.mat.api:overview`.

## ⚠️ Pitfall 2 — special characters in the dump filename

`%`, spaces, etc. (common from `-XX:HeapDumpPath=app_%p.hprof`) break Equinox/URI handling
and cause the *same* silent exit 14. **Always analyze a clean-named hardlink** (see the
skill's Prerequisites). This also protects the data if the original is later deleted.

## ⚠️ Pitfall 3 — Gatekeeper quarantine (macOS)

A freshly cask-installed app may be quarantined. If launch is blocked:

```bash
xattr -dr com.apple.quarantine /Applications/MemoryAnalyzer.app
```

## Outputs

A successful parse writes, next to the dump:

- Index files: `*.index`, `*.idx.index`, `*.domIn.index`, `*.domOut.index`,
  `*.o2ret.index`, `*.threads`, … (the dominator tree + retained-size indices).
- Report zips: `<name>_Leak_Suspects.zip`, `<name>_Top_Components.zip`,
  `<name>_System_Overview.zip`.

The presence of `*.index` files is the reliable "parsing actually started" signal when
watching progress.

## Reading the report headlessly

The zips are HTML. Convert to text with macOS `textutil` (or any html-to-text tool):

```bash
cd /tmp && rm -rf leak && mkdir leak && cd leak
unzip -o '/path/to/<name>_Leak_Suspects.zip' >/dev/null
textutil -convert txt -stdout index.html | sed '/^[[:space:]]*$/d' | head -120
```

What to extract from **Leak Suspects**:

- **Problem Suspect 1**: "one instance of `<HolderClass>` … occupies `N` bytes (`P`%) …"
  → the dominator and its retained-heap share.
- "The memory is accumulated in one instance of `<X>[capacity]`" → the accumulation point
  (often a `ConcurrentHashMap$Node[]` / `Object[]` table).
- "Thread `…` has a local variable … on the shortest path to …" → the GC-root path.

Confirm this holder matches what `trace_referrers.py` converged on. Detailed dominator
trees and per-object paths live in `pages/*.html` and `_Top_Components` / `_System_Overview`.

## Quick decision

- MAT runs → use it to corroborate + get retained sizes. Strongest evidence.
- MAT won't run after ~2 attempts → **don't keep fighting it.** The pure-Python Stages 1/2/4
  fully locate and prove the holder chain on their own.
