tracewick / symfony-bundle
Official Symfony integration for Tracewick (ext-excimer sampling profiler → spool / Pyroscope)
Package info
github.com/tracewick/symfony-bundle
Type:symfony-bundle
pkg:composer/tracewick/symfony-bundle
Requires
- php: ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0
- psr/log: ^1.1 || ^2.0 || ^3.0
- symfony/config: ^6.4 || ^7.0 || ^8.0
- symfony/console: ^6.4 || ^7.0 || ^8.0
- symfony/dependency-injection: ^6.4 || ^7.0 || ^8.0
- symfony/event-dispatcher: ^6.4 || ^7.0 || ^8.0
- symfony/http-kernel: ^6.4 || ^7.0 || ^8.0
- tracewick/tracewick: ^0.2
Requires (Dev)
- ergebnis/composer-normalize: ^2.48
- friendsofphp/php-cs-fixer: ^3.89
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^11.5
- rector/rector: ^2.2
- shipmonk/composer-dependency-analyser: ^1.8
- symfony/framework-bundle: ^6.4 || ^7.0 || ^8.0
- symfony/http-foundation: ^6.4 || ^7.0 || ^8.0
- symfony/messenger: ^6.4 || ^7.0 || ^8.0
- symfony/routing: ^6.4 || ^7.0 || ^8.0
Suggests
- ext-excimer: Required to collect profiles (install with PIE: https://php.github.io/pie/ — pie install wikimedia/excimer)
- symfony/messenger: Required when enabling tracewick.profile_messenger
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-10 14:12:41 UTC
README
Warning
Tracewick Symfony Bundle 0.x is alpha. The public API is not stable until 1.0.0 — expect breaking changes in minor releases. Pin a version and read the changelog before upgrading.
Official Symfony integration for
tracewick/tracewick — a PHP sampling profiler on
ext-excimer that writes profiles to a local spool
(recommended) or optionally to Grafana Pyroscope.
Requirements
| PHP | 8.2–8.5 |
| Symfony | 6.4 / 7.x / 8.x (CI matrix: 6.4, 7.4, 8.1) |
| Tracewick | ^0.2 |
| ext-excimer | suggested (fail-open no-op without it) |
Symfony 6.4 on PHP 8.1 is not supported (same PHP floor as the core library).
Install
The Flex recipe lives in
symfony/recipes-contrib
(tracewick/symfony-bundle/0.1). Enable contrib recipes once per app, then require
the package:
composer config extra.symfony.allow-contrib true
composer require tracewick/symfony-bundle
Flex registers the bundle, copies a minimal config/packages/tracewick.yaml, and
appends TRACEWICK_APPLICATION / TRACEWICK_SPOOL_DIR to .env.
Without Flex (or before the contrib recipe is merged), enable the bundle and copy config by hand:
// config/bundles.php return [ // ... Tracewick\Bridge\Symfony\TracewickBundle::class => ['all' => true], ];
cp vendor/tracewick/symfony-bundle/config/packages/tracewick.yaml config/packages/tracewick.yaml
Install the extension separately when you want samples (prefer PIE):
pie install wikimedia/excimer
php -m | grep -i excimer
Minimal config (what the recipe installs):
# config/packages/tracewick.yaml tracewick: application_name: '%env(TRACEWICK_APPLICATION)%' spool_directory: '%env(TRACEWICK_SPOOL_DIR)%'
enabled defaults to true, sample_rate to 0.1, sink to spool. Optional
keys (label_controller, profile_console, profile_messenger, base_labels,
Pyroscope) are documented in the fuller example:
config/packages/tracewick.yaml.
What it does
On each main HTTP request the bundle:
- Starts a unit-of-work profile early (
kernel.request, priority4096) withroute=unmatchedand the HTTP method. - After the router (priority
31), sets theroutelabel to the Symfony route name (never the raw URI). Optionally also setscontrollerwhenlabel_controller: true. - On
kernel.terminate(priority-4096), stops and flushes to the configured sink.
On each console command (opt-in: profile_console: true):
- Starts on
console.command(priority4096) withcommand=<name>(never argv). - On
console.terminate(priority-4096), stops and flushes.
On each consumed Messenger message (opt-in: profile_messenger: true, needs symfony/messenger):
- Middleware starts only when the envelope has a
ReceivedStamp(worker path), withmessage=<FQCN>(never the payload). Syncdispatch()inside HTTP is left to the request profile. - Stops and flushes in
finallyafter the rest of the bus stack.
HTTP is profiled by default; console and Messenger stay off until enabled. For
messenger:consume, prefer profile_messenger: true. If both flags are on, the console
subscriber skips messenger:consume / messenger:consume-messages so the long-lived consumer
command does not share one unit of work with every message.
One shared UnitOfWorkProfiler is built per worker process.
Fail-open (runtime): listener/middleware boundaries swallow errors; factory construction
failures (including a broken logger or an unknown sink after env resolve) return a no-op
profiler so the worker keeps serving. Not fail-open: invalid YAML / config-tree values still
fail at container build / cache:warmup — that is intentional so a half-wired profiler never
reaches request handling. Missing %env(...)% values also fail when Symfony resolves service
arguments, before the factory try/catch.
Inspect this process counters (collected / dropped / delivered / inactive sampler, …):
bin/console tracewick:stats
That command starts a new CLI process, so it shows zeros unless something profiled in that
same process. FPM / RoadRunner / messenger:consume counters live in those workers —
use HttpProfiler::diagnostics() from app code there, or scrape host metrics from core when
configured. Same snapshot API is available as HttpProfiler::diagnostics().
Production path
- Prefer
SpoolSink(sink: spool) from the request worker. - Deliver spool files with
tracewick-drainor the Go agent beside the app. - Direct
sink: pyroscopefrom PHP-FPM is for experiments only — not the default.
vendor/bin/tracewick-drain \ --spool-dir=/var/spool/tracewick \ --pyroscope-url=http://127.0.0.1:4040
Put the spool on a local volume or tmpfs. Details: core runtime, drain.
Kill switch
Turn profiling off without a deploy:
| Layer | How |
|---|---|
| Bundle | tracewick.enabled: false (no subscriber / profiler services) |
| Core | TRACEWICK_DISABLE_FILE — if the path exists, profiling is off |
| Core | TRACEWICK_ENABLED=0 / false / off / no |
Core kill switches still apply when the bundle is enabled. See
docs/runtime.md.
Without ext-excimer, Tracewick is a no-op (warn once, then idle). The app keeps serving.
Development
composer install
make ci
make infection # needs the Infection PHAR (same version as CI)