janiaje/benchmark
A lightweight benchmarking toolkit for Laravel and PHP applications. Measure execution time, compare code snippets, and profile performance with a simple, expressive API. Includes helpers for timing closures, database queries, and custom benchmarks, plus formatted reports to help you spot bottleneck...
Install with Composer:
composer require janiaje/laravel-benchmark --dev
The package registers itself via Laravel auto-discovery, so there is no config file to publish for basic use. Import the facade and wrap the code you want to time:
use Janiaje\Benchmark\Facades\Benchmark;
Benchmark::dd(fn () => User::with('posts')->get());
dd() measures the closure, dumps the elapsed time, and stops execution — the fastest way to answer "how long does this actually take?" from php artisan tinker or a throwaway route. Check the README's facade class in vendor/janiaje/laravel-benchmark/src before your first run: the API is a handful of static helpers (measure, dump, dd, and a repeat-style each), and method names are the whole surface area, so a two-minute read covers it.
The first realistic use case is a suspected N+1 query or a slow accessor during local development. Don't install it to micro-optimize; install it to get a number instead of a hunch.
Compare two implementations side by side. This is where the package earns its keep:
Benchmark::dump(fn () => User::with('posts')->get());
Benchmark::dump(fn () => User::all()->load('posts'));
Two lines in tinker and you have a defensible answer for a code review.
Repeat for stability. A single measurement is noise on a shared dev machine. Use the repeat helper to run the same closure N times and read the average:
Benchmark::each(100, fn () => $service->calculate($payload));
Benchmark inside a test. Wrap a hot path in a Pest test so you notice when a refactor makes it three times slower. Keep the assertion loose (an order-of-magnitude ceiling, not a millisecond), or it will fail on CI hardware.
Probe one layer at a time. Measure the query, then the collection transformation, then the serialization separately. A single number around a controller method tells you nothing about which of the three is the problem.
Wrap the hot path, not the boot. Time the specific call — a scope, a cast, a formatter — rather than Route::dispatch(). Framework boot dwarfs your code and drowns the signal.
Use it as a scratch endpoint. A routes/web.php entry guarded by app()->isLocal() that dumps a few measurements is quicker than juggling tinker sessions when you're iterating on a query shape.
Support/Benchmark.php and drop the dependency — you lose nothing.--dev drift. Install it as a dev dependency. If it sneaks into production require, add a guard or you'll ship benchmarking code on live requests.Benchmark::each(100, fn () => User::create([...])) inserts 100 rows. Wrap writes in a transaction you roll back, or benchmark read-only paths only.dd() in a queued job kills the worker. It is exit under the hood. Reach for dump() (or log the output) anywhere that isn't an interactive session.php artisan package:discover or clear the cached manifest — auto-discovery only registers on a fresh boot.memory_get_peak_usage() or reach for Telescope/Debugbar, which give you the query log and timeline the package doesn't.How can I help you explore Laravel packages today?