1#![doc = include_str!("../README.md")]
19#![cfg_attr(docsrs, feature(doc_cfg))]
20#![cfg_attr(docsrs, doc(auto_cfg))]
21#![deny(missing_docs)]
22
23use 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
94pub const DEFAULT_BYTES_BUCKETS: &[f64] = &[
97 4.0 * KIB, 64.0 * KIB, 256.0 * KIB, 1.0 * MIB, 4.0 * MIB, 16.0 * MIB, 64.0 * MIB, 256.0 * MIB, 1.0 * GIB, 5.0 * GIB, ];
108
109pub const DEFAULT_BYTES_RATE_BUCKETS: &[f64] = &[
115 8.0 * KIB, 32.0 * KIB, 128.0 * KIB, 1.0 * MIB, 8.0 * MIB, 32.0 * MIB, 128.0 * MIB, 512.0 * MIB, 2.0 * GIB, 8.0 * GIB, 32.0 * GIB, ];
131
132pub const DEFAULT_ENTRIES_BUCKETS: &[f64] = &[
135 1.0, 5.0, 10.0, 50.0, 100.0, 500.0, 1000.0, 5000.0, 10000.0, ];
145
146pub const DEFAULT_ENTRIES_RATE_BUCKETS: &[f64] = &[
149 1.0, 10.0, 50.0, 100.0, 500.0, 1000.0, 5000.0, 10000.0, ];
158
159pub const DEFAULT_DURATION_SECONDS_BUCKETS: &[f64] = &[
162 0.001, 0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0, ];
175
176pub const DEFAULT_TTFB_BUCKETS: &[f64] = &[
179 0.001, 0.01, 0.025, 0.05, 0.1, 0.2, 0.4, 0.8, 1.6, 3.2, ];
190
191pub static LABEL_SCHEME: &str = "scheme";
193pub static LABEL_NAMESPACE: &str = "namespace";
195pub static LABEL_ROOT: &str = "root";
197pub static LABEL_OPERATION: &str = "operation";
199pub static LABEL_ERROR: &str = "error";
201pub static LABEL_STATUS_CODE: &str = "status_code";
203pub static LABEL_SERVICE_OPERATION: &str = "service_operation";
205
206#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
212pub struct MetricLabels {
213 pub scheme: &'static str,
216 pub namespace: Arc<str>,
219 pub root: Arc<str>,
222 pub operation: &'static str,
225 pub error: Option<ErrorKind>,
229 pub status_code: Option<StatusCode>,
233 pub service_operation: Option<&'static str>,
235}
236
237impl MetricLabels {
238 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 fn with_error(mut self, err: ErrorKind) -> Self {
251 self.error = Some(err);
252 self
253 }
254
255 fn with_status_code(mut self, code: StatusCode) -> Self {
257 self.status_code = Some(code);
258 self
259 }
260}
261
262#[non_exhaustive]
269#[derive(Clone, Copy, Debug)]
270pub enum MetricValue {
271 OperationBytes(u64),
274 OperationBytesRate(f64),
277 OperationEntries(u64),
280 OperationEntriesRate(f64),
283 OperationDurationSeconds(Duration),
286 OperationErrorsTotal,
289 OperationExecuting(isize),
292 OperationTtfbSeconds(Duration),
295 HttpExecuting(isize),
298 HttpRequestBytes(u64),
301 HttpRequestBytesRate(f64),
304 HttpRequestDurationSeconds(Duration),
307 HttpResponseBytes(u64),
310 HttpResponseBytesRate(f64),
313 HttpResponseDurationSeconds(Duration),
316 HttpConnectionErrorsTotal,
319 HttpStatusErrorsTotal,
322}
323
324impl MetricValue {
325 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 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 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
432pub trait MetricsIntercept: Debug + Clone + Send + Sync + Unpin + 'static {
436 fn observe(&self, labels: MetricLabels, value: MetricValue) {
438 let _ = (labels, value);
439 }
440}
441
442#[derive(Clone, Debug)]
452pub struct MetricsLayer<I: MetricsIntercept> {
453 interceptor: I,
454}
455
456impl<I: MetricsIntercept> MetricsLayer<I> {
457 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 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
501struct ExecutingGuard<I: MetricsIntercept> {
503 interceptor: Option<I>,
505 labels: MetricLabels,
506 start: Instant,
507 kind: ExecutingGuardKind,
508 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 Respond(StatusCode),
1416 ConnectionError,
1418 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}