You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by ko...@apache.org on 2025/03/27 14:50:36 UTC

(opendal) 01/03: refactor(layer/prometheus): Migrate PrometheusLayer to support IO metrics

This is an automated email from the ASF dual-hosted git repository.

koushiro pushed a commit to branch koushiro/impl-obsever-for-prometheus
in repository https://gitbox.apache.org/repos/asf/opendal.git

commit 72ac25d6810d53885b1bcaa59952e84fbe691bb6
Author: koushiro <ko...@gmail.com>
AuthorDate: Thu Mar 27 22:20:44 2025 +0800

    refactor(layer/prometheus): Migrate PrometheusLayer to support IO metrics
---
 core/src/layers/prometheus.rs | 736 +++++++++++++++++++-----------------------
 1 file changed, 328 insertions(+), 408 deletions(-)

diff --git a/core/src/layers/prometheus.rs b/core/src/layers/prometheus.rs
index fc0c349ef..37f715f2a 100644
--- a/core/src/layers/prometheus.rs
+++ b/core/src/layers/prometheus.rs
@@ -15,18 +15,20 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::sync::Arc;
 use std::time::Duration;
 
+use prometheus::core::AtomicI64;
 use prometheus::core::AtomicU64;
 use prometheus::core::GenericCounterVec;
-use prometheus::exponential_buckets;
-use prometheus::histogram_opts;
+use prometheus::core::GenericGaugeVec;
+use prometheus::register_histogram_vec_with_registry;
+use prometheus::register_int_counter_vec_with_registry;
+use prometheus::register_int_gauge_vec_with_registry;
 use prometheus::HistogramVec;
-use prometheus::Opts;
 use prometheus::Registry;
 
 use crate::layers::observe;
+use crate::layers::observe::{MetricLabels, MetricValue};
 use crate::raw::Access;
 use crate::raw::*;
 use crate::*;
@@ -129,18 +131,7 @@ impl PrometheusLayer {
     /// # }
     /// ```
     pub fn builder() -> PrometheusLayerBuilder {
-        let operation_duration_seconds_buckets = exponential_buckets(0.01, 2.0, 16).unwrap();
-        let operation_bytes_buckets = exponential_buckets(1.0, 2.0, 16).unwrap();
-        let http_request_duration_seconds_buckets = exponential_buckets(0.01, 2.0, 16).unwrap();
-        let http_request_bytes_buckets = exponential_buckets(1.0, 2.0, 16).unwrap();
-        let path_label_level = 0;
-        PrometheusLayerBuilder::new(
-            operation_duration_seconds_buckets,
-            operation_bytes_buckets,
-            http_request_duration_seconds_buckets,
-            http_request_bytes_buckets,
-            path_label_level,
-        )
+        PrometheusLayerBuilder::default()
     }
 }
 
