Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Stopwatch Laravel Package

symfony/stopwatch

Symfony Stopwatch profiles PHP code: start and stop named events, group them into sections, and record lap times to measure where execution time goes. Install via Composer.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • Stopwatch is a leaf utility, not a subsystem: roughly a dozen classes, no I/O, no configuration, no container coupling, no network surface. It can be called from a controller, a service, a job, a middleware, or an Artisan command without altering application architecture. Integration risk is close to zero; value risk is entirely in what we build around it.
  • It is in-memory and request-scoped by design. That is the correct shape for Laravel's share-nothing PHP-FPM model and the wrong shape for long-lived workers (Octane, Swoole, RoadRunner, queue daemons) unless the binding is container-scoped and reset per unit of work.
  • The real architectural gap: Stopwatch produces timings, it does not export them. There is no reporter, no sink, no log channel, no serialization contract. Every team that adopts it writes the same thin wrapper. That gap is where the opportunity score (~55.7) sits — the library itself is a commodity, so the product decision is whether we standardize the instrumentation contract (naming taxonomy, section conventions, thresholds, output format) or let every squad improvise and pay a reconciliation cost later.
  • It complements rather than replaces observability. Position it as dev / staging / pre-deploy instrumentation with a narrow, sampled, flag-gated production carve-out — not as an APM substitute. Framing matters: if engineers expect it to answer production incident questions, the initiative is judged against the wrong bar.

