R&D·02 ·SHIPPED·2026

Cinder: live allocation flame graph

Attach it to a running .NET process and watch the memory move: allocations rising as a live flame graph, garbage collections sweeping through, and a leak revealing itself as the one glowing ridge that won't come down.

Shippedgraphicssystems
Cinder02
cinder attach 4821
120 fpssampled · EventPipeleak: CreateEntry0.4% overhead

For a long stretch of my career the job was making things move at a fixed number of milliseconds a frame, no excuses. Polygon fills on x86 tuned under V-Tune until the inner loop stopped hurting. Sound and physics engines where a dropped frame was a bug you heard. You learn, after enough years of it, that smooth is a property you design the whole thing around from the start. Bolt it on at the end and you never get it.

That instinct went quiet for a while. It's back, pointed at an unglamorous target: watching a .NET program allocate memory.

Every profiler I've reached for treats memory as a crime scene. You take a snapshot, the program pauses, and you sift through a static heap dump after the fact trying to reconstruct what happened. dotMemory, PerfView, the Visual Studio diagnostics tools, they're all capable and they're all the same shape: freeze, dump, squint. Nobody has made the live experience any good, and nobody has made watching allocations pleasurable. Which is odd, because the runtime is sitting there the whole time, more than willing to tell you what it's doing.

Cinder attaches to a running .NET process and shows you the allocations as they happen, as a flame graph that moves. Hot paths rise and glow. Garbage collections sweep through the scene and knock the reclaimed memory back down. A leak stops being a number in a table and becomes the one ridge that keeps climbing while everything around it breathes. No recompile, no profiler DLL injected into your process, no code changes to the thing you're watching. You point it at a process id and watch.

The two hard problems

The first is the picture. A flame graph with a few thousand live nodes, every one of them easing toward a new size as fresh data arrives sixty or more times a second, is not something you get from a chart library. Retained-mode UI toolkits fall over well before you need them to. So the renderer is a proper GPU one: a single Direct3D 11 instanced draw call, the easing resolved on the GPU, the frame time measured and shown on screen so it can't lie to you. The spike that decided the whole project rendered twenty thousand eased bars in about four tenths of a millisecond on a mid-range card. This is the part squarely in my wheelhouse, and the part most memory tools never even attempt.

The second problem is subtler, and it's the one that makes the thing readable. When the underlying numbers change every frame, the naive move is to relayout, and then the bars jump around as their neighbours resize, and the eye can't follow anything. The fix is to separate the data from what's drawn: a target layout that updates as samples arrive, and a render layout that eases toward it on a fixed timestep, with siblings ordered by a stable identity rather than by their current size so a bar keeps its place. The fixed timestep took some convincing to justify. It earns its keep twice, because a simulation built as a pure function of the events and a fixed clock comes out perfectly reproducible for free.

Where the numbers come from, honestly

The data is real and I'll say exactly how real. The runtime raises an allocation event roughly once per hundred kilobytes allocated, per type. Cinder reads that stream out of process over EventPipe and stitches the call stacks back onto it. So the figures come from a sample, and everywhere one does, it's labelled as sampled. A bar's width is a path's share of recent allocation pressure, so read it as pressure rather than an exact byte count. Cinder measures its own overhead on the process it's watching and puts the number on screen, because a profiler that quietly changes what it measures isn't worth running. In the default mode that overhead sits at a fraction of one percent.

cinder attach 4821                 # watch a running process live
cinder record 4821 -o leak.cndr    # capture a session to a portable file
cinder play leak.cndr --at leak-reveal
cinder export leak.cndr --aspect 9:16 --format gif

Because a recording holds only the raw events, replay recomputes the whole film every time, so the same .cndr renders frame for frame identically on any machine, and the showcase and analyst views are the same events presented two ways. The obvious first question is whether the pretty demo is cheating. It isn't, and it can't be. The animation only ever shows real captured allocation and collection events. The pacing and the camera are directed. The events are left alone.

IF THE GLOWING RIDGE WERE SCRIPTED, THE WHOLE THING WOULD BE WORTHLESS. SO IT ISN'T.

The recordings play back in the browser too, on WebGPU with a WebGL2 fallback, which is how the gallery on this page works with nothing installed. The animation in the panel above is an illustration built for the web. The real thing runs on your desktop, reading real memory off a real process, at a frame rate you can feel.

There's plenty left. Exact source lines for methods already running when you attached, more of the presentation polish, the eventual CPU flame graph the same renderer would take without much argument. The core is built, and it does the one thing it set out to do. You watch a memory leak reveal itself while the process is alive, in front of you. The old way was picking through a heap dump after the process was already dead.