Laravel performance profiling
for AI coding agents.
Capture queries, detect performance issues or N+1 patterns, and run EXPLAIN ANALYZE — all via Artisan commands that output structured JSON to stdout. No browser, no GUI.
# 1. install the package$ composer require mateffy/laraperf --dev# 2. start a 2-minute capture window$ php artisan perf:watch --seconds=120✓ session=session-20260416-143201-xK9mQp pid=47821 duration=120s# 3. exercise the app, then analyse$ php artisan perf:query{ "n1_candidate_count": 3, "slowest_query_ms": 890, "total_queries": 183 }# 4. drill into the query plan$ php artisan perf:explain --hash=a1b2c3d4e5f6 | jq '.[0].Plan'{ "Node Type": "Index Scan", "Actual Rows": 47 }
Designed for
how agents work
not for humans
Standard tools — Debugbar, Clockwork, Telescope — require a browser UI. LLM agents invoke commands, read JSON, and loop. laraperf is built around that model.
- Every command exits immediately
- All output is structured JSON to stdout
- Status/errors go to stderr — safe to pipe
- Hashes let agents reference queries across commands
- Stack traces filtered to
app/— no vendor noise
Capture
Start a session and let it run. Captures every query automatically while your agent exercises the app.
Analyse
Get a JSON summary with total queries, slow queries, and N+1 candidates with source file and line number.
Explain
Run EXPLAIN ANALYZE on any query. Pass raw SQL or reference a hash from the query output.
Slow query detection
Find queries exceeding your threshold. Returns execution time, SQL hash, and exact source location in your codebase.
N+1 Detection
Identifies repeated queries that should be eager loaded. Groups identical SQL patterns and counts occurrences.
Clean stack traces
Your agent only sees your app code. Vendor frames are filtered out so the source location points directly to your code.
Multi-tenant
Override the database name at runtime. No config changes needed, works with any tenancy setup.
Pest integration
Write performance assertions directly in your test suite — query counts, N+1 detection, duration limits — with a fluent API.
How agents improve performance
Your agent runs commands, reads JSON output, and iterates. No browser UI, no human intervention.
- →Capture runs in background while agent exercises the app
- →Query returns structured data with file paths and line numbers
- →Explain shows query plans to diagnose performance
- →Agent fixes code and repeats until clean
Run perf:watchStart a capture session in the background
Interact with your applicationOpen pages, trigger actions, run tests — whatever exercises your queries
Run perf:queryFind slow queries and N+1s in the captured session, with exact source file and line number
Run perf:explainInvestigate issues, find missing indexes, and understand query performance with EXPLAIN ANALYZE output
Fix and repeatYour agent iterates — updating code, re-running captures, and improving until all issues are resolved
CLI reference
All output goes to stdout. Status lines go to stderr. Safe to pipe anywhere.
perf:watch
Start a capture session. Returns immediately by default (detached). Workers run in the background and append queries to a JSON file.
--syncRun in foreground; Ctrl+C or timeout ends it--seconds=NWindow duration. Default: 300--foreverKeep alive indefinitely (detached only)--tag=labelLabel stored in session metadata$ php artisan perf:watch --seconds=120✓ session=session-20260416-143201-xK9mQp pid=47821 duration=120sUse `perf:stop` to stop early, or wait for the timeout.Then run: php artisan perf:query --session=session-20260416-143201-xK9mQp
Built-in Pest testing
Write performance assertions directly in your test suite. Measure queries, memory, and N+1 patterns with a fluent API — no CLI needed.
Declarative constraints
Chain constraints onto any Pest test. They're validated automatically after the test runs — no manual assertions needed.
| ->maxQueryCount(10) | Max allowed queries |
| ->maxDuration(500) | Max total duration in ms |
| ->maxMemory('10M') | Max memory usage |
| ->maxN1Candidates(0) | Max N+1 patterns |
| ->noN1Patterns() | Require zero N+1 issues |
| ->maxQueryDuration(100) | Max single query ms |
test('user list has no N+1 queries')->maxQueryCount(10)->noN1Patterns()->maxDuration(500);
use function Mateffy\Laraperf\Testing\{measure};test('dashboard loads fast', function () {$result = measure(fn () =>User::with('posts')->paginate());expect($result->queryCount())->toBeLessThan(20);});test('contact query performance', function () {$result = measure(fn () =>Contact::with('company')->get());expect($result->durationMs())->toBeLessThan(100);});
measure()
Wrap any callback with measure() and get a full PerformanceResult — duration, memory, query count, N+1 candidates, and timeline events. Works in tests, tinker, or anywhere in your app.
| durationMs() | Total execution time in ms |
| peakMemoryHuman() | Peak memory (e.g. "2.4 MB") |
| queryCount() | Number of queries executed |
| n1Candidates(3) | N+1 patterns detected |
| slowQueries(100) | Queries above a threshold |
| summary() | Quick overview array |
Fluent expectation API
Chain assertions on duration, query count, N+1 detection, and more. Filter queries by table, operation, or connection before asserting.
| ->performance()->duration() | Assert on total duration |
| ->performance()->queries()->count() | Assert on query count |
| ->performance()->queries()->whereTable('users') | Filter before asserting |
| ->performance()->toHaveNoN1() | Zero N+1 patterns |
| ->performance()->toHaveNoSlowQueries(50) | No queries above 50ms |
| ->performance()->n1(5) | Custom N+1 threshold |
test('contacts page performance', function () {$result = measure(fn () =>Contact::with('company')->get());expect($result)->performance()->duration()->toBeLessThan(100)->performance()->queries()->whereTable('contacts')->count()->toBeLessThan(5)->performance()->toHaveNoN1()->performance()->toHaveNoSlowQueries(50);});
use function Mateffy\Laraperf\Testing\{capture, timeline_mark};test('import progress tracking', function () {$cap = capture();timeline_mark('start');$importer = new ContactImporter();$importer->import($csv);timeline_mark('imported');$result = $cap->stop();// Timeline marks let you measure phases$importMs = $result->durationBetween('start', 'imported');expect($importMs)->toBeLessThan(5000);});
capture() & timeline marks
For finer control, start and stop capture manually. Drop timeline_mark() between phases to measure specific steps — then query the deltas with durationBetween() and memoryDelta().
| capture() | Start a manual capture session |
| timeline_mark('label') | Mark a point in the timeline |
| $cap->stop() | Stop and get PerformanceResult |
| durationBetween('a','b') | Time between two marks |
| memoryDelta('a','b') | Memory change between marks |
Installation
Two ways to get started — manual install or let your agent handle it.
Manual install
composer require mateffy/laraperfIf you have Laravel Boost installed, run the following after installing laraperf — the skill is automatically added.
php artisan boost:updateLet your agent do it
Install the skill permanently with the CLI, or paste a prompt for a one-shot setup.
npx skills add mateffy/laraperfOr paste this prompt for a quick one-shot:
Using Laravel Boost? Run php artisan boost:update after installing — the skill is added automatically.
Agent skill
laraperf ships a skill that teaches your agent the full capture-analyse-explain loop. Install it permanently or use it on-the-fly — one command either way.
What the skill teaches
The laraperf-profiling skill is a markdown document that lives in the repo. It contains the complete workflow: which commands to run, how to parse their JSON output, and how to iterate from detection to a fix.
Capture queries
Start perf:watch, exercise the code path, collect the session
Detect issues
Run perf:query to find N+1 patterns and slow queries with file:line sources
Analyse plans
Run perf:explain on flagged queries to find missing indexes or seq scans
Fix and verify
Apply the fix (eager load, index) and re-capture to confirm improvement
Install via skills CLI
Permanently adds the skill to your project so every agent session has it. The skill is copied into your agent's skills directory and tracked in skills-lock.json.
npx skills add mateffy/laraperf-gInstall globally for all projects-a claude-codeTarget a specific agent-ySkip confirmation promptsOr paste a one-shot prompt
No install needed. Send this prompt to your agent — it will fetch the skill and set everything up.
Compatible agents
Full support via npx skills or prompt
Install to .cursorrules or paste in composer
Add to AGENTS.md or paste as a task prompt
Install to .agents/skills/ via npx skills
Auto-installed — run php artisan boost:update after composer require
Fetch skill.md directly — it's plain markdown, no auth needed