Skip to content

Commit afb7b79

Browse files
authored
perf(http/prom): avoid oneshot channel for request duration start time (#4448)
RecordRequestDuration knows the start time at call() time, whereas RecordResponseDuration defers it until the request body flushes. The latter needs async delivery from the request body wrapper, but the former is known synchronously. Replace the shared oneshot::Receiver<Instant> in ResponseState with an enum supporting both immediate and deferred start times, removing one heap allocation per request. Signed-off-by: Alejandro Martinez Ruiz <amr@buoyant.io>
1 parent 5fac714 commit afb7b79

File tree

3 files changed

+25
-6
lines changed

3 files changed

+25
-6
lines changed

linkerd/http/prom/src/record_response.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,27 @@ where
8181
struct ResponseState<L: StreamLabel> {
8282
labeler: L,
8383
duration: DurationFamily<L::DurationLabels>,
84-
start: oneshot::Receiver<time::Instant>,
84+
start: StartTime,
85+
}
86+
87+
/// Start time for a duration measurement.
88+
///
89+
/// Request duration knows the start time immediately whereas response duration
90+
/// needs to wait until the request body is fully flushed.
91+
pub(crate) enum StartTime {
92+
/// Start time is already known.
93+
Known(Option<time::Instant>),
94+
/// Start time will be sent when the request body finishes streaming.
95+
Pending(oneshot::Receiver<time::Instant>),
96+
}
97+
98+
impl StartTime {
99+
fn recv(&mut self) -> Option<time::Instant> {
100+
match self {
101+
Self::Known(t) => t.take(),
102+
Self::Pending(rx) => rx.try_recv().ok(),
103+
}
104+
}
85105
}
86106

87107
type DurationFamily<L> = Family<L, Histogram, MkDurationHistogram>;
@@ -269,7 +289,7 @@ fn end_stream<L>(
269289

270290
labeler.end_response(res);
271291

272-
let elapsed = if let Ok(start) = start.try_recv() {
292+
let elapsed = if let Some(start) = start.recv() {
273293
time::Instant::now().saturating_duration_since(start)
274294
} else {
275295
time::Duration::ZERO

linkerd/http/prom/src/record_response/request.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
sync::Arc,
99
task::{Context, Poll},
1010
};
11-
use tokio::{sync::oneshot, time};
11+
use tokio::time;
1212

1313
/// Metrics type that tracks completed requests.
1414
#[derive(Debug)]
@@ -81,8 +81,7 @@ where
8181

8282
fn call(&mut self, req: http::Request<ReqB>) -> Self::Future {
8383
let state = self.labeler.mk_stream_labeler(&req).map(|labeler| {
84-
let (tx, start) = oneshot::channel();
85-
tx.send(time::Instant::now()).unwrap();
84+
let start = super::StartTime::Known(Some(time::Instant::now()));
8685
let RequestMetrics { duration } = self.metric.clone();
8786
super::ResponseState {
8887
labeler,

linkerd/http/prom/src/record_response/response.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ where
9595
let ResponseMetrics { duration } = self.metric.clone();
9696
Some(super::ResponseState {
9797
labeler,
98-
start,
98+
start: super::StartTime::Pending(start),
9999
duration,
100100
})
101101
} else {

0 commit comments

Comments
 (0)