1#![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
96pub const DEFAULT_BYTES_BUCKETS: &[f64] = &[
99 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, ];
110
111pub const DEFAULT_BYTES_RATE_BUCKETS: &[f64] = &[
117 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, ];
133
134pub const DEFAULT_ENTRIES_BUCKETS: &[f64] = &[
137 1.0, 5.0, 10.0, 50.0, 100.0, 500.0, 1000.0, 5000.0, 10000.0, ];
147
148pub const DEFAULT_ENTRIES_RATE_BUCKETS: &[f64] = &[
151 1.0, 10.0, 50.0, 100.0, 500.0, 1000.0, 5000.0, 10000.0, ];
160
161pub const DEFAULT_DURATION_SECONDS_BUCKETS: &[f64] = &[
164 0.001, 0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0, ];
177
178pub const DEFAULT_TTFB_BUCKETS: &[f64] = &[
181 0.001, 0.01, 0.025, 0.05, 0.1, 0.2, 0.4, 0.8, 1.6, 3.2, ];
192
193pub static LABEL_SCHEME: &str = "scheme";
195pub static LABEL_NAMESPACE: &str = "namespace";
197pub static LABEL_ROOT: &str = "root";
199pub static LABEL_OPERATION: &str = "operation";
201pub static LABEL_ERROR: &str = "error";
203pub static LABEL_STATUS_CODE: &str = "status_code";
205pub static LABEL_SERVICE_OPERATION: &str = "service_operation";
207
208#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
214pub struct MetricLabels {
215 pub scheme: &'static str,
218 pub namespace: Arc<str>,
221 pub root: Arc<str>,
224 pub operation: &'static str,
227 pub error: Option<ErrorKind>,
231 pub status_code: Option<StatusCode>,
235 pub service_operation: Option<&'static str>,
237}
238
239impl MetricLabels {
240 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 fn with_error(mut self, err: ErrorKind) -> Self {
253 self.error = Some(err);
254 self
255 }
256
257 fn with_status_code(mut self, code: StatusCode) -> Self {
259 self.status_code = Some(code);
260 self
261 }
262}
263
264#[non_exhaustive]
271#[derive(Clone, Copy, Debug)]
272pub enum MetricValue {
273 OperationBytes(u64),
276 OperationBytesRate(f64),
279 OperationEntries(u64),
282 OperationEntriesRate(f64),
285 OperationDurationSeconds(Duration),
288 OperationErrorsTotal,
291 OperationExecuting(isize),
294 OperationTtfbSeconds(Duration),
297 HttpExecuting(isize),
300 HttpRequestBytes(u64),
303 HttpRequestBytesRate(f64),
306 HttpRequestDurationSeconds(Duration),
309 HttpResponseBytes(u64),
312 HttpResponseBytesRate(f64),
315 HttpResponseDurationSeconds(Duration),
318 HttpConnectionErrorsTotal,
321 HttpStatusErrorsTotal,
324}
325
326impl MetricValue {
327 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 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 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
434pub trait MetricsIntercept: Debug + Clone + Send + Sync + Unpin + 'static {
438 fn observe(&self, labels: MetricLabels, value: MetricValue) {
440 let _ = (labels, value);
441 }
442}
443
444#[derive(Clone, Debug)]
446pub struct MetricsLayer<I: MetricsIntercept> {
447 interceptor: I,
448}
449
450impl<I: MetricsIntercept> MetricsLayer<I> {
451 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 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
495struct ExecutingGuard<I: MetricsIntercept> {
497 interceptor: Option<I>,
499 labels: MetricLabels,
500 start: Instant,
501 kind: ExecutingGuardKind,
502 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 Respond(StatusCode),
1410 ConnectionError,
1412 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}