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