@@ -154,218 +145,73 @@ impl<A: Access> Layer<A> for PrometheusLayer {
 
 /// [`PrometheusLayerBuilder`] is a config builder to build a [`PrometheusLayer`].
 pub struct PrometheusLayerBuilder {
-    operation_duration_seconds_buckets: Vec<f64>,
-    operation_bytes_buckets: Vec<f64>,
-    http_request_duration_seconds_buckets: Vec<f64>,
-    http_request_bytes_buckets: Vec<f64>,
-    path_label_level: usize,
+    bytes_buckets: Vec<f64>,
+    bytes_rate_buckets: Vec<f64>,
+    entries_buckets: Vec<f64>,
+    entries_rate_buckets: Vec<f64>,
+    duration_seconds_buckets: Vec<f64>,
+    ttfb_buckets: Vec<f64>,
 }
 
-impl PrometheusLayerBuilder {
-    fn new(
-        operation_duration_seconds_buckets: Vec<f64>,
-        operation_bytes_buckets: Vec<f64>,
-        http_request_duration_seconds_buckets: Vec<f64>,
-        http_request_bytes_buckets: Vec<f64>,
-        path_label_level: usize,
-    ) -> Self {
+impl Default for PrometheusLayerBuilder {
+    fn default() -> Self {
         Self {
-            operation_duration_seconds_buckets,
-            operation_bytes_buckets,
-            http_request_duration_seconds_buckets,
-            http_request_bytes_buckets,
-            path_label_level,
+            bytes_buckets: observe::DEFAULT_BYTES_BUCKETS.to_vec(),
+            bytes_rate_buckets: observe::DEFAULT_BYTES_RATE_BUCKETS.to_vec(),
+            entries_buckets: observe::DEFAULT_ENTRIES_BUCKETS.to_vec(),
+            entries_rate_buckets: observe::DEFAULT_ENTRIES_RATE_BUCKETS.to_vec(),
+            duration_seconds_buckets: observe::DEFAULT_DURATION_SECONDS_BUCKETS.to_vec(),
+            ttfb_buckets: observe::DEFAULT_TTFB_BUCKETS.to_vec(),
         }
     }
+}
 
-    /// Set buckets for `operation_duration_seconds` histogram.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// # use log::debug;
-    /// # use opendal::layers::PrometheusLayer;
-    /// # use opendal::services;
-    /// # use opendal::Operator;
-    /// # use opendal::Result;
-    /// #
-    /// # #[tokio::main]
-    /// # async fn main() -> Result<()> {
-    /// // Pick a builder and configure it.
-    /// let builder = services::Memory::default();
-    /// let registry = prometheus::default_registry();
-    ///
-    /// let buckets = prometheus::exponential_buckets(0.01, 2.0, 16).unwrap();
-    /// let op = Operator::new(builder)?
-    ///     .layer(
-    ///         PrometheusLayer::builder()
-    ///             .operation_duration_seconds_buckets(buckets)
-    ///             .register(registry)
-    ///             .expect("register metrics successfully"),
-    ///     )
-    ///     .finish();
-    /// debug!("operator: {op:?}");
-    ///
-    /// Ok(())
-    /// # }
-    /// ```
-    pub fn operation_duration_seconds_buckets(mut self, buckets: Vec<f64>) -> Self {
+impl PrometheusLayerBuilder {
+    /// Set buckets for bytes related histogram like `operation_bytes`.
+    pub fn bytes_buckets(mut self, buckets: Vec<f64>) -> Self {
         if !buckets.is_empty() {
-            self.operation_duration_seconds_buckets = buckets;
+            self.bytes_buckets = buckets;
         }
         self
     }
 
-    /// Set buckets for `operation_bytes` histogram.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// # use log::debug;
-    /// # use opendal::layers::PrometheusLayer;
-    /// # use opendal::services;
-    /// # use opendal::Operator;
-    /// # use opendal::Result;
-    /// #
-    /// # #[tokio::main]
-    /// # async fn main() -> Result<()> {
-    /// // Pick a builder and configure it.
-    /// let builder = services::Memory::default();
-    /// let registry = prometheus::default_registry();
-    ///
-    /// let buckets = prometheus::exponential_buckets(1.0, 2.0, 16).unwrap();
-    /// let op = Operator::new(builder)?
-    ///     .layer(
-    ///         PrometheusLayer::builder()
-    ///             .operation_bytes_buckets(buckets)
-    ///             .register(registry)
-    ///             .expect("register metrics successfully"),
-    ///     )
-    ///     .finish();
-    /// debug!("operator: {op:?}");
-    ///
-    /// Ok(())
-    /// # }
-    /// ```
-    pub fn operation_bytes_buckets(mut self, buckets: Vec<f64>) -> Self {
+    /// Set buckets for bytes rate related histogram like `operation_bytes_rate`.
+    pub fn bytes_rate_buckets(mut self, buckets: Vec<f64>) -> Self {
         if !buckets.is_empty() {
-            self.operation_bytes_buckets = buckets;
+            self.bytes_rate_buckets = buckets;
         }
         self
     }
 
-    /// Set buckets for `http_request_duration_seconds` histogram.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// # use log::debug;
-    /// # use opendal::layers::PrometheusLayer;
-    /// # use opendal::services;
-    /// # use opendal::Operator;
-    /// # use opendal::Result;
-    /// #
-    /// # #[tokio::main]
-    /// # async fn main() -> Result<()> {
-    /// // Pick a builder and configure it.
-    /// let builder = services::Memory::default();
-    /// let registry = prometheus::default_registry();
-    ///
-    /// let buckets = prometheus::exponential_buckets(0.01, 2.0, 16).unwrap();
-    /// let op = Operator::new(builder)?
-    ///     .layer(
-    ///         PrometheusLayer::builder()
-    ///             .http_request_duration_seconds_buckets(buckets)
-    ///             .register(registry)
-    ///             .expect("register metrics successfully"),
-    ///     )
-    ///     .finish();
-    /// debug!("operator: {op:?}");
-    ///
-    /// Ok(())
-    /// # }
-    /// ```
-    pub fn http_request_duration_seconds_buckets(mut self, buckets: Vec<f64>) -> Self {
+    /// Set buckets for entries related histogram like `operation_entries`.
+    pub fn entries_buckets(mut self, buckets: Vec<f64>) -> Self {
         if !buckets.is_empty() {
-            self.http_request_duration_seconds_buckets = buckets;
+            self.entries_buckets = buckets;
         }
         self
     }
 
-    /// Set buckets for `http_request_bytes` histogram.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// # use log::debug;
-    /// # use opendal::layers::PrometheusLayer;
-    /// # use opendal::services;
-    /// # use opendal::Operator;
-    /// # use opendal::Result;
-    /// #
-    /// # #[tokio::main]
-    /// # async fn main() -> Result<()> {
-    /// // Pick a builder and configure it.
-    /// let builder = services::Memory::default();
-    /// let registry = prometheus::default_registry();
-    ///
-    /// let buckets = prometheus::exponential_buckets(1.0, 2.0, 16).unwrap();
-    /// let op = Operator::new(builder)?
-    ///     .layer(
-    ///         PrometheusLayer::builder()
-    ///             .http_request_bytes_buckets(buckets)
-    ///             .register(registry)
-    ///             .expect("register metrics successfully"),
-    ///     )
-    ///     .finish();
-    /// debug!("operator: {op:?}");
-    ///
-    /// Ok(())
-    /// # }
-    /// ```
-    pub fn http_request_bytes_buckets(mut self, buckets: Vec<f64>) -> Self {
+    /// Set buckets for entries rate related histogram like `operation_entries_rate`.
+    pub fn entries_rate_buckets(mut self, buckets: Vec<f64>) -> Self {
         if !buckets.is_empty() {
-            self.http_request_bytes_buckets = buckets;
+            self.entries_rate_buckets = buckets;
         }
         self
     }
 
-    /// Set the level of path label.
-    ///
-    /// - level = 0: we will ignore the path label.
-    /// - level > 0: the path label will be the path split by "/" and get the last n level,
-    ///   if n=1 and input path is "abc/def/ghi", and then we will get "abc/" as the path label.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// # use log::debug;
-    /// # use opendal::layers::PrometheusLayer;
-    /// # use opendal::services;
-    /// # use opendal::Operator;
-    /// # use opendal::Result;
-    /// #
-    /// # #[tokio::main]
-    /// # async fn main() -> Result<()> {
-    /// // Pick a builder and configure it.
-    /// let builder = services::Memory::default();
-    /// let registry = prometheus::default_registry();
-    ///
-    /// let op = Operator::new(builder)?
-    ///     .layer(
-    ///         PrometheusLayer::builder()
-    ///             .path_label(1)
-    ///             .register(registry)
-    ///             .expect("register metrics successfully"),
-    ///     )
-    ///     .finish();
-    /// debug!("operator: {op:?}");
-    ///
-    /// Ok(())
-    /// # }
-    /// ```
-    pub fn path_label(mut self, level: usize) -> Self {
-        self.path_label_level = level;
+    /// Set buckets for duration seconds related histogram like `operation_duration_seconds`.
+    pub fn duration_seconds_buckets(mut self, buckets: Vec<f64>) -> Self {
+        if !buckets.is_empty() {
+            self.duration_seconds_buckets = buckets;
+        }
+        self
+    }
+
+    /// Set buckets for ttfb related histogram like `operation_ttfb_seconds`.
+    pub fn ttfb_buckets(mut self, buckets: Vec<f64>) -> Self {
+        if !buckets.is_empty() {
+            self.ttfb_buckets = buckets;
+        }
         self
     }
 
@@ -399,80 +245,187 @@ impl PrometheusLayerBuilder {
     /// # }
     /// ```
     pub fn register(self, registry: &Registry) -> Result<PrometheusLayer> {
-        let labels = OperationLabels::names(false, self.path_label_level > 0);
-        let operation_duration_seconds = HistogramVec::new(
-            histogram_opts!(
-                observe::METRIC_OPERATION_DURATION_SECONDS.name(),
-                observe::METRIC_OPERATION_DURATION_SECONDS.help(),
-                self.operation_duration_seconds_buckets
-            ),
-            &labels,
-        )
-        .map_err(parse_prometheus_error)?;
-        let operation_bytes = HistogramVec::new(
-            histogram_opts!(
-                observe::METRIC_OPERATION_BYTES.name(),
-                observe::METRIC_OPERATION_BYTES.help(),
-                self.operation_bytes_buckets
-            ),
-            &labels,
-        )
-        .map_err(parse_prometheus_error)?;
-
-        let labels = OperationLabels::names(true, self.path_label_level > 0);
-        let operation_errors_total = GenericCounterVec::new(
-            Opts::new(
-                observe::METRIC_OPERATION_ERRORS_TOTAL.name(),
-                observe::METRIC_OPERATION_ERRORS_TOTAL.help(),
-            ),
-            &labels,
-        )
-        .map_err(parse_prometheus_error)?;
-
-        let labels = OperationLabels::names(false, false);
-        let http_request_duration_seconds = HistogramVec::new(
-            histogram_opts!(
-                observe::METRIC_HTTP_REQUEST_DURATION_SECONDS.name(),
-                observe::METRIC_HTTP_REQUEST_DURATION_SECONDS.help(),
-                self.http_request_duration_seconds_buckets
-            ),
-            &labels,
-        )
-        .map_err(parse_prometheus_error)?;
-        let http_request_bytes = HistogramVec::new(
-            histogram_opts!(
-                observe::METRIC_HTTP_REQUEST_BYTES.name(),
-                observe::METRIC_HTTP_REQUEST_BYTES.help(),
-                self.http_request_bytes_buckets
-            ),
-            &labels,
-        )
-        .map_err(parse_prometheus_error)?;
-
-        registry
-            .register(Box::new(operation_duration_seconds.clone()))
-            .map_err(parse_prometheus_error)?;
-        registry
-            .register(Box::new(operation_bytes.clone()))
-            .map_err(parse_prometheus_error)?;
-        registry
-            .register(Box::new(operation_errors_total.clone()))
-            .map_err(parse_prometheus_error)?;
-        registry
-            .register(Box::new(http_request_duration_seconds.clone()))
-            .map_err(parse_prometheus_error)?;
-        registry
-            .register(Box::new(http_request_bytes.clone()))
-            .map_err(parse_prometheus_error)?;
+        let labels = OperationLabels::names();
+
+        let operation_bytes = {
+            let metric = observe::MetricValue::OperationBytes(0);
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.bytes_buckets.clone(),
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let operation_bytes_rate = {
+            let metric = observe::MetricValue::OperationBytesRate(0.0);
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.bytes_rate_buckets.clone(),
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let operation_entries = {
+            let metric = observe::MetricValue::OperationEntries(0);
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.entries_buckets,
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let operation_entries_rate = {
+            let metric = observe::MetricValue::OperationEntriesRate(0.0);
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.entries_rate_buckets,
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let operation_duration_seconds = {
+            let metric = observe::MetricValue::OperationDurationSeconds(Duration::default());
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.duration_seconds_buckets.clone(),
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let operation_errors_total = {
+            let metric = observe::MetricValue::OperationErrorsTotal;
+            register_int_counter_vec_with_registry!(metric.name(), metric.help(), &labels, registry)
+                .map_err(parse_prometheus_error)?
+        };
+        let operation_executing = {
+            let metric = observe::MetricValue::OperationExecuting(0);
+            register_int_gauge_vec_with_registry!(metric.name(), metric.help(), &labels, registry)
+                .map_err(parse_prometheus_error)?
+        };
+        let operation_ttfb_seconds = {
+            let metric = observe::MetricValue::OperationTtfbSeconds(Duration::default());
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.ttfb_buckets.clone(),
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+
+        let http_executing = {
+            let metric = observe::MetricValue::HttpExecuting(0);
+            register_int_gauge_vec_with_registry!(metric.name(), metric.help(), &labels, registry)
+                .map_err(parse_prometheus_error)?
+        };
+        let http_request_bytes = {
+            let metric = observe::MetricValue::HttpRequestBytes(0);
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.bytes_buckets.clone(),
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let http_request_bytes_rate = {
+            let metric = observe::MetricValue::HttpRequestBytesRate(0.0);
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.bytes_rate_buckets.clone(),
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let http_request_duration_seconds = {
+            let metric = observe::MetricValue::HttpRequestDurationSeconds(Duration::default());
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.duration_seconds_buckets.clone(),
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let http_response_bytes = {
+            let metric = observe::MetricValue::HttpResponseBytes(0);
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.bytes_buckets,
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let http_response_bytes_rate = {
+            let metric = observe::MetricValue::HttpResponseBytesRate(0.0);
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.bytes_rate_buckets,
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let http_response_duration_seconds = {
+            let metric = observe::MetricValue::HttpResponseDurationSeconds(Duration::default());
+            register_histogram_vec_with_registry!(
+                metric.name(),
+                metric.help(),
+                &labels,
+                self.duration_seconds_buckets,
+                registry
+            )
+            .map_err(parse_prometheus_error)?
+        };
+        let http_connection_errors_total = {
+            let metric = observe::MetricValue::HttpConnectionErrorsTotal;
+            register_int_counter_vec_with_registry!(metric.name(), metric.help(), &labels, registry)
+                .map_err(parse_prometheus_error)?
+        };
+        let http_status_errors_total = {
+            let metric = observe::MetricValue::HttpStatusErrorsTotal;
+            register_int_counter_vec_with_registry!(metric.name(), metric.help(), &labels, registry)
+                .map_err(parse_prometheus_error)?
+        };
 
         Ok(PrometheusLayer {
             interceptor: PrometheusInterceptor {
-                operation_duration_seconds,
                 operation_bytes,
+                operation_bytes_rate,
+                operation_entries,
+                operation_entries_rate,
+                operation_duration_seconds,
                 operation_errors_total,
-                http_request_duration_seconds,
+                operation_executing,
+                operation_ttfb_seconds,
+
+                http_executing,
                 http_request_bytes,
-                path_label_level: self.path_label_level,
+                http_request_bytes_rate,
+                http_request_duration_seconds,
+                http_response_bytes,
+                http_response_bytes_rate,
+                http_response_duration_seconds,
+                http_connection_errors_total,
+                http_status_errors_total,
             },
         })
     }
@@ -518,167 +471,134 @@ fn parse_prometheus_error(err: prometheus::Error) -> Error {
 
 #[derive(Clone, Debug)]
 pub struct PrometheusInterceptor {
-    operation_duration_seconds: HistogramVec,
     operation_bytes: HistogramVec,
+    operation_bytes_rate: HistogramVec,
+    operation_entries: HistogramVec,
+    operation_entries_rate: HistogramVec,
+    operation_duration_seconds: HistogramVec,
     operation_errors_total: GenericCounterVec<AtomicU64>,
-    http_request_duration_seconds: HistogramVec,
+    operation_executing: GenericGaugeVec<AtomicI64>,
+    operation_ttfb_seconds: HistogramVec,
+
+    http_executing: GenericGaugeVec<AtomicI64>,
     http_request_bytes: HistogramVec,
-    path_label_level: usize,
+    http_request_bytes_rate: HistogramVec,
+    http_request_duration_seconds: HistogramVec,
+    http_response_bytes: HistogramVec,
+    http_response_bytes_rate: HistogramVec,
+    http_response_duration_seconds: HistogramVec,
+    http_connection_errors_total: GenericCounterVec<AtomicU64>,
+    http_status_errors_total: GenericCounterVec<AtomicU64>,
 }
 
 impl observe::MetricsIntercept for PrometheusInterceptor {
-    fn observe_operation_duration_seconds(
-        &self,
-        info: Arc<AccessorInfo>,
-        path: &str,
-        op: Operation,
-        duration: Duration,
-    ) {
-        let labels = OperationLabels {
-            scheme: info.scheme(),
-            name: info.name(),
-            root: info.root(),
-            operation: op,
-            error: None,
-            path: observe::path_label_value(path, self.path_label_level),
-        };
-
-        self.operation_duration_seconds
-            .with_label_values(&labels.values())
-            .observe(duration.as_secs_f64())
-    }
-
-    fn observe_operation_bytes(
-        &self,
-        info: Arc<AccessorInfo>,
-        path: &str,
-        op: Operation,
-        bytes: usize,
-    ) {
-        let labels = OperationLabels {
-            scheme: info.scheme(),
-            name: info.name(),
-            root: info.root(),
-            operation: op,
-            error: None,
-            path: observe::path_label_value(path, self.path_label_level),
-        };
-
-        self.operation_bytes
-            .with_label_values(&labels.values())
-            .observe(bytes as f64);
-    }
-
-    fn observe_operation_errors_total(
-        &self,
-        info: Arc<AccessorInfo>,
-        path: &str,
-        op: Operation,
-        error: ErrorKind,
-    ) {
-        let labels = OperationLabels {
-            scheme: info.scheme(),
-            name: info.name(),
-            root: info.root(),
-            operation: op,
-            error: Some(error),
-            path: observe::path_label_value(path, self.path_label_level),
-        };
-
-        self.operation_errors_total
-            .with_label_values(&labels.values())
-            .inc();
-    }
-
-    fn observe_http_request_duration_seconds(
-        &self,
-        info: Arc<AccessorInfo>,
-        op: Operation,
-        duration: Duration,
-    ) {
-        let labels = OperationLabels {
-            scheme: info.scheme(),
-            name: info.name(),
-            root: info.root(),
-            operation: op,
-            error: None,
-            path: None,
-        };
-
-        self.http_request_duration_seconds
-            .with_label_values(&labels.values())
-            .observe(duration.as_secs_f64())
-    }
-
-    fn observe_http_request_bytes(&self, info: Arc<AccessorInfo>, op: Operation, bytes: usize) {
-        let labels = OperationLabels {
-            scheme: info.scheme(),
-            name: info.name(),
-            root: info.root(),
-            operation: op,
-            error: None,
-            path: None,
-        };
-
-        self.http_request_bytes
-            .with_label_values(&labels.values())
-            .observe(bytes as f64);
+    fn observe(&self, labels: MetricLabels, value: MetricValue) {
+        let labels = OperationLabels(labels);
+        match value {
+            MetricValue::OperationBytes(v) => self
+                .operation_bytes
+                .with_label_values(&labels.values())
+                .observe(v as f64),
+            MetricValue::OperationBytesRate(v) => self
+                .operation_bytes_rate
+                .with_label_values(&labels.values())
+                .observe(v),
+            MetricValue::OperationEntries(v) => self
+                .operation_entries
+                .with_label_values(&labels.values())
+                .observe(v as f64),
+            MetricValue::OperationEntriesRate(v) => self
+                .operation_entries_rate
+                .with_label_values(&labels.values())
+                .observe(v),
+            MetricValue::OperationDurationSeconds(v) => self
+                .operation_duration_seconds
+                .with_label_values(&labels.values())
+                .observe(v.as_secs_f64()),
+            MetricValue::OperationErrorsTotal => self
+                .operation_errors_total
+                .with_label_values(&labels.values())
+                .inc(),
+            MetricValue::OperationExecuting(v) => self
+                .operation_executing
+                .with_label_values(&labels.values())
+                .add(v as i64),
+            MetricValue::OperationTtfbSeconds(v) => self
+                .operation_ttfb_seconds
+                .with_label_values(&labels.values())
+                .observe(v.as_secs_f64()),
+
+            MetricValue::HttpExecuting(v) => self
+                .http_executing
+                .with_label_values(&labels.values())
+                .add(v as i64),
+            MetricValue::HttpRequestBytes(v) => self
+                .http_request_bytes
+                .with_label_values(&labels.values())
+                .observe(v as f64),
+            MetricValue::HttpRequestBytesRate(v) => self
+                .http_request_bytes_rate
+                .with_label_values(&labels.values())
+                .observe(v),
+            MetricValue::HttpRequestDurationSeconds(v) => self
+                .http_request_duration_seconds
+                .with_label_values(&labels.values())
+                .observe(v.as_secs_f64()),
+            MetricValue::HttpResponseBytes(v) => self
+                .http_response_bytes
+                .with_label_values(&labels.values())
+                .observe(v as f64),
+            MetricValue::HttpResponseBytesRate(v) => self
+                .http_response_bytes_rate
+                .with_label_values(&labels.values())
+                .observe(v),
+            MetricValue::HttpResponseDurationSeconds(v) => self
+                .http_response_duration_seconds
+                .with_label_values(&labels.values())
+                .observe(v.as_secs_f64()),
+            MetricValue::HttpConnectionErrorsTotal => self
+                .http_connection_errors_total
+                .with_label_values(&labels.values())
+                .inc(),
+            MetricValue::HttpStatusErrorsTotal => self
+                .http_status_errors_total
+                .with_label_values(&labels.values())
+                .inc(),
+        }
     }
 }
 
-struct OperationLabels<'a> {
-    scheme: Scheme,
-    name: Arc<str>,
-    root: Arc<str>,
-    operation: Operation,
-    path: Option<&'a str>,
-    error: Option<ErrorKind>,
-}
-
-impl<'a> OperationLabels<'a> {
-    fn names(error: bool, path: bool) -> Vec<&'a str> {
-        let mut names = Vec::with_capacity(6);
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+struct OperationLabels(observe::MetricLabels);
 
-        names.extend([
+impl OperationLabels {
+    fn names() -> Vec<&'static str> {
+        vec![
             observe::LABEL_SCHEME,
             observe::LABEL_NAMESPACE,
             observe::LABEL_ROOT,
             observe::LABEL_OPERATION,
-        ]);
-
-        if path {
-            names.push(observe::LABEL_PATH);
-        }
-
-        if error {
-            names.push(observe::LABEL_ERROR);
-        }
-
-        names
+            observe::LABEL_ERROR,
+            observe::LABEL_STATUS_CODE,
+        ]
     }
 
-    /// labels:
-    ///
-    /// 1. `["scheme", "namespace", "root", "operation"]`
-    /// 2. `["scheme", "namespace", "root", "operation", "path"]`
-    /// 3. `["scheme", "namespace", "root", "operation", "error"]`
-    /// 4. `["scheme", "namespace", "root", "operation", "path", "error"]`
-    fn values(&'a self) -> Vec<&'a str> {
+    fn values<'a>(&'a self) -> Vec<&'a str> {
         let mut labels = Vec::with_capacity(6);
 
         labels.extend([
-            self.scheme.into_static(),
-            self.name.as_ref(),
-            self.root.as_ref(),
-            self.operation.into_static(),
+            self.0.scheme.into_static(),
+            self.0.namespace.as_ref(),
+            self.0.root.as_ref(),
+            self.0.operation,
         ]);
 
-        if let Some(path) = self.path {
-            labels.push(path);
+        if let Some(error) = self.0.error {
+            labels.push(error.into_static());
         }
 
-        if let Some(error) = self.error {
-            labels.push(error.into_static());
+        if let Some(status_code) = &self.0.status_code {
+            labels.push(status_code.as_str());
         }
 
         labels