Integration feasibility

  • Trivial to install: composer require symfony/stopwatch. Laravel 11/12 already pulls Symfony components, so the package is likely already in vendor/ transitively (notably via Debugbar's timeline). Verify with composer why symfony/stopwatch before adding a direct constraint — the risk is not installation, it is version locking against the Symfony major already pinned by the framework.
  • The dependents: 0 figure is a metadata artifact, not a signal. Stopwatch ships through the Symfony monorepo split and is consumed indirectly across the ecosystem. Do not present that number to stakeholders as adoption evidence, in either direction.
  • The API surface is small and stable enough to wrap cleanly: start / stop / lap, openSection / stopSection, getEvent, getSectionEvents, plus StopwatchEvent::getDuration(), getMemory(), getPeriods(). Symfony 7.2 added Stopwatch::ROOT and getLastPeriod(), which meaningfully improve ergonomics — a reason to floor at 7.2+ rather than 6.4 LTS.
  • Two API footguns must be designed around up front, not discovered in production: getDuration() returns integer milliseconds (sub-millisecond work floors to zero — fine for endpoint/query profiling, disqualifying for micro-benchmarks), and start() on an already-started event throws LogicException, which surfaces as a 500 the first time re-entrant code or double-applied middleware hits it.
  • The listed last release date (2026-05-29) is future-dated relative to a normal review cadence. Treat it as a pipeline artifact and verify against Packagist and the GitHub tag list before committing a constraint to composer.json. This is a five-minute check that protects the credibility of the whole proposal.

Technical risk

  • Low, bounded, reversible. MIT license, Symfony governance, zero runtime dependencies, no data egress. It will not generate a security or compliance finding and does not require a privacy review.
  • Primary risk is false confidence, not breakage. Teams instrument three hot paths, see green numbers, and stop. Or they add lap() inside a loop over 100k iterations and the StopwatchEvent period array becomes the memory problem it was meant to detect. Instrumentation not tied to a threshold, a CI gate, or a dashboard is theater.
  • Secondary risk is instrumentation drift: without a shared taxonomy, output becomes db.query, db_query, query:users — un-joinable, un-aggregable. That is a governance cost we pay later at roughly five times the price.
  • Adoption risk outranks technical risk. Stopwatch requires manual start/stop calls. If the team will not add them, the package fails regardless of its quality, and no amount of wrapper tooling fixes it.

Key questions

  1. Is the goal ad-hoc developer profiling (adopt as-is, near-zero cost, no ticket) or a repeatable pre-merge performance gate (requires a wrapper, a threshold policy, and named CI ownership)? Different owners, different budgets — the decision drives everything downstream.
  2. Which surface first — HTTP middleware, queue jobs, or Artisan commands? Recommendation: queue jobs and commands, where vendor APM coverage is weakest and ROI is highest.
  3. Do we accept manual instrumentation as the team norm, or will adoption stall without automated coverage? If the answer is "manual, but nobody has time," the initiative is already dead.
  4. Under Octane and queue workers, has the team agreed the Stopwatch binding must be scoped, not singleton?
  5. Who is the named owner of the instrumentation taxonomy and the threshold policy, and what is the escalation path when a gate fails a merge?
  6. What is the exit plan if we later adopt OpenTelemetry — can the wrapper emit OTel spans without rewriting call sites?
  7. Are we prepared to publicly cap scope at two gated paths? A narrow gate that survives is worth more than broad instrumentation that rots.

Integration Approach

Stack fit

Highest-value insertion points in a Laravel application, in ROI order:

Priority Surface Why Effort
1 Queue jobs (job middleware) Weakest existing APM coverage; clean hook with no base-class tax ~1 day
2 Artisan commands (--profile flag) Named phases (fetch / transform / persist) map naturally to sections ~0.5 day
3 HTTP middleware One outer event plus named sub-events for DB, cache, outbound HTTP — sampled, staging-first ~1.5 days
4 Targeted service methods The 3–5 paths carrying the SLO: checkout, search ranking, auth Per-path
  • Bind as a scoped container singleton ($this->app->scoped(Stopwatch::class)) and expose a thin Profiler facade that owns the naming taxonomy, feature-flag gating, and the serializer. Application code must never touch Stopwatch directly — that is the single architectural decision that keeps the exit ramp to OTel or a vendor APM open.

Migration path

  • Phase 0 — Verify (1 day, no ticket needed beyond a spike): confirm existing vendor presence and the resolved Symfony major; add an explicit ^7.2|^8.0 constraint aligned to the framework; confirm the release-date anomaly against Packagist.
  • Phase 1 — Wrapper (3–5 eng-days): ProfilerServiceProvider, Profiler facade, ProfilingJobMiddleware (Laravel job middleware is the cleanest hook — no inheritance, no base-class tax), an Artisan --profile flag, and a JSON-lines log channel.
  • Phase 2 — Gate (2–3 eng-days): a threshold config file, a test helper asserting job/endpoint budgets, and one blocking gate on the two most latency-sensitive paths only.
  • No data migration, no schema change, no backfill. Rollback is deleting a provider registration. This is a rare initiative with a one-line rollback — say so explicitly to reviewers; it is the cheapest way to get approval.

Compatibility

  • The constraint must match the Symfony major already resolved by the framework. Mixing a v8 Stopwatch into a Laravel app on Symfony 7 components will fail dependency resolution.
  • The PHP floor follows Laravel, not Stopwatch. Laravel 12 requires PHP 8.2+, so target Stopwatch 7.2+ (for ROOT and getLastPeriod()) and treat v8 as gated on a PHP 8.4 upgrade. Do not let v8's PHP 8.4 requirement silently force an unrelated platform upgrade into this initiative's scope.
  • The gate helper should be skipped by default on CI runners shared with unrelated suites, or flaky timings will be attributed to the feature rather than the runner.

Sequencing

  1. Taxonomy and thresholds agreed before any instrumentation lands — retrofitting names is the expensive failure mode.
  2. Wrapper merged with zero call sites, then one job class instrumented end-to-end as a reference implementation.
  3. Reference implementation demoed to squad leads; instrumentation then spreads by opt-in pull, not mandate.
  4. CI gate last, and only after two paths have produced stable, trusted numbers for a sprint. Gating on untrusted data is how the initiative loses its mandate in week three.

Operational Impact

Maintenance

  • Near-zero upstream maintenance: the component is stable, has had no significant API change across 5.4→8.1, and is governed by Symfony's release process. Budget is essentially our wrapper, not theirs.
  • Realistic sustained cost: ~0.1 FTE to own the wrapper and taxonomy, plus a small quarterly review of thresholds. That owner must be named at approval time, not after launch — an unowned wrapper is a future outage with a delay fuse.
  • Version bumps are driven by the framework's Symfony major, not by this package's roadmap. Track it as a transitive concern rather than a standalone dependency to manage.

Support

  • Support burden is inverted from typical packages: nobody files a ticket against Stopwatch, they file one against the wrapper ("why is my job's profile empty / why did this event throw"). Every support path runs through the named owner.
  • Expect a predictable first-month question load: how to name events, why a section is missing, why getDuration() returned 0 (integer-ms flooring), and why a 500 appeared from a double start(). Pre-empt all four in a one-page README; this eliminates most of the tail.
  • Document the exit ramp alongside the usage guide. When OTel adoption arrives, the wrapper is the migration seam, and that story is what keeps this from being classified as technical debt later.

Scaling

  • Per-request memory overhead is small but unbounded by construction: period arrays grow with lap() call count. Cap laps, or forbid laps inside loops over large datasets, in the wrapper.
  • Under Octane, an unreset scoped binding silently accumulates events across requests and quietly inflates memory and distorts timings. This is the highest-severity scaling failure mode and is preventable with one line of container config plus a test.
  • Horizontal scale is unaffected — no shared state, no external calls, no coordination. Cost scales with instrumented code volume, and the serialized JSON-lines output scales with log ingestion spend; that is the number to watch once more than a handful of paths are instrumented.
  • Sampling is not built in. Any production carve-out needs a flag, a sample rate, and a hard cap enforced in the wrapper, not in application code.

Failure modes

Failure Blast radius Mitigation
Double start() → LogicException 500 on a previously healthy path ensureStarted() in the wrapper; static analysis rule against direct Stopwatch use
Octane binding not reset Memory growth; corrupted timings scoped() binding + per-unit-of-work reset test
Unbounded lap() in loops Memory pressure proportional to iteration count Wrapper cap; code review checklist
Integer-ms flooring to 0 Teams conclude the path is free; micro-optimization chases nothing Document unit and precision limits in the README
Un-joinable event names Dashboards unusable; gate thresholds meaningless Taxonomy owned by one named person before rollout
Flaky timing gate Merge friction; initiative loses credibility Gate only two paths; generous initial budget; tune down over sprints
Orphaned wrapper after owner moves Rot, then silent removal Ownership assigned at approval; quarterly review on the calendar

Ramp-up

  • Individual engineer ramp-up is under an hour — the API is four methods. The real ramp cost is organizational: agreeing a taxonomy, agreeing who owns thresholds, and agreeing what happens when a merge is blocked.
  • Budget one 30-minute demo plus a one-page README per squad. If more than that is required, the wrapper has failed at its job.
  • Expect a 2–3 sprint adoption curve: reference implementation in sprint one, opt-in pull across 2–3 squads in sprint two, gate live in sprint three. Anything faster means the gate was not actually blocking.

Success criteria to agree before kickoff

  • ≥ 70% of queue job classes instrumented within two sprints.
  • Gate blocks on exactly two paths and holds an acceptable false-positive rate (< 1 in 20 CI runs) after tuning.
  • One performance regression caught pre-merge within the first quarter — the concrete proof point that justifies continued cost.
  • Zero production incidents attributable to instrumentation.

If those four are not agreed up front, the honest recommendation is to adopt Stopwatch informally as a developer tool with no wrapper, no gate, and no roadmap commitment — and revisit only when a regression actually escapes.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table