Skip to content

Draw the heap dump's references as an expandable graph - #2932

Open
pyricau wants to merge 1 commit into
mainfrom
build-new-visualization-in-sha
Open

Draw the heap dump's references as an expandable graph#2932
pyricau wants to merge 1 commit into
mainfrom
build-new-visualization-in-sha

Conversation

@pyricau

@pyricau pyricau commented Aug 4, 2026

Copy link
Copy Markdown
Member

A fourth shape in Shark Explorer, next to the treemap, the rings and the stack.

The three shapes so far all divide an area between the nodes of the dominator tree. They say how much
an object retains and what it sits inside, and they can never say which field holds it, or whether
anything else does — that is the heap dump's own references, which the tree is a summary of.

So this one draws the references: circles joined by arrows, expanded outwards a click at a time from
the node the view is rooted at, the way Xcode's memory graph is read.

  • A click expands rather than goes there. The whole of what this shape says is what has been
    expanded, so a click that re-rooted the window would throw that away every time. Pressing a circle
    opens it, pressing it again closes it — and closing keeps what was read, so opening it again reads
    nothing. Going somewhere is still a click in the panels beside the view, as on every other screen.
  • Arrows are named after the field that points, on the class that declares it, and a dominator
    edge is ringed: how an object is reached, and whether that is why it is alive, on one picture.
  • Pan and zoom instead of fitting the window, since what is drawn is what was asked for rather
    than a cut of the tree — and expanding never moves what is already on screen.
  • Fan-out is paged, not truncated: the 24 heaviest references, with the rest behind the same
    "leftovers" cell the other shapes already use for children they didn't draw.
  • The root can be the whole heap dump, which is no object: that node answers with the tree's own
    children, so the shape opens where every other one does and the references start one click in. One
    code path, no mode flag.

The drawing style is shared, not copied. PathStyle.kt is the circle, the badge letter, the ring,
the connector colours, the weak-reference dash and the arrow head; both the chain pane (PathDrawing,
a column joined top to bottom) and the graph (GraphView, joined left to right) draw through it, so a
colour or a radius changed in one place changes both pictures. The reference-naming is shared the same
way, down in HeapDominatorTreemap.

Where the work happens. ObjectGraph (what has been expanded, and what each circle references,
grown by one heap dump read per circle) and GraphLayout (arranging it into columns and rows) are both
in shark-explorer-core and unit tested there. Arranging happens in composition rather than on the
heap dump's thread, because it reads nothing — which is what makes expanding a circle instant rather
than a spinner, and what TreeLayoutTest holds it to: switching to this shape lays the tree out no
further.

Tests: ObjectGraphTest, GraphLayoutTest, ObjectGraphReadingTest (core), GraphTransformTest,
GraphViewTest (app). notes/treemap-rendering.md and notes/decisions.md say why it is shaped this
way.

🤖 Generated with Claude Code

The three shapes so far all divide an area between the nodes of the
dominator tree, which says how much an object retains but never which
field holds it. This adds a fourth: circles joined by arrows, expanded
outwards a click at a time from the node the view is rooted at, the way
Xcode's memory graph is read. Arrows carry the field name, dominator
edges are ringed, and the view pans and zooms rather than fitting the
window, since what is drawn is what was asked for rather than a cut of
the tree.

The style is shared with the chain pane rather than copied:
PathStyle.kt is the circle, the badge letter, the ring, the connector
colours, the weak-reference dash and the arrow head, drawn by both
PathDrawing and GraphView, so a change to either picture is a change to
both.

What is drawn lives in ObjectGraph, grown by one read of the heap dump
per circle expanded, and GraphLayout arranges it — both in core, both
unit tested. Arranging happens in composition rather than on the heap
dump's thread, because it reads nothing, which is what makes expanding
a circle instant instead of a spinner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant