Skip to content

Commit d53fcc3

Browse files
committed
Added support for http.server.active_requests metric
1 parent c71cf4e commit d53fcc3

13 files changed

+667
-95
lines changed

src/lib.rs

Lines changed: 124 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,9 @@ http_requests_duration_seconds_sum{http_route="UNMATCHED",http_request_method="G
226226
*/
227227
#![deny(missing_docs)]
228228

229+
use actix_web::http::Uri;
229230
use log::warn;
230-
use metrics::{describe_histogram, gauge, histogram, Unit};
231+
use metrics::{describe_gauge, describe_histogram, gauge, histogram, Unit};
231232
use std::collections::{HashMap, HashSet};
232233
use std::future::{ready, Future, Ready};
233234
use std::marker::PhantomData;
@@ -378,12 +379,14 @@ impl ActixWebMetricsBuilder {
378379
"HTTP response size in bytes for all requests"
379380
);
380381

381-
let version: Option<&'static str> = if let Some(ref v) = self.metrics_config.labels.version
382-
{
383-
Some(Box::leak(Box::new(v.clone())))
384-
} else {
385-
None
386-
};
382+
let http_server_active_requests_name = format!(
383+
"{namespace_prefix}{}",
384+
self.metrics_config.http_server_active_requests_name
385+
);
386+
describe_gauge!(
387+
http_server_active_requests_name.clone(),
388+
"Number of active HTTP server requests."
389+
);
387390

388391
let mut const_labels: Vec<(&'static str, String)> = self
389392
.const_labels
@@ -396,23 +399,32 @@ impl ActixWebMetricsBuilder {
396399
const_labels.sort_by_key(|v| v.0);
397400

398401
ActixWebMetrics {
399-
exclude: self.exclude,
400-
exclude_regex: self.exclude_regex,
401-
exclude_status: self.exclude_status,
402-
enable_http_version_label: self.metrics_config.labels.version.is_some(),
403-
unmatched_patterns_mask: self.unmatched_patterns_mask,
404-
names: MetricsMetadata {
405-
http_requests_duration_seconds: Box::leak(Box::new(
406-
http_server_request_duration_name,
407-
)),
408-
http_request_size_bytes: Box::leak(Box::new(http_server_request_body_size_name)),
409-
http_response_size_bytes: Box::leak(Box::new(http_server_response_body_size_name)),
410-
endpoint: Box::leak(Box::new(self.metrics_config.labels.route)),
411-
method: Box::leak(Box::new(self.metrics_config.labels.method)),
412-
status: Box::leak(Box::new(self.metrics_config.labels.status)),
413-
version,
414-
const_labels,
415-
},
402+
inner: Arc::new(ActixWebMetricsInner {
403+
exclude: self.exclude,
404+
exclude_regex: self.exclude_regex,
405+
exclude_status: self.exclude_status,
406+
unmatched_patterns_mask: self.unmatched_patterns_mask,
407+
names: MetricsMetadata {
408+
http_requests_duration_seconds: Box::leak(Box::new(
409+
http_server_request_duration_name,
410+
)),
411+
http_request_size_bytes: Box::leak(Box::new(
412+
http_server_request_body_size_name,
413+
)),
414+
http_response_size_bytes: Box::leak(Box::new(
415+
http_server_response_body_size_name,
416+
)),
417+
http_server_active_requests: Box::leak(Box::new(
418+
http_server_active_requests_name,
419+
)),
420+
http_route: Box::leak(Box::new(self.metrics_config.labels.route)),
421+
http_request_method: Box::leak(Box::new(self.metrics_config.labels.method)),
422+
http_response_status: Box::leak(Box::new(self.metrics_config.labels.status)),
423+
http_version: Box::leak(Box::new(self.metrics_config.labels.version)),
424+
url_scheme: Box::leak(Box::new(self.metrics_config.labels.url_scheme)),
425+
const_labels,
426+
},
427+
}),
416428
}
417429
}
418430
}
@@ -429,7 +441,8 @@ pub struct LabelsConfig {
429441
route: String,
430442
method: String,
431443
status: String,
432-
version: Option<String>,
444+
version: String,
445+
url_scheme: String,
433446
}
434447

435448
impl Default for LabelsConfig {
@@ -438,7 +451,8 @@ impl Default for LabelsConfig {
438451
route: String::from("http.route"),
439452
method: String::from("http.request.method"),
440453
status: String::from("http.response.status_code"),
441-
version: None,
454+
version: String::from("network.protocol.version"),
455+
url_scheme: String::from("url.scheme"),
442456
}
443457
}
444458
}
@@ -464,7 +478,13 @@ impl LabelsConfig {
464478

465479
/// set http version label
466480
pub fn version<T: Into<String>>(mut self, name: T) -> Self {
467-
self.version = Some(name.into());
481+
self.version = name.into();
482+
self
483+
}
484+
485+
/// set url.scheme label
486+
pub fn url_scheme<T: Into<String>>(mut self, name: T) -> Self {
487+
self.url_scheme = name.into();
468488
self
469489
}
470490
}
@@ -477,6 +497,7 @@ pub struct ActixWebMetricsConfig {
477497
http_server_request_duration_name: String,
478498
http_server_request_body_size_name: String,
479499
http_server_response_body_size_name: String,
500+
http_server_active_requests_name: String,
480501
labels: LabelsConfig,
481502
}
482503

@@ -486,6 +507,7 @@ impl Default for ActixWebMetricsConfig {
486507
http_server_request_duration_name: String::from("http.server.request.duration"),
487508
http_server_request_body_size_name: String::from("http.server.request.body.size"),
488509
http_server_response_body_size_name: String::from("http.server.response.body.size"),
510+
http_server_active_requests_name: String::from("http.server.active_requests"),
489511
labels: LabelsConfig::default(),
490512
}
491513
}
@@ -515,6 +537,12 @@ impl ActixWebMetricsConfig {
515537
self.http_server_response_body_size_name = name.into();
516538
self
517539
}
540+
541+
/// Set name for `http.server.active_requests` metric
542+
pub fn http_server_active_requests_name<T: Into<String>>(mut self, name: T) -> Self {
543+
self.http_server_active_requests_name = name.into();
544+
self
545+
}
518546
}
519547

520548
/// Static references to variable metrics/label names.
@@ -524,10 +552,12 @@ struct MetricsMetadata {
524552
http_requests_duration_seconds: &'static str,
525553
http_request_size_bytes: &'static str,
526554
http_response_size_bytes: &'static str,
527-
endpoint: &'static str,
528-
method: &'static str,
529-
status: &'static str,
530-
version: Option<&'static str>,
555+
http_server_active_requests: &'static str,
556+
http_route: &'static str,
557+
http_request_method: &'static str,
558+
http_response_status: &'static str,
559+
http_version: &'static str,
560+
url_scheme: &'static str,
531561
const_labels: Vec<(&'static str, String)>,
532562
}
533563

@@ -544,32 +574,63 @@ struct MetricsMetadata {
544574
#[derive(Clone)]
545575
#[must_use = "must be set up as middleware for actix-web"]
546576
pub struct ActixWebMetrics {
577+
inner: Arc<ActixWebMetricsInner>,
578+
}
579+
580+
struct ActixWebMetricsInner {
547581
pub(crate) names: MetricsMetadata,
548582

549583
pub(crate) exclude: HashSet<String>,
550584
pub(crate) exclude_regex: RegexSet,
551585
pub(crate) exclude_status: HashSet<StatusCode>,
552-
pub(crate) enable_http_version_label: bool,
553586
pub(crate) unmatched_patterns_mask: Option<String>,
554587
}
555588

556589
impl ActixWebMetrics {
590+
fn pre_request_update_metrics(&self, req: &ServiceRequest) {
591+
let this = &*self.inner;
592+
593+
let mut labels = Vec::with_capacity(2);
594+
labels.push((
595+
this.names.http_request_method,
596+
req.method().as_str().to_string(),
597+
));
598+
labels.push((this.names.url_scheme, url_scheme(&req.uri()).to_string()));
599+
600+
gauge!(this.names.http_server_active_requests, &labels).increment(1);
601+
}
602+
557603
#[allow(clippy::too_many_arguments)]
558-
fn update_metrics(
604+
fn post_request_update_metrics(
559605
&self,
560606
http_version: Version,
561607
mixed_pattern: &str,
562608
fallback_pattern: &str,
563609
method: &Method,
564610
status: StatusCode,
611+
scheme: &str,
565612
clock: Instant,
566613
was_path_matched: bool,
567614
request_size: usize,
568615
response_size: usize,
569616
) {
570-
if self.exclude.contains(mixed_pattern)
571-
|| self.exclude_regex.is_match(mixed_pattern)
572-
|| self.exclude_status.contains(&status)
617+
let this = &*self.inner;
618+
619+
// NOTE: active_requests cannot be skips as we need to decrement the increment we did that
620+
// the beginning of the request.
621+
let active_request_labels = vec![
622+
(this.names.http_request_method, method.as_str().to_string()),
623+
(this.names.url_scheme, scheme.to_string()),
624+
];
625+
gauge!(
626+
this.names.http_server_active_requests,
627+
&active_request_labels
628+
)
629+
.decrement(1);
630+
631+
if this.exclude.contains(mixed_pattern)
632+
|| this.exclude_regex.is_match(mixed_pattern)
633+
|| this.exclude_status.contains(&status)
573634
{
574635
return;
575636
}
@@ -584,34 +645,32 @@ impl ActixWebMetrics {
584645

585646
let final_pattern = if was_path_matched {
586647
final_pattern
587-
} else if let Some(mask) = &self.unmatched_patterns_mask {
648+
} else if let Some(mask) = &this.unmatched_patterns_mask {
588649
mask
589650
} else {
590651
final_pattern
591652
};
592653

593-
let mut labels = Vec::with_capacity(4 + self.names.const_labels.len());
594-
labels.push((self.names.endpoint, final_pattern.to_string()));
595-
labels.push((self.names.method, method.as_str().to_string()));
596-
labels.push((self.names.status, status.as_str().to_string()));
654+
let mut labels = Vec::with_capacity(4 + this.names.const_labels.len());
655+
labels.push((this.names.http_route, final_pattern.to_string()));
656+
labels.push((this.names.http_request_method, method.as_str().to_string()));
657+
labels.push((this.names.http_response_status, status.as_str().to_string()));
597658

598-
if self.enable_http_version_label {
599-
labels.push((
600-
self.names.version.unwrap(),
601-
Self::http_version_label(http_version).to_string(),
602-
));
603-
}
659+
labels.push((
660+
this.names.http_version,
661+
Self::http_version_label(http_version).to_string(),
662+
));
604663

605-
for (k, v) in &self.names.const_labels {
664+
for (k, v) in &this.names.const_labels {
606665
labels.push((k, v.clone()));
607666
}
608667

609668
let elapsed = clock.elapsed();
610669
let duration =
611670
(elapsed.as_secs() as f64) + f64::from(elapsed.subsec_nanos()) / 1_000_000_000_f64;
612-
histogram!(self.names.http_requests_duration_seconds, &labels).record(duration);
613-
histogram!(self.names.http_request_size_bytes, &labels).record(request_size as f64);
614-
histogram!(self.names.http_response_size_bytes, &labels).record(response_size as f64);
671+
histogram!(this.names.http_requests_duration_seconds, &labels).record(duration);
672+
histogram!(this.names.http_request_size_bytes, &labels).record(request_size as f64);
673+
histogram!(this.names.http_response_size_bytes, &labels).record(response_size as f64);
615674
}
616675

617676
fn http_version_label(version: Version) -> &'static str {
@@ -639,7 +698,7 @@ where
639698
fn new_transform(&self, service: S) -> Self::Future {
640699
ready(Ok(MetricsMiddleware {
641700
service,
642-
inner: Arc::new(self.clone()),
701+
inner: self.clone(),
643702
}))
644703
}
645704
}
@@ -653,7 +712,7 @@ pin_project! {
653712
#[pin]
654713
fut: S::Future,
655714
time: Instant,
656-
inner: Arc<ActixWebMetrics>,
715+
inner: ActixWebMetrics,
657716
_t: PhantomData<()>,
658717
}
659718
}
@@ -721,6 +780,7 @@ where
721780
.and_then(|v| v.parse::<usize>().ok())
722781
.unwrap_or(0);
723782

783+
let scheme = url_scheme(&req.uri()).to_string();
724784
let inner = this.inner.clone();
725785
Poll::Ready(Ok(res.map_body(move |head, body| StreamLog {
726786
body,
@@ -729,6 +789,7 @@ where
729789
clock: time,
730790
inner,
731791
status: head.status,
792+
scheme,
732793
mixed_pattern,
733794
fallback_pattern,
734795
method,
@@ -742,7 +803,7 @@ where
742803
#[doc(hidden)]
743804
pub struct MetricsMiddleware<S> {
744805
service: S,
745-
inner: Arc<ActixWebMetrics>,
806+
inner: ActixWebMetrics,
746807
}
747808

748809
impl<S, B> Service<ServiceRequest> for MetricsMiddleware<S>
@@ -756,6 +817,8 @@ where
756817
dev::forward_ready!(service);
757818

758819
fn call(&self, req: ServiceRequest) -> Self::Future {
820+
self.inner.pre_request_update_metrics(&req);
821+
759822
LoggerResponse {
760823
fut: self.service.call(req),
761824
time: Instant::now(),
@@ -773,8 +836,9 @@ pin_project! {
773836
response_size: usize,
774837
request_size: usize,
775838
clock: Instant,
776-
inner: Arc<ActixWebMetrics>,
839+
inner: ActixWebMetrics,
777840
status: StatusCode,
841+
scheme: String,
778842
// a route pattern with some params not-filled and some params filled in by user-defined
779843
mixed_pattern: String,
780844
fallback_pattern: String,
@@ -788,7 +852,7 @@ pin_project! {
788852
fn drop(this: Pin<&mut Self>) {
789853
// update the metrics for this request at the very end of responding
790854
this.inner
791-
.update_metrics(this.version, &this.mixed_pattern, &this.fallback_pattern, &this.method, this.status, this.clock, this.was_path_matched, this.request_size, this.response_size);
855+
.post_request_update_metrics(this.version, &this.mixed_pattern, &this.fallback_pattern, &this.method, this.status, &this.scheme, this.clock, this.was_path_matched, this.request_size, this.response_size);
792856
}
793857
}
794858
}
@@ -815,3 +879,7 @@ impl<B: MessageBody> MessageBody for StreamLog<B> {
815879
}
816880
}
817881
}
882+
883+
fn url_scheme(uri: &Uri) -> &str {
884+
uri.scheme().map(|s| s.as_str()).unwrap_or("http")
885+
}

0 commit comments

Comments
 (0)