JFR vs async-profiler for a Scala service: choosing a profiling setup without distorting the bottleneck
0 reputation · 21 Dec 2020, 11:42 UTC
Goal
Before optimizing a latency-critical Scala service, I want to measure where time actually goes — CPU, allocation/GC, lock contention, or blocking I/O — rather than guessing from code shape. The constraint is that the profiling setup itself must not distort the result enough to send optimization effort in the wrong direction.
Context and uncertainty
The service shows elevated p99 latency under load, but it is unclear whether threads are busy (hot methods, allocation pressure from immutable collections, boxing, and closures) or idle-waiting (executor starvation, connection pools, downstream calls). CPU sampling alone can mislead in the second case, so wall-clock profiling seems necessary — yet adding allocation profiling increases overhead. Assume Scala 2.13 on a recent JDK; exact GC defaults and flags are still open.
Questions
- For a workload that may be I/O- or contention-bound, what combination of JFR events and/or async-profiler modes (cpu, wall, alloc) gives trustworthy attribution at acceptable overhead in production?
- How should safepoint bias and profiler overhead be accounted for when interpreting hot methods in JIT-compiled Scala code?
- When a suspected hot path is small, what criteria decide between a JMH microbenchmark and a production-like load test, given that the two can contradict each other?