Skip to main content

opendal_layer_observe_metrics_common/
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)]
22
23//! # OpenDAL Metrics Reference
24//!
25//! This document describes all metrics exposed by OpenDAL.
26//!
27//! ## Operation Metrics
28//!
29//! These metrics track operations at the storage abstraction level.
30//!
31//! | Metric Name                      | Type      | Description                                                                                | Labels                                          |
32//! |----------------------------------|-----------|--------------------------------------------------------------------------------------------|-------------------------------------------------|
33//! | operation_bytes                  | Histogram | Current operation size in bytes, represents the size of data being processed               | scheme, namespace, root, operation, path        |
34//! | operation_bytes_rate             | Histogram | Histogram of data processing rates in bytes per second within individual operations        | scheme, namespace, root, operation, path        |
35//! | operation_entries                | Histogram | Current operation size in entries, represents the entries being processed                  | scheme, namespace, root, operation, path        |
36//! | operation_entries_rate           | Histogram | Histogram of entries processing rates in entries per second within individual operations   | scheme, namespace, root, operation, path        |
37//! | operation_duration_seconds       | Histogram | Duration of operations in seconds, measured from start to completion                       | scheme, namespace, root, operation, path        |
38//! | operation_errors_total           | Counter   | Total number of failed operations                                                          | scheme, namespace, root, operation, path, error |
39//! | operation_executing              | Gauge     | Number of operations currently being executed                                              | scheme, namespace, root, operation              |
40//! | operation_ttfb_seconds           | Histogram | Time to first byte in seconds for operations                                               | scheme, namespace, root, operation, path        |
41//!
42//! ## HTTP Metrics
43//!
44//! These metrics track the underlying HTTP requests made by OpenDAL services that use HTTP.
45//!
46//! | Metric Name                      | Type      | Description                                                                                | Labels                                          |
47//! |----------------------------------|-----------|--------------------------------------------------------------------------------------------|-------------------------------------------------|
48//! | http_connection_errors_total     | Counter   | Total number of HTTP requests that failed before receiving a response                      | scheme, namespace, root, operation, error       |
49//! | http_status_errors_total         | Counter   | Total number of HTTP requests that received error status codes (non-2xx responses)         | scheme, namespace, root, operation, status      |
50//! | http_executing                   | Gauge     | Number of HTTP requests currently in flight from this client                               | scheme, namespace, root                         |
51//! | http_request_bytes               | Histogram | Histogram of HTTP request body sizes in bytes                                              | scheme, namespace, root, operation              |
52//! | http_request_bytes_rate          | Histogram | Histogram of HTTP request bytes per second rates                                           | scheme, namespace, root, operation              |
53//! | http_request_duration_seconds    | Histogram | Histogram of time spent sending HTTP requests, from first byte sent to first byte received | scheme, namespace, root, operation              |
54//! | http_response_bytes              | Histogram | Histogram of HTTP response body sizes in bytes                                             | scheme, namespace, root, operation              |
55//! | http_response_bytes_rate         | Histogram | Histogram of HTTP response bytes per second rates                                          | scheme, namespace, root, operation              |
56//! | http_response_duration_seconds   | Histogram | Histogram of time spent receiving HTTP responses, from first byte to last byte received    | scheme, namespace, root, operation              |
57//!
58//! ## Label Descriptions
59//!
60//! | Label     | Description                                                   | Example Values                         |
61//! |-----------|---------------------------------------------------------------|----------------------------------------|
62//! | scheme    | The storage service scheme                                    | s3, gcs, azblob, fs, memory            |
63//! | namespace | The storage service namespace (bucket, container, etc.)       | my-bucket, my-container                |
64//! | root      | The root path within the namespace                            | /data, /backup                         |
65//! | operation | The operation being performed                                 | read, write, stat, list, delete        |
66//! | path      | The path of the object being operated on                      | /path/to/file.txt                      |
67//! | error     | The error type or message for error metrics                   | not_found, permission_denied           |
68//! | status    | The HTTP status code for HTTP error metrics                   | 404, 403, 500                          |
69//!
70//! ## Metric Types
71//!
72//! * **Histogram**: Distribution of values with configurable buckets, includes count, sum and quantiles
73//! * **Counter**: Cumulative metric that only increases over time (resets on restart)
74//! * **Gauge**: Point-in-time metric that can increase and decrease
75
76use std::fmt::Debug;
77use std::fmt::Formatter;
78use std::pin::Pin;
79use std::sync::Arc;
80use std::task::Context;
81use std::task::Poll;
82use std::task::ready;
83
84use futures::Stream;
85use futures::StreamExt;
86use http::StatusCode;
87use opendal_core::raw::*;
88use opendal_core::*;
89
90const KIB: f64 = 1024.0;
91const MIB: f64 = 1024.0 * KIB;
92const GIB: f64 = 1024.0 * MIB;
93
94/// Buckets for data size metrics like OperationBytes
95/// Covers typical file and object sizes from small files to large objects
96pub const DEFAULT_BYTES_BUCKETS: &[f64] = &[
97    4.0 * KIB,   // Small files
98    64.0 * KIB,  // File system block size
99    256.0 * KIB, //
100    1.0 * MIB,   // Common size threshold for many systems
101    4.0 * MIB,   // Best size for most http based systems
102    16.0 * MIB,  //
103    64.0 * MIB,  // Widely used threshold for multipart uploads
104    256.0 * MIB, //
105    1.0 * GIB,   // Considered large for many systems
106    5.0 * GIB,   // Maximum size in single upload for many cloud storage services
107];
108
109/// Buckets for data transfer rate metrics like OperationBytesRate
110///
111/// Covers various network speeds from slow connections to high-speed transfers
112///
113/// Note: this is for single operation rate, not for total bandwidth.
114pub const DEFAULT_BYTES_RATE_BUCKETS: &[f64] = &[
115    // Low-speed network range (mobile/weak connections)
116    8.0 * KIB,   // ~64Kbps - 2G networks
117    32.0 * KIB,  // ~256Kbps - 3G networks
118    128.0 * KIB, // ~1Mbps - Basic broadband
119    // Standard broadband range
120    1.0 * MIB,  // ~8Mbps - Entry-level broadband
121    8.0 * MIB,  // ~64Mbps - Fast broadband
122    32.0 * MIB, // ~256Mbps - Gigabit broadband
123    // High-performance network range
124    128.0 * MIB, // ~1Gbps - Standard datacenter
125    512.0 * MIB, // ~4Gbps - Fast datacenter
126    2.0 * GIB,   // ~16Gbps - High-end interconnects
127    // Ultra-high-speed range
128    8.0 * GIB,  // ~64Gbps - InfiniBand/RDMA
129    32.0 * GIB, // ~256Gbps - Top-tier datacenters
130];
131
132/// Buckets for batch operation entry counts (OperationEntriesCount)
133/// Covers scenarios from single entry operations to large batch operations
134pub const DEFAULT_ENTRIES_BUCKETS: &[f64] = &[
135    1.0,     // Single item operations
136    5.0,     // Very small batches
137    10.0,    // Small batches
138    50.0,    // Medium batches
139    100.0,   // Standard batch size
140    500.0,   // Large batches
141    1000.0,  // Very large batches, API limits for some services
142    5000.0,  // Huge batches, multi-page operations
143    10000.0, // Extremely large operations, multi-request batches
144];
145
146/// Buckets for batch operation processing rates (OperationEntriesRate)
147/// Measures how many entries can be processed per second
148pub const DEFAULT_ENTRIES_RATE_BUCKETS: &[f64] = &[
149    1.0,     // Slowest processing, heavy operations per entry
150    10.0,    // Slow processing, complex operations
151    50.0,    // Moderate processing speed
152    100.0,   // Good processing speed, efficient operations
153    500.0,   // Fast processing, optimized operations
154    1000.0,  // Very fast processing, simple operations
155    5000.0,  // Extremely fast processing, bulk operations
156    10000.0, // Maximum speed, listing operations, local systems
157];
158
159/// Buckets for operation duration metrics like OperationDurationSeconds
160/// Covers timeframes from fast metadata operations to long-running transfers
161pub const DEFAULT_DURATION_SECONDS_BUCKETS: &[f64] = &[
162    0.001, // 1ms - Fastest operations, cached responses
163    0.01,  // 10ms - Fast metadata operations, local operations
164    0.05,  // 50ms - Quick operations, nearby cloud resources
165    0.1,   // 100ms - Standard API response times, typical cloud latency
166    0.25,  // 250ms - Medium operations, small data transfers
167    0.5,   // 500ms - Medium-long operations, larger metadata operations
168    1.0,   // 1s - Long operations, small file transfers
169    2.5,   // 2.5s - Extended operations, medium file transfers
170    5.0,   // 5s - Long-running operations, large transfers
171    10.0,  // 10s - Very long operations, very large transfers
172    30.0,  // 30s - Extended operations, complex batch processes
173    60.0,  // 1min - Near timeout operations, extremely large transfers
174];
175
176/// Buckets for time to first byte metrics like OperationTtfbSeconds
177/// Focuses on initial response times, which are typically shorter than full operations
178pub const DEFAULT_TTFB_BUCKETS: &[f64] = &[
179    0.001, // 1ms - Cached or local resources
180    0.01,  // 10ms - Very fast responses, same region
181    0.025, // 25ms - Fast responses, optimized configurations
182    0.05,  // 50ms - Good response times, standard configurations
183    0.1,   // 100ms - Average response times for cloud storage
184    0.2,   // 200ms - Slower responses, cross-region or throttled
185    0.4,   // 400ms - Poor response times, network congestion
186    0.8,   // 800ms - Very slow responses, potential issues
187    1.6,   // 1.6s - Problematic responses, retry territory
188    3.2,   // 3.2s - Critical latency issues, close to timeouts
189];
190
191/// The metric label for the scheme like s3, fs, cos.
192pub static LABEL_SCHEME: &str = "scheme";
193/// The metric label for the namespace like bucket name in s3.
194pub static LABEL_NAMESPACE: &str = "namespace";
195/// The metric label for the root path.
196pub static LABEL_ROOT: &str = "root";
197/// The metric label for the operation like read, write, list.
198pub static LABEL_OPERATION: &str = "operation";
199/// The metric label for the error.
200pub static LABEL_ERROR: &str = "error";
201/// The metric label for the http code.
202pub static LABEL_STATUS_CODE: &str = "status_code";
203/// The metric label for the service-specific operation (e.g., "GetObject", "UploadPart").
204pub static LABEL_SERVICE_OPERATION: &str = "service_operation";
205
206/// MetricLabels are the labels for the metrics.
207///
208/// `scheme`, `namespace`, and `root` always come from the final service stack's
209/// [`ServiceInfo`]. HTTP requests only provide request-level labels such as
210/// [`Operation`] and [`ServiceOperation`] through extensions.
211#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
212pub struct MetricLabels {
213    /// The storage scheme identifier (e.g., "s3", "gcs", "azblob", "fs").
214    /// Used to differentiate between different storage backends.
215    pub scheme: &'static str,
216    /// The storage namespace (e.g., bucket name, container name).
217    /// Identifies the specific storage container being accessed.
218    pub namespace: Arc<str>,
219    /// The root path within the namespace that was configured.
220    /// Used to track operations within a specific path prefix.
221    pub root: Arc<str>,
222    /// The operation being performed (e.g., "read", "write", "list").
223    /// Identifies which API operation generated this metric.
224    pub operation: &'static str,
225    /// The specific error kind that occurred during an operation.
226    /// Only populated for `OperationErrorsTotal` metric.
227    /// Used to track frequency of specific error types.
228    pub error: Option<ErrorKind>,
229    /// The HTTP status code received in an error response.
230    /// Only populated for `HttpStatusErrorsTotal` metric.
231    /// Used to track frequency of specific HTTP error status codes.
232    pub status_code: Option<StatusCode>,
233    /// The service-specific operation name as an optional supplement for operation name.
234    pub service_operation: Option<&'static str>,
235}
236
237impl MetricLabels {
238    /// Create a new set of MetricLabels.
239    fn new(info: ServiceInfo, op: &'static str) -> Self {
240        MetricLabels {
241            scheme: info.scheme(),
242            namespace: info.name(),
243            root: info.root(),
244            operation: op,
245            ..MetricLabels::default()
246        }
247    }
248
249    /// Add error to the metric labels.
250    fn with_error(mut self, err: ErrorKind) -> Self {
251        self.error = Some(err);
252        self
253    }
254
255    /// Add status code to the metric labels.
256    fn with_status_code(mut self, code: StatusCode) -> Self {
257        self.status_code = Some(code);
258        self
259    }
260}
261
262/// MetricValue is the value the opendal sends to the metrics impls.
263///
264/// Metrics impls can be `prometheus_client`, `metrics` etc.
265///
266/// Every metrics impls SHOULD implement observe over the MetricValue to make
267/// sure they provide the consistent metrics for users.
268#[non_exhaustive]
269#[derive(Clone, Copy, Debug)]
270pub enum MetricValue {
271    /// Record the size of data processed in bytes.
272    /// Metrics impl: Update a Histogram with the given byte count.
273    OperationBytes(u64),
274    /// Record the rate of data processing in bytes/second.
275    /// Metrics impl: Update a Histogram with the calculated rate value.
276    OperationBytesRate(f64),
277    /// Record the number of entries (files, objects, keys) processed.
278    /// Metrics impl: Update a Histogram with the entry count.
279    OperationEntries(u64),
280    /// Record the rate of entries processing in entries/second.
281    /// Metrics impl: Update a Histogram with the calculated rate value.
282    OperationEntriesRate(f64),
283    /// Record the total duration of an operation.
284    /// Metrics impl: Update a Histogram with the duration converted to seconds (as f64).
285    OperationDurationSeconds(Duration),
286    /// Increment the counter for operation errors.
287    /// Metrics impl: Increment a Counter by 1.
288    OperationErrorsTotal,
289    /// Update the current number of executing operations.
290    /// Metrics impl: Add the value (positive or negative) to a Gauge.
291    OperationExecuting(isize),
292    /// Record the time to first byte duration.
293    /// Metrics impl: Update a Histogram with the duration converted to seconds (as f64).
294    OperationTtfbSeconds(Duration),
295    /// Update the current number of executing HTTP requests.
296    /// Metrics impl: Add the value (positive or negative) to a Gauge.
297    HttpExecuting(isize),
298    /// Record the size of HTTP request body in bytes.
299    /// Metrics impl: Update a Histogram with the given byte count.
300    HttpRequestBytes(u64),
301    /// Record the rate of HTTP request data in bytes/second.
302    /// Metrics impl: Update a Histogram with the calculated rate value.
303    HttpRequestBytesRate(f64),
304    /// Record the duration of sending an HTTP request (until first byte received).
305    /// Metrics impl: Update a Histogram with the duration converted to seconds (as f64).
306    HttpRequestDurationSeconds(Duration),
307    /// Record the size of HTTP response body in bytes.
308    /// Metrics impl: Update a Histogram with the given byte count.
309    HttpResponseBytes(u64),
310    /// Record the rate of HTTP response data in bytes/second.
311    /// Metrics impl: Update a Histogram with the calculated rate value.
312    HttpResponseBytesRate(f64),
313    /// Record the duration of receiving an HTTP response (from first byte to last).
314    /// Metrics impl: Update a Histogram with the duration converted to seconds (as f64).
315    HttpResponseDurationSeconds(Duration),
316    /// Increment the counter for HTTP connection errors.
317    /// Metrics impl: Increment a Counter by 1.
318    HttpConnectionErrorsTotal,
319    /// Increment the counter for HTTP status errors (non-2xx responses).
320    /// Metrics impl: Increment a Counter by 1.
321    HttpStatusErrorsTotal,
322}
323
324impl MetricValue {
325    /// Returns the full metric name for this metric value.
326    pub fn name(&self) -> &'static str {
327        match self {
328            MetricValue::OperationBytes(_) => "opendal_operation_bytes",
329            MetricValue::OperationBytesRate(_) => "opendal_operation_bytes_rate",
330            MetricValue::OperationEntries(_) => "opendal_operation_entries",
331            MetricValue::OperationEntriesRate(_) => "opendal_operation_entries_rate",
332            MetricValue::OperationDurationSeconds(_) => "opendal_operation_duration_seconds",
333            MetricValue::OperationErrorsTotal => "opendal_operation_errors_total",
334            MetricValue::OperationExecuting(_) => "opendal_operation_executing",
335            MetricValue::OperationTtfbSeconds(_) => "opendal_operation_ttfb_seconds",
336
337            MetricValue::HttpConnectionErrorsTotal => "opendal_http_connection_errors_total",
338            MetricValue::HttpStatusErrorsTotal => "opendal_http_status_errors_total",
339            MetricValue::HttpExecuting(_) => "opendal_http_executing",
340            MetricValue::HttpRequestBytes(_) => "opendal_http_request_bytes",
341            MetricValue::HttpRequestBytesRate(_) => "opendal_http_request_bytes_rate",
342            MetricValue::HttpRequestDurationSeconds(_) => "opendal_http_request_duration_seconds",
343            MetricValue::HttpResponseBytes(_) => "opendal_http_response_bytes",
344            MetricValue::HttpResponseBytesRate(_) => "opendal_http_response_bytes_rate",
345            MetricValue::HttpResponseDurationSeconds(_) => "opendal_http_response_duration_seconds",
346        }
347    }
348
349    /// Returns the metric name along with unit for this metric value.
350    ///
351    /// # Notes
352    ///
353    /// This API is designed for the metrics impls that unit aware. They will handle the names by themselves like append `_total` for counters.
354    pub fn name_with_unit(&self) -> (&'static str, Option<&'static str>) {
355        match self {
356            MetricValue::OperationBytes(_) => ("opendal_operation", Some("bytes")),
357            MetricValue::OperationBytesRate(_) => ("opendal_operation_bytes_rate", None),
358            MetricValue::OperationEntries(_) => ("opendal_operation_entries", None),
359            MetricValue::OperationEntriesRate(_) => ("opendal_operation_entries_rate", None),
360            MetricValue::OperationDurationSeconds(_) => {
361                ("opendal_operation_duration", Some("seconds"))
362            }
363            MetricValue::OperationErrorsTotal => ("opendal_operation_errors", None),
364            MetricValue::OperationExecuting(_) => ("opendal_operation_executing", None),
365            MetricValue::OperationTtfbSeconds(_) => ("opendal_operation_ttfb", Some("seconds")),
366
367            MetricValue::HttpConnectionErrorsTotal => ("opendal_http_connection_errors", None),
368            MetricValue::HttpStatusErrorsTotal => ("opendal_http_status_errors", None),
369            MetricValue::HttpExecuting(_) => ("opendal_http_executing", None),
370            MetricValue::HttpRequestBytes(_) => ("opendal_http_request", Some("bytes")),
371            MetricValue::HttpRequestBytesRate(_) => ("opendal_http_request_bytes_rate", None),
372            MetricValue::HttpRequestDurationSeconds(_) => {
373                ("opendal_http_request_duration", Some("seconds"))
374            }
375            MetricValue::HttpResponseBytes(_) => ("opendal_http_response", Some("bytes")),
376            MetricValue::HttpResponseBytesRate(_) => ("opendal_http_response_bytes_rate", None),
377            MetricValue::HttpResponseDurationSeconds(_) => {
378                ("opendal_http_response_duration", Some("seconds"))
379            }
380        }
381    }
382
383    /// Returns the help text for this metric value.
384    pub fn help(&self) -> &'static str {
385        match self {
386            MetricValue::OperationBytes(_) => {
387                "Current operation size in bytes, represents the size of data being processed in the current operation"
388            }
389            MetricValue::OperationBytesRate(_) => {
390                "Histogram of data processing rates in bytes per second within individual operations"
391            }
392            MetricValue::OperationEntries(_) => {
393                "Current operation size in entries, represents the entries being processed in the current operation"
394            }
395            MetricValue::OperationEntriesRate(_) => {
396                "Histogram of entries processing rates in entries per second within individual operations"
397            }
398            MetricValue::OperationDurationSeconds(_) => {
399                "Duration of operations in seconds, measured from start to completion"
400            }
401            MetricValue::OperationErrorsTotal => "Total number of failed operations",
402            MetricValue::OperationExecuting(_) => "Number of operations currently being executed",
403            MetricValue::OperationTtfbSeconds(_) => "Time to first byte in seconds for operations",
404
405            MetricValue::HttpConnectionErrorsTotal => {
406                "Total number of HTTP requests that failed before receiving a response (DNS failures, connection refused, timeouts, TLS errors)"
407            }
408            MetricValue::HttpStatusErrorsTotal => {
409                "Total number of HTTP requests that received error status codes (non-2xx responses)"
410            }
411            MetricValue::HttpExecuting(_) => {
412                "Number of HTTP requests currently in flight from this client"
413            }
414            MetricValue::HttpRequestBytes(_) => "Histogram of HTTP request body sizes in bytes",
415            MetricValue::HttpRequestBytesRate(_) => {
416                "Histogram of HTTP request bytes per second rates"
417            }
418            MetricValue::HttpRequestDurationSeconds(_) => {
419                "Histogram of time durations in seconds spent sending HTTP requests, from first byte sent to receiving the first byte"
420            }
421            MetricValue::HttpResponseBytes(_) => "Histogram of HTTP response body sizes in bytes",
422            MetricValue::HttpResponseBytesRate(_) => {
423                "Histogram of HTTP response bytes per second rates"
424            }
425            MetricValue::HttpResponseDurationSeconds(_) => {
426                "Histogram of time durations in seconds spent receiving HTTP responses, from first byte received to last byte received"
427            }
428        }
429    }
430}
431
432/// The interceptor for metrics.
433///
434/// All metrics related libs should implement this trait to observe opendal's internal operations.
435pub trait MetricsIntercept: Debug + Clone + Send + Sync + Unpin + 'static {
436    /// Observe the metric value.
437    fn observe(&self, labels: MetricLabels, value: MetricValue) {
438        let _ = (labels, value);
439    }
440}
441
442/// `MetricsLayer` reports operation and HTTP metrics to an interceptor.
443///
444/// The layer wraps both the storage service and the HTTP transport in the
445/// operator's [`OperationContext`]. It reports the metric names and labels
446/// documented in the crate-level metrics reference through
447/// [`MetricsIntercept::observe`].
448///
449/// The interceptor must be cheap to clone because each wrapped operation and
450/// HTTP request can retain a clone.
451#[derive(Clone, Debug)]
452pub struct MetricsLayer<I: MetricsIntercept> {
453    interceptor: I,
454}
455
456impl<I: MetricsIntercept> MetricsLayer<I> {
457    /// Create a metrics layer that reports values to `interceptor`.
458    pub fn new(interceptor: I) -> Self {
459        Self { interceptor }
460    }
461}
462
463impl<I: MetricsIntercept> Layer for MetricsLayer<I> {
464    fn apply_service(&self, inner: Servicer) -> Servicer {
465        Arc::new(self.layer(inner))
466    }
467
468    fn apply_context(&self, srv: Servicer, inner: OperationContext) -> OperationContext {
469        // HTTP metrics share the same interceptor and service identity as operation metrics.
470        let transport = HttpTransporter::new(MetricsHttpTransport {
471            inner: inner.http_transport().clone(),
472            info: srv.info(),
473            interceptor: self.interceptor.clone(),
474        });
475        inner.with_http_transport(transport)
476    }
477}
478
479impl<I: MetricsIntercept> MetricsLayer<I> {
480    fn layer(&self, inner: Servicer) -> MetricsAccessor<I> {
481        let info = inner.info();
482        MetricsAccessor {
483            inner,
484            info,
485            interceptor: self.interceptor.clone(),
486        }
487    }
488}
489
490struct MetricsHttpTransport<I: MetricsIntercept> {
491    inner: HttpTransporter,
492    info: ServiceInfo,
493    interceptor: I,
494}
495
496enum ExecutingGuardKind {
497    Http,
498    Operation,
499}
500
501/// Guard to ensure metrics accounted even if the async future is cancelled.
502struct ExecutingGuard<I: MetricsIntercept> {
503    /// The interceptor to use for recording metrics. `None` if defused.
504    interceptor: Option<I>,
505    labels: MetricLabels,
506    start: Instant,
507    kind: ExecutingGuardKind,
508    /// Whether the async operation finished.
509    completed: bool,
510}
511
512impl<I: MetricsIntercept> ExecutingGuard<I> {
513    fn new_http(interceptor: I, labels: MetricLabels, start: Instant) -> Self {
514        Self {
515            interceptor: Some(interceptor),
516            labels,
517            start,
518            kind: ExecutingGuardKind::Http,
519            completed: false,
520        }
521    }
522
523    fn new_operation(interceptor: I, labels: MetricLabels, start: Instant) -> Self {
524        Self {
525            interceptor: Some(interceptor),
526            labels,
527            start,
528            kind: ExecutingGuardKind::Operation,
529            completed: false,
530        }
531    }
532
533    fn complete(&mut self) {
534        self.completed = true;
535    }
536
537    fn defuse(&mut self) {
538        self.interceptor = None;
539    }
540}
541
542impl<I: MetricsIntercept> Drop for ExecutingGuard<I> {
543    fn drop(&mut self) {
544        let Some(ref interceptor) = self.interceptor else {
545            return;
546        };
547
548        if !self.completed {
549            match self.kind {
550                ExecutingGuardKind::Http => {
551                    interceptor
552                        .observe(self.labels.clone(), MetricValue::HttpConnectionErrorsTotal);
553                    interceptor.observe(
554                        self.labels.clone(),
555                        MetricValue::HttpRequestDurationSeconds(self.start.elapsed()),
556                    );
557                }
558                ExecutingGuardKind::Operation => {
559                    interceptor.observe(
560                        self.labels.clone().with_error(ErrorKind::Unexpected),
561                        MetricValue::OperationErrorsTotal,
562                    );
563                    interceptor.observe(
564                        self.labels.clone(),
565                        MetricValue::OperationDurationSeconds(self.start.elapsed()),
566                    );
567                }
568            }
569        }
570
571        match self.kind {
572            ExecutingGuardKind::Http => {
573                interceptor.observe(
574                    std::mem::take(&mut self.labels),
575                    MetricValue::HttpExecuting(-1),
576                );
577            }
578            ExecutingGuardKind::Operation => {
579                interceptor.observe(
580                    std::mem::take(&mut self.labels),
581                    MetricValue::OperationExecuting(-1),
582                );
583            }
584        }
585    }
586}
587
588impl<I: MetricsIntercept> HttpTransport for MetricsHttpTransport<I> {
589    async fn fetch(&self, req: http::Request<Buffer>) -> Result<http::Response<HttpBody>> {
590        let mut labels = MetricLabels::new(
591            self.info.clone(),
592            req.extensions()
593                .get::<Operation>()
594                .copied()
595                .map(Operation::into_static)
596                .unwrap_or("unknown"),
597        );
598        labels.service_operation = req.extensions().get::<ServiceOperation>().map(|s| s.0);
599
600        let start = Instant::now();
601        let req_size = req.body().len();
602
603        self.interceptor
604            .observe(labels.clone(), MetricValue::HttpExecuting(1));
605        let mut guard = ExecutingGuard::new_http(self.interceptor.clone(), labels.clone(), start);
606
607        let res = self.inner.fetch(req).await;
608        guard.complete();
609        let req_duration = start.elapsed();
610
611        match res {
612            Err(err) => {
613                self.interceptor
614                    .observe(labels, MetricValue::HttpConnectionErrorsTotal);
615                Err(err)
616            }
617            Ok(resp) if resp.status().is_client_error() || resp.status().is_server_error() => {
618                self.interceptor.observe(
619                    labels.with_status_code(resp.status()),
620                    MetricValue::HttpStatusErrorsTotal,
621                );
622                Ok(resp)
623            }
624            Ok(resp) => {
625                guard.defuse();
626                self.interceptor.observe(
627                    labels.clone(),
628                    MetricValue::HttpRequestBytes(req_size as u64),
629                );
630                self.interceptor.observe(
631                    labels.clone(),
632                    MetricValue::HttpRequestBytesRate(req_size as f64 / req_duration.as_secs_f64()),
633                );
634                self.interceptor.observe(
635                    labels.clone(),
636                    MetricValue::HttpRequestDurationSeconds(req_duration),
637                );
638
639                let (parts, body) = resp.into_parts();
640                let body = body.map_inner(|s| {
641                    Box::new(MetricsStream {
642                        inner: s,
643                        interceptor: self.interceptor.clone(),
644                        labels: labels.clone(),
645                        size: 0,
646                        start: Instant::now(),
647                        succeeded: false,
648                    })
649                });
650
651                Ok(http::Response::from_parts(parts, body))
652            }
653        }
654    }
655}
656
657struct MetricsStream<S, I: MetricsIntercept> {
658    inner: S,
659    interceptor: I,
660
661    labels: MetricLabels,
662    size: u64,
663    start: Instant,
664    succeeded: bool,
665}
666
667impl<S, I> Stream for MetricsStream<S, I>
668where
669    S: Stream<Item = Result<Buffer>> + Unpin + 'static,
670    I: MetricsIntercept,
671{
672    type Item = Result<Buffer>;
673
674    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
675        match ready!(self.inner.poll_next_unpin(cx)) {
676            Some(Ok(bs)) => {
677                self.size += bs.len() as u64;
678                Poll::Ready(Some(Ok(bs)))
679            }
680            Some(Err(err)) => Poll::Ready(Some(Err(err))),
681            None => {
682                self.succeeded = true;
683                Poll::Ready(None)
684            }
685        }
686    }
687}
688
689impl<S, I: MetricsIntercept> Drop for MetricsStream<S, I> {
690    fn drop(&mut self) {
691        let resp_size = self.size;
692        let resp_duration = self.start.elapsed();
693
694        if !self.succeeded {
695            self.interceptor
696                .observe(self.labels.clone(), MetricValue::HttpConnectionErrorsTotal);
697        }
698
699        self.interceptor.observe(
700            self.labels.clone(),
701            MetricValue::HttpResponseBytes(resp_size),
702        );
703        self.interceptor.observe(
704            self.labels.clone(),
705            MetricValue::HttpResponseBytesRate(resp_size as f64 / resp_duration.as_secs_f64()),
706        );
707        self.interceptor.observe(
708            self.labels.clone(),
709            MetricValue::HttpResponseDurationSeconds(resp_duration),
710        );
711        self.interceptor
712            .observe(self.labels.clone(), MetricValue::HttpExecuting(-1));
713    }
714}
715
716#[doc(hidden)]
717pub struct MetricsAccessor<I: MetricsIntercept> {
718    inner: Servicer,
719    info: ServiceInfo,
720    interceptor: I,
721}
722
723impl<I: MetricsIntercept> Debug for MetricsAccessor<I> {
724    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
725        f.debug_struct("MetricsAccessor")
726            .field("inner", &self.inner)
727            .finish_non_exhaustive()
728    }
729}
730
731impl<I: MetricsIntercept> Service for MetricsAccessor<I> {
732    type Reader = MetricsReader<oio::Reader, I>;
733    type Writer = MetricsWrapper<oio::Writer, I>;
734    type Lister = MetricsWrapper<oio::Lister, I>;
735    type Deleter = MetricsWrapper<oio::Deleter, I>;
736    type Copier = MetricsWrapper<oio::Copier, I>;
737
738    fn info(&self) -> ServiceInfo {
739        self.info.clone()
740    }
741
742    fn capability(&self) -> Capability {
743        self.inner.capability()
744    }
745
746    async fn create_dir(
747        &self,
748        ctx: &OperationContext,
749        path: &str,
750        args: OpCreateDir,
751    ) -> Result<RpCreateDir> {
752        let labels = MetricLabels::new(self.info.clone(), Operation::CreateDir.into_static());
753
754        let start = Instant::now();
755
756        self.interceptor
757            .observe(labels.clone(), MetricValue::OperationExecuting(1));
758        let mut guard =
759            ExecutingGuard::new_operation(self.interceptor.clone(), labels.clone(), start);
760
761        let res = self
762            .inner
763            .create_dir(ctx, path, args)
764            .await
765            .inspect(|_| {
766                self.interceptor.observe(
767                    labels.clone(),
768                    MetricValue::OperationDurationSeconds(start.elapsed()),
769                );
770            })
771            .inspect_err(|err| {
772                self.interceptor.observe(
773                    labels.clone().with_error(err.kind()),
774                    MetricValue::OperationErrorsTotal,
775                );
776            });
777
778        guard.complete();
779        res
780    }
781
782    fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
783        let labels = MetricLabels::new(self.info.clone(), Operation::Read.into_static());
784
785        let start = Instant::now();
786
787        self.interceptor
788            .observe(labels.clone(), MetricValue::OperationExecuting(1));
789        let mut guard =
790            ExecutingGuard::new_operation(self.interceptor.clone(), labels.clone(), start);
791
792        match self.inner.read(ctx, path, args) {
793            Ok(reader) => {
794                guard.complete();
795                Ok(MetricsReader::new(reader, self.interceptor.clone(), labels))
796            }
797            Err(err) => {
798                self.interceptor.observe(
799                    labels.clone().with_error(err.kind()),
800                    MetricValue::OperationErrorsTotal,
801                );
802                guard.complete();
803                Err(err)
804            }
805        }
806    }
807
808    fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
809        let labels = MetricLabels::new(self.info.clone(), Operation::Write.into_static());
810
811        let start = Instant::now();
812
813        self.interceptor
814            .observe(labels.clone(), MetricValue::OperationExecuting(1));
815        let mut guard =
816            ExecutingGuard::new_operation(self.interceptor.clone(), labels.clone(), start);
817
818        match self.inner.write(ctx, path, args) {
819            Ok(writer) => {
820                guard.defuse();
821                Ok(MetricsWrapper::new(
822                    writer,
823                    self.interceptor.clone(),
824                    labels,
825                    start,
826                ))
827            }
828            Err(err) => {
829                self.interceptor.observe(
830                    labels.clone().with_error(err.kind()),
831                    MetricValue::OperationErrorsTotal,
832                );
833                guard.complete();
834                Err(err)
835            }
836        }
837    }
838
839    fn copy(
840        &self,
841        ctx: &OperationContext,
842        from: &str,
843        to: &str,
844        args: OpCopy,
845        opts: OpCopier,
846    ) -> Result<Self::Copier> {
847        let labels = MetricLabels::new(self.info.clone(), Operation::Copy.into_static());
848
849        let start = Instant::now();
850
851        self.interceptor
852            .observe(labels.clone(), MetricValue::OperationExecuting(1));
853        let mut guard =
854            ExecutingGuard::new_operation(self.interceptor.clone(), labels.clone(), start);
855
856        match self.inner.copy(ctx, from, to, args, opts.clone()) {
857            Ok(copier) => {
858                guard.defuse();
859                Ok(MetricsWrapper::new(
860                    copier,
861                    self.interceptor.clone(),
862                    labels,
863                    start,
864                ))
865            }
866            Err(err) => {
867                self.interceptor.observe(
868                    labels.clone().with_error(err.kind()),
869                    MetricValue::OperationErrorsTotal,
870                );
871                guard.complete();
872                Err(err)
873            }
874        }
875    }
876
877    async fn rename(
878        &self,
879        ctx: &OperationContext,
880        from: &str,
881        to: &str,
882        args: OpRename,
883    ) -> Result<RpRename> {
884        let labels = MetricLabels::new(self.info.clone(), Operation::Rename.into_static());
885
886        let start = Instant::now();
887
888        self.interceptor
889            .observe(labels.clone(), MetricValue::OperationExecuting(1));
890        let mut guard =
891            ExecutingGuard::new_operation(self.interceptor.clone(), labels.clone(), start);
892
893        let res = self
894            .inner
895            .rename(ctx, from, to, args)
896            .await
897            .inspect(|_| {
898                self.interceptor.observe(
899                    labels.clone(),
900                    MetricValue::OperationDurationSeconds(start.elapsed()),
901                );
902            })
903            .inspect_err(|err| {
904                self.interceptor.observe(
905                    labels.clone().with_error(err.kind()),
906                    MetricValue::OperationErrorsTotal,
907                );
908            });
909
910        guard.complete();
911        res
912    }
913
914    async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
915        let labels = MetricLabels::new(self.info.clone(), Operation::Stat.into_static());
916
917        let start = Instant::now();
918
919        self.interceptor
920            .observe(labels.clone(), MetricValue::OperationExecuting(1));
921        let mut guard =
922            ExecutingGuard::new_operation(self.interceptor.clone(), labels.clone(), start);
923
924        let res = self
925            .inner
926            .stat(ctx, path, args)
927            .await
928            .inspect(|_| {
929                self.interceptor.observe(
930                    labels.clone(),
931                    MetricValue::OperationDurationSeconds(start.elapsed()),
932                );
933            })
934            .inspect_err(|err| {
935                self.interceptor.observe(
936                    labels.clone().with_error(err.kind()),
937                    MetricValue::OperationErrorsTotal,
938                );
939            });
940
941        guard.complete();
942        res
943    }
944
945    fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
946        let labels = MetricLabels::new(self.info.clone(), Operation::Delete.into_static());
947
948        let start = Instant::now();
949
950        self.interceptor
951            .observe(labels.clone(), MetricValue::OperationExecuting(1));
952        let mut guard =
953            ExecutingGuard::new_operation(self.interceptor.clone(), labels.clone(), start);
954
955        match self.inner.delete(ctx) {
956            Ok(deleter) => {
957                guard.defuse();
958                Ok(MetricsWrapper::new(
959                    deleter,
960                    self.interceptor.clone(),
961                    labels,
962                    start,
963                ))
964            }
965            Err(err) => {
966                self.interceptor.observe(
967                    labels.clone().with_error(err.kind()),
968                    MetricValue::OperationErrorsTotal,
969                );
970                guard.complete();
971                Err(err)
972            }
973        }
974    }
975
976    fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
977        let labels = MetricLabels::new(self.info.clone(), Operation::List.into_static());
978
979        let start = Instant::now();
980
981        self.interceptor
982            .observe(labels.clone(), MetricValue::OperationExecuting(1));
983        let mut guard =
984            ExecutingGuard::new_operation(self.interceptor.clone(), labels.clone(), start);
985
986        match self.inner.list(ctx, path, args) {
987            Ok(lister) => {
988                guard.defuse();
989                Ok(MetricsWrapper::new(
990                    lister,
991                    self.interceptor.clone(),
992                    labels,
993                    start,
994                ))
995            }
996            Err(err) => {
997                self.interceptor.observe(
998                    labels.clone().with_error(err.kind()),
999                    MetricValue::OperationErrorsTotal,
1000                );
1001                guard.complete();
1002                Err(err)
1003            }
1004        }
1005    }
1006
1007    async fn presign(
1008        &self,
1009        ctx: &OperationContext,
1010        path: &str,
1011        args: OpPresign,
1012    ) -> Result<RpPresign> {
1013        let labels = MetricLabels::new(self.info.clone(), Operation::Presign.into_static());
1014
1015        let start = Instant::now();
1016
1017        self.interceptor
1018            .observe(labels.clone(), MetricValue::OperationExecuting(1));
1019        let mut guard =
1020            ExecutingGuard::new_operation(self.interceptor.clone(), labels.clone(), start);
1021
1022        let res = self
1023            .inner
1024            .presign(ctx, path, args)
1025            .await
1026            .inspect(|_| {
1027                self.interceptor.observe(
1028                    labels.clone(),
1029                    MetricValue::OperationDurationSeconds(start.elapsed()),
1030                );
1031            })
1032            .inspect_err(|err| {
1033                self.interceptor.observe(
1034                    labels.clone().with_error(err.kind()),
1035                    MetricValue::OperationErrorsTotal,
1036                );
1037            });
1038
1039        guard.complete();
1040        res
1041    }
1042}
1043
1044#[doc(hidden)]
1045pub struct MetricsReader<R, I: MetricsIntercept> {
1046    inner: R,
1047    interceptor: I,
1048    labels: MetricLabels,
1049}
1050
1051impl<R, I: MetricsIntercept> MetricsReader<R, I> {
1052    fn new(inner: R, interceptor: I, labels: MetricLabels) -> Self {
1053        Self {
1054            inner,
1055            interceptor,
1056            labels,
1057        }
1058    }
1059}
1060
1061impl<R: oio::Read, I: MetricsIntercept> oio::Read for MetricsReader<R, I> {
1062    async fn open(&self, range: BytesRange) -> Result<(RpRead, Box<dyn oio::ReadStreamDyn>)> {
1063        let labels = self.labels.clone();
1064        let start = Instant::now();
1065
1066        self.interceptor
1067            .observe(labels.clone(), MetricValue::OperationExecuting(1));
1068        let mut guard =
1069            ExecutingGuard::new_operation(self.interceptor.clone(), labels.clone(), start);
1070
1071        match self.inner.open(range).await {
1072            Ok((rp, stream)) => {
1073                self.interceptor.observe(
1074                    labels.clone(),
1075                    MetricValue::OperationTtfbSeconds(start.elapsed()),
1076                );
1077                guard.defuse();
1078                Ok((
1079                    rp,
1080                    Box::new(MetricsWrapper::new(
1081                        stream,
1082                        self.interceptor.clone(),
1083                        labels,
1084                        start,
1085                    )) as Box<dyn oio::ReadStreamDyn>,
1086                ))
1087            }
1088            Err(err) => {
1089                self.interceptor.observe(
1090                    labels.clone().with_error(err.kind()),
1091                    MetricValue::OperationErrorsTotal,
1092                );
1093                guard.complete();
1094                Err(err)
1095            }
1096        }
1097    }
1098
1099    async fn read(&self, range: BytesRange) -> Result<(RpRead, Buffer)> {
1100        let labels = self.labels.clone();
1101        let start = Instant::now();
1102
1103        self.interceptor
1104            .observe(labels.clone(), MetricValue::OperationExecuting(1));
1105        let mut metrics = MetricsWrapper::new((), self.interceptor.clone(), labels, start);
1106
1107        let result = self.inner.read(range).await.inspect_err(|err| {
1108            metrics.record_error(err);
1109        });
1110        if let Ok((_, buffer)) = &result {
1111            metrics.size = buffer.len() as u64;
1112            metrics.completed = true;
1113        }
1114        result
1115    }
1116}
1117
1118#[doc(hidden)]
1119pub struct MetricsWrapper<R, I: MetricsIntercept> {
1120    inner: R,
1121    interceptor: I,
1122    labels: MetricLabels,
1123
1124    start: Instant,
1125    size: u64,
1126    completed: bool,
1127}
1128
1129impl<R, I: MetricsIntercept> Drop for MetricsWrapper<R, I> {
1130    fn drop(&mut self) {
1131        let size = self.size;
1132        let duration = self.start.elapsed();
1133
1134        if !self.completed {
1135            self.interceptor.observe(
1136                self.labels.clone().with_error(ErrorKind::Unexpected),
1137                MetricValue::OperationErrorsTotal,
1138            );
1139        }
1140
1141        if self.labels.operation == Operation::Read.into_static()
1142            || self.labels.operation == Operation::Write.into_static()
1143            || self.labels.operation == Operation::Copy.into_static()
1144        {
1145            self.interceptor
1146                .observe(self.labels.clone(), MetricValue::OperationBytes(self.size));
1147            self.interceptor.observe(
1148                self.labels.clone(),
1149                MetricValue::OperationBytesRate(size as f64 / duration.as_secs_f64()),
1150            );
1151        } else {
1152            self.interceptor.observe(
1153                self.labels.clone(),
1154                MetricValue::OperationEntries(self.size),
1155            );
1156            self.interceptor.observe(
1157                self.labels.clone(),
1158                MetricValue::OperationEntriesRate(size as f64 / duration.as_secs_f64()),
1159            );
1160        }
1161
1162        self.interceptor.observe(
1163            self.labels.clone(),
1164            MetricValue::OperationDurationSeconds(duration),
1165        );
1166        self.interceptor
1167            .observe(self.labels.clone(), MetricValue::OperationExecuting(-1));
1168    }
1169}
1170
1171impl<R, I: MetricsIntercept> MetricsWrapper<R, I> {
1172    fn new(inner: R, interceptor: I, labels: MetricLabels, start: Instant) -> Self {
1173        Self {
1174            inner,
1175            interceptor,
1176            labels,
1177            start,
1178            size: 0,
1179            completed: false,
1180        }
1181    }
1182
1183    fn record_error(&mut self, err: &Error) {
1184        self.completed = true;
1185        self.interceptor.observe(
1186            self.labels.clone().with_error(err.kind()),
1187            MetricValue::OperationErrorsTotal,
1188        );
1189    }
1190}
1191
1192impl<R: oio::ReadStream, I: MetricsIntercept> oio::ReadStream for MetricsWrapper<R, I> {
1193    async fn read(&mut self) -> Result<Buffer> {
1194        self.inner
1195            .read()
1196            .await
1197            .inspect(|bs| {
1198                if bs.is_empty() {
1199                    self.completed = true;
1200                }
1201                self.size += bs.len() as u64;
1202            })
1203            .inspect_err(|err| self.record_error(err))
1204    }
1205}
1206
1207impl<R: oio::Write, I: MetricsIntercept> oio::Write for MetricsWrapper<R, I> {
1208    async fn write(&mut self, bs: Buffer) -> Result<()> {
1209        let size = bs.len();
1210
1211        self.inner
1212            .write(bs)
1213            .await
1214            .inspect(|_| {
1215                self.size += size as u64;
1216            })
1217            .inspect_err(|err| self.record_error(err))
1218    }
1219
1220    async fn close(&mut self) -> Result<Metadata> {
1221        let result = self
1222            .inner
1223            .close()
1224            .await
1225            .inspect_err(|err| self.record_error(err));
1226        self.completed = true;
1227        result
1228    }
1229
1230    async fn abort(&mut self) -> Result<()> {
1231        let result = self
1232            .inner
1233            .abort()
1234            .await
1235            .inspect_err(|err| self.record_error(err));
1236        self.completed = true;
1237        result
1238    }
1239}
1240
1241impl<R: oio::List, I: MetricsIntercept> oio::List for MetricsWrapper<R, I> {
1242    async fn next(&mut self) -> Result<Option<oio::Entry>> {
1243        self.inner
1244            .next()
1245            .await
1246            .inspect(|entry| {
1247                if entry.is_some() {
1248                    self.size += 1;
1249                } else {
1250                    self.completed = true;
1251                }
1252            })
1253            .inspect_err(|err| self.record_error(err))
1254    }
1255}
1256
1257impl<R: oio::Delete, I: MetricsIntercept> oio::Delete for MetricsWrapper<R, I> {
1258    async fn delete(&mut self, path: &str, args: OpDelete) -> Result<()> {
1259        self.inner
1260            .delete(path, args)
1261            .await
1262            .inspect(|_| {
1263                self.size += 1;
1264            })
1265            .inspect_err(|err| self.record_error(err))
1266    }
1267
1268    async fn close(&mut self) -> Result<()> {
1269        let result = self
1270            .inner
1271            .close()
1272            .await
1273            .inspect_err(|err| self.record_error(err));
1274        self.completed = true;
1275        result
1276    }
1277}
1278
1279impl<C: oio::Copy, I: MetricsIntercept> oio::Copy for MetricsWrapper<C, I> {
1280    async fn next(&mut self) -> Result<Option<usize>> {
1281        self.inner
1282            .next()
1283            .await
1284            .inspect(|n| match n {
1285                Some(n) => self.size += *n as u64,
1286                None => self.completed = true,
1287            })
1288            .inspect_err(|err| self.record_error(err))
1289    }
1290
1291    async fn close(&mut self) -> Result<Metadata> {
1292        let result = self
1293            .inner
1294            .close()
1295            .await
1296            .inspect_err(|err| self.record_error(err));
1297        self.completed = true;
1298        result
1299    }
1300
1301    async fn abort(&mut self) -> Result<()> {
1302        let result = self
1303            .inner
1304            .abort()
1305            .await
1306            .inspect_err(|err| self.record_error(err));
1307        self.completed = true;
1308        result
1309    }
1310}
1311
1312#[cfg(test)]
1313mod tests {
1314    use std::collections::VecDeque;
1315    use std::sync::Mutex;
1316    use std::time::Instant;
1317
1318    use futures::StreamExt;
1319    use futures::stream;
1320
1321    use super::*;
1322
1323    #[derive(Debug, Clone, Default)]
1324    struct MockInterceptor {
1325        observations: Arc<Mutex<Vec<(MetricLabels, MetricValue)>>>,
1326    }
1327
1328    impl MetricsIntercept for MockInterceptor {
1329        fn observe(&self, labels: MetricLabels, value: MetricValue) {
1330            self.observations.lock().unwrap().push((labels, value));
1331        }
1332    }
1333
1334    impl MockInterceptor {
1335        fn has_metric(&self, name: &str) -> bool {
1336            self.observations
1337                .lock()
1338                .unwrap()
1339                .iter()
1340                .any(|(_, v)| v.name() == name)
1341        }
1342
1343        fn count_metric(&self, name: &str) -> usize {
1344            self.observations
1345                .lock()
1346                .unwrap()
1347                .iter()
1348                .filter(|(_, v)| v.name() == name)
1349                .count()
1350        }
1351
1352        fn count_metric_with_status(&self, name: &str, code: StatusCode) -> usize {
1353            self.observations
1354                .lock()
1355                .unwrap()
1356                .iter()
1357                .filter(|(l, v)| v.name() == name && l.status_code == Some(code))
1358                .count()
1359        }
1360
1361        fn gauge_value(&self, name: &str) -> isize {
1362            self.observations
1363                .lock()
1364                .unwrap()
1365                .iter()
1366                .filter_map(|(_, v)| match v {
1367                    MetricValue::HttpExecuting(n) if v.name() == name => Some(*n),
1368                    MetricValue::OperationExecuting(n) if v.name() == name => Some(*n),
1369                    _ => None,
1370                })
1371                .sum()
1372        }
1373
1374        fn get_duration_seconds(&self, name: &str) -> Option<Duration> {
1375            self.observations.lock().unwrap().iter().find_map(|(_, v)| {
1376                if v.name() != name {
1377                    return None;
1378                }
1379                match v {
1380                    MetricValue::HttpRequestDurationSeconds(d)
1381                    | MetricValue::HttpResponseDurationSeconds(d)
1382                    | MetricValue::OperationDurationSeconds(d)
1383                    | MetricValue::OperationTtfbSeconds(d) => Some(*d),
1384                    _ => None,
1385                }
1386            })
1387        }
1388
1389        fn get_value_u64(&self, name: &str) -> Option<u64> {
1390            self.observations.lock().unwrap().iter().find_map(|(_, v)| {
1391                if v.name() != name {
1392                    return None;
1393                }
1394                match v {
1395                    MetricValue::HttpResponseBytes(n)
1396                    | MetricValue::HttpRequestBytes(n)
1397                    | MetricValue::OperationBytes(n)
1398                    | MetricValue::OperationEntries(n) => Some(*n),
1399                    _ => None,
1400                }
1401            })
1402        }
1403    }
1404
1405    fn test_info() -> ServiceInfo {
1406        ServiceInfo::new("test", "", "")
1407    }
1408
1409    fn test_labels() -> MetricLabels {
1410        MetricLabels::new(test_info(), Operation::Read.into_static())
1411    }
1412
1413    enum MockFetchBehavior {
1414        /// Return a response with the given status code and an empty body.
1415        Respond(StatusCode),
1416        /// Fail before receiving a response.
1417        ConnectionError,
1418        /// Return HTTP 200 with a body stream that yields the given items.
1419        StreamBody(Vec<Result<Buffer>>),
1420    }
1421
1422    struct MockHttpTransport {
1423        behavior: Mutex<MockFetchBehavior>,
1424    }
1425
1426    impl HttpTransport for MockHttpTransport {
1427        async fn fetch(&self, _req: http::Request<Buffer>) -> Result<http::Response<HttpBody>> {
1428            let behavior = std::mem::replace(
1429                &mut *self.behavior.lock().unwrap(),
1430                MockFetchBehavior::ConnectionError,
1431            );
1432            match behavior {
1433                MockFetchBehavior::Respond(status) => {
1434                    let body = HttpBody::new(stream::empty(), None);
1435                    let resp = http::Response::builder().status(status).body(body).unwrap();
1436                    Ok(resp)
1437                }
1438                MockFetchBehavior::ConnectionError => {
1439                    Err(Error::new(ErrorKind::Unexpected, "mock connection refused"))
1440                }
1441                MockFetchBehavior::StreamBody(items) => {
1442                    let body = HttpBody::new(stream::iter(items), None);
1443                    let resp = http::Response::builder()
1444                        .status(StatusCode::OK)
1445                        .body(body)
1446                        .unwrap();
1447                    Ok(resp)
1448                }
1449            }
1450        }
1451    }
1452
1453    fn build_metrics_http_fetcher(
1454        mock: MockInterceptor,
1455        behavior: MockFetchBehavior,
1456    ) -> MetricsHttpTransport<MockInterceptor> {
1457        let inner_fetch = MockHttpTransport {
1458            behavior: Mutex::new(behavior),
1459        };
1460        MetricsHttpTransport {
1461            inner: HttpTransporter::new(inner_fetch),
1462            info: test_info(),
1463            interceptor: mock,
1464        }
1465    }
1466
1467    fn build_http_request() -> http::Request<Buffer> {
1468        let mut req = http::Request::new(Buffer::new());
1469        *req.uri_mut() = "https://example.com/test".parse().unwrap();
1470        req.extensions_mut().insert(Operation::Read);
1471        req
1472    }
1473
1474    struct MockRawReader {
1475        open_result: Mutex<Option<Result<Vec<Result<Buffer>>>>>,
1476        read_result: Mutex<Option<Result<Buffer>>>,
1477    }
1478
1479    impl MockRawReader {
1480        fn with_open(chunks: Vec<Result<Buffer>>) -> Self {
1481            Self {
1482                open_result: Mutex::new(Some(Ok(chunks))),
1483                read_result: Mutex::new(None),
1484            }
1485        }
1486
1487        fn with_read(buffer: Buffer) -> Self {
1488            Self {
1489                open_result: Mutex::new(None),
1490                read_result: Mutex::new(Some(Ok(buffer))),
1491            }
1492        }
1493    }
1494
1495    impl oio::Read for MockRawReader {
1496        async fn open(&self, _: BytesRange) -> Result<(RpRead, Box<dyn oio::ReadStreamDyn>)> {
1497            match self
1498                .open_result
1499                .lock()
1500                .unwrap()
1501                .take()
1502                .unwrap_or_else(|| Err(Error::new(ErrorKind::Unexpected, "open not configured")))
1503            {
1504                Ok(chunks) => Ok((
1505                    RpRead::default(),
1506                    Box::new(MockReadStream {
1507                        chunks: chunks.into_iter().collect(),
1508                    }) as Box<dyn oio::ReadStreamDyn>,
1509                )),
1510                Err(err) => Err(err),
1511            }
1512        }
1513
1514        async fn read(&self, _: BytesRange) -> Result<(RpRead, Buffer)> {
1515            match self
1516                .read_result
1517                .lock()
1518                .unwrap()
1519                .take()
1520                .unwrap_or_else(|| Err(Error::new(ErrorKind::Unexpected, "read not configured")))
1521            {
1522                Ok(buffer) => Ok((RpRead::default(), buffer)),
1523                Err(err) => Err(err),
1524            }
1525        }
1526    }
1527
1528    struct MockReadStream {
1529        chunks: VecDeque<Result<Buffer>>,
1530    }
1531
1532    impl oio::ReadStream for MockReadStream {
1533        async fn read(&mut self) -> Result<Buffer> {
1534            self.chunks.pop_front().unwrap_or_else(|| Ok(Buffer::new()))
1535        }
1536    }
1537
1538    #[tokio::test]
1539    async fn test_http_transport_error_records_status_error() {
1540        let mock = MockInterceptor::default();
1541        let fetcher = build_metrics_http_fetcher(
1542            mock.clone(),
1543            MockFetchBehavior::Respond(StatusCode::NOT_FOUND),
1544        );
1545
1546        let _ = fetcher.fetch(build_http_request()).await;
1547
1548        assert_eq!(
1549            mock.count_metric_with_status(
1550                "opendal_http_status_errors_total",
1551                StatusCode::NOT_FOUND
1552            ),
1553            1
1554        );
1555        assert_eq!(mock.gauge_value("opendal_http_executing"), 0);
1556    }
1557
1558    #[tokio::test]
1559    async fn test_http_server_error_records_status_error() {
1560        let mock = MockInterceptor::default();
1561        let fetcher = build_metrics_http_fetcher(
1562            mock.clone(),
1563            MockFetchBehavior::Respond(StatusCode::INTERNAL_SERVER_ERROR),
1564        );
1565
1566        let _ = fetcher.fetch(build_http_request()).await;
1567
1568        assert_eq!(
1569            mock.count_metric_with_status(
1570                "opendal_http_status_errors_total",
1571                StatusCode::INTERNAL_SERVER_ERROR
1572            ),
1573            1
1574        );
1575        assert_eq!(mock.gauge_value("opendal_http_executing"), 0);
1576    }
1577
1578    #[tokio::test]
1579    async fn test_http_success_records_full_metrics() {
1580        let mock = MockInterceptor::default();
1581        let fetcher = build_metrics_http_fetcher(
1582            mock.clone(),
1583            MockFetchBehavior::StreamBody(vec![
1584                Ok(Buffer::from("hello")),
1585                Ok(Buffer::from(" world")),
1586            ]),
1587        );
1588
1589        let resp = fetcher.fetch(build_http_request()).await.unwrap();
1590        assert_eq!(resp.status(), StatusCode::OK);
1591
1592        let (_, mut body) = resp.into_parts();
1593        let buf = body.to_buffer().await.unwrap();
1594        assert_eq!(buf.len(), 11);
1595        drop(body);
1596
1597        assert_eq!(mock.count_metric("opendal_http_status_errors_total"), 0);
1598        assert_eq!(mock.count_metric("opendal_http_connection_errors_total"), 0);
1599        assert_eq!(mock.get_value_u64("opendal_http_request_bytes"), Some(0));
1600        assert!(
1601            mock.get_duration_seconds("opendal_http_request_duration_seconds")
1602                .unwrap()
1603                > Duration::ZERO
1604        );
1605        assert_eq!(mock.get_value_u64("opendal_http_response_bytes"), Some(11));
1606        assert!(
1607            mock.get_duration_seconds("opendal_http_response_duration_seconds")
1608                .unwrap()
1609                > Duration::ZERO
1610        );
1611        assert_eq!(mock.gauge_value("opendal_http_executing"), 0);
1612    }
1613
1614    #[tokio::test]
1615    async fn test_http_connection_error_records_connection_error() {
1616        let mock = MockInterceptor::default();
1617        let fetcher = build_metrics_http_fetcher(mock.clone(), MockFetchBehavior::ConnectionError);
1618
1619        let res = fetcher.fetch(build_http_request()).await;
1620        assert!(res.is_err());
1621
1622        assert_eq!(mock.count_metric("opendal_http_connection_errors_total"), 1);
1623        assert_eq!(mock.count_metric("opendal_http_status_errors_total"), 0);
1624        assert_eq!(mock.gauge_value("opendal_http_executing"), 0);
1625    }
1626
1627    #[tokio::test]
1628    async fn test_http_body_read_failure_records_metrics() {
1629        let mock = MockInterceptor::default();
1630        let fetcher = build_metrics_http_fetcher(
1631            mock.clone(),
1632            MockFetchBehavior::StreamBody(vec![
1633                Ok(Buffer::from("partial")),
1634                Err(Error::new(ErrorKind::Unexpected, "connection reset")),
1635            ]),
1636        );
1637
1638        let resp = fetcher.fetch(build_http_request()).await.unwrap();
1639        assert_eq!(resp.status(), StatusCode::OK);
1640
1641        let (_, mut body) = resp.into_parts();
1642        let res = body.to_buffer().await;
1643        assert!(res.is_err());
1644        drop(body);
1645
1646        assert_eq!(mock.get_value_u64("opendal_http_response_bytes"), Some(7));
1647        assert_eq!(mock.count_metric("opendal_http_connection_errors_total"), 1);
1648        assert_eq!(mock.gauge_value("opendal_http_executing"), 0);
1649    }
1650
1651    #[tokio::test]
1652    async fn test_stream_completed_records_response_metrics() {
1653        let mock = MockInterceptor::default();
1654        let labels = test_labels();
1655        mock.observe(labels.clone(), MetricValue::HttpExecuting(1));
1656        let inner = stream::iter(vec![Ok(Buffer::from("hello"))]);
1657
1658        let mut s = MetricsStream {
1659            inner,
1660            interceptor: mock.clone(),
1661            labels,
1662            size: 0,
1663            start: Instant::now(),
1664            succeeded: false,
1665        };
1666
1667        while s.next().await.is_some() {}
1668        drop(s);
1669
1670        assert_eq!(mock.get_value_u64("opendal_http_response_bytes"), Some(5));
1671        assert_eq!(mock.gauge_value("opendal_http_executing"), 0);
1672        assert_eq!(mock.count_metric("opendal_http_connection_errors_total"), 0);
1673    }
1674
1675    #[tokio::test]
1676    async fn test_stream_dropped_early_records_response_metrics() {
1677        let mock = MockInterceptor::default();
1678        let labels = test_labels();
1679        mock.observe(labels.clone(), MetricValue::HttpExecuting(1));
1680        let inner = stream::iter(vec![Ok(Buffer::from("chunk1")), Ok(Buffer::from("chunk2"))]);
1681
1682        let mut s = MetricsStream {
1683            inner,
1684            interceptor: mock.clone(),
1685            labels,
1686            size: 0,
1687            start: Instant::now(),
1688            succeeded: false,
1689        };
1690
1691        let _ = s.next().await;
1692        drop(s);
1693
1694        assert_eq!(mock.get_value_u64("opendal_http_response_bytes"), Some(6));
1695        assert_eq!(mock.gauge_value("opendal_http_executing"), 0);
1696        assert_eq!(mock.count_metric("opendal_http_connection_errors_total"), 1);
1697    }
1698
1699    #[tokio::test]
1700    async fn test_stream_error_records_response_metrics() {
1701        let mock = MockInterceptor::default();
1702        let labels = test_labels();
1703        mock.observe(labels.clone(), MetricValue::HttpExecuting(1));
1704        let inner = stream::iter(vec![
1705            Ok(Buffer::from("data")),
1706            Err(Error::new(ErrorKind::Unexpected, "read error")),
1707        ]);
1708
1709        let mut s = MetricsStream {
1710            inner,
1711            interceptor: mock.clone(),
1712            labels,
1713            size: 0,
1714            start: Instant::now(),
1715            succeeded: false,
1716        };
1717
1718        while let Some(res) = s.next().await {
1719            if res.is_err() {
1720                break;
1721            }
1722        }
1723        drop(s);
1724
1725        assert_eq!(mock.get_value_u64("opendal_http_response_bytes"), Some(4));
1726        assert_eq!(mock.gauge_value("opendal_http_executing"), 0);
1727        assert_eq!(mock.count_metric("opendal_http_connection_errors_total"), 1);
1728    }
1729
1730    #[test]
1731    fn test_http_guard_cancelled_records_connection_error() {
1732        let mock = MockInterceptor::default();
1733        let labels = test_labels();
1734        mock.observe(labels.clone(), MetricValue::HttpExecuting(1));
1735        let guard = ExecutingGuard::new_http(mock.clone(), labels, Instant::now());
1736        drop(guard);
1737
1738        assert_eq!(mock.count_metric("opendal_http_connection_errors_total"), 1);
1739        assert!(mock.has_metric("opendal_http_request_duration_seconds"));
1740        assert_eq!(mock.gauge_value("opendal_http_executing"), 0);
1741    }
1742
1743    #[test]
1744    fn test_http_guard_completed_only_decrements_executing() {
1745        let mock = MockInterceptor::default();
1746        let labels = test_labels();
1747        mock.observe(labels.clone(), MetricValue::HttpExecuting(1));
1748        let mut guard = ExecutingGuard::new_http(mock.clone(), labels, Instant::now());
1749        guard.complete();
1750        drop(guard);
1751
1752        assert_eq!(mock.count_metric("opendal_http_connection_errors_total"), 0);
1753        assert_eq!(mock.gauge_value("opendal_http_executing"), 0);
1754    }
1755
1756    #[test]
1757    fn test_http_guard_defused_records_nothing() {
1758        let mock = MockInterceptor::default();
1759        let labels = test_labels();
1760        mock.observe(labels.clone(), MetricValue::HttpExecuting(1));
1761        let mut guard = ExecutingGuard::new_http(mock.clone(), labels, Instant::now());
1762        guard.complete();
1763        guard.defuse();
1764        drop(guard);
1765
1766        assert_eq!(mock.gauge_value("opendal_http_executing"), 1);
1767        assert_eq!(mock.count_metric("opendal_http_connection_errors_total"), 0);
1768    }
1769
1770    #[test]
1771    fn test_operation_guard_cancelled_records_operation_error() {
1772        let mock = MockInterceptor::default();
1773        let labels = test_labels();
1774        mock.observe(labels.clone(), MetricValue::OperationExecuting(1));
1775        let guard = ExecutingGuard::new_operation(mock.clone(), labels, Instant::now());
1776        drop(guard);
1777
1778        assert_eq!(mock.count_metric("opendal_operation_errors_total"), 1);
1779        assert!(mock.has_metric("opendal_operation_duration_seconds"));
1780        assert_eq!(mock.gauge_value("opendal_operation_executing"), 0);
1781    }
1782
1783    #[test]
1784    fn test_operation_guard_completed_only_decrements_executing() {
1785        let mock = MockInterceptor::default();
1786        let labels = test_labels();
1787        mock.observe(labels.clone(), MetricValue::OperationExecuting(1));
1788        let mut guard = ExecutingGuard::new_operation(mock.clone(), labels, Instant::now());
1789        guard.complete();
1790        drop(guard);
1791
1792        assert_eq!(mock.count_metric("opendal_operation_errors_total"), 0);
1793        assert_eq!(mock.gauge_value("opendal_operation_executing"), 0);
1794    }
1795
1796    #[test]
1797    fn test_reader_drop_records_no_operation_metrics() {
1798        let mock = MockInterceptor::default();
1799        let labels = MetricLabels::new(test_info(), Operation::Read.into_static());
1800
1801        let reader = MetricsReader::new(
1802            MockRawReader::with_read(Buffer::new()),
1803            mock.clone(),
1804            labels,
1805        );
1806        drop(reader);
1807
1808        assert_eq!(mock.count_metric("opendal_operation_errors_total"), 0);
1809        assert_eq!(mock.get_value_u64("opendal_operation_bytes"), None);
1810        assert_eq!(mock.gauge_value("opendal_operation_executing"), 0);
1811    }
1812
1813    #[tokio::test]
1814    async fn test_reader_open_stream_records_operation_metrics_on_stream_drop() {
1815        let mock = MockInterceptor::default();
1816        let labels = MetricLabels::new(test_info(), Operation::Read.into_static());
1817        let reader = MetricsReader::new(
1818            MockRawReader::with_open(vec![Ok(Buffer::from("hello"))]),
1819            mock.clone(),
1820            labels,
1821        );
1822
1823        let (_, mut stream) = oio::Read::open(&reader, BytesRange::from(0_u64..5))
1824            .await
1825            .unwrap();
1826        assert_eq!(mock.gauge_value("opendal_operation_executing"), 1);
1827
1828        let chunk = oio::ReadStream::read(&mut stream).await.unwrap();
1829        assert_eq!(chunk.len(), 5);
1830        let chunk = oio::ReadStream::read(&mut stream).await.unwrap();
1831        assert!(chunk.is_empty());
1832        drop(stream);
1833        drop(reader);
1834
1835        assert_eq!(mock.count_metric("opendal_operation_errors_total"), 0);
1836        assert!(mock.has_metric("opendal_operation_ttfb_seconds"));
1837        assert_eq!(mock.get_value_u64("opendal_operation_bytes"), Some(5));
1838        assert_eq!(mock.gauge_value("opendal_operation_executing"), 0);
1839    }
1840
1841    #[tokio::test]
1842    async fn test_reader_read_records_operation_metrics() {
1843        let mock = MockInterceptor::default();
1844        let labels = MetricLabels::new(test_info(), Operation::Read.into_static());
1845        let reader = MetricsReader::new(
1846            MockRawReader::with_read(Buffer::from("hello")),
1847            mock.clone(),
1848            labels,
1849        );
1850
1851        let (_, buffer) = oio::Read::read(&reader, BytesRange::from(0_u64..5))
1852            .await
1853            .unwrap();
1854        drop(reader);
1855
1856        assert_eq!(buffer.len(), 5);
1857        assert_eq!(mock.count_metric("opendal_operation_errors_total"), 0);
1858        assert_eq!(mock.get_value_u64("opendal_operation_bytes"), Some(5));
1859        assert_eq!(mock.gauge_value("opendal_operation_executing"), 0);
1860    }
1861
1862    #[test]
1863    fn test_list_wrapper_drop_records_operation_entries() {
1864        let mock = MockInterceptor::default();
1865        let labels = MetricLabels::new(test_info(), Operation::List.into_static());
1866        mock.observe(labels.clone(), MetricValue::OperationExecuting(1));
1867
1868        let mut wrapper = MetricsWrapper {
1869            inner: (),
1870            interceptor: mock.clone(),
1871            labels,
1872            start: Instant::now(),
1873            size: 0,
1874            completed: true,
1875        };
1876        wrapper.size = 42;
1877        drop(wrapper);
1878
1879        assert_eq!(mock.get_value_u64("opendal_operation_entries"), Some(42));
1880        assert!(mock.has_metric("opendal_operation_entries_rate"));
1881        assert_eq!(mock.get_value_u64("opendal_operation_bytes"), None);
1882        assert_eq!(mock.gauge_value("opendal_operation_executing"), 0);
1883    }
1884
1885    #[test]
1886    fn test_wrapper_cancelled_records_operation_error() {
1887        let mock = MockInterceptor::default();
1888        let labels = MetricLabels::new(test_info(), Operation::Read.into_static());
1889        mock.observe(labels.clone(), MetricValue::OperationExecuting(1));
1890
1891        let wrapper = MetricsWrapper {
1892            inner: (),
1893            interceptor: mock.clone(),
1894            labels,
1895            start: Instant::now(),
1896            size: 0,
1897            completed: false,
1898        };
1899        drop(wrapper);
1900
1901        assert_eq!(mock.count_metric("opendal_operation_errors_total"), 1);
1902        assert_eq!(mock.gauge_value("opendal_operation_executing"), 0);
1903    }
1904}