Skip to content

Commit 2e0868b

Browse files
committed
ui: add Heap Dump Explorer diff feature with palette-modulated flamegraph
Adds a baseline-vs-current diff mode to the Heap Dump Explorer with per-tab diff views (Overview, Classes, Objects, Dominators, Bitmaps, Strings, Arrays) and a same-trace flamegraph diff that paints each node by Δ direction *while preserving the standard per-name palette hue* — grew nodes saturate and darken, shrunk nodes lighten and desaturate towards the background, unchanged nodes stay neutral, and new nodes pop with a small saturation boost. The natural pprof-style palette is intact so users see colour continuity between diff and non-diff modes. Both same-trace (two heap_graph snapshots in one .pftrace) and cross-trace (separate primary + baseline .hprof or .pftrace files) workflows are supported. Changes vs. the upstream heap_diff branch (zezeozue/heap_diff): - Fix engine-identity comparison in heap_dump_page.ts and baseline/state.ts. Trace.engine returns a fresh per-access proxy via createTraceProxy, so `getActiveBaseline()?.trace.engine === engine` is always false and the same-trace flamegraph diff path never fired. Use the stable `disposable === false` flag instead. - Update header.ts to the new SegmentedButtons children-based API (selectedValue + SegmentedButton vnodes) — the upstream branch was written against the old `options:` prop and produced a runtime error. - Replace the absolute hsl(0, ...) / hsl(220, ...) red/blue diff colouring with palette-modulated colours so each class keeps its natural pprof-style hue and we only modulate saturation/lightness to encode direction. Implemented in ui/src/widgets/flamegraph.ts (new modulatePaletteScheme helper + extended getColorSchemeFromHint signature) with the SQL emitting 'palette:DIR[:INTENSITY]' hints. - Add tools/heap_dump_diff_test_app/HeapDumpDiffTest.java: a real Java app that builds a deep, branchy Android-app-shaped object graph (Application → ActivityManager → Activity → … → byte[]) and captures two .hprof snapshots via HotSpotDiagnosticMXBean.dumpHeap for the cross-trace diff workflow. - Add tools/heap_dump_diff_test_app/build_rich_fixture.py: textproto generator for the synthetic two-snapshot fixture used by the same-trace flamegraph diff test. - Add docs/heap_diff/{README.md,screenshots/}: end-to-end screenshots of every diff tab and the primary-dump popup, captured against the rich fixture and against real JVM hprofs (cross-trace screenshots under cross_trace/). Rebased onto current upstream/main so both the new 'Default to Heapdump Explorer' callout and the diff feature work together. Change-Id: I61f8e5e7f9988a89a7fd861b2e68ff9922568510
1 parent 840992f commit 2e0868b

52 files changed

Lines changed: 8300 additions & 552 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Android.bp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ cc_library_shared {
808808
],
809809
shared_libs: [
810810
"android.hardware.atrace@1.0",
811-
"android.hardware.health-V2-ndk",
811+
"android.hardware.health-V5-ndk",
812812
"android.hardware.health@2.0",
813813
"android.hardware.power.stats-V1-cpp",
814814
"android.hardware.power.stats@1.0",
@@ -23159,8 +23159,8 @@ phony {
2315923159
// TODO(primiano): uncomment after ag/38422283 lands.
2316023160
// "system_tracing_protos_descriptor",
2316123161
],
23162-
}
23163-
}
23162+
},
23163+
},
2316423164
}
2316523165

