Open
Conversation
Global telemetry handler attached lazily on first use via persistent_term. Per-request opt-in through process dictionary flag — when no proxy has opted in, the handler cost is a single Process.get/1 returning nil. Captures queue, connect, send, recv durations and idle_time from Finch telemetry events. Converts native time units to microseconds.
Covers lazy attachment idempotency, no-op when not opted in, capture+collect round-trip, native-to-microsecond conversion, reused_connection flag, process dictionary cleanup after collect, mutual exclusivity of connect vs reused_connection, idle_time_us extraction, and graceful handling when no events fire.
Breaking change: finished_result.duration_us is replaced by a timing map containing total_us (same value) plus per-phase breakdown fields (queue_us, connect_us, send_us, recv_us, idle_time_us, reused_connection?). Phase fields are populated when collect_timing: true is passed to proxy/2. When timing capture is off, phase fields are nil. Adds stream_with_timing/5 wrapper around Finch.stream_while that optionally captures telemetry-based phase timing. The catch clause cleans up process dictionary entries on raises/exits.
Adapts existing assertions from result.duration_us to result.timing.total_us. Adds new describe block for timing-specific tests: default nil phase fields when collect_timing is not set, populated phase fields with collect_timing: true, and timing presence on error paths.
Breaking change to finished_result warrants minor version bump.
Breaking change: body_observation no longer has duration_us or time_to_first_byte_us. Observations are now purely content metadata (hash, size, preview, body). All timing lives in finished_result.timing. Removes started_at and first_byte_at from Observation struct. The response_started callback's time_to_first_byte_us is unchanged (it measures request-level TTFB, not body-level).
- Document when finished_result.status is nil (error before response) - Remove misplaced timing comment from README observation example - Hide internal Timing.t() type from docs (consumers use Handler.timing())
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
finished_result.duration_usis replaced by atimingmap containingtotal_us(same value) plus per-phase breakdown fieldscollect_timing: trueoption forproxy/2enables timing capture from Finch telemetry eventsPhilter.Timingmodule with a single globally-attached telemetry handler (lazy, near-zero cost when unused)Timing structure
When
collect_timing: true:When
collect_timingis not set (default):Design
persistent_termguard, avoids O(N²) attach/detach per requestProcess.get(:philter_timing_ref)on every Finch event; returnsnilimmediately when no proxy is capturingcatchclause instream_with_timing/5cleans up process dictionary entries before re-raisingMigration
All
timingkeys are always present. Phase fields arenilunlesscollect_timing: trueis passed.Test plan
Philter.Timingunit tests (11 tests): attachment idempotency, round-trip capture, duration conversion, cleanup, mutual exclusivity, edge casesPhilterTestintegration tests updated for newtimingstructure + new timing-specific tests (139 total, 0 failures)mix compile --warnings-as-errorscleanmix format --check-formattedclean