laracraft-tech/laravel-xhprof
Laravel XHProf is an easy profiling package that helps you find performance bottlenecks in your Laravel apps. Enable it, browse your app, then inspect detailed XHProf or Tideways call graphs through a built-in web UI to see exactly where time is spent.
Architecture fit. The package is Laravel-native rather than bolted on. A service provider registers middleware into the HTTP kernel and listeners cover queue jobs and Artisan commands, so a single profile captures routing, middleware, controllers, Eloquent/query time, and view rendering in one call tree. For a TPM this is the difference between "the database looks fine" and a ranked list of named functions with wall time, CPU, memory, and call counts — the evidence needed to write a ticket like "reduce checkout p95 by 40%" instead of "improve performance."
The coverage beyond HTTP is the underrated half. In most Laravel applications the real cost sits in imports, nightly batches, and notification fan-out, not in the request path. Profiling those is what lets a TPM defend — or decline — infrastructure spend before approving bigger instances or more workers, with a number attached to the recommendation.
Three extension points matter for later leverage:
ProfileSaved events — release markers, incident annotation, archival, without forking.start()/stop() — performance assertions inside tests, the mechanism that turns an NFR from a wiki page into a merge gate.The honest boundary: this is point-in-time inspection, not observability. No RUM, no distributed tracing, no trend series, no alerting. Positioning it as "APM, but free" will not survive the first steering review.
Integration feasibility. Application-code footprint is genuinely small: composer require, publish config and migration, register middleware, enable the extension. No custom controllers, no routes, no external service. Realistically a one-sprint spike with same-day rollback. The gating question is not code — it is the runtime. XHProf (or tideways_xhprof) must be installed and enabled in both the web and CLI SAPIs; if the team does not control the PHP base image (shared hosting, locked managed runtime, extensions via support ticket), stop there rather than discover a silent no-op at sprint end.
Technical risk.
| Risk | TPM-relevant impact | Mitigation |
|---|---|---|
| Extension missing in web or CLI SAPI | Blocks the pilot outright; cost discovered at sprint end | Verify php -m | grep xhprof on both SAPIs before approving the spike |
| Silent no-op when misconfigured | False confidence — team "profiles" for a week and concludes the app is fine | Pilot acceptance test: a profile row must exist after a request and after a job |
| Unbounded growth in the runs table | Staging DB instability, slower backups, a surprise cleanup project | Set a sample rate and a pruning routine before broad enablement, or switch to disk storage |
| Middleware ordering | Wall-time attribution is inaccurate; flame graphs mislead the fix | Register early in the stack; sanity-check against a route already known to be slow |
| Small project (235 stars, 0 dependents, maintainer-driven) | Maintenance risk if the author goes quiet | Pin the version, vendor it, treat a fork as roughly a day of work |
Legacy schema drift (v1.0.10 changed id to primary key, dropped idcount) |
Upgrade pain if adopted onto an older install | Treat the migration as part of the initial install, not a later chore |
| Queue/command coverage assumed to be automatic | The highest-value half of the tool never activates | Explicit instrumentation for jobs and commands — verify as a pilot exit criterion |
Key questions to resolve before committing.
Stack fit. Correct fit is narrow and testable: Laravel 10–13.x, PHP runtime under your control, and a staging environment that mirrors production closely enough for profiles to transfer. It is not a fit for shared hosting or a locked managed runtime, and it is not a fit where a licensed APM already provides code-level staging profiles — the second profiler buys little and adds a training curve.
The package sits in the same layer as framework logging or a debug toolbar: it wraps the request lifecycle and produces local artifacts. It does not require an outbound network path, which is why it clears environments where a SaaS APM would need a security review and a procurement cycle. That is the practical reason to choose it for a timeboxed investigation.
Migration path. Two distinct paths, and the TPM should name which one is in play:
id became the primary key, idcount removed). This is a migrate-again step, not automatic. Budget it explicitly and don't fold it into an unrelated release.Rollback in both cases is Composer removal plus disabling the extension — same-day, no data loss risk beyond the profiles themselves. That low reversal cost is what makes a pilot approvable.
Compatibility. Release history shows steady Laravel-major tracking: 10 → 11 → 12 → 13 across v1.0.9, v1.1.0, v1.2.0, v1.3.0, with the compatibility PRs largely contributed via laravel-shift. The v1.3.0 release (2026-03-09) confirms current Laravel 13 support and added the URL-needle skip. Practical read: the project stays current on the framework dimension, and the compatibility work is being done by automated/community contributors rather than by the maintainer alone — mildly reassuring on bus factor, not a guarantee.
Pin the exact version. Treat "compatible with the next Laravel major" as an assumption to re-verify each upgrade cycle, not a standing property.
Sequencing. Four gates, each with a decision:
Explicit non-goal throughout: production. Scoping it to local and staging is itself the product decision — it keeps overhead off the request path, avoids a retention argument, and keeps the "we need real APM" conversation honest and separate.
Maintenance. Ongoing burden is small but not zero, and it should be named in the pilot readout rather than discovered later:
Support. No commercial support, no SLA, no vendor relationship to escalate through. Support model is internal: whoever owns the pilot owns the knowledge. That is a real cost in an organization where tooling without a vendor "doesn't count" — anticipate it in the stakeholder conversation rather than arguing about it.
The counter-argument is the honest comparison: this package against a homegrown microtime() / DB::listen harness, or against nothing. An MIT package with Laravel 10–13 support that installs in about an hour is almost always the cheaper option for targeted internal investigation, and it avoids a procurement cycle entirely. Say that plainly; it is the strongest argument and it doesn't require overstating the tool.
Scaling. Two independent scaling concerns:
Neither concern applies to production, because the tool shouldn't be there.
Failure modes. Ranked by how much damage they cause relative to how likely they are:
| Failure mode | What it looks like | Detection | Response |
|---|---|---|---|
| Silent no-op (extension not loaded) | "Profiling" runs for a week; no profiles exist; team concludes app is healthy | Acceptance test: profile row after a request and after a job | Hard gate at pre-flight; fail the pilot rather than debug later |
| Unbounded table growth | Staging DB slow, backups bloated, surprise cleanup epic | Row-count alert or the prune job's own logs | Sample rate + prune job before broad enablement |
| Misleading flame graphs (middleware order) | Hotspot attributed to the wrong layer; fix ships, latency unchanged | Compare against a route already known to be slow | Register middleware early; re-verify attribution |
| Coverage gap on jobs/commands | The highest-cost code is never profiled; conclusions drawn from HTTP alone | Explicit check that a queue job produced a profile | Instrument jobs/commands in sequencing step 3 |
| Schema drift surprise | Migration fails or requires manual steps on an older install | Read the release notes before upgrading | Treat the v1.0.10 migration as part of the install |
| Maintainer goes quiet at a Laravel major | Upgrade blocked; app upgrade blocked behind it | Watch release cadence against framework releases | Pinned version buys time; fork is ~a day |
Note the shape of this list: every failure mode is cheap to detect and expensive to discover late. That asymmetry is the argument for the pre-flight gate and the acceptance test — both are hours, and both prevent a sprint of false confidence.
Ramp-up. The tool's learning curve splits into two separate skills, and the TPM should plan for both:
Mitigation for ramp-up is structural, not pedagogical: make the pilot exit criteria shipped fix with before/after numbers, not profiles collected. That single change makes the readout useful to stakeholders and forces the reading skill to develop on the first flow.
Pilot framing to stakeholders. Approve a timeboxed pilot: staging only, two flows, one sprint, one-page readout covering bottlenecks found, fixes shipped, and measured before/after. Approximately an hour of engineering plus an approved PHP extension and no license fee. It is not production monitoring and it will not page anyone; if RUM or cross-service tracing is needed later, that is a separate and larger buy — and this pilot is the cheapest way to scope that RFP with data from your own application rather than from a vendor demo.
How can I help you explore Laravel packages today?