Search by

philiprehberger / php-stopwatch

philiprehberger

Precise code execution timer with lap tracking and memory measurement

Package info

github.com/philiprehberger/php-stopwatch

pkg:composer/philiprehberger/php-stopwatch

Fund package maintenance!

philiprehberger

Statistics

Installs: 68

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.3.0 2026-04-01 13:46 UTC

This package is auto-updated.

Last update: 2026-10-07 04:39:16 UTC


README

Tests Latest Version on Packagist Last updated

Precise code execution timer with lap tracking and memory measurement.

Requirements

  • PHP 8.2+

Installation

composer require philiprehberger/php-stopwatch

Usage

Quick measurement

use PhilipRehberger\Stopwatch\Stopwatch;

$result = Stopwatch::measure(function () {
    // Code to measure
    file_get_contents('https://example.com');
});

echo $result->durationFormatted; // "123.45ms"
echo $result->memoryFormatted;   // "1.50KB"

Measurement with return value

$outcome = Stopwatch::measureWithResult(function () {
    return User::query()->where('active', true)->get();
});

$users = $outcome['result'];
echo $outcome['measure']->durationFormatted; // "45.12ms"

Manual start/stop with laps

$sw = Stopwatch::start('data-pipeline');

// Phase 1
$data = loadData();
$sw->lap('load');

// Phase 2
$transformed = transform($data);
$sw->lap('transform');

// Phase 3
save($transformed);
$sw->lap('save');

$result = $sw->stop();

echo $result->report();
// Stopwatch [data-pipeline]
// -------------------------
// Duration: 1.23s
// Memory:   3.25MB
// Peak:     5.10MB
//
// Laps:
//   load — 200.00ms (cumulative: 200.00ms)
//   transform — 800.00ms (cumulative: 1000.00ms)
//   save — 230.00ms (cumulative: 1230.00ms)

Check elapsed time while running

$sw = Stopwatch::start();

doSomeWork();

if ($sw->elapsed() > 5000) {
    // Already over 5 seconds, skip remaining work
}

$result = $sw->stop();

Pause and resume

$sw = Stopwatch::start();

$data = fetchFromApi();
$sw->pause();  // Pause while waiting for user input

$input = readline('Continue? ');

$sw->resume(); // Resume timing
processData($data, $input);

echo $sw->getElapsedSoFar() . 's elapsed so far';

$result = $sw->stop();
echo $result->durationFormatted; // Excludes time spent paused

Nested stopwatches

$sw = Stopwatch::start('parent');

$child = $sw->child('database');
// ... database queries ...
$childResult = $child->stop();

$result = $sw->stop();

foreach ($result->children() as $child) {
    echo $child->name . ': ' . $child->durationFormatted;
}

Benchmark comparisons

$result = Stopwatch::benchmark([
    fn () => array_map(fn ($x) => $x * 2, range(1, 1000)),
    fn () => array_walk($arr = range(1, 1000), fn (&$x) => $x *= 2),
], iterations: 100);

echo $result->report();
// Benchmark Results
// ------------------
// #0 — mean: 0.12ms | median: 0.11ms | min: 0.09ms | max: 0.25ms (100 iterations)
// #1 — mean: 0.15ms | median: 0.14ms | min: 0.11ms | max: 0.30ms (100 iterations)

Threshold alerts

// Fire a callback when measurement exceeds a threshold
$result = Stopwatch::measureWithThreshold(
    callback: function () {
        processLargeDataset();
    },
    thresholdMs: 500.0,
    onExceeded: function (MeasureResult $result) {
        Log::warning("Slow operation: {$result->durationFormatted}");
    },
);

// Register thresholds on a running stopwatch
$sw = Stopwatch::start();
$sw->onThreshold(1000.0, function (StopwatchResult $result) {
    alert("Operation exceeded 1 second: {$result->durationFormatted}");
});
$sw->onThreshold(5000.0, function (StopwatchResult $result) {
    alert("Critical: operation exceeded 5 seconds!");
});
doWork();
$result = $sw->stop(); // Callbacks fire here if thresholds exceeded

Comparison reports

$report = Stopwatch::compare([
    'array_map' => fn () => array_map(fn ($x) => $x * 2, range(1, 1000)),
    'foreach' => fn () => array_walk($arr = range(1, 1000), fn (&$x) => $x *= 2),
], iterations: 100);

