Get started →

Laravel performance, in your terminal

Inspect Laravel performance locally. Catch regressions in CI.

Find slow routes, repeated queries, and performance regressions from your local test workload.

Laravel 12 · PHP 8.2+ Laravel 13 · PHP 8.3+ MIT
Pinpoint /
$ php artisan pinpoint:diff --baseline=storage/pinpoint/baselines/main.json
Route Baseline (main) Current (Workload) Change
orders.index p95 182ms · 2q p95 241ms · 51q +32.4% ⚠ +49q ✗
orders.show p95 94ms · 3q p95 91ms · 3q -3.2% ✓
users.index p95 45ms · 8q p95 48ms · 27q +6.7% +19q ⚠
dashboard p95 310ms · 4q p95 305ms · 4q -1.6% ✓
✗ REGRESSION DETECTED • orders.index
+49 queries
Duration: 182ms → 241ms (+32.4%)
Queries: 2 → 51 (+49)
N+1 Candidate: none in baseline → 50 repeats in current
RAM: 4.0 MB → 4.1 MB (+2.5%)
Trigger: app/Http/Controllers/OrderController.php:42
Suggested fix: Order::with('customer')

Measured Runtime Overhead

In benchmark testing, in-memory query and route listening added ~0.3–0.4 ms per request across 200 requests (10 queries/req in included Testbench suite). Persistence writes are deferred to Laravel terminating callbacks after HTTP dispatch.

The Silent Regression Problem

When a refactor silently changes query behavior

2 queries → 51 queries
app/Http/Controllers/OrderController.php
- $orders = Order::with('customer')->paginate(50);
+ $orders = Order::paginate(50);
// Refactored by assistant: tests still pass, app renders fine.
✓ Tests pass
✓ Response is correct
✗ Query count: 2 → 51

pinpoint:diff compares recorded PR metrics against a baseline snapshot (main) and exits 1 on regression.

GET /api/orders
Measured after eager loading
Before eager loading
1,420ms

42 queries · 14× lazy-loaded relations

After eager loading
89ms

3 queries · Order::with('items')

Example measurement: 1,420 ms → 89 ms (42 queries → 3 queries) after adding eager loading on local PHP 8.3 / MySQL 8.0 (50 records). Inspect the recorded workload with pinpoint:report
REPEATED QUERY bindings_hash match

Identical SQL + Identical Bindings

Same query executed multiple times during the request. Review whether the result can be reused or avoided.

Possible action: Cache::remember(), memoization, or query reuse
N+1 CANDIDATE varying bindings

Repeated SQL + Varying Bindings

Repeated SQL with varying bindings can indicate a relationship being loaded once per model.

Possible action: eager load the relationship with Model::with('relation')

Core Capabilities

Focused performance diagnostics for local dev and CI.

Pinpoint correlates recorded queries and requests with available source locations so you can trace a finding back to the code that triggered it.

01 Detect

Repeated queries & N+1 candidates

Find repeated queries and N+1 candidates in your recorded workload. Repeated queries with identical bindings are grouped separately from varying-binding N+1 candidates so you can decide whether to reuse, memoize, cache, or remove the query.

Eloquent signal SQL fingerprint Bindings hash
Query pattern N+1 ×14
Order → items
SELECT * FROM `items` WHERE `order_id` = ?
Identical bindings CACHE ×3
02 Diff

Performance regression diff

Compare the current workload against a recorded baseline and report changes in latency, query count, repeated queries, and memory.

pinpoint:diff +Query spike alert Baseline snapshot
orders.index +32.4% ⚠ +49q ✗
Duration: 182ms → 241ms | Queries: 2 → 51
03 Measure

Route latency & percentile rollups

Report p50, p95, p99, and average request duration by route. Summaries can be computed on demand from recorded request rows or snapshotted for regression diffing.

P95
1.42s
Avg
890ms
Queries
42
04 Memory

Peak PHP memory per route

Flag requests whose measured peak PHP memory exceeds the configured budget ('memory_budget_kb' => 20 * 1024); baseline diffs also detect memory growth between runs.

Peak PHP memory 20 MB Budget
api.reports.export 64.2 MB
Budget exceeded (over 20 MB)
05 Locate

Source-level context & IDE jump

Where source locations are available, Pinpoint emits terminal links that can open the reported file and line in supported IDEs (VS Code · PhpStorm). Local-first: data stays in your database.

Terminal links · VS Code · PhpStorm
OrderController.php:84
Suggested: Order::with('items')

CI Regression Gate

Fail CI when a recorded workload violates your performance budget.

Automate performance checks in CI. Exit with code 1 when query budgets explode or performance regresses against a named baseline.

