PHPackages                             sandermuller/stopwatch - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Debugging &amp; Profiling](/categories/debugging)
4. /
5. sandermuller/stopwatch

ActiveLibrary[Debugging &amp; Profiling](/categories/debugging)

sandermuller/stopwatch
======================

Stopwatch to measure execution times (profile code) for Laravel and PHP projects

v0.4.2(1mo ago)531.8k↓30%MITPHPPHP ^8.3CI passing

Since Jun 20Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/SanderMuller/Stopwatch)[ Packagist](https://packagist.org/packages/sandermuller/stopwatch)[ Docs](https://github.com/SanderMuller/Stopwatch)[ RSS](/packages/sandermuller-stopwatch/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (41)Versions (28)Used By (0)

Stopwatch for PHP &amp; Laravel
===============================

[](#stopwatch-for-php--laravel)

A lightweight profiler for PHP and Laravel. Add checkpoints to your code, measure closures, track queries and memory, and see where time is spent. Output as HTML, Server-Timing headers, log entries, or Debugbar timelines.

**Requires PHP 8.3+**

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require sandermuller/stopwatch
```

Optionally publish the config file:

```
php artisan vendor:publish --tag=stopwatch-config
```

Configuration
-------------

[](#configuration)

All settings can be configured via environment variables or the `config/stopwatch.php` file:

SettingEnv VariableDefaultDescription`enabled``STOPWATCH_ENABLED``true`Disable to make all calls no-ops with near-zero overhead`output``STOPWATCH_OUTPUT``silent`Default output mode (`silent`, `log`, `stderr`, `dump`)`log_level``STOPWATCH_LOG_LEVEL``debug`Log level when output is `log``slow_threshold``STOPWATCH_SLOW_THRESHOLD``50`Highlight checkpoints slower than this (ms)`track_queries``STOPWATCH_TRACK_QUERIES``false`Auto-track query count and duration per checkpoint`track_memory``STOPWATCH_TRACK_MEMORY``false`Auto-track memory usage per checkpoint`notify_threshold``STOPWATCH_NOTIFY_THRESHOLD``null`Notify via channels if total duration exceeds this (ms)`mail.to``STOPWATCH_MAIL_TO``null`Recipient address for `MailChannel` notifications`mail.subject``STOPWATCH_MAIL_SUBJECT``null`Email subject (defaults to duration if not set)Usage
-----

[](#usage)

### Checkpoints

[](#checkpoints)

```
stopwatch()->checkpoint('First checkpoint');
stopwatch()->checkpoint('Second checkpoint');
stopwatch()->lap('Third checkpoint'); // alias for checkpoint()
```

Calling `checkpoint()` auto-starts the stopwatch if it hasn't been started yet. You can also start it explicitly with `stopwatch()->start()`. Note that `start()` resets any existing checkpoints, use it to begin a fresh measurement.

You can attach metadata to any checkpoint:

```
stopwatch()->checkpoint('Query executed', ['table' => 'users', 'rows' => 42]);
```

### Output each checkpoint

[](#output-each-checkpoint)

Configure where each checkpoint is emitted using `outputTo()`:

```
use SanderMuller\Stopwatch\StopwatchOutput;

stopwatch()->outputTo(StopwatchOutput::Log)->start();

stopwatch()->checkpoint('First checkpoint');  // Automatically logged
stopwatch()->checkpoint('Second checkpoint'); // Automatically logged
```

Available output modes:

ModeDescription`StopwatchOutput::Silent`Collect only, render later (default)`StopwatchOutput::Log`Send to Laravel log`StopwatchOutput::Stderr`Write to stderr`StopwatchOutput::Dump`Use Laravel's `dump()`You can override the output for a single checkpoint:

```
stopwatch()->checkpoint('Debug this', output: StopwatchOutput::Dump);
```

Or use the `log()` shortcut to send a single checkpoint to the log:

```
stopwatch()->log('Query executed');
stopwatch()->log('Query executed', level: 'warning');
```

### Measure a closure

[](#measure-a-closure)

Wrap a closure to automatically create a checkpoint after execution. Auto-starts the stopwatch if needed.

```
$result = stopwatch()->measure('Heavy computation', function () {
    return doExpensiveWork();
});
```

### Query tracking

[](#query-tracking)

Automatically track the number of database queries and their total duration between each checkpoint. Requires `illuminate/database`.

```
stopwatch()->withQueryTracking()->start();

User::all();
stopwatch()->checkpoint('Load users');
// Checkpoint includes: 1q / 2.3ms

Order::where('status', 'pending')->get();
stopwatch()->checkpoint('Load orders');
// Checkpoint includes: 1q / 1.5ms
```

Can also be enabled via config (`STOPWATCH_TRACK_QUERIES=true`).

### Memory tracking

[](#memory-tracking)

Track memory usage changes between each checkpoint:

```
stopwatch()->withMemoryTracking()->start();

$data = loadLargeDataset();
stopwatch()->checkpoint('Load data');
// Checkpoint includes: +2.4MB
```

In the HTML output, memory is shown as a compact delta badge with full details on hover (current usage, delta, peak). In plain-text output (`toStderr`, `toLog`), the delta is included inline. Can also be enabled via config (`STOPWATCH_TRACK_MEMORY=true`).

Both tracking methods can be combined:

```
stopwatch()->withQueryTracking()->withMemoryTracking()->start();
```

### Write a full report

[](#write-a-full-report)

Write all checkpoints and the total duration to stderr or your log:

```
stopwatch()->checkpoint('Validation');
stopwatch()->checkpoint('DB inserts');

// Write to stderr
stopwatch()->toStderr('Profile:');

// Or write to the log
stopwatch()->toLog('Profile:', level: 'info');
```

### Conditional notifications

[](#conditional-notifications)

Get notified when a request or operation exceeds a time threshold. Notifications are dispatched when the stopwatch finishes:

```
stopwatch()->notifyIfSlowerThan(500);

stopwatch()->checkpoint('Fetch order');
stopwatch()->checkpoint('Generate PDF');
stopwatch()->checkpoint('Upload to S3');

stopwatch()->finish(); // notifications dispatch here if total >= 500ms
```

The threshold is also checked on implicit finishes (`render()`, `toArray()`, `toLog()`, `toStderr()`), and also accepts `CarbonInterval`:

```
stopwatch()->notifyIfSlowerThan(CarbonInterval::seconds(2));
```

The threshold and channels can be configured entirely via config/env:

```
STOPWATCH_NOTIFY_THRESHOLD=500
```

This pairs well with the middleware. Every request that exceeds the threshold will trigger a notification automatically.

Or set it programmatically in a service provider:

```
// AppServiceProvider::boot()
stopwatch()->notifyIfSlowerThan(500);
```

Configure which channels are used in `config/stopwatch.php`:

```
'notification_channels' => [
    \SanderMuller\Stopwatch\Notifications\LogChannel::class,
],
```

#### Email notifications

[](#email-notifications)

Add `MailChannel` to receive an email with the stopwatch's HTML report when a threshold is exceeded:

```
'notification_channels' => [
    \SanderMuller\Stopwatch\Notifications\LogChannel::class,
    \SanderMuller\Stopwatch\Notifications\MailChannel::class,
],
```

Configure the recipient in your `.env`:

```
STOPWATCH_MAIL_TO=dev-team@example.com
STOPWATCH_MAIL_SUBJECT="Slow request detected"  # optional
```

Or bind the channel with constructor arguments:

```
$this->app->bind(MailChannel::class, fn () => new MailChannel(
    to: 'dev-team@example.com',
    subject: 'Slow request',
));
```

#### Custom notification channels

[](#custom-notification-channels)

Create your own channel by implementing `StopwatchNotificationChannel`:

```
use SanderMuller\Stopwatch\Notifications\StopwatchNotificationChannel;
use SanderMuller\Stopwatch\Stopwatch;

class SlackChannel implements StopwatchNotificationChannel
{
    public function notify(Stopwatch $stopwatch): void
    {
        Slack::message("Slow request: {$stopwatch->totalRunDurationReadable()}");
    }
}
```

Register it in your config:

```
'notification_channels' => [
    \SanderMuller\Stopwatch\Notifications\LogChannel::class,
    \App\Stopwatch\SlackChannel::class,
],
```

Or set channels at runtime:

```
stopwatch()->notifyUsing([new SlackChannel()]);
```

### Render as HTML

[](#render-as-html)

Render an HTML report with the total execution time, each checkpoint, and the time between them. Slow checkpoints are highlighted.

```
stopwatch()->checkpoint('First checkpoint');
stopwatch()->checkpoint('Second checkpoint');

// Render the output
{{ stopwatch()->render() }}
```

Or use the Blade directive:

```
@stopwatch
```

[![rendered-stopwatch.png](rendered-stopwatch.png)](rendered-stopwatch.png)

### Laravel Debugbar

[](#laravel-debugbar)

If you have [barryvdh/laravel-debugbar](https://github.com/barryvdh/laravel-debugbar) installed, checkpoint timings automatically appear as a timeline tab in Debugbar with a duration badge.

### Server-Timing header

[](#server-timing-header)

Add a `Server-Timing` HTTP header to your responses so you can inspect checkpoint timings in the browser's DevTools Network tab.

Register the middleware to automatically add the header whenever the stopwatch has been started:

```
// bootstrap/app.php
use SanderMuller\Stopwatch\StopwatchMiddleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->append(StopwatchMiddleware::class);
    })
    // ...
