-
Notifications
You must be signed in to change notification settings - Fork 227
feat(trace): add abstract Clock trait supporting custom timing impl
#710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,8 @@ | |
|
|
||
| use crate::on_early_drop::failure::{BodyDropped, DroppedFailure, FutureDropped}; | ||
| use crate::on_early_drop::traits::{OnBodyDrop, OnDropCallback, OnFutureDrop}; | ||
| use crate::trace::OnFailure; | ||
| use crate::trace::{Clock, DefaultClock, OnFailure}; | ||
| use http::{response, Request, StatusCode}; | ||
| use std::time::Instant; | ||
| use tracing::Span; | ||
|
|
||
| /// Bridges early-drop events to [`trace::OnFailure`](crate::trace::OnFailure). | ||
|
|
@@ -25,30 +24,52 @@ use tracing::Span; | |
| /// | ||
| /// [`OnEarlyDropLayer`]: super::OnEarlyDropLayer | ||
| #[derive(Debug, Clone, Copy)] | ||
| pub struct EarlyDropsAsFailures<F> { | ||
| pub struct EarlyDropsAsFailures<F, Clk = DefaultClock> | ||
| where | ||
| Clk: Clock, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need this bound here? |
||
| { | ||
| on_failure: F, | ||
| clock: Clk, | ||
| } | ||
|
|
||
| impl<F> EarlyDropsAsFailures<F> { | ||
| /// Wrap an [`OnFailure`] implementation. | ||
| /// Wrap an [`OnFailure`] implementation with [`DefaultClock`]. | ||
| pub fn new(on_failure: F) -> Self { | ||
| Self { on_failure } | ||
| Self { | ||
| on_failure, | ||
| clock: DefaultClock, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<F, Clk> EarlyDropsAsFailures<F, Clk> | ||
| where | ||
| Clk: Clock, | ||
| { | ||
| /// Wrap an [`OnFailure`] implementation with a custom [`Clock`]. | ||
| pub fn with_clock(on_failure: F, clock: Clk) -> Self { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This appears untested, there is enough plumbing with the callbacks that it would be good to test. |
||
| Self { on_failure, clock } | ||
| } | ||
| } | ||
|
|
||
| /// Future-drop callback produced by [`EarlyDropsAsFailures`]. | ||
| pub struct FutureDropFailureCallback<F> { | ||
| start: Instant, | ||
| pub struct FutureDropFailureCallback<F, Clk = DefaultClock> | ||
| where | ||
| Clk: Clock, | ||
| { | ||
| start: Clk::Instant, | ||
| on_failure: F, | ||
| span: Span, | ||
| clock: Clk, | ||
| } | ||
|
|
||
| impl<F> OnDropCallback for FutureDropFailureCallback<F> | ||
| impl<F, Clk> OnDropCallback for FutureDropFailureCallback<F, Clk> | ||
| where | ||
| F: OnFailure<DroppedFailure> + Send + 'static, | ||
| Clk: Clock + Send + 'static, | ||
| F: OnFailure<DroppedFailure, Clk> + Send + 'static, | ||
| { | ||
| fn on_drop(mut self) { | ||
| let latency = self.start.elapsed(); | ||
| let latency = self.clock.elapsed(self.start); | ||
| let _entered = self.span.enter(); | ||
| self.on_failure | ||
| .on_failure(DroppedFailure::Future(FutureDropped), latency, &self.span); | ||
|
|
@@ -58,26 +79,35 @@ where | |
| /// Intermediate produced by [`OnBodyDrop::make_at_call`] for | ||
| /// [`EarlyDropsAsFailures`], carrying state forward to | ||
| /// [`OnBodyDrop::make_at_response`]. | ||
| pub struct PreResponseBodyDropCallback<F> { | ||
| start: Instant, | ||
| pub struct PreResponseBodyDropCallback<F, Clk = DefaultClock> | ||
| where | ||
| Clk: Clock, | ||
| { | ||
| start: Clk::Instant, | ||
| on_failure: F, | ||
| span: Span, | ||
| clock: Clk, | ||
| } | ||
|
|
||
| /// Body-drop callback produced by [`EarlyDropsAsFailures`]. | ||
| pub struct BodyDropFailureCallback<F> { | ||
| start: Instant, | ||
| pub struct BodyDropFailureCallback<F, Clk = DefaultClock> | ||
| where | ||
| Clk: Clock, | ||
| { | ||
| start: Clk::Instant, | ||
| on_failure: F, | ||
| span: Span, | ||
| status: StatusCode, | ||
| clock: Clk, | ||
| } | ||
|
|
||
| impl<F> OnDropCallback for BodyDropFailureCallback<F> | ||
| impl<F, Clk> OnDropCallback for BodyDropFailureCallback<F, Clk> | ||
| where | ||
| F: OnFailure<DroppedFailure> + Send + 'static, | ||
| Clk: Clock + Send + 'static, | ||
| F: OnFailure<DroppedFailure, Clk> + Send + 'static, | ||
| { | ||
| fn on_drop(mut self) { | ||
| let latency = self.start.elapsed(); | ||
| let latency = self.clock.elapsed(self.start); | ||
| let _entered = self.span.enter(); | ||
| self.on_failure.on_failure( | ||
| DroppedFailure::Body(BodyDropped { | ||
|
|
@@ -89,33 +119,37 @@ where | |
| } | ||
| } | ||
|
|
||
| impl<F, ReqB> OnFutureDrop<ReqB> for EarlyDropsAsFailures<F> | ||
| impl<F, Clk, ReqB> OnFutureDrop<ReqB> for EarlyDropsAsFailures<F, Clk> | ||
| where | ||
| F: OnFailure<DroppedFailure> + Clone + Send + 'static, | ||
| Clk: Clock + Send + 'static, | ||
| F: OnFailure<DroppedFailure, Clk> + Clone + Send + 'static, | ||
| { | ||
| type Callback = FutureDropFailureCallback<F>; | ||
| type Callback = FutureDropFailureCallback<F, Clk>; | ||
|
|
||
| fn make(&mut self, _request: &Request<ReqB>) -> Self::Callback { | ||
| FutureDropFailureCallback { | ||
| start: Instant::now(), | ||
| start: self.clock.now(), | ||
| on_failure: self.on_failure.clone(), | ||
| span: Span::current(), | ||
| clock: self.clock, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<F, ReqB> OnBodyDrop<ReqB> for EarlyDropsAsFailures<F> | ||
| impl<F, Clk, ReqB> OnBodyDrop<ReqB> for EarlyDropsAsFailures<F, Clk> | ||
| where | ||
| F: OnFailure<DroppedFailure> + Clone + Send + 'static, | ||
| Clk: Clock + Send + 'static, | ||
| F: OnFailure<DroppedFailure, Clk> + Clone + Send + 'static, | ||
| { | ||
| type Intermediate = PreResponseBodyDropCallback<F>; | ||
| type Callback = BodyDropFailureCallback<F>; | ||
| type Intermediate = PreResponseBodyDropCallback<F, Clk>; | ||
| type Callback = BodyDropFailureCallback<F, Clk>; | ||
|
|
||
| fn make_at_call(&mut self, _request: &Request<ReqB>) -> Self::Intermediate { | ||
| PreResponseBodyDropCallback { | ||
| start: Instant::now(), | ||
| start: self.clock.now(), | ||
| on_failure: self.on_failure.clone(), | ||
| span: Span::current(), | ||
| clock: self.clock, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -129,6 +163,7 @@ where | |
| on_failure: intermediate.on_failure, | ||
| span: intermediate.span, | ||
| status: response_parts.status, | ||
| clock: intermediate.clock, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| use std::time::{Duration, Instant}; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that the clock belongs in |
||
|
|
||
| /// Extension trait for duration types that need to be formatted as latencies. | ||
| /// | ||
| /// The [`Clock`] trait's `Duration` associated type must implement this trait, | ||
| /// which provides conversion methods used by [`Latency`](super::Latency). | ||
| pub trait DurationExt { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This As best as I can tell, the wasm support issues are only about Is there a reason we can't simplify to: |
||
| /// Returns the duration in seconds as an `f64`. | ||
| fn as_secs_f64(&self) -> f64; | ||
| /// Returns the duration in whole milliseconds. | ||
| fn as_millis(&self) -> u128; | ||
| /// Returns the duration in whole microseconds. | ||
| fn as_micros(&self) -> u128; | ||
| /// Returns the duration in whole nanoseconds. | ||
| fn as_nanos(&self) -> u128; | ||
| } | ||
|
|
||
| impl DurationExt for Duration { | ||
| fn as_secs_f64(&self) -> f64 { | ||
| Duration::as_secs_f64(self) | ||
| } | ||
| fn as_millis(&self) -> u128 { | ||
| Duration::as_millis(self) | ||
| } | ||
| fn as_micros(&self) -> u128 { | ||
| Duration::as_micros(self) | ||
| } | ||
| fn as_nanos(&self) -> u128 { | ||
| Duration::as_nanos(self) | ||
| } | ||
| } | ||
|
|
||
| /// An abstract clock for measuring time. | ||
| /// | ||
| /// Used by the trace middleware to record latencies and stream durations. | ||
| /// The default implementation ([`DefaultClock`]) uses [`std::time::Instant`] | ||
| /// and [`std::time::Duration`], but custom clocks can be provided — for | ||
| /// example, to mock time in tests or to use a WASM-compatible time source. | ||
| pub trait Clock: Copy { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need And then anyway
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And then, I would probably require
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also without |
||
| /// An instant in time. | ||
| type Instant: Copy + Send; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would probably keep |
||
|
|
||
| /// A duration of time. | ||
| type Duration: Copy + DurationExt + Send; | ||
|
|
||
| /// Returns the current instant. | ||
| fn now(&self) -> Self::Instant; | ||
|
|
||
| /// Returns the duration elapsed from `instant` to now. | ||
| fn elapsed(&self, instant: Self::Instant) -> Self::Duration; | ||
| } | ||
|
|
||
| /// The default [`Clock`] implementation, backed by [`std::time::Instant`]. | ||
| #[derive(Debug, Clone, Copy, Default)] | ||
| pub struct DefaultClock; | ||
|
|
||
| impl DefaultClock { | ||
| /// Create a new `DefaultClock`. | ||
| pub fn new() -> Self { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: seems redundant with the |
||
| Self | ||
| } | ||
| } | ||
|
|
||
| impl Clock for DefaultClock { | ||
| type Instant = Instant; | ||
| type Duration = Duration; | ||
|
|
||
| #[inline] | ||
| fn now(&self) -> Instant { | ||
| Instant::now() | ||
| } | ||
|
|
||
| #[inline] | ||
| fn elapsed(&self, instant: Instant) -> Duration { | ||
| instant.elapsed() | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I would probably prefer either
CorClock