.github/workflows/ci.yml (Illustrative Recipe) GitHub Actions
# Step 1: Restore main branch baseline snapshot
- name: Restore Baseline
uses: actions/cache/restore@v4
with:
path: storage/pinpoint/baselines/main.json
key: pinpoint-baseline-main
# Step 2: Run PR workload / test suite
- name: Run Tests
run: php artisan test
# Step 3: Compare PR against baseline
- name: Performance Gate
run: |
php artisan pinpoint:diff \
--baseline=storage/pinpoint/baselines/main.json \
--fail-on-regression
Baseline JSON exported on main via pinpoint:snapshot --file=storage/pinpoint/baselines/main.json and cached for PR jobs. Decoupled from application database state.
GitHub Actions • Run #412
Exit Code 1 • CI Check Failed
$ php artisan pinpoint:diff --baseline=storage/pinpoint/baselines/main.json --fail-on-regression
✗ REGRESSION: api.orders.index
N+1 Introduced: none (baseline) → 14× loop (current) · p95 +32.4%
Relationship Order → items lazy-loaded inside collection loop.
Trigger: app/Http/Controllers/OrderController.php:84
PR status set to failure. Exit code 1 (blocks merge). 0.14s duration
Workload Proof

Example output from a Laravel application.

Illustrative report showing latency, memory, and repeated-query findings.

Example Pinpoint CLI Report output from a Laravel application
Critical Bottlenecks

v1.historical-records.search clocked at 4,350ms, flagged in the red tier.

Repeated Query Alerts

Detects relationship loops and duplicate executions up to x11 queries per HTTP request.

Instant Locate Block

The CLI can render route labels and caller locations as terminal links that open your IDE.

Where Pinpoint Fits

A focused tool for your terminal and CI.

Pinpoint doesn't replace browser debuggers or deep system monitors. It complements them by focusing specifically on terminal request inspection, baseline diffing, and automated CI performance gates.

Workflow & Use Case Pinpoint Recommended Ecosystem Tool
Terminal Request Reports & Tiers p95/avg latency percentiles, memory alerts, and IDE jump links ✔ pinpoint:report Primary use case for local CLI profiling
Named Baseline Diffs Compare current test runs against a tagged snapshot (e.g. main) ✔ pinpoint:diff Automated regression delta detection
CI Performance Budget Gates Exit code 1 on N+1 queries or query count budget violations (blocks CI when required) ✔ pinpoint:check Headless CI check without browser or UI
Browser Request & DOM Inspection In-page tabs for rendered views, request headers, and live queries — Use Laravel Debugbar
Queues, Jobs, Events & Mails Deep application lifecycle insight via local web dashboard — Use Laravel Telescope

Pinpoint is focused on local development and CI regression checks; it is not a distributed tracing or production alerting system.

Quickstart & Commands

Get started in minutes.

Install, migrate, run your app. Then one command tells you what's slow.

01

Install the package

composer require asimali/pinpoint
02

Publish & run migrations

php artisan vendor:publish --tag="pinpoint-migrations" && php artisan migrate
03

Generate your first latency report

php artisan pinpoint:report

After running migrations, hit the routes you want to profile — via browser, curl, or tests — to generate data for the report.

Core Artisan Commands

The essential commands for local diagnostics, CI gating, and baseline diffing.

$ php artisan pinpoint:report
Interactive Diagnostic

Generate terminal summary tables with latency rollups (p50/p95/p99/avg), peak memory per route with budget alerts, N+1 vs CACHE classification (exact-duplicate detection), terminal IDE links, and machine-readable JSON payloads.

Flags --tier=critical --route=<name> --since=30m --limit=10 --json --json-to=<file>
$ php artisan pinpoint:diff
Baseline Diffing

Compare current test run performance against a baseline snapshot (main). Flags regressions in p95 latency, repeated-query increases, and peak memory in terminal and CI with exit code 1.

Flags --baseline=main --since=30m --fail-on-regression --json --show-stable
$ php artisan pinpoint:check
CI Performance Gate

Evaluate query counts, durations, and repeated-query violations (N+1 candidates and duplicate queries) as an automated CI gate, exiting with code 1 upon violation.

Flags --fail-on-n1 --fail-on-repeat --max-queries=<n> --max-duration-ms=<ms> --since=30m --allow-empty --json --json-to=<file>
$ php artisan pinpoint:snapshot
Baseline Capture

Capture current performance metrics as a named baseline tag or exported JSON file for future regression diffing (e.g. before merging to main).

Flags --tag=main --file=<path.json> --since=1h --no-overwrite
Maintenance Commands (aggregate, prune, reset) ▼
php artisan pinpoint:aggregate

Compute raw request rollups and persist an offline percentile snapshot in pinpoint_summaries for external tools or reporting integrations (CLI reports compute on-demand from raw rows).

php artisan pinpoint:prune

Delete recorded request and query data older than the retention window (default 30 days).

--days=30
php artisan pinpoint:reset

Delete all recorded Pinpoint data (requests, queries, lazy loads, summaries) with confirmation prompt.

--force

Start profiling your Laravel routes today.

Local-first: request data stays in your application database. Install, exercise a route, and inspect the resulting report.