### Agent Workflow Example Source: https://github.com/mateffy/laraperf/blob/main/docs/skill.md A step-by-step example demonstrating how an AI agent can use laraperf commands to identify and fix performance issues. ```bash 1. **Start capture**: `php artisan perf:watch --seconds=120` 2. **Exercise the app**: Run tests, load pages, trigger API calls 3. **Analyze**: `php artisan perf:query --n1=3 --slow=100` 4. **Investigate**: Use `perf:explain` on slow queries 5. **Apply fixes**: Add eager loading (`::with()`) or create indexes 6. **Verify**: Re-run capture and confirm improvements ``` -------------------------------- ### One-Shot LaraPerf Skill Installation Prompt Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/detecting-n-plus-one-queries-with-laraperf/index.html Use this prompt with your AI agent for a quick, one-time setup of the LaraPerf skill and package. ```bash Fetch and read the laraperf skill from https://laraperf.dev/skill.md, then install the package in this Laravel project using composer require mateffy/laraperf --dev. Run a quick performance capture to verify it's working. ``` -------------------------------- ### Start a capture session (sync mode) Source: https://github.com/mateffy/laraperf/blob/main/README.md Starts a performance capture session that runs in the foreground, blocking the terminal. The session can be ended with Ctrl+C or a timeout. ```bash php artisan perf:watch --sync --seconds=60 ``` -------------------------------- ### Start a Timed Capture Session Source: https://github.com/mateffy/laraperf/blob/main/docs/index.html Starts a performance capture session for a specified number of seconds. The command provides feedback on the session and suggests how to analyze it afterward. ```bash $ php artisan perf:watch --seconds=120 Use `perf:stop` to stop early, or wait for the timeout. Then run: php artisan perf:query --session=session-20260416-143201-xK9mQp ``` -------------------------------- ### Start Profiling with Watch Source: https://github.com/mateffy/laraperf/blob/main/skills/laraperf-profiling/SKILL.md Starts capturing database queries. Sessions are stored in storage/perf. Use --sync for synchronous mode or --forever to profile until manually stopped. Sessions can be tagged for easier identification. ```bash php artisan perf:watch ``` ```bash php artisan perf:watch --sync ``` ```bash php artisan perf:watch --seconds=300 ``` ```bash php artisan perf:watch --forever ``` ```bash php artisan perf:watch --tag="filament-users-resource" ``` -------------------------------- ### Install laraperf Source: https://github.com/mateffy/laraperf/blob/main/docs/skill.md Install the laraperf package as a development dependency using Composer. ```bash composer require mateffy/laraperf --dev ``` -------------------------------- ### Start Database Query Capture Source: https://github.com/mateffy/laraperf/blob/main/docs/skill.md Initiate a session to record all database queries. The watcher runs in the background by default, saving captured queries to `storage/perf/`. ```bash # Start a 2-minute capture (detached mode) php artisan perf:watch --seconds=120 ``` ```bash # Start a 5-minute capture with a descriptive tag php artisan perf:watch --seconds=300 --tag=checkout-flow ``` ```bash # Run in foreground (sync mode) php artisan perf:watch --seconds=60 --sync ``` -------------------------------- ### Start a capture session (detached mode) Source: https://github.com/mateffy/laraperf/blob/main/README.md Starts a performance capture session in the background. The session runs for 5 minutes by default or until stopped. Use `perf:stop` to terminate. ```bash php artisan perf:watch # → perf:watch [detached] session=session-20260416-143201-xK9mQp pid=47821 duration=300s # → Use `php artisan perf:stop` to stop, or wait for the timeout. # → Then run: php artisan perf:query --session=session-20260416-143201-xK9mQp ``` -------------------------------- ### Get Query Plan with Raw SQL Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/using-explain-analyze-to-optimize-queries/index.html Alternatively, provide the raw SQL query directly to the perf:explain command using the --sql flag. ```bash php artisan perf:explain --sql="SELECT ..." ``` -------------------------------- ### Start Performance Capture Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/llm-coding-agents-and-performance-workflows/index.html Initiates a performance capture session for a specified duration. Useful for monitoring application performance during specific tasks or test runs. ```bash $ perf:watch --seconds=60 ``` -------------------------------- ### EXPLAIN ANALYZE Output Example Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/using-explain-analyze-to-optimize-queries/index.html Example JSON output from EXPLAIN ANALYZE, showing a 'Seq Scan' operation with actual rows and time, and rows removed by a filter. ```json [ { "Plan": { "Node Type": "Seq Scan", "Relation Name": "contacts", "Actual Rows": 4721, "Actual Total Time": 8432.11, "Filter": "(email ~~* '%gmail%'::text)", "Rows Removed by Filter": 15279 } } ] ``` -------------------------------- ### Explain Captured Queries Source: https://github.com/mateffy/laraperf/blob/main/docs/skill.md Run EXPLAIN ANALYZE on a captured query using its hash or directly on raw SQL. This can be useful for multi-tenant setups where you might need to specify the database connection. ```bash # Reference a query by its hash php artisan perf:explain --hash=a1b2c3d4e5f6 ``` ```bash # Override database for multi-tenant setups php artisan perf:explain --hash=a1b2c3d4 --db=tenant_acme_prod ``` ```bash # Explain raw SQL directly php artisan perf:explain --sql="SELECT * FROM users WHERE email LIKE '%gmail%'" ``` -------------------------------- ### Install Laraperf with Composer Source: https://github.com/mateffy/laraperf/blob/main/README.md Install the Laraperf package using Composer. This command requires PHP 8.3+ and Laravel 11+. ```bash composer require mateffy/laraperf ``` -------------------------------- ### Example Query Plan Output Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/llm-coding-agents-and-performance-workflows/index.html A sample JSON output representing the execution plan of a database query. This format is used by agents to analyze query performance. ```json { "Plan": { "Node Type": "Seq Scan"... } } ``` -------------------------------- ### Find Slow Queries and Explain Plan Source: https://github.com/mateffy/laraperf/blob/main/website/public/skill.md This pattern shows how to identify slow queries using perf:query and then use perf:explain to get the EXPLAIN ANALYZE plan for a specific query hash. Look for 'Seq Scan' in the plan output. ```bash php artisan perf:query --slow=50 php artisan perf:explain --hash= ``` -------------------------------- ### Configure Database Name Override Source: https://github.com/mateffy/laraperf/blob/main/README.md Override the database name for multi-tenant setups using the PERF_DB environment variable. ```bash PERF_DB= # Override database name (for multi-tenant) ``` -------------------------------- ### CI/CD Integration for Performance Checks Source: https://github.com/mateffy/laraperf/blob/main/website/public/skill.md This example shows how to integrate laraperf into a CI/CD pipeline to automatically check for N+1 queries. The pipeline fails if any N+1 candidates are detected. ```yaml name: Performance Check run: | php artisan perf:watch --seconds=120 & php artisan test COUNT=$(php artisan perf:query --n1=3 | jq '.n1.candidates | length') if [ "$COUNT" -gt 0 ]; then exit 1; fi ``` -------------------------------- ### N+1 Query Example (PHP) Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/detecting-n-plus-one-queries-with-laraperf/index.html This PHP code demonstrates the N+1 query problem where fetching all posts and then accessing the 'user' relationship for each post results in multiple individual queries. ```php $posts = Post::all(); foreach ($posts as $post) { echo $post->user->name; } ``` -------------------------------- ### Detect N+1 Queries Pattern Source: https://github.com/mateffy/laraperf/blob/main/docs/skill.md This pattern demonstrates how to detect N+1 queries by starting a capture, running tests, and then analyzing the results for N+1 candidates. ```bash php artisan perf:watch --seconds=60 & php artisan test php artisan perf:query --n1=3 | jq '.n1.candidates' ``` -------------------------------- ### Conditional Eager Loading Example Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/detecting-n-plus-one-queries-with-laraperf/index.html Conditionally eager load a relationship using the `when` method based on a variable. ```php $posts->when($needsUser, fn($q) => $q->with('user')) ``` -------------------------------- ### Analyze Captured Queries Source: https://github.com/mateffy/laraperf/blob/main/docs/index.html Analyze the captured performance session to get a summary of queries, including N+1 candidates and slow queries. ```bash $ php artisan perf:query { "n1_candidate_count": 3, "slowest_query_ms": 890, "total_queries": 183 } ``` -------------------------------- ### Get Query Plan by Hash Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/using-explain-analyze-to-optimize-queries/index.html Retrieve the EXPLAIN ANALYZE plan for a query previously captured by laraperf, identified by its hash. This avoids manual SQL copying. ```bash $ php artisan perf:explain --hash=a1b2c3d4 ``` -------------------------------- ### Start Production Capture for 5 Minutes Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/capturing-production-performance-data/index.html Initiates a detached performance capture for a specified duration with a descriptive tag. The capture runs in the background and saves its PID for later reference. ```bash $ php artisan perf:watch --seconds=300 --tag=peak-hours ``` -------------------------------- ### Lazy Eager Loading Example Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/detecting-n-plus-one-queries-with-laraperf/index.html Eager load a relationship after filtering records to only load what is actually needed. ```php $posts->load('user') ``` -------------------------------- ### Override Tenant Database Connection Source: https://github.com/mateffy/laraperf/blob/main/skills/laraperf-profiling/SKILL.md Use the --db flag to specify the tenant's database name when running the perf:explain command. This is useful in multi-tenant setups where each tenant has a separate database. ```bash # The default "tenant" connection often has a template database name in config. # Override it with the actual tenant database: php artisan perf:explain --hash=abc123 --connection=tenant --db=tenant_mytenant ``` -------------------------------- ### Add LaraPerf Skill Permanently via CLI Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/detecting-n-plus-one-queries-with-laraperf/index.html Install the LaraPerf skill permanently into your project using this npx command. ```bash npx skills add mateffy/laraperf ``` -------------------------------- ### Selective Field Eager Loading Example Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/detecting-n-plus-one-queries-with-laraperf/index.html Limit the fields loaded for a relationship during eager loading to conserve memory. ```php Post::with(['user:id,name']) ``` -------------------------------- ### Manual Capture with Timeline Marks Source: https://github.com/mateffy/laraperf/blob/main/docs/index.html Manually start and stop capture sessions using capture() and stop(). Use timeline_mark() to measure specific phases within the captured code, then query deltas using durationBetween() and memoryDelta(). ```php 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); }); ``` -------------------------------- ### Manual Capture and Timeline Marks Source: https://github.com/mateffy/laraperf/blob/main/README.md Manually start and stop performance captures using the `capture` function and add custom `timeline_mark` events. This provides fine-grained control over what is measured. ```php use function Mateffy\Laraperf\Testing\{measure, capture, is_capturing, timeline_mark}; // Manual start/stop with timeline marks $cap = capture(); // starts capture timeline_mark('before-query'); User::all(); timeline_mark('after-query'); $result = $cap->stop(); // stops and returns PerformanceResult ``` -------------------------------- ### Update Laravel Boost with LaraPerf Skill Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/detecting-n-plus-one-queries-with-laraperf/index.html If you are using Laravel Boost, run this command after installing LaraPerf to automatically add the skill. ```bash php artisan boost:update ``` -------------------------------- ### Example Stack Trace Filtering Source: https://github.com/mateffy/laraperf/blob/main/README.md This JSON structure represents filtered stack trace information for a query, showing the file, line number, and function. ```json { "source": [ { "file": "/app/Domains/Deals/Resources/DealResource/Pages/ListDeals.php", "line": 47, "function": "getTableQuery" } ] } ``` -------------------------------- ### Manual Performance Capture in Pest Tests Source: https://github.com/mateffy/laraperf/blob/main/README.md Manually start and stop performance captures within Pest tests using `$this->startPerformanceCapture()` and `$this->stopPerformanceCapture()`. This is useful for testing specific code blocks. ```php // Manual capture in tests test('specific operation', function () { $this->startPerformanceCapture(); // ... code under test ... $result = $this->stopPerformanceCapture(); expect($result->n1Count())->toBe(0); }); ``` -------------------------------- ### N+1 Query Detection Output (JSON) Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/detecting-n-plus-one-queries-with-laraperf/index.html Example JSON output from laraperf's `perf:query` command, showing detected N+1 candidates with their count, table, normalized SQL, and source location. ```json { "n1": { "candidates": [ { "count": 47, "table": "contacts", "normalized_sql": "select * from \"contacts\" where \"id\" = ?", "example_source": [ { "file": "app/Domains/Deals/Resources/DealResource/Pages/ListDeals.php", "line": 47, "function": "getTableQuery" } ] } ] } } ``` -------------------------------- ### Typical Laraperf Workflow Source: https://github.com/mateffy/laraperf/blob/main/README.md Demonstrates the command-line steps for capturing, analyzing, and explaining query performance in Laravel. Use this for quick, ad-hoc performance checks. ```bash # 1. Start a 2-minute capture window php artisan perf:watch --seconds=120 # → session=session-20260416-143201-xK9mQp # 2. Use the application (browser, API calls, etc.) # Queries are automatically captured to the session file # 3. Get a summary php artisan perf:query # → { "summary": {...}, "slow": {...}, "n1": {...} } # 4. Drill into the worst N+1 php artisan perf:query --n1=3 | jq '.n1.candidates[0]' # → { "count": 47, "table": "contacts", "example_source": {...} } # 5. Get the EXPLAIN plan php artisan perf:explain --hash=a1b2c3d4e5f6 | jq '.[0].Plan' # 6. Stop early if needed php artisan perf:stop ``` -------------------------------- ### Explain Queries for a Specific Tenant Database Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/capturing-production-performance-data/index.html Use the `--db` flag with `perf:explain` to analyze queries against a specific tenant's database at runtime. This is crucial for debugging multi-tenant applications where performance varies per tenant. ```bash php artisan perf:explain --hash=abc123 --db=tenant_acme_prod ``` -------------------------------- ### EXPLAIN ANALYZE output structure Source: https://github.com/mateffy/laraperf/blob/main/README.md The output of `perf:explain` includes driver details, connection information, and the query plan. Errors are reported if any occur during execution. ```json { "driver": "pgsql", "connection": "tenant", "database": "tenant_dev", "plan": [{ "Plan": { "Node Type": "Index Scan", ... } }], "error": null } ``` -------------------------------- ### Run EXPLAIN ANALYZE for PostgreSQL Source: https://github.com/mateffy/laraperf/blob/main/README.md Executes `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)` for PostgreSQL or plain `EXPLAIN` for other drivers. Wraps non-SELECT statements in `BEGIN`/`ROLLBACK` to prevent mutation. ```bash # Direct SQL php artisan perf:explain \ --sql "select * from \"estates\" where id = \'834b7d2a-...'" \ --connection=tenant --db=tenant_dev ``` ```bash # Reference a query hash from perf:query output php artisan perf:explain --hash=a1b2c3d4e5f6 --db=tenant_dev ``` ```bash # Pipe into jq php artisan perf:explain --hash=a1b2c3d4e5f6 --db=tenant_dev | jq '.[0].Plan' ``` -------------------------------- ### Explain Query Plan Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/llm-coding-agents-and-performance-workflows/index.html Retrieves and displays the execution plan for a given query hash. This is essential for understanding why a query is slow and how to optimize it. ```bash $ perf:explain --hash=a1b2c3d4 ``` -------------------------------- ### Run EXPLAIN ANALYZE with Explain Source: https://github.com/mateffy/laraperf/blob/main/skills/laraperf-profiling/SKILL.md Runs EXPLAIN ANALYZE on a query identified by its hash or raw SQL. For non-SELECT statements, EXPLAIN is wrapped in a rolled-back transaction. Output is JSON on stdout. ```bash php artisan perf:explain --hash=abc123def456 ``` ```bash php artisan perf:explain --sql="SELECT * FROM users WHERE active = 1" ``` ```bash php artisan perf:explain --hash=abc123def456 --db=tenant_acme ``` ```bash php artisan perf:explain --hash=abc123def456 --connection=tenant ``` -------------------------------- ### Explain Output Format Source: https://github.com/mateffy/laraperf/blob/main/skills/laraperf-profiling/SKILL.md The output format for `perf:explain` is JSON on stdout, with status messages on stderr. It includes driver, connection, database details, the execution plan, and any errors. ```json { "driver": "pgsql", "connection": "tenant", "database": "tenant_acme", "plan": [ ... ], "error": null } ``` -------------------------------- ### Explain a Query Plan Source: https://github.com/mateffy/laraperf/blob/main/docs/index.html Drill into the execution plan of a specific query using EXPLAIN ANALYZE. Requires jq to parse the JSON output. ```bash $ php artisan perf:explain --hash=a1b2c3d4e5f6 | jq '.["0"].Plan' { "Node Type": "Index Scan", "Actual Rows": 47 } ``` -------------------------------- ### Profile Filament Resource List Source: https://github.com/mateffy/laraperf/blob/main/skills/laraperf-profiling/SKILL.md Watch and profile the database queries generated when loading a Filament resource list. Use --sync for immediate results. Press Ctrl+C to finalize. ```bash php artisan perf:watch --sync --tag="filament-users-list" # Visit the Filament users list page in the browser # Queries are captured automatically via PHP-FPM interception # Ctrl+C to finalize php artisan perf:query --n1=3 ``` -------------------------------- ### Identify Performance Issues Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/llm-coding-agents-and-performance-workflows/index.html Queries for potential N+1 query candidates and queries exceeding a specified latency threshold. This command helps pinpoint specific performance problems. ```bash $ perf:query --n1=3 --slow=50 ``` -------------------------------- ### Read Session Data with Query Source: https://github.com/mateffy/laraperf/blob/main/skills/laraperf-profiling/SKILL.md Reads and displays profiling session data. By default, it outputs summary, slow queries (>=100ms), and N+1 candidates (>=3). Options allow filtering by threshold, connection, operation, and limiting results. Output is JSON on stdout. ```bash php artisan perf:query ``` ```bash php artisan perf:query --summary ``` ```bash php artisan perf:query --slow=50 ``` ```bash php artisan perf:query --n1=3 ``` ```bash php artisan perf:query --summary --slow=50 --n1=3 ``` ```bash php artisan perf:query --slow=50 --connection=mysql --operation=SELECT ``` ```bash php artisan perf:query --session=session-20260416-143022-abc123 ``` ```bash php artisan perf:query --slow=50 --limit=20 ``` -------------------------------- ### Run Tests and Capture Performance Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/llm-coding-agents-and-performance-workflows/index.html Executes the application's test suite while simultaneously capturing performance metrics. This helps identify performance regressions introduced by code changes. ```bash $ php artisan test ``` -------------------------------- ### Fluent Expectation API for Performance Source: https://github.com/mateffy/laraperf/blob/main/README.md Utilize the fluent `expect()` API with the `performance()` matcher to make detailed assertions on various performance metrics. This provides a readable and powerful way to test performance. ```php // Fluent expectation API test('user query performance', function () { $result = measure(fn () => User::with('posts')->paginate()); expect($result) ->performance()->duration()->toBeLessThan(100) ->performance()->queries()->count()->toBeLessThan(10) ->performance()->queries()->whereTable('users')->count()->toBe(1) ->performance()->n1()->toBe(0) ->performance()->toHaveNoN1() ->performance()->toHaveNoSlowQueries(50); }); ``` -------------------------------- ### Analyze and Verify Performance Fixes Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/llm-coding-agents-and-performance-workflows/index.html Re-runs performance captures and queries after applying fixes to verify improvements. This ensures that optimizations are effective and do not reintroduce issues. ```bash $ perf:watch --seconds=60 && perf:query ``` -------------------------------- ### Analyze captured queries (JSON output) Source: https://github.com/mateffy/laraperf/blob/main/README.md Provides a detailed JSON output of the performance analysis, including summary, slow queries, and N+1 candidates. The structure includes counts and query details. ```json { "summary": { "type": "summary", "session_id": "...", "total_queries": 183 }, "slow": { "type": "slow", "threshold_ms": 100, "count": 3, "queries": [...] }, "n1": { "type": "n1", "threshold": 3, "candidate_count": 2, "candidates": [...] } } ``` -------------------------------- ### Investigate Specific Slow Query Source: https://github.com/mateffy/laraperf/blob/main/skills/laraperf-profiling/SKILL.md Investigate a specific slow database query by filtering by duration and then explaining its execution plan. Specify the connection and database if not default. ```bash php artisan perf:query --slow=100 # Find the hash from the output php artisan perf:explain --hash=abc123def456 --connection=tenant --db=tenant_acme ``` -------------------------------- ### Find Missing Indexes Pattern Source: https://github.com/mateffy/laraperf/blob/main/docs/skill.md This pattern shows how to identify potential missing indexes by looking for slow queries and then using `perf:explain` to analyze the query plan for sequential scans. ```bash php artisan perf:query --slow=50 php artisan perf:explain --hash= # Look for "Seq Scan" in the plan ``` -------------------------------- ### Specify Tenant Database Connection Source: https://github.com/mateffy/laraperf/blob/main/resources/boost/skills/laraperf-profiling/SKILL.md Use the --db flag to override the default database name for a specific tenant connection at runtime. This is useful in multi-tenant applications. ```bash php artisan perf:explain --hash=abc123 --connection=tenant --db=tenant_mytenant ``` -------------------------------- ### Profile Livewire Component Interaction Source: https://github.com/mateffy/laraperf/blob/main/skills/laraperf-profiling/SKILL.md Watch and profile database queries during interaction with a Livewire component. Use --tag to identify the session. Stop the watch and then query for slow operations. ```bash php artisan perf:watch --tag="property-search-component" # Interact with the Livewire component in the browser php artisan perf:stop php artisan perf:query --slow=50 --limit=50 ``` -------------------------------- ### Analyze Capture Sessions Source: https://github.com/mateffy/laraperf/blob/main/docs/skill.md Analyze completed capture sessions to identify performance bottlenecks. Use flags to filter for specific issues like N+1 queries or slow queries. ```bash # Analyze the most recent session php artisan perf:query ``` ```bash # Find N+1 query candidates (queries repeating 3+ times) php artisan perf:query --n1=3 ``` ```bash # Find slow queries (over 100ms) php artisan perf:query --slow=100 ``` ```bash # Use a specific session php artisan perf:query --session=session-20260416-143201-xK9mQp ``` ```bash # Get everything php artisan perf:query --n1=3 --slow=100 --summary ``` -------------------------------- ### Configure Database Connection Source: https://github.com/mateffy/laraperf/blob/main/README.md Set the default database connection for Laraperf commands using the PERF_CONNECTION environment variable. The default is 'pgsql'. ```bash PERF_CONNECTION=pgsql # Default DB connection for perf commands ``` -------------------------------- ### Detect N+1 Queries with laraperf (Bash) Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/detecting-n-plus-one-queries-with-laraperf/index.html Use the `php artisan perf:query --n1=3` command to detect SQL patterns that repeat 3 or more times, indicating potential N+1 queries. The output includes the exact file path and line number of the N+1 origin. ```bash $ php artisan perf:query --n1=3 ``` -------------------------------- ### GitHub Actions Workflow for Performance Check Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/llm-coding-agents-and-performance-workflows/index.html This workflow automates performance checks on pull requests. It captures application performance, runs tests, and checks for N+1 or slow queries, failing the build if regressions are found. ```yaml name: Performance Check on: [pull_request] jobs: perf: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Start capture run: php artisan perf:watch --seconds=120 & - name: Run tests run: php artisan test - name: Check for regressions run: | N1_COUNT=$(php artisan perf:query --n1=3 | jq '.n1.candidates | length') SLOW_COUNT=$(php artisan perf:query --slow=100 | jq '.slow_queries | length') if [ "$N1_COUNT" -gt 0 ] || [ "$SLOW_COUNT" -gt 0 ]; then echo "Found $N1_COUNT N+1 queries and $SLOW_COUNT slow queries" php artisan perf:query --n1=3 --slow=100 exit 1 fi ``` -------------------------------- ### Fix N+1 Queries with Eager Loading (PHP) Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/detecting-n-plus-one-queries-with-laraperf/index.html This PHP code demonstrates how to fix N+1 queries by using Eloquent's `with()` method for eager loading, reducing the number of queries from N+1 to 2. ```php // ❌ N+1 - queries = 1 + N $posts = Post::all(); foreach ($posts as $post) { $post->user->name; // Query #2, #3, #4... } // ✅ Eager loading - queries = 2 $posts = Post::with('user')->get(); foreach ($posts as $post) { $post->user->name; // Already loaded! } ``` -------------------------------- ### Clear Old Performance Captures Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/capturing-production-performance-data/index.html Remove accumulated session files from `storage/perf/` to reclaim disk space. Use `--force` to skip the confirmation prompt, making it suitable for automated cron jobs. ```bash $ php artisan perf:clear --force ``` ```bash 0 0 * * 0 cd /var/www && php artisan perf:clear --force ``` -------------------------------- ### Capture Performance Data During Specific Issues Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/capturing-production-performance-data/index.html Run `perf:watch` for a set duration when investigating a reported performance problem to capture specific conditions. This helps identify slow query plans, N+1 patterns, and code optimization points. ```bash perf:watch --seconds=600 ``` -------------------------------- ### Declarative Constraints for Pest Tests Source: https://github.com/mateffy/laraperf/blob/main/docs/index.html Chain constraints like maxQueryCount, noN1Patterns, and maxDuration directly onto Pest tests for automatic validation after execution. ```php test('user list has no N+1 queries') ->maxQueryCount(10) ->noN1Patterns() ->maxDuration(500); ``` -------------------------------- ### Analyze captured queries (specific reports) Source: https://github.com/mateffy/laraperf/blob/main/README.md Allows filtering analysis results to specific reports like N+1 candidates, slow queries, or a combination. Also supports human-readable table output. ```bash php artisan perf:query --n1=3 # N+1 candidates only ``` ```bash php artisan perf:query --slow=50 # Queries slower than 50ms ``` ```bash php artisan perf:query --summary --slow=50 --n1=3 # Combine flags ``` ```bash php artisan perf:query --format=table # Human-readable table output ``` -------------------------------- ### Add Storage Directory to Gitignore Source: https://github.com/mateffy/laraperf/blob/main/README.md Ensure the performance data directory is not committed to version control by adding it to your .gitignore file. ```gitignore /storage/perf/ ``` -------------------------------- ### Query Output Format Source: https://github.com/mateffy/laraperf/blob/main/skills/laraperf-profiling/SKILL.md The output format for `perf:query` is JSON on stdout. When multiple output sections are requested, a composite object is returned. Otherwise, the selected section is returned directly. ```json { "summary": { "type": "summary", "session_id": "session-20260416-143022-abc123", "session_tag": null, "status": "completed", "total_queries": 47, "unique_query_templates": 12, "total_time_ms": 847.3, "n1_candidate_count": 2, "slow_query_count_100ms": 3, "slow_query_count_500ms": 0 }, "slow": { "type": "slow", "threshold_ms": 100, "count": 3, "queries": [ ... ] }, "n1": { "type": "n1", "threshold": 3, "candidate_count": 2, "candidates": [ ... ] } } ``` -------------------------------- ### Find Slow Queries Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/using-explain-analyze-to-optimize-queries/index.html Identify queries that exceed a specified execution time threshold (e.g., 100ms) using the perf:query command. ```bash perf:query --slow=100 ``` -------------------------------- ### Clear Old Session Files Source: https://github.com/mateffy/laraperf/blob/main/docs/skill.md Clean up old session files generated by the `perf:watch` command. Use the `--force` flag to bypass confirmation. ```bash php artisan perf:clear --force ``` -------------------------------- ### Pest Declarative Performance Constraints Source: https://github.com/mateffy/laraperf/blob/main/README.md Set performance constraints declaratively on Pest tests using methods like `maxQueryCount`, `noN1Patterns`, `maxDuration`, and `maxMemory`. This automates performance validation. ```php // Declarative constraints via test() chain test('dashboard does not trigger N+1 queries') ->maxQueryCount(10) ->noN1Patterns() ->maxDuration(500) // ms ->maxMemory('10M'); ``` -------------------------------- ### Create Index Concurrently Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/using-explain-analyze-to-optimize-queries/index.html Add a new index to a PostgreSQL table without locking it, using the CONCURRENTLY option. This is crucial for production environments. ```sql CREATE INDEX CONCURRENTLY to avoid locking the table ``` -------------------------------- ### Measure Performance of a Callback Source: https://github.com/mateffy/laraperf/blob/main/docs/index.html Use the measure() function to wrap a callback and obtain a PerformanceResult object containing metrics like query count and duration. This is useful for testing specific code blocks. ```php use function Mateffy\Laraperf\Testing\{measure}; test('dashboard loads fast', function () { $result = measure(fn () => User::with('posts')->paginate() ); expect($result->queryCount()) ->toBeLessThan(20); }); ``` ```php use function Mateffy\Laraperf\Testing\{measure}; test('contact query performance', function () { $result = measure(fn () => Contact::with('company')->get() ); expect($result->durationMs()) ->toBeLessThan(100); }); ``` -------------------------------- ### Measure Single Operation Programmatically Source: https://github.com/mateffy/laraperf/blob/main/README.md Use the `measure` function for in-process performance measurement of a single closure. This is useful for isolated performance tests within your application. ```php use function Mateffy\Laraperf\Testing\{measure, capture, is_capturing, timeline_mark}; // Measure a single operation $result = measure(fn () => User::with('posts')->get()); ``` -------------------------------- ### Fluent API for Performance Assertions Source: https://github.com/mateffy/laraperf/blob/main/docs/index.html Utilize the fluent API to chain assertions on performance metrics such as duration, query count, and N+1 detection. You can filter queries by table or operation before asserting. ```php 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); }); ``` -------------------------------- ### Stop Capture Session Source: https://github.com/mateffy/laraperf/blob/main/docs/skill.md Stop a running database query capture session. You can stop all active watchers or a specific session by its ID. ```bash # Stop all watchers php artisan perf:stop ``` ```bash # Stop specific session php artisan perf:stop --session=session-20260416-143201-xK9mQp ``` -------------------------------- ### Scroll Restoration Script Source: https://github.com/mateffy/laraperf/blob/main/docs/blog/llm-coding-agents-and-performance-workflows/index.html A JavaScript snippet for managing scroll restoration across sessions. It handles saving and restoring scroll positions for windows and specific elements. ```javascript (function(t){let s;try{s=JSON.parse(sessionStorage.getItem(t.storageKey)||"{}")}catch(e){console.error(e);return}const c=t.key||window.history.state?.\_\_TSR_key,r=c?s[c]:void 0;if(t.shouldScrollRestoration&&r&&typeof r=="object"&&Object.keys(r).length>0){for(const e in r){const o=r[e];if(!o||typeof o!="object")continue;const l=o.scrollX,i=o.scrollY;if(!(!Number.isFinite(l)||!Number.isFinite(i))){if(e==="window")window.scrollTo({top:i,left:l,behavior:t.behavior});else if(e){let n;try{n=document.querySelector(e)}catch{continue}n&&(n.scrollLeft=l,n.scrollTop=i)}}}return}const a=window.location.hash.split("#",2)[1];if(a){const e=window.history.state?.\_\_hashScrollIntoViewOptions??!0;if(e){const o=document.getElementById(a);o&&o.scrollIntoView(e)}return}window.scrollTo({top:0,left:0,behavior:t.behavior})})({"storageKey":"tsr-scroll-restoration-v1_3","shouldScrollRestoration":true});document.currentScript.remove() ``` -------------------------------- ### Access Performance Results in Pest Source: https://github.com/mateffy/laraperf/blob/main/README.md Access the `PerformanceResult` object within a Pest test using the `perf()` helper function. This allows for custom assertions on performance metrics. ```php // Access results with perf() test('user list is fast', function () { $result = perf(); // PerformanceResult for this test expect($result->queryCount())->toBeLessThan(20); }); ```