echo $report->toTable();
// | Name      | Mean    | Median  | Δ vs Best  |
// |-----------|---------|---------|------------|
// | array_map | 0.12ms  | 0.11ms  | baseline   |
// | foreach   | 0.18ms  | 0.17ms  | +50.00%    |

$report->fastest(); // BenchmarkEntry with lowest mean
$report->slowest(); // BenchmarkEntry with highest mean
$report->rankings(); // Sorted by mean ascending

Profiling decorator

$proxy = Stopwatch::profile($myService);

// Use the proxy like the original object — all calls are timed
$proxy->fetchUsers();
$proxy->fetchUsers();
$proxy->processData($input);

$profile = Stopwatch::getProfile($proxy);
// ['fetchUsers' => StopwatchStats, 'processData' => StopwatchStats]

foreach ($profile as $method => $stats) {
    echo "{$method}: mean={$stats->mean()}ms, calls={$stats->min()}..{$stats->max()}ms\n";
}

$original = $proxy->getTarget(); // Access the wrapped object

Statistical analysis

$sw = Stopwatch::start();
$sw->lap('step-1');
$sw->lap('step-2');
$sw->lap('step-3');
$result = $sw->stop();

$stats = $result->stats();
echo $stats->mean();              // Mean lap duration in ms
echo $stats->median();            // Median lap duration in ms
echo $stats->p95();               // 95th percentile
echo $stats->standardDeviation(); // Standard deviation

API

Method Returns Description
Stopwatch::start(?string $name) RunningStopwatch Start a new stopwatch with an optional name
Stopwatch::measure(callable $fn) MeasureResult Measure execution time and memory of a callable
Stopwatch::measureWithResult(callable $fn) array{result, measure} Measure while preserving the return value
Stopwatch::benchmark(array $callables, int $iterations) BenchmarkResult Run each callable N times and compare performance
Stopwatch::measureWithThreshold(callable, float, callable) MeasureResult Measure and fire callback if threshold exceeded
Stopwatch::compare(array $benchmarks, int $iterations) ComparisonReport Compare named callables with rankings and ASCII table
Stopwatch::profile(object $target) ProfilingProxy Create a profiling proxy for automatic method timing
Stopwatch::getProfile(ProfilingProxy $proxy) array<string, StopwatchStats> Get profiling data from a proxy
RunningStopwatch->onThreshold(float $ms, callable) self Register threshold callback fired on stop()
RunningStopwatch->lap(?string $name) self Record a lap with an optional name
RunningStopwatch->child(string $name) RunningStopwatch Create a nested child stopwatch
RunningStopwatch->stop() StopwatchResult Stop the timer and return results
RunningStopwatch->pause() void Pause timing without stopping the stopwatch
RunningStopwatch->resume() void Resume timing after a pause
RunningStopwatch->getElapsedSoFar() float Get elapsed seconds without stopping
RunningStopwatch->elapsed() float Get elapsed milliseconds while still running
RunningStopwatch->isRunning() bool Check if the stopwatch is still active
RunningStopwatch->isPaused() bool Check if the stopwatch is currently paused
StopwatchResult->report() string Generate a formatted report with all laps
StopwatchResult->children() array<StopwatchResult> Get child stopwatch results
StopwatchResult->stats() StopwatchStats Get statistical analysis of lap durations

Value Objects

StopwatchResult — duration (float, ms), durationFormatted (string), memory (int, bytes), memoryFormatted (string), peakMemory (int, bytes), laps (array), name (?string), children (array)

MeasureResult — duration (float, ms), durationFormatted (string), memory (int, bytes), memoryFormatted (string)

Lap — name (?string), duration (float, ms), cumulativeDuration (float, ms)

BenchmarkResult — results() (array of BenchmarkEntry), report() (string)

BenchmarkEntry — mean (float, ms), median (float, ms), min (float, ms), max (float, ms), iterations (int)

StopwatchStats — mean(), median(), min(), max(), p95(), p99(), standardDeviation() (all float, ms)

ComparisonReport — toTable() (string), fastest() (BenchmarkEntry), slowest() (BenchmarkEntry), rankings() (array), toArray() (array)

ThresholdMonitor — addThreshold(float $ms, callable) (self), check(StopwatchResult|MeasureResult) (void)

ProfilingProxy — getProfile() (array of StopwatchStats), getTarget() (object), getRawProfile() (array)

Development

composer install
vendor/bin/phpunit
vendor/bin/pint --test

Support

If you find this project useful:

⭐ Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT