foyer_common/
tracing.rs

1// Copyright 2025 foyer Project Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    future::Future,
17    ops::Deref,
18    pin::Pin,
19    sync::atomic::{AtomicU64, Ordering},
20    task::{Context, Poll},
21    time::Duration,
22};
23
24use fastrace::prelude::*;
25use pin_project::pin_project;
26use serde::{Deserialize, Serialize};
27
28/// Configurations for tracing.
29#[derive(Debug, Default)]
30pub struct TracingConfig {
31    /// Threshold for recording the hybrid cache `insert` and `insert_with_context` operation in us.
32    record_hybrid_insert_threshold_us: AtomicU64,
33    /// Threshold for recording the hybrid cache `get` operation in us.
34    record_hybrid_get_threshold_us: AtomicU64,
35    /// Threshold for recording the hybrid cache `obtain` operation in us.
36    record_hybrid_obtain_threshold_us: AtomicU64,
37    /// Threshold for recording the hybrid cache `remove` operation in us.
38    record_hybrid_remove_threshold_us: AtomicU64,
39    /// Threshold for recording the hybrid cache `fetch` operation in us.
40    record_hybrid_fetch_threshold_us: AtomicU64,
41}
42
43impl TracingConfig {
44    /// Update tracing config with options.
45    pub fn update(&self, options: TracingOptions) {
46        if let Some(threshold) = options.record_hybrid_insert_threshold {
47            self.record_hybrid_insert_threshold_us
48                .store(threshold.as_micros() as _, Ordering::Relaxed);
49        }
50
51        if let Some(threshold) = options.record_hybrid_get_threshold {
52            self.record_hybrid_get_threshold_us
53                .store(threshold.as_micros() as _, Ordering::Relaxed);
54        }
55
56        if let Some(threshold) = options.record_hybrid_obtain_threshold {
57            self.record_hybrid_obtain_threshold_us
58                .store(threshold.as_micros() as _, Ordering::Relaxed);
59        }
60
61        if let Some(threshold) = options.record_hybrid_remove_threshold {
62            self.record_hybrid_remove_threshold_us
63                .store(threshold.as_micros() as _, Ordering::Relaxed);
64        }
65
66        if let Some(threshold) = options.record_hybrid_fetch_threshold {
67            self.record_hybrid_fetch_threshold_us
68                .store(threshold.as_micros() as _, Ordering::Relaxed);
69        }
70    }
71
72    /// Threshold for recording the hybrid cache `insert` and `insert_with_context` operation.
73    pub fn record_hybrid_insert_threshold(&self) -> Duration {
74        Duration::from_micros(self.record_hybrid_insert_threshold_us.load(Ordering::Relaxed))
75    }
76
77    /// Threshold for recording the hybrid cache `get` operation.
78    pub fn record_hybrid_get_threshold(&self) -> Duration {
79        Duration::from_micros(self.record_hybrid_get_threshold_us.load(Ordering::Relaxed))
80    }
81
82    /// Threshold for recording the hybrid cache `obtain` operation.
83    pub fn record_hybrid_obtain_threshold(&self) -> Duration {
84        Duration::from_micros(self.record_hybrid_obtain_threshold_us.load(Ordering::Relaxed))
85    }
86
87    /// Threshold for recording the hybrid cache `remove` operation.
88    pub fn record_hybrid_remove_threshold(&self) -> Duration {
89        Duration::from_micros(self.record_hybrid_remove_threshold_us.load(Ordering::Relaxed))
90    }
91
92    /// Threshold for recording the hybrid cache `fetch` operation.
93    pub fn record_hybrid_fetch_threshold(&self) -> Duration {
94        Duration::from_micros(self.record_hybrid_fetch_threshold_us.load(Ordering::Relaxed))
95    }
96}
97
98/// Options for tracing.
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct TracingOptions {
101    /// Threshold for recording the hybrid cache `insert` and `insert_with_context` operation.
102    record_hybrid_insert_threshold: Option<Duration>,
103    /// Threshold for recording the hybrid cache `get` operation.
104    record_hybrid_get_threshold: Option<Duration>,
105    /// Threshold for recording the hybrid cache `obtain` operation.
106    record_hybrid_obtain_threshold: Option<Duration>,
107    /// Threshold for recording the hybrid cache `remove` operation.
108    record_hybrid_remove_threshold: Option<Duration>,
109    /// Threshold for recording the hybrid cache `fetch` operation.
110    record_hybrid_fetch_threshold: Option<Duration>,
111}
112
113impl Default for TracingOptions {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl TracingOptions {
120    /// Create an empty tracing options.
121    pub fn new() -> Self {
122        Self {
123            record_hybrid_insert_threshold: None,
124            record_hybrid_get_threshold: None,
125            record_hybrid_obtain_threshold: None,
126            record_hybrid_remove_threshold: None,
127            record_hybrid_fetch_threshold: None,
128        }
129    }
130
131    /// Set the threshold for recording the hybrid cache `insert` and `insert_with_context` operation.
132    pub fn with_record_hybrid_insert_threshold(mut self, threshold: Duration) -> Self {
133        self.record_hybrid_insert_threshold = Some(threshold);
134        self
135    }
136
137    /// Set the threshold for recording the hybrid cache `get` operation.
138    pub fn with_record_hybrid_get_threshold(mut self, threshold: Duration) -> Self {
139        self.record_hybrid_get_threshold = Some(threshold);
140        self
141    }
142
143    /// Set the threshold for recording the hybrid cache `obtain` operation.
144    pub fn with_record_hybrid_obtain_threshold(mut self, threshold: Duration) -> Self {
145        self.record_hybrid_obtain_threshold = Some(threshold);
146        self
147    }
148
149    /// Set the threshold for recording the hybrid cache `remove` operation.
150    pub fn with_record_hybrid_remove_threshold(mut self, threshold: Duration) -> Self {
151        self.record_hybrid_remove_threshold = Some(threshold);
152        self
153    }
154
155    /// Set the threshold for recording the hybrid cache `fetch` operation.
156    pub fn with_record_hybrid_fetch_threshold(mut self, threshold: Duration) -> Self {
157        self.record_hybrid_fetch_threshold = Some(threshold);
158        self
159    }
160}
161
162/// [`InRootSpan`] provides similar features like [`fastrace::future::InSpan`] with more controls.
163#[pin_project]
164pub struct InRootSpan<F> {
165    #[pin]
166    inner: F,
167
168    root: Option<Span>,
169    threshold: Option<Duration>,
170}
171
172impl<F> InRootSpan<F> {
173    /// Create a traced future with the given root span.
174    pub fn new(inner: F, root: Span) -> Self
175    where
176        F: Future,
177    {
178        Self {
179            inner,
180            root: Some(root),
181            threshold: None,
182        }
183    }
184
185    /// Set record threshold for the root span.
186    ///
187    /// If the duration of the root span is lower than the threshold, the root span will not be recorded.
188    pub fn with_threshold(mut self, threshold: Duration) -> Self {
189        self.threshold = Some(threshold);
190        self
191    }
192}
193
194impl<F> Future for InRootSpan<F>
195where
196    F: Future,
197{
198    type Output = F::Output;
199
200    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
201        let this = self.project();
202
203        let _guard = this.root.as_ref().map(|s| s.set_local_parent());
204        let res = match this.inner.poll(cx) {
205            Poll::Ready(res) => res,
206            Poll::Pending => return Poll::Pending,
207        };
208
209        let root = this.root.take().unwrap();
210
211        if let (Some(elapsed), Some(threshold)) = (root.elapsed(), this.threshold.as_ref()) {
212            if &elapsed < threshold {
213                root.cancel();
214            }
215        }
216
217        Poll::Ready(res)
218    }
219}
220
221impl<F> Deref for InRootSpan<F> {
222    type Target = F;
223
224    fn deref(&self) -> &Self::Target {
225        &self.inner
226    }
227}