```

By default the middleware is passive, it only adds the `Server-Timing` header if the stopwatch was started somewhere in your code (e.g. via `stopwatch()->start()` or `stopwatch()->checkpoint()`). Requests where the stopwatch is never started will not have the header.

To auto-start the stopwatch on every request, use `StopwatchMiddleware::autoStart()`:

```
$middleware->append(StopwatchMiddleware::autoStart());
```

Or add the header manually without the middleware:

```
return response('OK')
    ->header('Server-Timing', stopwatch()->toServerTiming());
```

### Manually stop the stopwatch

[](#manually-stop-the-stopwatch)

You can manually stop the stopwatch to freeze the timing. It will also stop automatically when output is rendered (e.g. `render()`, `toArray()`, `toStderr()`).

```
stopwatch()->checkpoint('First checkpoint');

// Stop the stopwatch
stopwatch()->stop();

// Do something else you don't want to measure

// Finally render the output
{{ stopwatch()->render() }}
```

You can get the total duration as a string with `stopwatch()->toString()` (e.g. `"116ms"`).

### Enable / disable at runtime

[](#enable--disable-at-runtime)

Enable or disable the stopwatch at runtime. When disabled, all calls become no-ops:

```
stopwatch()->disable();

stopwatch()->checkpoint('Skipped'); // no-op

