MEASURED, 22 SEPTEMBER 2026

Xdebug in production: we measured it

40 ms became 1012 ms, and sixty requests wrote 1.2 GB of cachegrind files. Here is the method, the raw numbers, and where our own tool costs the most.

“Just turn on Xdebug's profiler for a minute on production” is advice that circulates in every PHP team. We wanted a number instead of a feeling, so we measured it.

To be clear up front: Xdebug is excellent at the job it was built for. Step debugging, local profiling, code coverage — nothing else comes close, and its documentation never claims the profiler belongs on a live server. This article is about what happens when you try anyway, because people do.

The method

One container, PHP 8.3.33 on Apache, one page doing 720,000 user function calls — the shape of a template-heavy request. The page times itself with microtime(), so the numbers are PHP work, not HTTP overhead. Twelve requests per measurement, median taken. Variants were interleaved across five rounds rather than run in blocks, so drift in the machine cannot favour one of them.

# the page under test, in short
function pomocnik($x) { return $x % 7 === 0 ? $x * 2 : $x + 1; }
function warstwa($n) { $s = 0; for ($i = 0; $i < $n; $i++) { $s += pomocnik($i); } return $s; }
function render($n) { $out = 0; for ($i = 0; $i < 12; $i++) { $out += warstwa($n); } return $out; }
render(60000);   # 720,000 calls

The numbers

ConfigurationMedianvs baseline
Nothing loaded40.2 ms
PHPRay, request recording only40.7 mswithin noise
PHPRay, with per-function attribution51.0 ms+27%
Xdebug, xdebug.mode=profile1012 ms25×

Five interleaved rounds, spread of the Xdebug figure 999–1034 ms, spread of the baseline 39.5–40.8 ms. The gap is not subtle enough to need statistics.

The second cost is storage. Sixty requests produced 30 cachegrind files totalling 1.2 GB — roughly 40 MB per profiled request. On a site doing a few requests per second, the disk is gone before you have finished reading the first file.

Where our own tool costs the most

The inconvenient row in our own table

Per-function attribution cost us 27% on this page. That is our worst case and we are publishing it rather than quoting only the flattering number: this page is 720,000 user function calls, which is precisely what a function observer has to pay for. On a real WordPress page rendering in 48 ms we measured about 4% (method and raw numbers).

The row above it is the one that matters for “can I leave this on”: recording every request — time, SQL, outbound HTTP, errors, N+1 — came out at 40.7 ms against a 40.2 ms baseline. Indistinguishable, on the page built to be hostile to it.

So what do you actually use?

Honestly, it depends on what you can install and what you are willing to pay.

We build the third one, so read the table rather than the sentence. The point of the measurement is that the first row and the last row are not competing for the same slot: one is a profiler you open when you already know which request to look at, the other is the recording that tells you which request that is.

Read next

Look at your own traffic

Open source, Apache-2.0, no account needed for the local install.

How it works in production → See a real report