sandermuller/stopwatch
A simple Laravel/PHP stopwatch package for measuring elapsed time, starting and stopping timers, and tracking laps or durations in your application.
Architecture fit. Stopwatch is a Laravel-first instrumentation package: a service provider wires middleware, config publishing, Artisan commands (stopwatch:runs:list), a Debugbar timeline panel, and an MCP surface that exposes run data to agent/tooling consumers. That places it in the developer-observability layer of the stack, not the request-serving path — which is the right architectural slot for a TPM to defend. The MCP filter surface (threw) is the notable architectural wrinkle: profiling data is now consumable by automated tooling, so its correctness has downstream consumers beyond humans reading a CLI table.
Integration feasibility. Low-friction: composer install, publish config, opt-in via env. The v0.16.0 fixes are largely correctness fixes to behavior that was silently wrong (STOPWATCH_INJECT coercion, dropped deferred-job runs, missing TRANSIENT_EXCEPTION runs). Low integration cost, but the release also carries two semantic changes that touch contract-level behavior rather than internals.
Technical risk.
recorded_at now means start time; the CLI column is relabeled Started. Any downstream consumer — dashboards, saved queries, custom reports, MCP-driven analysis — that assumed end-time semantics is now silently misreading latency by the run's duration. The migration window is worse than a clean break: run files written pre-upgrade retain end-time semantics until pruning removes them, so a single runs directory will hold mixed-semantics records for one retention cycle.started() contract change. Returning false after an auto-dropped no-checkpoint run changes control flow for any caller using started() as a guard. Not a signature change, so it won't fail loudly — it will fail quietly.run_log configs lacking min_duration_ms now skip sub-50ms runs. This is a good default, but it retroactively changes volume and coverage on existing installs without a config edit — and anyone who was relying on capturing every run for a specific investigation will see gaps.false-valued STOPWATCH_INJECT still forced tracking will now genuinely stop tracking. Teams that believed they had data will find they don't.Key questions.
recorded_at semantics persist in our runs directory?recorded_at or the old column header Recorded?started() as a boolean guard where a new false changes behavior?min_duration_ms: 50 acceptable as our floor, or do we need full capture in any environment?stopwatch:runs:list --threw / MCP threw for failure reporting, and were their counts previously understated?Stack fit. Adopt as a dev/CI-only dependency. Keep it out of production runtime paths unless there is a specific, justified need to profile live traffic; the injection middleware and Debugbar toolbar are the two components most likely to cause production surprises, and this release exists partly because injection control was not being honored. Restrict installation to require-dev where the middleware is only registered in local/CI environments.
Migration path.
^0.16.0 with a committed composer.lock.run_log config into version control before upgrading, and explicitly set min_duration_ms rather than relying on the new 50ms fallback — the fallback only applies to configs missing the key, so declaring it makes the behavior intentional and reviewable.STOPWATCH_INJECT to a real boolean in each environment's .env; do not rely on coercion. Audit for false, null, or misspelled values that were previously masked by the bug and will now change behavior.started() call sites and recorded_at consumers before merging.Compatibility. Two compatibility surfaces need explicit owners: (a) the runs-file format, which is version-dependent and mixed during transition, and (b) the CLI/MCP output schema, where a column was renamed. Anything that scrapes stopwatch:runs:list output by position or by header text is a break. The --threw and MCP threw fixes are compat-positive — filters now return runs they always should have — but they will change counts, so any alert threshold or baseline built on the old (understated) numbers needs recalibration.
Sequencing.
Maintenance. Pre-1.0 with a single maintainer and a fast minor cadence. Budget a recurring review of each minor's changelog — this release alone contains two behavior changes, one renamed output column, one changed default, and four substantive bug fixes. Pin exact versions, upgrade deliberately, and never float ^0.x without a lockfile. The addition of StructArmed architecture checks to CI is a positive maintainability signal: internal coupling is being governed, which lowers the odds of sprawling breakage in later minors.
Support. No vendor SLA. Internal ownership should sit with whoever owns developer tooling/CI, with a documented fallback: disable injection via env (now actually honored, per this release) or remove the package. The env-coercion fix is a support win — "tracking is on and I can't turn it off" stops being an unexplainable ticket — but expect a brief inverse wave of "why did my profiling data disappear" from environments that were only tracking due to the bug.
Scaling. Cost scales with run volume × retention, on disk. The new 50ms floor is a meaningful volume reducer for high-throughput local/CI suites and should be treated as the intended default. Watch the deferred-job fix: previously-lost runs now get recorded, so background/queue-heavy environments will see a step increase in run files immediately after upgrade. Confirm pruning runs on a schedule, not manually.
Failure modes to monitor post-upgrade.
started() returning false in a guard path, silently skipping profiling blocks (medium/high likelihood, silent).threw consumers seeing a count jump and mistaking it for a regression rather than a fix (high likelihood, low harm if pre-announced).Ramp-up. Low. A developer can be productive in under an hour: install, publish config, wrap a code path in checkpoints, read stopwatch:runs:list. The MCP surface is the only piece requiring conceptual ramp-up, and it should be documented with the same care as the CLI since it is now a first-class consumer contract. Recommended enablement: a short internal note covering the recorded_at semantics change, the Started column rename, the 50ms default, and the fact that threw counts will rise — because that last one will otherwise be read as a regression by the first person who sees it.
How can I help you explore Laravel packages today?