Execution Timer

Overview

The ExecutionTimer class measures elapsed wall-clock time using PHP's high-resolution microtime(true). It supports start/stop/reset/restart cycles, live reading while the timer is still running, and a static measure() helper for timing any callable in one call.

  • Class: JiFramework\Core\Utilities\ExecutionTimer
  • Access: $app->executionTimer
  • Lazy-loaded — instantiated on first access
// Time a block of code
$app->executionTimer->start();
doSomethingExpensive();
$app->executionTimer->stop();

echo $app->executionTimer->getElapsedTimeInMilliseconds() . " ms";
// One-liner with the static helper
$result = ExecutionTimer::measure(function () {
    return fetchDataFromApi();
});

echo "API call took " . round($result['elapsed_ms'], 2) . " ms";
echo "Response: " . json_encode($result['result']);

start()

start(): void

Start the timer. Records the current high-resolution timestamp and marks the timer as running. Any previous measurement is discarded — calling start() on a stopped timer is equivalent to reset() + start().

$timer = new ExecutionTimer();
$timer->start();

// do work …

$timer->stop();
echo $timer->getElapsedTime() . " s";

While the timer is running, getElapsedTime() returns the live elapsed time since start() was called — you do not need to stop it first to take a reading.

stop()

stop(): void

Stop the timer and record the final elapsed time. Safe to call multiple times — subsequent calls are no-ops that leave the recorded time unchanged. Also safe to call before start() — does nothing in that case.

$timer->start();
usleep(50000); // 50 ms
$timer->stop();

echo $timer->getElapsedTimeInMilliseconds() . " ms"; // ~50 ms

$timer->stop(); // second call — no effect
echo $timer->getElapsedTimeInMilliseconds() . " ms"; // same value

reset() and restart()

reset(): void

Reset all state to the initial zero values. The timer is stopped and all elapsed time is cleared. Use this when you want to reuse the same instance for a new measurement without starting immediately.

$timer->start();
usleep(30000);
$timer->stop();
echo $timer->getElapsedTimeInMilliseconds(); // ~30 ms

$timer->reset();
echo $timer->getElapsedTime();  // 0.0
echo $timer->isRunning() ? "running" : "stopped"; // stopped
restart(): void

Reset and immediately start a fresh measurement. Equivalent to reset() + start() in one call. Use this when you want to discard the current measurement and begin timing a new operation right away.

$timer->start();
usleep(50000); // 50 ms measurement
$timer->stop();

// Begin a completely new measurement
$timer->restart();
// timer is now running — previous 50 ms is gone
doMoreWork();
$timer->stop();
echo $timer->getElapsedTimeInMilliseconds(); // only the new duration

isRunning()

isRunning(): bool

Returns true when the timer has been started and not yet stopped, false otherwise. Useful for conditional logic and assertions.

$timer = new ExecutionTimer();
$timer->isRunning(); // false — never started

$timer->start();
$timer->isRunning(); // true

$timer->stop();
$timer->isRunning(); // false

$timer->reset();
$timer->isRunning(); // false

Reading Elapsed Time

getElapsedTime(): float
getElapsedTimeInMilliseconds(): float
getElapsedTimeInMicroseconds(): float

Return the elapsed duration in seconds, milliseconds, and microseconds respectively. All three methods share the same behaviour:

  • Before start() — returns 0.0 (never a Unix timestamp).
  • While running — returns the live elapsed time since start() was called. You do not need to stop the timer to read it.
  • After stop() — returns the recorded duration (frozen at stop time).
$timer = new ExecutionTimer();

// Safe zero before start
$timer->getElapsedTime();                 // 0.0
$timer->getElapsedTimeInMilliseconds();   // 0.0
$timer->getElapsedTimeInMicroseconds();   // 0.0

$timer->start();

// Live reading — no need to stop
usleep(10000);
echo $timer->getElapsedTimeInMilliseconds(); // ~10 ms (live)

$timer->stop();

// Recorded values
echo $timer->getElapsedTime();                 // e.g. 0.010312 s
echo $timer->getElapsedTimeInMilliseconds();   // e.g. 10.312 ms
echo $timer->getElapsedTimeInMicroseconds();   // e.g. 10312 µs

measure()

static measure(callable $callback, mixed ...$args): array

Time any callable in one call. Creates a fresh timer internally, executes the callback, stops the timer, and returns an array with the callback's return value and the elapsed duration.

  • $callback(callable) The function to time.
  • ...$args(mixed) Arguments forwarded to the callback.

Returns: array with three keys:

  • result — The return value of the callback.
  • elapsed_time(float) Elapsed time in seconds.
  • elapsed_ms(float) Elapsed time in milliseconds.
// Time an anonymous function
$result = ExecutionTimer::measure(function () {
    return fetchDataFromExternalApi();
});

echo round($result['elapsed_ms'], 2) . " ms";
echo json_encode($result['result']);
// Pass arguments to the callback
$result = ExecutionTimer::measure(fn($id) => User::find($id), 42);

echo "User: " . $result['result']['name'];
echo round($result['elapsed_ms'], 2) . " ms";
// Compare two implementations
$a = ExecutionTimer::measure(fn() => slowSort($data));
$b = ExecutionTimer::measure(fn() => fastSort($data));

echo "slow: " . round($a['elapsed_ms'], 2) . " ms
";
echo "fast: " . round($b['elapsed_ms'], 2) . " ms
";
echo "speedup: " . round($a['elapsed_ms'] / $b['elapsed_ms'], 1) . "x";

Each measure() call is fully independent — it creates its own timer and does not affect any shared state.

Examples

Profile a database query

$app->executionTimer->start();

$users = $app->db->table('users')
    ->where('active', 1)
    ->orderBy('name')
    ->get();

$app->executionTimer->stop();

$app->logger->debug("User query took " . round($app->executionTimer->getElapsedTimeInMilliseconds(), 2) . " ms");

Live progress reporting

$timer = new ExecutionTimer();
$timer->start();

foreach ($largeDataset as $row) {
    processRow($row);

    // Log every 1 000 rows without stopping the timer
    if ($i % 1000 === 0) {
        $elapsed = $timer->getElapsedTimeInMilliseconds();
        echo "Processed $i rows in " . round($elapsed) . " ms
";
    }
}

$timer->stop();
echo "Done in " . round($timer->getElapsedTimeInMilliseconds()) . " ms";

Reuse a single timer across multiple operations

$timer = $app->executionTimer;

$timer->start();
$data = fetchData();
$timer->stop();
$app->logger->debug("fetch: " . round($timer->getElapsedTimeInMilliseconds(), 2) . " ms");

$timer->restart(); // discards previous measurement, starts fresh
$processed = processData($data);
$timer->stop();
$app->logger->debug("process: " . round($timer->getElapsedTimeInMilliseconds(), 2) . " ms");

Benchmark two approaches

$a = ExecutionTimer::measure(fn() => approachA($input));
$b = ExecutionTimer::measure(fn() => approachB($input));

$winner = $a['elapsed_ms'] < $b['elapsed_ms'] ? 'A' : 'B';
echo "Winner: approach $winner ("
   . round(min($a['elapsed_ms'], $b['elapsed_ms']), 2) . " ms vs "
   . round(max($a['elapsed_ms'], $b['elapsed_ms']), 2) . " ms)";