LCOV - code coverage report
Current view: top level - libs/tracing-utils/src - perf_span.rs (source / functions) Coverage Total Hit
Test: 98683a8629f0f7f0031d02e04512998d589d76ea.info Lines: 0.0 % 46 0
Test Date: 2025-04-11 16:58:57 Functions: 0.0 % 69 0

            Line data    Source code
       1              : //! Crutch module to work around tracing infrastructure deficiencies
       2              : //!
       3              : //! We wish to collect granular request spans without impacting performance
       4              : //! by much. Ideally, we should have zero overhead for a sampling rate of 0.
       5              : //!
       6              : //! The approach taken by the pageserver crate is to use a completely different
       7              : //! span hierarchy for the performance spans. Spans are explicitly stored in
       8              : //! the request context and use a different [`tracing::Subscriber`] in order
       9              : //! to avoid expensive filtering.
      10              : //!
      11              : //! [`tracing::Span`] instances record their [`tracing::Dispatch`] and, implcitly,
      12              : //! their [`tracing::Subscriber`] at creation time. However, upon exiting the span,
      13              : //! the global default [`tracing::Dispatch`] is used. This is problematic if one
      14              : //! wishes to juggle different subscribers.
      15              : //!
      16              : //! In order to work around this, this module provides a [`PerfSpan`] type which
      17              : //! wraps a [`Span`] and sets the default subscriber when exiting the span. This
      18              : //! achieves the correct routing.
      19              : //!
      20              : //! There's also a modified version of [`tracing::Instrument`] which works with
      21              : //! [`PerfSpan`].
      22              : 
      23              : use core::{
      24              :     future::Future,
      25              :     marker::Sized,
      26              :     mem::ManuallyDrop,
      27              :     pin::Pin,
      28              :     task::{Context, Poll},
      29              : };
      30              : use pin_project_lite::pin_project;
      31              : use tracing::{Dispatch, span::Span};
      32              : 
      33              : #[derive(Debug, Clone)]
      34              : pub struct PerfSpan {
      35              :     inner: ManuallyDrop<Span>,
      36              :     dispatch: Dispatch,
      37              : }
      38              : 
      39              : #[must_use = "once a span has been entered, it should be exited"]
      40              : pub struct PerfSpanEntered<'a> {
      41              :     span: &'a PerfSpan,
      42              : }
      43              : 
      44              : impl PerfSpan {
      45            0 :     pub fn new(span: Span, dispatch: Dispatch) -> Self {
      46            0 :         Self {
      47            0 :             inner: ManuallyDrop::new(span),
      48            0 :             dispatch,
      49            0 :         }
      50            0 :     }
      51              : 
      52            0 :     pub fn enter(&self) -> PerfSpanEntered {
      53            0 :         if let Some(ref id) = self.inner.id() {
      54            0 :             self.dispatch.enter(id);
      55            0 :         }
      56              : 
      57            0 :         PerfSpanEntered { span: self }
      58            0 :     }
      59              : 
      60            0 :     pub fn inner(&self) -> &Span {
      61            0 :         &self.inner
      62            0 :     }
      63              : }
      64              : 
      65              : impl Drop for PerfSpan {
      66            0 :     fn drop(&mut self) {
      67            0 :         // Bring the desired dispatch into scope before explicitly calling
      68            0 :         // the span destructor. This routes the span exit to the correct
      69            0 :         // [`tracing::Subscriber`].
      70            0 :         let _dispatch_guard = tracing::dispatcher::set_default(&self.dispatch);
      71            0 :         // SAFETY: ManuallyDrop in Drop implementation
      72            0 :         unsafe { ManuallyDrop::drop(&mut self.inner) }
      73            0 :     }
      74              : }
      75              : 
      76              : impl Drop for PerfSpanEntered<'_> {
      77            0 :     fn drop(&mut self) {
      78            0 :         assert!(self.span.inner.id().is_some());
      79              : 
      80            0 :         let _dispatch_guard = tracing::dispatcher::set_default(&self.span.dispatch);
      81            0 :         self.span.dispatch.exit(&self.span.inner.id().unwrap());
      82            0 :     }
      83              : }
      84              : 
      85              : pub trait PerfInstrument: Sized {
      86            0 :     fn instrument(self, span: PerfSpan) -> PerfInstrumented<Self> {
      87            0 :         PerfInstrumented {
      88            0 :             inner: ManuallyDrop::new(self),
      89            0 :             span,
      90            0 :         }
      91            0 :     }
      92              : }
      93              : 
      94              : pin_project! {
      95              :     #[project = PerfInstrumentedProj]
      96              :     #[derive(Debug, Clone)]
      97              :     #[must_use = "futures do nothing unless you `.await` or poll them"]
      98              :     pub struct PerfInstrumented<T> {
      99              :         // `ManuallyDrop` is used here to to enter instrument `Drop` by entering
     100              :         // `Span` and executing `ManuallyDrop::drop`.
     101              :         #[pin]
     102              :         inner: ManuallyDrop<T>,
     103              :         span: PerfSpan,
     104              :     }
     105              : 
     106              :     impl<T> PinnedDrop for PerfInstrumented<T> {
     107              :         fn drop(this: Pin<&mut Self>) {
     108              :             let this = this.project();
     109              :             let _enter = this.span.enter();
     110              :             // SAFETY: 1. `Pin::get_unchecked_mut()` is safe, because this isn't
     111              :             //             different from wrapping `T` in `Option` and calling
     112              :             //             `Pin::set(&mut this.inner, None)`, except avoiding
     113              :             //             additional memory overhead.
     114              :             //         2. `ManuallyDrop::drop()` is safe, because
     115              :             //            `PinnedDrop::drop()` is guaranteed to be called only
     116              :             //            once.
     117              :             unsafe { ManuallyDrop::drop(this.inner.get_unchecked_mut()) }
     118              :         }
     119              :     }
     120              : }
     121              : 
     122              : impl<'a, T> PerfInstrumentedProj<'a, T> {
     123              :     /// Get a mutable reference to the [`Span`] a pinned mutable reference to
     124              :     /// the wrapped type.
     125            0 :     fn span_and_inner_pin_mut(self) -> (&'a mut PerfSpan, Pin<&'a mut T>) {
     126            0 :         // SAFETY: As long as `ManuallyDrop<T>` does not move, `T` won't move
     127            0 :         //         and `inner` is valid, because `ManuallyDrop::drop` is called
     128            0 :         //         only inside `Drop` of the `Instrumented`.
     129            0 :         let inner = unsafe { self.inner.map_unchecked_mut(|v| &mut **v) };
     130            0 :         (self.span, inner)
     131            0 :     }
     132              : }
     133              : 
     134              : impl<T: Future> Future for PerfInstrumented<T> {
     135              :     type Output = T::Output;
     136              : 
     137            0 :     fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
     138            0 :         let (span, inner) = self.project().span_and_inner_pin_mut();
     139            0 :         let _enter = span.enter();
     140            0 :         inner.poll(cx)
     141            0 :     }
     142              : }
     143              : 
     144              : impl<T: Sized> PerfInstrument for T {}
        

Generated by: LCOV version 2.1-beta