docs java javascript python c / c++ c# reading reports

How to read a profiler report

flamelens's rule-based report is a deterministic reading of your profile: no AI, no guesswork — the same recording always produces the same report. This page defines the words the report uses, walks one example end to end, and says what to actually do with each kind of finding.

The vocabulary

Sample
A profiler doesn't watch every instruction — it stops by, typically about a hundred times a second, and writes down what each thread was doing. One of those snapshots is a sample. With enough samples, the share of samples a function appears in is the share of time it costs. That's the entire trick of sampling profilers, and it's why more samples (a longer recording under real load) means a more trustworthy report.
Stack / leaf frame
Each sample records the call stack: main → handleRequest → toJson → stringify. The leaf is the innermost frame — stringify — the code actually running on the CPU at that instant. flamelens ranks leaves because that's where time is spent; everything above the leaf is just how you got there.
Self time
Time attributed to a function's own code, not the functions it calls. The hot-frames ranking in your report is self time. A function with huge total time but no self time isn't slow — something it calls is.
“— via” (the entry point)
When the hot leaf is library or runtime code (Arrays.copyOf, memcpy, an ORM loader), the fix is almost never there — it's in your code that calls it. The — via pointer names the nearest frame from your own codebase on those hot stacks: the place in your repo to open first.
CPU vs wall-clock sampling
A CPU profile samples only running threads: shares mean CPU cost. A wall-clock profile (async-profiler's default, IntelliJ's default) samples every thread whether running or parked — so a big “waiting” share is the sampling mode, not your app. The report says which mode it detected and suppresses idle-based conclusions for wall-clock recordings.
Idle share
In a CPU profile, samples whose leaf is a wait (parked thread, epoll, sleep). A mostly-idle profile is itself the finding: your slowness isn't CPU — go look at I/O, locks, or a downstream service.
GC pause share
How much of the recording the garbage collector stopped the application for. Pauses come from allocation: fixing the code that churns short-lived objects fixes the pauses. “Not recorded” means the profiler didn't emit GC events — which is different from zero collections, and the report never confuses the two.
Lock contention / convoy
Time threads spent blocked trying to enter a synchronized section, grouped by what they were blocked on. A convoy is the worse variant: three or more different threads serializing on one lock — the parallelism your thread pool was sized for isn't happening.
Thread imbalance
Per-thread shares of the work. One thread holding ~90% while others idle means the work isn't partitioned (or a serial stage funnels everything through one thread); a named pool that's entirely idle means work never reaches it.
Severity and categories
HIGH / MEDIUM / LOW is how much of the profile a finding explains — fix HIGH first. The category (CPU, ALLOCATION, GC, LOCK_CONTENTION, IO, CONFIGURATION) says which kind of fix it is.

A worked example

Here's the shape of a real rule-based report on a Java service (this is the actual summary format your report shows under “What the profile showed”):

Recording: 51,412 events over 12s, avg machine CPU 41%
Thread state: 8.5% of samples were threads WAITING, 91.5% were running code

Hot methods (share of ON-CPU samples; waiting threads excluded):
   38.2%  1830 samples  java.util.Arrays.copyOf  — via com.acme.ReportWriter.buildReport
   21.4%  1025 samples  java.lang.StringBuilder.append  — via com.acme.ReportWriter.buildReport
    6.1%   292 samples  com.acme.InventoryService.scanInventory

Threads (share of all execution samples, waits included):
   72.3%  3471 samples  http-exec-1 — top frame java.util.Arrays.copyOf (52.8% of this thread)
   ...

GC: 214 collections, total pause 780ms, max pause 95ms.

Reading it: the two hottest leaves are JDK code — but both are entered from ReportWriter.buildReport, and append + copyOf together is the signature of string concatenation in a loop (each append past capacity recopies the whole buffer). So the report's headline finding is not “Arrays.copyOf is slow” — it's “String concatenation in a loop, entered from buildReport”, severity HIGH, with the fix (pre-size the builder, or stream the output) and the ceiling on what it's worth (~60% of samples). The GC line supports it: 214 collections in 12 seconds is the allocation churn those copies create.

What to do with each finding

When to reach for the AI analysis

The rule engine matches thresholds and known signatures — instant and free. Claude reads the same profile summary and reasons across all of it at once: cross-referencing the allocation ranking against the GC shape against the hot list, naming causes the signatures don't cover, and writing recommendations specific to your frames rather than to the pattern. Every rule-based report has an Analyze with AI button that re-analyzes the same recording's summary — no re-upload, one scan credit.

Try it on your own profile: capture one (Java, JavaScript, Python, C/C++, C#) and run a free rule-based scan — the report comes back in seconds.