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/pprofandgo test -cpuprofilewrite (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, asgo tool pprofdoes. -
Collapsed / folded stacks — one stack per line,
main;handle;parse 42: async-profiler-o collapsed, Brendan Gregg’sstackcollapse-*.pl, inferno andcargo 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.ploutput) opens as a before/after pair. -
Linux
perf scripttext — the process name becomes the root frame as instackcollapse-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.ExecutionSamplecounts 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.ExecutionSamplecount, top frames and totaljdk.ObjectAllocationSampleweight asjfr summaryandjfr 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.ploutput, 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).
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser; nothing you type leaves this page.