2316623166
filegroup {

docs/heap_diff/README.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Heap Dump Explorer — Diff feature
2+
3+
End-to-end screenshots of every diff view in the Heap Dump Explorer.
4+
5+
## Two diff modes
6+
7+
The Heap Dump Explorer supports two diff workflows:
8+
9+
1. **Same-trace diff** — two heap dumps in one trace. Activates the
10+
*flamegraph diff* path with palette-modulated colouring (the
11+
flamegraph is rendered with the standard per-name palette hues, but
12+
each node is darkened / saturated for *grew*, lightened / desaturated
13+
for *shrank*, kept neutral for *unchanged*, and pop-saturated for
14+
*new*).
15+
16+
2. **Cross-trace diff** — two separate traces (or hprofs) loaded as
17+
primary + baseline. The Overview, Classes, Objects, Dominators,
18+
Bitmaps, Strings and Arrays tabs show diff columns; the flamegraph
19+
stays in single-engine mode because each trace has its own SQLite
20+
engine.
21+
22+
Status of each row is colour-coded: <span style="color:#c62828">**GREW
23+
/ NEW**</span> (red), <span style="color:#1565c0">**SHRANK / GONE**</span>
24+
(blue), gray for unchanged.
25+
26+
## Same-trace fixture (synthetic, two snapshots in one pftrace)
27+
28+
Built by `tools/heap_dump_diff_test_app/build_rich_fixture.py`
29+
`test/data/heap_diff_multi.pftrace`. ~1400 objects across two
30+
Android-app-shaped snapshots: dump 1 has a busy UI (4 activities × 3
31+
fragments × …); dump 2 has a quieter UI but heavier background services
32+
and network connections. Some classes appear (`NewlyAddedClass`),
33+
disappear (`RemovedClass`), grow, shrink or stay flat.
34+
35+
### Overview tab
36+
37+
Reachable instances and bytes-retained-by-heap with Δ columns.
38+
39+
![Overview](screenshots/01_overview_diff.png)
40+
41+
### Flamegraph tab
42+
43+
Same-trace flamegraph diff, palette colours preserved per class,
44+
saturation / lightness modulated by Δ direction. UI sub-tree (left:
45+
Application → Activity → Fragment → ViewHolder → TextView / ImageView →
46+
String / Bitmap) reads as vivid (grew). Service sub-tree (right:
47+
ServiceManager → BackgroundService → Worker → Task) reads as faded
48+
(shrank).
49+
50+
![Flamegraph](screenshots/02_flamegraph_diff.png)
51+
52+
### Classes tab
53+
54+
Per-class diff breakdown. Status pills (GREW / SHRANK / NEW / GONE) and
55+
signed deltas next to baseline / current values.
56+
57+
![Classes](screenshots/03_classes_diff.png)
58+
59+
### Objects tab
60+
61+
![Objects](screenshots/04_objects_diff.png)
62+
63+
### Dominators tab
64+
65+
Dominator-tree retained-size diff per root class (size, count, native
66+
size, retained obj count — all four columns are diffable).
67+
68+
![Dominators](screenshots/05_dominators_diff.png)
69+
70+
### Bitmaps tab
71+
72+
![Bitmaps](screenshots/06_bitmaps_diff.png)
73+
74+
### Strings tab
75+
76+
![Strings](screenshots/07_strings_diff.png)
77+
78+
### Arrays tab
79+
80+
![Arrays](screenshots/08_arrays_diff.png)
81+
82+
### Primary-dump popup
83+
84+
The primary-dump selector showing the "Diff against this dump" section.
85+
Selecting another dump from the same trace activates the same-trace
86+
flamegraph diff.
87+
88+
![Popup](screenshots/09_popup.png)
89+
90+
## Cross-trace fixture (real JVM hprofs)
91+
92+
Built by running the Java app at
93+
`tools/heap_dump_diff_test_app/HeapDumpDiffTest.java` against a 1-GB
94+
JVM. The app constructs an object graph with deep reference chains
95+
(Application → ActivityManager → … → byte[]), then calls
96+
`HotSpotDiagnosticMXBean.dumpHeap` twice with different scales between
97+
the two dumps. The resulting `current.hprof` (smaller, services-heavy)
98+
is opened as the primary trace and `baseline.hprof` (larger, UI-heavy)
99+
is loaded as the cross-trace baseline.
100+
101+
### Overview (cross-trace)
102+
103+
![Overview cross-trace](screenshots/cross_trace/01_overview_diff.png)
104+
105+
### Classes (cross-trace)
106+
107+
![Classes cross-trace](screenshots/cross_trace/03_classes_diff.png)
108+
109+
### Dominators (cross-trace)
110+
111+
![Dominators cross-trace](screenshots/cross_trace/05_dominators_diff.png)
112+
113+
### Strings (cross-trace)
114+
115+
![Strings cross-trace](screenshots/cross_trace/07_strings_diff.png)
116+
117+
### Arrays (cross-trace)
118+
119+
![Arrays cross-trace](screenshots/cross_trace/08_arrays_diff.png)
120+
121+
## Reproducing locally
122+
123+
```sh
124+
# Capture two real hprofs.
125+
cd tools/heap_dump_diff_test_app
126+
javac HeapDumpDiffTest.java
127+
java -Xmx1g HeapDumpDiffTest baseline.hprof current.hprof
128+
129+
# Build the same-trace synthetic fixture.
130+
python3 build_rich_fixture.py
131+
out/ui/protoc --encode=perfetto.protos.Trace -I . protos/perfetto/trace/trace.proto \
132+
< /tmp/hprof_test/multi_dump_rich.textproto \
133+
> test/data/heap_diff_multi.pftrace
134+
135+
# Serve the UI.
136+
cd ui && pnpm install && node build.js --serve --watch
137+
138+
# In another shell, run the playwright e2e suites.
139+
cd ui && pnpm exec playwright test src/test/heap_dump_flamegraph_diff.test.ts
140+
cd ui && pnpm exec playwright test src/test/heap_dump_diff.test.ts
141+
cd ui && pnpm exec playwright test src/test/heap_dump_diff_hprof_primary.test.ts
142+
```
103 KB
Loading
129 KB
Loading
258 KB
Loading
77.6 KB
Loading
85.9 KB
Loading
82.6 KB
Loading
81.6 KB
Loading
81.4 KB
Loading

0 commit comments

Comments
 (0)