Chris Collins

Tools · dev

Flame graph and profile viewer

Open a CPU, heap or lock profile — Go pprof, collapsed stacks from async-profiler, perf or cargo flamegraph, JDK Flight Recorder, a Node/Chrome .cpuprofile, speedscope or perf script — and read it as a flame graph or icicle with search, a top table, callers and callees, and a red/blue differential against a baseline, all in the browser.

Added

How to use it

A profile viewer for the files profilers already write, without installing Graphviz, a JDK Mission Control or a local pprof web server. Drop a file in and read where the time (or memory) went. Nothing is uploaded: the file is parsed in this page.

How to use it

Open a file, drop one anywhere on the tool, or paste text. The format is detected from the content, not the file name:

  • Go pprof — what runtime/pprof, net/http/pprof and go test -cpuprofile write (profile.proto, gzipped or not): CPU, heap (alloc_objects, alloc_space, inuse_objects, inuse_space), allocs, goroutine, mutex and block profiles. Other tools that write pprof (pprof-rs, async-profiler’s pprof output, Pyroscope exports) work too. Inlined functions are shown as their own frames, as go tool pprof does.

  • Collapsed / folded stacks — one stack per line, main;handle;parse 42: async-profiler -o collapsed, Brendan Gregg’s stackcollapse-*.pl, inferno and cargo flamegraph. The async-profiler annotations [j] (JIT), [i] (inlined) and _[k] (kernel) are read and can colour the frames. A line with two counts (difffolded.pl output) opens as a before/after pair.

  • Linux perf script text — the process name becomes the root frame as in stackcollapse-perf.pl; one sample type per event, and the event period as well when perf printed one. Java frames from perf-map-agent (Lcom/acme/Foo;.bar) are tidied.

  • JDK Flight Recorder (.jfr, from -XX:StartFlightRecording, jcmd JFR.dump, JMC or async-profiler -o jfr). Every event type that carries a stack trace becomes a sample type: jdk.ExecutionSample counts samples, allocation events are weighted by bytes, and lock, park and I/O events by their duration.

  • Chrome / V8 .cpuprofile (node --cpu-prof, the DevTools JavaScript profiler) with sample counts and time, and the DevTools Performance trace JSON (one profile per thread).

  • speedscope JSON, sampled or evented, one profile per thread.

Sample type picks what is measured (for a heap profile, inuse_space against alloc_space is the usual first question). Several threads or profiles in one file get a second picker.

Flame graph. Each box is a function; its width is its share of the samples, and the boxes above it are what it called. The root is at the bottom (Icicle turns it upside down). Click a box to zoom into it, click one of the faded boxes below it to zoom back out. Search takes text or a /regex/, paints matching frames magenta and says what share of all samples passes through them (nested matches counted once). Press / to jump to the search box. Inverted puts the leaf functions at the root, which answers "what is hot, and who calls it".

Names. Profiles of Java, Go and Rust code have long names. short drops package and module paths (github.com/acme/shop/cart.(Service).Checkout → cart.(*Service).Checkout, org.springframework.web.servlet.DispatcherServlet.doDispatch → DispatcherServlet.doDispatch, <alloc::vec::Vec<T> as core::ops::drop::Drop>::drop → <Vec<T> as Drop>::drop); *no generics drops type parameters ([go.shape.int], <T,A>); fold closures merges func1, lambda$placeOrder$2, $$Lambda and {{closure}} frames into the function that made them; no arguments drops argument lists, JVM descriptors and Rust symbol hashes. Frames whose names become equal are merged, so the numbers change with them. Hide frames drops frames matching a regular expression (for example runtime\.|java\.lang\.reflect) and joins what was above and below them.

Top is the table go tool pprof -top prints: self is the value of the samples where the function was running; total adds the time in everything it called, with recursion counted once. Sort by self, total or name; the search filters it. Click a name for its callers and callees.

Callers / callees shows one function: who called it (an inverted tree with the function at the top) and what it called, merged over all the places it was called from, with the direct callers and callees in tables.

Compare draws a differential flame graph. The profile loaded above is B (after); open a baseline A (before) of the same program. Frames are matched by their names (after the Names options), so profiles of two builds line up. Red frames take a bigger share of B than of A, blue a smaller one; the colour scale is the largest change. Normalise compares shares, which is what you want for runs of different length; untick it to compare raw values. Widths follow B by default (what the code does now), or A to see what went away. The table lists the biggest changes in self share. This is the idea of Brendan Gregg’s differential flame graphs.

Save collapsed writes the current sample type as collapsed stacks with the names as shown, for inferno, flamegraph.pl, speedscope or a diff in git.

Examples

The examples were made for this page. The Go ones are a toy order service (JSON, regular expressions, SHA-256, gzip) profiled with runtime/pprof twice — first compiling its regular expressions on every request, then once at start-up. Go before → after opens the pair in Compare with widths from A and colour by total change: main.validate and regexp.MustCompile go blue, and the heap profile shows the same code allocating most of the bytes. The Node example is node --cpu-prof of a CSV report renderer; the Java (Spring Boot, async-profiler style) and Rust (cargo flamegraph style) examples are short hand-written collapsed files.

Checked against

  • Go: CPU, heap, allocs, goroutine, mutex and block profiles from Go 1.23 give the same flat and cumulative values and percentages as go tool pprof -top, and files this page re-encodes read back identically in pprof.

  • JFR: a recording from OpenJDK 21 gives the same jdk.ExecutionSample count, top frames and total jdk.ObjectAllocationSample weight as jfr summary and jfr print.

  • perf script: the 13 sample files in Brendan Gregg’s FlameGraph repository give the same totals and counts per leaf function as its reference stackcollapse-perf.pl output, except that this page keeps the last sample of a file that does not end in a blank line (the script drops it) and tidies a few names differently.

Limits

  • Async-profiler’s own JFR extensions are read as far as they follow the standard JFR layout (execution and allocation samples do). I checked JFR against OpenJDK recordings, not async-profiler ones.

  • Labels (pprof tags, goroutine labels) are not shown or filtered on yet.

  • Big profiles: a file with 150,000 distinct stacks parses and draws in a second or two on a laptop; the flame graph only draws boxes wider than about a third of a pixel.

  • There is no source view: pprof files carry line numbers but not the source.

Browser support

Any current browser. Gzipped pprof needs DecompressionStream (Chrome 80, Firefox 113, Safari 16.4 and later).

Privacy

Your profiles stay in this page: they are read with the browser’s file API and parsed here, and nothing is sent anywhere. The page remembers its settings (search, sample type, name options) in this browser; links carry those settings and an example’s name, never your profile.

Related: OpenTelemetry trace viewer (where a request spent its time across services), latency numbers, queueing, big-O, sandbox (run a snippet in the browser).

Runs entirely in your browser; nothing you type leaves this page.