Skip to main content

opendal_layer_metrics/
lib.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18#![doc = include_str!("../README.md")]
19#![cfg_attr(docsrs, feature(doc_cfg))]
20#![cfg_attr(docsrs, doc(auto_cfg))]
21#![deny(missing_docs)]
22use metrics::Label;
23use metrics::counter;
24use metrics::gauge;
25use metrics::histogram;
26use opendal_core::OperationContext;
27use opendal_core::raw::*;
28use opendal_layer_observe_metrics_common as observe;
29
30/// `MetricsLayer` records every operation with
31/// [metrics](https://docs.rs/metrics/).
32///
33/// # Metrics
34///
35/// The [`observe`] module documents the metrics this layer records.
36///
37/// # Notes
38///
39/// Please make sure the exporter has been pulled in regular time.
40/// Otherwise, the histogram data collected by `requests_duration_seconds`
41/// could result in OOM.
42///
43/// # Examples
44///
45/// ```no_run
46/// # use opendal_core::services;
47/// # use opendal_core::Operator;
48/// # use opendal_core::Result;
49/// # use opendal_layer_metrics::MetricsLayer;
50/// #
51/// # fn main() -> Result<()> {
52/// let _ = Operator::new(services::Memory::default())?
53///     .layer(MetricsLayer::default());
54/// # Ok(())
55/// # }
56/// ```
57///
58/// # Output
59///
60/// OpenDAL uses [`metrics`](https://docs.rs/metrics/latest/metrics/) internally.
61///
62/// To enable metrics output, please enable one of the exporters that `metrics` supports.
63///
64/// Take [`metrics_exporter_prometheus`](https://docs.rs/metrics-exporter-prometheus/latest/metrics_exporter_prometheus/) as an example:
65///
66/// ```ignore
67/// let builder = PrometheusBuilder::new()
68///     .set_buckets_for_metric(
69///         Matcher::Suffix("bytes".into()),
70///         &observe::DEFAULT_BYTES_BUCKETS,
71///     )
72///     .set_buckets_for_metric(
73///         Matcher::Suffix("duration_seconds".into()),
74///         &observe::DEFAULT_DURATION_SECONDS_BUCKETS,
75///     )
76///     // ..
77///     .expect("failed to create builder");
78/// builder.install().expect("failed to install recorder/exporter");
79/// let handle = builder.install_recorder().expect("failed to install recorder");
80/// let (recorder, exporter) = builder.build().expect("failed to build recorder/exporter");
81/// let recorder = builder.build_recorder().expect("failed to build recorder");
82/// ```
83#[derive(Clone, Debug, Default)]
84#[non_exhaustive]
85pub struct MetricsLayer {}
86
87impl MetricsLayer {
88    /// Create a new [`MetricsLayer`].
89    pub fn new() -> Self {
90        Self::default()
91    }
92}
93
94impl Layer for MetricsLayer {
95    fn apply_service(&self, inner: Servicer) -> Servicer {
96        let interceptor = MetricsInterceptor {};
97        observe::MetricsLayer::new(interceptor).apply_service(inner)
98    }
99
100    fn apply_context(&self, srv: Servicer, inner: OperationContext) -> OperationContext {
101        let interceptor = MetricsInterceptor {};
102        observe::MetricsLayer::new(interceptor).apply_context(srv, inner)
103    }
104}
105
106#[doc(hidden)]
107#[derive(Clone, Debug)]
108pub struct MetricsInterceptor;
109
110impl observe::MetricsIntercept for MetricsInterceptor {
111    fn observe(&self, labels: observe::MetricLabels, value: observe::MetricValue) {
112        let labels = OperationLabels(labels).into_labels();
113
114        match value {
115            observe::MetricValue::OperationBytes(v) => {
116                histogram!(value.name(), labels).record(v as f64)
117            }
118            observe::MetricValue::OperationBytesRate(v) => {
119                histogram!(value.name(), labels).record(v)
120            }
121            observe::MetricValue::OperationEntries(v) => {
122                histogram!(value.name(), labels).record(v as f64)
123            }
124            observe::MetricValue::OperationEntriesRate(v) => {
125                histogram!(value.name(), labels).record(v)
126            }
127            observe::MetricValue::OperationDurationSeconds(v) => {
128                histogram!(value.name(), labels).record(v)
129            }
130            observe::MetricValue::OperationErrorsTotal => {
131                counter!(value.name(), labels).increment(1)
132            }
133            observe::MetricValue::OperationExecuting(v) => {
134                gauge!(value.name(), labels).increment(v as f64)
135            }
136            observe::MetricValue::OperationTtfbSeconds(v) => {
137                histogram!(value.name(), labels).record(v)
138            }
139
140            observe::MetricValue::HttpExecuting(v) => {
141                gauge!(value.name(), labels).increment(v as f64)
142            }
143            observe::MetricValue::HttpRequestBytes(v) => {
144                histogram!(value.name(), labels).record(v as f64)
145            }
146            observe::MetricValue::HttpRequestBytesRate(v) => {
147                histogram!(value.name(), labels).record(v)
148            }
149            observe::MetricValue::HttpRequestDurationSeconds(v) => {
150                histogram!(value.name(), labels).record(v)
151            }
152            observe::MetricValue::HttpResponseBytes(v) => {
153                histogram!(value.name(), labels).record(v as f64)
154            }
155            observe::MetricValue::HttpResponseBytesRate(v) => {
156                histogram!(value.name(), labels).record(v)
157            }
158            observe::MetricValue::HttpResponseDurationSeconds(v) => {
159                histogram!(value.name(), labels).record(v)
160            }
161            observe::MetricValue::HttpConnectionErrorsTotal => {
162                counter!(value.name(), labels).increment(1)
163            }
164            observe::MetricValue::HttpStatusErrorsTotal => {
165                counter!(value.name(), labels).increment(1)
166            }
167            _ => {}
168        }
169    }
170}
171
172#[derive(Clone, Debug, PartialEq, Eq, Hash)]
173struct OperationLabels(observe::MetricLabels);
174
175impl OperationLabels {
176    fn into_labels(self) -> Vec<Label> {
177        let mut labels = Vec::with_capacity(6);
178
179        labels.extend([
180            Label::new(observe::LABEL_SCHEME, self.0.scheme),
181            Label::new(observe::LABEL_NAMESPACE, self.0.namespace),
182            Label::new(observe::LABEL_ROOT, self.0.root),
183            Label::new(observe::LABEL_OPERATION, self.0.operation),
184        ]);
185
186        if let Some(error) = self.0.error {
187            labels.push(Label::new(observe::LABEL_ERROR, error.into_static()));
188        }
189
190        if let Some(status_code) = self.0.status_code {
191            labels.push(Label::new(
192                observe::LABEL_STATUS_CODE,
193                status_code.as_str().to_owned(),
194            ));
195        }
196
197        if let Some(service_operation) = self.0.service_operation {
198            labels.push(Label::new(
199                observe::LABEL_SERVICE_OPERATION,
200                service_operation,
201            ));
202        }
203
204        labels
205    }
206}