Profiling
Where cost goes inside one process. CPU and allocation profiles, reading a flame graph without fooling yourself, and the CPU-bound versus I/O-bound distinction that decides which fix can possibly work.
Every lesson below starts from an observable symptom and ends with the measurement that proves the fix worked. Numbers carry a label saying whether they were measured, estimated, simulated or invented to show a shape.
The trace says `pricing-service` spent 240 ms and has no children. That is where tracing stops and profiling starts: one tells you which process is expensive, the other tells you which function inside it is.
A CPU profile ranks functions two ways, and confusing them wastes afternoons. Total time says "this subtree is expensive"; self time says "this function is expensive". Only one of them tells you where to put the fix.
Width is time, height is stack depth, and the horizontal axis is not time at all. Getting that last part wrong is the single most common flame-graph misreading, and it makes people look for patterns that cannot exist.
Memory that is allocated and immediately freed never shows up as growth, so leak hunting finds nothing. It still costs: every megabyte allocated is a megabyte the collector must eventually walk, and at 500 MB/s that is where your latency went.
The first fork in every performance investigation. A CPU-bound service wants better algorithms or more cores; an I/O-bound service wants concurrency, batching or a faster dependency. Applying either fix to the other problem reliably makes things worse.
Profiling during an incident means capturing a baseline you do not have, on an instance that may be healthy, after the pathology has passed. Continuous profiling makes the baseline a query — and turns "did this release get slower" into a diff.