feat: emit http_request_limit_rejected metric

Add the "http_request_limit_rejected" metric that is incremented once
each time a request is dropped due to the simultaneous request service
protection limit being exceeded.

This metric will allow us to effectively alert on router saturation, and
the increase rate/second will help inform us in how much capacity needs
adding.
pull/24376/head
Dom Dwyer 2022-04-25 16:33:21 +01:00
parent fb777e7e51
commit 8a208a814a
1 changed files with 19 additions and 1 deletions

View File

@ -227,6 +227,7 @@ pub struct HttpDelegate<D, T = SystemProvider> {
write_metric_tables: U64Counter, write_metric_tables: U64Counter,
write_metric_body_size: U64Counter, write_metric_body_size: U64Counter,
delete_metric_body_size: U64Counter, delete_metric_body_size: U64Counter,
request_limit_rejected: U64Counter,
} }
impl<D> HttpDelegate<D, SystemProvider> { impl<D> HttpDelegate<D, SystemProvider> {
@ -271,6 +272,12 @@ impl<D> HttpDelegate<D, SystemProvider> {
"cumulative byte size of successfully routed (decompressed) delete requests", "cumulative byte size of successfully routed (decompressed) delete requests",
) )
.recorder(&[]); .recorder(&[]);
let request_limit_rejected = metrics
.register_metric::<U64Counter>(
"http_request_limit_rejected",
"number of HTTP requests rejected due to exceeding parallel request limit",
)
.recorder(&[]);
Self { Self {
max_request_bytes, max_request_bytes,
@ -282,6 +289,7 @@ impl<D> HttpDelegate<D, SystemProvider> {
write_metric_tables, write_metric_tables,
write_metric_body_size, write_metric_body_size,
delete_metric_body_size, delete_metric_body_size,
request_limit_rejected,
} }
} }
} }
@ -305,6 +313,7 @@ where
Ok(p) => p, Ok(p) => p,
Err(TryAcquireError::NoPermits) => { Err(TryAcquireError::NoPermits) => {
error!("simultaneous request limit exceeded - dropping request"); error!("simultaneous request limit exceeded - dropping request");
self.request_limit_rejected.inc(1);
return Err(Error::RequestLimit); return Err(Error::RequestLimit);
} }
Err(e) => panic!("request limiter error: {}", e), Err(e) => panic!("request limiter error: {}", e),
@ -520,9 +529,10 @@ mod tests {
.expect("failed to get observer") .expect("failed to get observer")
.fetch(); .fetch();
assert!(counter > 0, "metric {} did not record any values", name);
if let Some(want) = value { if let Some(want) = value {
assert_eq!(want, counter, "metric does not have expected value"); assert_eq!(want, counter, "metric does not have expected value");
} else {
assert!(counter > 0, "metric {} did not record any values", name);
} }
} }
@ -1177,6 +1187,8 @@ mod tests {
// immediate drop of any subsequent requests. // immediate drop of any subsequent requests.
// //
assert_metric_hit(&*metrics, "http_request_limit_rejected", Some(0));
// Retain this tx handle for the second request and use it to prove the // Retain this tx handle for the second request and use it to prove the
// request dropped before anything was read from the body - the request // request dropped before anything was read from the body - the request
// should error _before_ anything is sent over tx, and subsequently // should error _before_ anything is sent over tx, and subsequently
@ -1200,6 +1212,9 @@ mod tests {
.expect_err("second request should be rejected"); .expect_err("second request should be rejected");
assert_matches!(err, Error::RequestLimit); assert_matches!(err, Error::RequestLimit);
// Ensure the "rejected requests" metric was incremented
assert_metric_hit(&*metrics, "http_request_limit_rejected", Some(1));
// Prove the dropped request body is not being read: // Prove the dropped request body is not being read:
body_2_tx body_2_tx
.send(Ok("wat")) .send(Ok("wat"))
@ -1232,5 +1247,8 @@ mod tests {
.with_timeout_panic(Duration::from_secs(1)) .with_timeout_panic(Duration::from_secs(1))
.await .await
.expect("empty write should succeed"); .expect("empty write should succeed");
// And the request rejected metric must remain unchanged
assert_metric_hit(&*metrics, "http_request_limit_rejected", Some(1));
} }
} }