stopwatch()->enable();
```

### Serialization

[](#serialization)

Convert the stopwatch data to an array or JSON:

```
$data = stopwatch()->toArray();
$json = stopwatch()->toJson();
```

### Debugging

[](#debugging)

```
stopwatch()->dump(); // dump the stopwatch instance
stopwatch()->dd();   // dump and die
```

### Without Laravel

[](#without-laravel)

You can use the stopwatch without the Laravel helper by creating instances directly:

```
$stopwatch = \SanderMuller\Stopwatch\Stopwatch::new();
$stopwatch->start();
$stopwatch->checkpoint('Done');
echo $stopwatch->toString();
```

The `stopwatch()` helper is not available outside Laravel. Query tracking requires `illuminate/database` and a Laravel application. Config-based setup and notification channel resolution from class strings also require the Laravel container.

License
-------

[](#license)

MIT

###  Health Score

50

—

FairBetter than 96% of packages

Maintenance89

Actively maintained with recent releases

Popularity33

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 98.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~28 days

Recently: every ~46 days

Total

24

Last Release

56d ago

PHP version history (2 changes)v0.0.1PHP ^8.3

v0.1.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/abeb7bd51fd77656b4133bdcdf4eca99cc8b495b83ca72f63674ff15f2b72a39?d=identicon)[SanderMuller](/maintainers/SanderMuller)

---

Top Contributors

[![SanderMuller](https://avatars.githubusercontent.com/u/9074391?v=4)](https://github.com/SanderMuller "SanderMuller (58 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

phplaravelprofilingstopwatch

###  Code Quality

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sandermuller-stopwatch/health.svg)

```
[![Health](https://phpackages.com/badges/sandermuller-stopwatch/health.svg)](https://phpackages.com/packages/sandermuller-stopwatch)
```

###  Alternatives

[spatie/laravel-ignition

A beautiful error page for Laravel applications.

573146.7M471](/packages/spatie-laravel-ignition)[spatie/laravel-responsecache

Speed up a Laravel application by caching the entire response

2.8k8.2M51](/packages/spatie-laravel-responsecache)[tightenco/jigsaw

Simple static sites with Laravel's Blade.

2.2k438.5k29](/packages/tightenco-jigsaw)[timacdonald/log-fake

A drop in fake logger for testing with the Laravel framework.

4235.9M56](/packages/timacdonald-log-fake)[laravel-zero/framework

The Laravel Zero Framework.

3371.4M369](/packages/laravel-zero-framework)[laracraft-tech/laravel-xhprof

Easy XHProf setup to profile your laravel application!

235321.4k](/packages/laracraft-tech-laravel-xhprof)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
