Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Xhprof Laravel Package

lox/xhprof

XHProf profiling for Laravel. Easily profile requests and inspect performance data with middleware and configuration for enabling or disabling profiling in your application.

Deep Wiki
Context7

Getting Started

lox/xhprof is a Composer-packaged fork of Facebook's XHProf: the Composer side ships the userland library (xhprof_lib, xhprof_html), but the actual profiler is still a PHP extension (xhprof.so) that Composer cannot install for you. Plan for two steps, not one.

  1. Get the extension loaded. Verify with php -m | grep xhprof. If it's missing, you must build/install it (PECL or from source). Because this fork is archived, expect to be on an older PHP (7.x); on PHP 8 you'll almost certainly need tideways/xhprof or a maintained fork instead.
  2. Pull in the library (dev only).
composer require --dev lox/xhprof
  1. Set the run directory in php.ini (or verify it's writable at bootstrap):
xhprof.output_dir = /tmp/xhprof
  1. First use case: profile one endpoint, not the whole app. Drop this into a controller action or a debug-only middleware:
if (extension_loaded('xhprof')) {
    xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);

    register_shutdown_function(function () {
        $data = xhprof_disable();
        $dir  = ini_get('xhprof.output_dir') ?: sys_get_temp_dir();
        $file = sprintf('%s/%s.%d.xhprof', rtrim($dir, '/'), uniqid('req', true), getmypid());
        file_put_contents($file, serialize($data));
    });
}
  1. Look at the result by serving the bundled UI (it's plain PHP, not a framework route):
php -S 127.0.0.1:8080 -t vendor/lox/xhprof/xhprof_html
# then open http://127.0.0.1:8080/index.php?run=<run-id>&source=your-app

That UI is where you'll spend your time: flat profile, inclusive vs. exclusive wall time, call counts, memory, and the callgraph view.

Implementation Patterns

Gate profiling behind an explicit switch. The classic pattern is a query param or env var checked before the framework bootstraps, so you capture the full request rather than just the controller:

// public/index.php, very top
if (getenv('XHPROF') || isset($_GET['_profile'])) {
    xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
}

Wrap it in one service, not scattered calls. A small Profiler class that owns enable(), disable(), run naming, and persistence keeps you from double-enabling (XHProf warns and refuses) and gives you a single place to add tags like route, user_id, or git sha into the filename. Metadata in filenames makes the UI's run list navigable when you have hundreds of runs.

Profile CLI commands and queue jobs separately. XHProf is request-agnostic; wrapping artisan commands gives you the clearest signal for batch jobs and imports. For queue workers, enable/disable per job and persist at the end — do not leave it enabled across the worker loop.

Use sampling mode for anything long-running or shared. xhprof_sample_enable() / xhprof_sample_disable() gives you a statistical profile with far lower overhead and no per-function call counts. That's the mode to consider on staging, never the full flags.

Compare before/after with the diff view. This is the highest-leverage feature day-to-day: run the endpoint, apply an optimization, run it again, then open callgraph.php?run1=...&run2=... (or the diff link in the UI). Absolute time rarely moves the needle; the diff view shows whether your change actually shifted the hot path or just moved it.

Prune aggressively. Nothing deletes .xhprof files for you. A nightly find /tmp/xhprof -mtime +7 -delete, or a cap on file count, prevents a directory full of serialized arrays.

Framework integration is intentionally minimal. There's no service provider to register. The integration surface is: enable early, disable late, write a file, read it in the bundled UI. Resist the urge to wire it into production observability — it's a local optimization tool.

Gotchas and Tips

  • Archived is the headline risk. lox/xhprof is unmaintained. It will not build against modern PHP, and XHProf's internals (function name handling, zend_execute hooks) are exactly the kind of thing PHP minor releases break. Pin it, fork it internally if you must, and have tideways/xhprof, the Xdebug profiler, or SPX on your migration list. Do not build new workflow around it.
  • Composer ≠ extension. A frequent failure: composer require lox/xhprof succeeds, then every call to xhprof_enable() fatals because the .so isn't loaded. Check extension_loaded('xhprof') before every call site.
  • xhprof.output_dir must exist and be writable by the FPM/CLI user. The default is /tmp; in containers that's often ephemeral and disappears on restart. Set it in php.ini rather than ini_set() at runtime if you hit permission or timing surprises.
  • Overhead is real. Full CPU+memory flags can cost multi-x slowdown and a large memory delta per request. Never enable globally, never on production traffic, and disable before benchmarking anything you care about.
  • Callgraph view needs Graphviz. The dot binary must be on PATH; without it the callgraph page renders broken images. This trips people up in slim Docker images.
  • Namespaced functions show with backslashes (App\Service\Foo::bar), and very long names can be truncated. Grep the serialized data if a function seems to be missing.
  • Inclusive time lies under recursion. Recursive functions inflate inclusive time; lean on exclusive time and call counts when reading the flat profile.
  • Internal functions are captured, so you'll see strlen, preg_match, PDO::query etc. mixed in. That's useful, but it means "top function" is often a builtin — read the whole call path, not the first row.
  • Don't run Xdebug and XHProf expecting clean numbers. Both hook execution. If you need Xdebug for step-debugging, profile in a separate process/container.
  • The HTML UI has no auth. It reads arbitrary run IDs from output_dir and is a directory-listing-ish surface. Bind it to localhost or put it behind your existing dev-only auth; never expose it on a shared host.
  • Two runs must be comparable. The diff view is only meaningful if the code path, data set, and cache state are the same. Warm the cache before the "after" run, or you'll attribute cache warmup to your optimization.
  • Serialization format matters. Runs are PHP-serialized arrays; if you store them yourself, keep the extension's format or the bundled viewers won't parse them.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource