skip to content

With Xdebug 3, what does trace mode record in a function trace, and when would you use it instead of profile mode?

level: middleimportance: should knowfreq 25%

answer

  1. order of calls vs totals per function
  2. trace.%c plus .xt
  3. trace_format 0, 1, 2; 3 and 4 flame graphs
  4. collect_return and collect_assignments off by default
  5. xdebug_start_trace() and xdebug_stop_trace()

basics

~20 s

Trace mode writes every function call in order, with time index, memory, arguments and optionally return values, to a .xt file; profile mode aggregates cost per function into a cachegrind file. Trace answers what happened; profile answers where time went.

solid answer

~40 s

`xdebug.mode=trace` writes a **function trace**: one line per call, indented by depth, with a time index, memory usage, the function name, its arguments (`xdebug.collect_params`, on by default) and the file and line. `xdebug.collect_return` and `xdebug.collect_assignments` add return values and assignments. The file goes to `xdebug.output_dir` named by `xdebug.trace_output_name` (default `trace.%c`, plus `.xt`). `xdebug.trace_format` picks human-readable (0), tab-separated (1), HTML (2) or flame-graph data (3 cost, 4 memory). Tracing starts on `XDEBUG_TRIGGER` by default, or from code with `xdebug_start_trace()`. A profile instead sums inclusive and self cost per function. Use a trace to learn the order of calls and which arguments reached them in unfamiliar code; use a profile to find where the time goes.

code

ini · 6 lines
ini
xdebug.mode=trace
xdebug.start_with_request=trigger
xdebug.trace_output_name=trace.%t.%R
xdebug.collect_return=1
; 0 human, 1 tab-separated, 2 HTML, 3 cost flame graph, 4 memory flame graph
xdebug.trace_format=0

go deeper

for a junior

Recall that trace mode logs every call in order to a .xt file and profile mode sums cost per function into a cachegrind file.

for a middle

Explain the trace line fields, what collect_return and trace_format change, how trace.%c names files, and how xdebug_start_trace() limits a trace to one slice.

for a senior

Choose the tool from the symptom, keep traces small with triggers and slices, and treat trace files as sensitive because they carry argument values.

for a principal

Judge when heavyweight tracing is worth it against lighter evidence such as logs or a profile, given how much disk and time a full trace of a big request costs.

## Two different questions Xdebug has two modes that both watch every function call, and they answer different questions: - A **function trace** (`xdebug.mode=trace`) is a **chronological log**: each call, in the order it happened, with its arguments and position in the call tree. It answers *"what did this request actually do, and in which order?"* - A **profile** (`xdebug.mode=profile`) is an **aggregate**: for every function, how often it was called and how much time and memory it cost, alone (**self**) and with its callees (**inclusive**). It answers *"where did the time go?"* | | Trace mode | Profile mode | |---|---|---| | Output | `.xt` file, one line per call | Cachegrind file, totals per function | | Order of calls | Preserved | Lost (summed) | | Arguments and return values | Available | Not recorded | | Best viewer | A text editor, `less`, a script | KCacheGrind, QCacheGrind, Webgrind, PhpStorm | | Default activation | On a trigger | Every request | ## What a trace line holds In the default human-readable format (`xdebug.trace_format=0`) each line shows the **time index** since the request started, the **memory** in use, an arrow indented by call depth, the **function** with its **arguments**, and the **file and line** of the call: ```text 0.0004 114272 -> str_split('Xdebug') /app/trace.php:8 0.0007 117424 -> ret_ord($c = 'X') /app/trace.php:10 0.0007 117584 -> ord('X') /app/trace.php:5 ``` What goes in is controlled by settings: - `xdebug.collect_params` (default `true`) — include arguments. Xdebug 3.0 removed this setting and 3.3 brought it back as an on/off switch. - `xdebug.collect_return` (default `false`) — add a `>=>` line with each return value. - `xdebug.collect_assignments` (default `false`) — record variable assignments too. - `xdebug.var_display_max_children`, `_data` and `_depth` — limit how much of each value is printed. ## Formats and file names `xdebug.trace_format` selects the layout: 1. `0` — human-readable, indented (default). 2. `1` — tab-separated entry and exit records, meant for scripts; Xdebug's source ships a `tracefile-analyser.php` for it. 3. `2` — a simple HTML table. 4. `3` and `4` — "cost" and "memory" **flame-graph** data, which an external tool turns into an SVG. Flame graphs are a special kind of trace, so they need `trace` in the mode. The file is written to `xdebug.output_dir` (default `/tmp`) with the name from `xdebug.trace_output_name`, default `trace.%c` — `%c` is a CRC32 of the working directory — plus `.xt`, plus `.gz` when `xdebug.use_compression` is on. Because every request from the same directory gets the same name, use `%t`, `%u` or `%R` to keep traces apart. `xdebug.trace_options` is a bitfield: `0x01` appends to an existing file, `0x08` stops the automatic `.xt` suffix. ## Starting and stopping a trace For trace mode, `xdebug.start_with_request` resolves to **`trigger`** by default, so a trace starts only when `XDEBUG_TRIGGER` (or the legacy `XDEBUG_TRACE`) is present in the query, POST data, a cookie or the environment. With `start_with_request=yes` every request is traced. You can also trace just a slice of code: ```php <?php xdebug_start_trace('/tmp/checkout'); // returns the file name, or false on failure $order = $checkout->place($cart); xdebug_stop_trace(); ``` `xdebug_start_trace()` works even with `start_with_request=no`, but only when `trace` is in the mode; otherwise Xdebug raises a "Functionality is not enabled" notice. ## Choosing between them - **Unfamiliar code path** ("which listener fired, with which arguments?") — trace. - **"Why is this request slow?"** — profile first; a trace of a slow request is often millions of lines. - **"Why did this value end up wrong?"** — a trace with `collect_return` shows each intermediate result without stopping the program, which suits code a step debugger disturbs, such as timeouts. - **Cost:** tracing writes a line for every call, so traces are large and slow the request more than people expect; and with `trace` in the mode Xdebug also switches off OPcache's optimizer so that every call stays visible. A trace also writes argument values to disk — passwords, tokens and personal data included — so treat trace files as sensitive and delete them after use.

  • You traced two different requests but only one .xt file is left in /tmp. Why?
    The default `xdebug.trace_output_name` is `trace.%c`, where `%c` is a CRC32 of the current working directory. Requests served from the same directory produce the same name, so the second trace overwrote the first. Add `%t`, `%u`, `%r` or `%R` to the pattern.
  • How do you produce a flame graph with Xdebug 3?
    Flame graphs are a variant of the function trace: set `xdebug.mode=trace` and `xdebug.trace_format=3` for a cost graph or `4` for a memory graph, trigger the request, then convert the output file to an SVG with an external flame-graph script. The docs advise tracing the whole request rather than a slice started with `xdebug_start_trace()`.

A trace is a till roll listing every item scanned in order; a profile is the end-of-day report totalling sales per product. The till roll shows what happened; the report shows where the money went.

saying these in an interview costs you the question

  • A trace and a profile contain the same data in different formats
  • Trace mode records return values by default
  • Trace mode starts on every request by default, like profile mode
  • Trace files are safe to share because they only hold function names