Skip to content

Commit 378e494

Browse files
committed
feat(app): Route request frame count metrics
Signed-off-by: katelyn martin <[email protected]>
1 parent cd2d1b9 commit 378e494

File tree

2 files changed

+186
-1
lines changed

2 files changed

+186
-1
lines changed

linkerd/http/prom/src/body_data/metrics.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
33
use linkerd_metrics::prom::{self, Counter, Family, Registry};
44

5+
/// Counters for request body frames.
6+
pub struct RequestBodyFamilies<L> {
7+
/// Counts the number of request body frames.
8+
req_body_frames_total: Family<L, Counter>,
9+
/// Counts the total number of bytes in request body frames.
10+
req_body_frames_bytes: Family<L, Counter>,
11+
}
12+
513
/// Counters for response body frames.
614
#[derive(Clone, Debug)]
715
pub struct ResponseBodyFamilies<L> {
@@ -20,6 +28,79 @@ pub struct BodyDataMetrics {
2028
pub frames_bytes: Counter,
2129
}
2230

31+
// === impl RequestBodyFamilies ===
32+
33+
impl<L> Default for RequestBodyFamilies<L>
34+
where
35+
L: Clone + std::hash::Hash + Eq,
36+
{
37+
fn default() -> Self {
38+
Self {
39+
req_body_frames_total: Default::default(),
40+
req_body_frames_bytes: Default::default(),
41+
}
42+
}
43+
}
44+
45+
impl<L> RequestBodyFamilies<L>
46+
where
47+
L: prom::encoding::EncodeLabelSet
48+
+ std::fmt::Debug
49+
+ std::hash::Hash
50+
+ Eq
51+
+ Clone
52+
+ Send
53+
+ Sync
54+
+ 'static,
55+
{
56+
const REQ_BODY_FRAMES_TOTAL_NAME: &'static str = "req_body_frames_total";
57+
const REQ_BODY_FRAMES_TOTAL_HELP: &'static str =
58+
"Counts the number of frames in request bodies.";
59+
60+
const REQ_BODY_FRAMES_BYTES_NAME: &'static str = "req_body_frames_bytes";
61+
const REQ_BODY_FRAMES_BYTES_HELP: &'static str =
62+
"Counts the total number of bytes in request bodies.";
63+
64+
/// Registers and returns a new family of body data metrics.
65+
pub fn register(registry: &mut Registry) -> Self {
66+
let req_body_frames_total = Family::default();
67+
registry.register(
68+
Self::REQ_BODY_FRAMES_TOTAL_NAME,
69+
Self::REQ_BODY_FRAMES_TOTAL_HELP,
70+
req_body_frames_total.clone(),
71+
);
72+
73+
let req_body_frames_bytes = Family::default();
74+
registry.register_with_unit(
75+
Self::REQ_BODY_FRAMES_BYTES_NAME,
76+
Self::REQ_BODY_FRAMES_BYTES_HELP,
77+
prom::Unit::Bytes,
78+
req_body_frames_bytes.clone(),
79+
);
80+
81+
Self {
82+
req_body_frames_total,
83+
req_body_frames_bytes,
84+
}
85+
}
86+
87+
/// Returns the [`BodyDataMetrics`] for the given label set.
88+
pub fn get(&self, labels: &L) -> BodyDataMetrics {
89+
let Self {
90+
req_body_frames_total,
91+
req_body_frames_bytes,
92+
} = self;
93+
94+
let frames_total = req_body_frames_total.get_or_create(labels).clone();
95+
let frames_bytes = req_body_frames_bytes.get_or_create(labels).clone();
96+
97+
BodyDataMetrics {
98+
frames_total,
99+
frames_bytes,
100+
}
101+
}
102+
}
103+
23104
// === impl ResponseBodyFamilies ===
24105

25106
impl<L> Default for ResponseBodyFamilies<L>
Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,105 @@
1-
// TODO(kate): write a middleware for request body.
1+
//! Tower middleware to instrument request bodies.
2+
3+
pub use super::metrics::{BodyDataMetrics, RequestBodyFamilies};
4+
5+
use http::{Request, Response};
6+
use http_body::Body;
7+
use linkerd_error::Error;
8+
use linkerd_http_box::BoxBody;
9+
use linkerd_stack::{self as svc, layer::Layer, ExtractParam, NewService, Service};
10+
use std::{future::Future, pin::Pin};
11+
12+
/// A [`NewService<T>`] that creates [`RecordBodyData`] services.
13+
#[derive(Clone, Debug)]
14+
pub struct NewRecordBodyData<X, N> {
15+
/// The [`ExtractParam<P, T>`] strategy for obtaining our parameters.
16+
extract: X,
17+
/// The inner [`NewService<T>`].
18+
inner: N,
19+
}
20+
21+
/// Tracks body frames for an inner `S`-typed [`Service`].
22+
#[derive(Clone, Debug)]
23+
pub struct RecordBodyData<S> {
24+
/// The inner [`Service<T>`].
25+
inner: S,
26+
/// The metrics to be affixed to the response body.
27+
metrics: BodyDataMetrics,
28+
}
29+
30+
// === impl NewRecordBodyData ===
31+
32+
impl<X: Clone, N> NewRecordBodyData<X, N> {
33+
/// Returns a [`Layer<S>`] that tracks body chunks.
34+
///
35+
/// This uses an `X`-typed [`ExtractParam<P, T>`] implementation to extract service parameters
36+
/// from a `T`-typed target.
37+
pub fn layer_via(extract: X) -> impl Layer<N, Service = Self> {
38+
svc::layer::mk(move |inner| Self {
39+
extract: extract.clone(),
40+
inner,
41+
})
42+
}
43+
}
44+
45+
impl<T, X, N> NewService<T> for NewRecordBodyData<X, N>
46+
where
47+
X: ExtractParam<BodyDataMetrics, T>,
48+
N: NewService<T>,
49+
{
50+
type Service = RecordBodyData<N::Service>;
51+
52+
fn new_service(&self, target: T) -> Self::Service {
53+
let Self { extract, inner } = self;
54+
55+
let metrics = extract.extract_param(&target);
56+
let inner = inner.new_service(target);
57+
58+
RecordBodyData { inner, metrics }
59+
}
60+
}
61+
62+
// === impl RecordBodyData ===
63+
64+
impl<ReqB, RespB, S> Service<Request<ReqB>> for RecordBodyData<S>
65+
where
66+
S: Service<Request<ReqB>, Response = Response<RespB>>,
67+
S::Future: Send + 'static,
68+
RespB: Body + Send + 'static,
69+
RespB::Data: Send + 'static,
70+
RespB::Error: Into<Error>,
71+
{
72+
type Response = Response<BoxBody>;
73+
type Error = S::Error;
74+
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
75+
76+
#[inline]
77+
fn poll_ready(
78+
&mut self,
79+
cx: &mut std::task::Context<'_>,
80+
) -> std::task::Poll<Result<(), Self::Error>> {
81+
self.inner.poll_ready(cx)
82+
}
83+
84+
fn call(&mut self, req: Request<ReqB>) -> Self::Future {
85+
use futures::{FutureExt, TryFutureExt};
86+
87+
let Self { inner, metrics } = self;
88+
let metrics = metrics.clone();
89+
let instrument = Box::new(|resp| Self::instrument_response(resp, metrics));
90+
91+
inner.call(req).map_ok(instrument).boxed()
92+
}
93+
}
94+
95+
impl<S> RecordBodyData<S> {
96+
fn instrument_response<B>(resp: Response<B>, metrics: BodyDataMetrics) -> Response<BoxBody>
97+
where
98+
B: Body + Send + 'static,
99+
B::Data: Send + 'static,
100+
B::Error: Into<Error>,
101+
{
102+
resp.map(|b| super::body::Body::new(b, metrics))
103+
.map(BoxBody::new)
104+
}
105+
}

0 commit comments

Comments
 (0)