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— viapointer 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
- Open the “via” frame first. The finding names the pattern; the entry point names the file. The fix is at the call site, not in the library.
- Trust shares, verify patterns. The numbers are measured; the pattern (“likely N+1”) is strong evidence with a “verify by” note — do the one-minute check (log the SQL, count the round-trips) before a big refactor.
- Fix one thing, rescan. Upload the next recording in the same tuning session and the report diffs against the last run — resolved findings, and the session table shows your CPU, heap, and GC moving across runs.
- A clean report is an answer too. “No significant problems at the thresholds” on a mostly-idle profile means the time is going to I/O or a downstream — profile the wait, not the CPU.
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.