1use serde::{Deserialize, Serialize};
29use std::sync::atomic::{AtomicU64, Ordering};
30use std::time::Duration;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub struct SpanId(pub u64);
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38pub struct TracerQueryKey {
39 pub query_type: &'static str,
41 pub cache_key_debug: String,
43}
44
45impl TracerQueryKey {
46 #[inline]
48 pub fn new(query_type: &'static str, cache_key_debug: impl Into<String>) -> Self {
49 Self {
50 query_type,
51 cache_key_debug: cache_key_debug.into(),
52 }
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58pub struct TracerAssetKey {
59 pub asset_type: &'static str,
61 pub key_debug: String,
63}
64
65impl TracerAssetKey {
66 #[inline]
68 pub fn new(asset_type: &'static str, key_debug: impl Into<String>) -> Self {
69 Self {
70 asset_type,
71 key_debug: key_debug.into(),
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum ExecutionResult {
79 Changed,
81 Unchanged,
83 CacheHit,
85 Suspended,
87 CycleDetected,
89 Error { message: String },
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
95pub enum TracerAssetState {
96 Loading,
98 Ready,
100 NotFound,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum InvalidationReason {
107 DependencyChanged { dep: TracerQueryKey },
109 AssetChanged { asset: TracerAssetKey },
111 ManualInvalidation,
113 AssetRemoved { asset: TracerAssetKey },
115}
116
117pub trait Tracer: Send + Sync + 'static {
131 fn new_span_id(&self) -> SpanId;
135
136 #[inline]
138 fn on_query_start(&self, _span_id: SpanId, _query: TracerQueryKey) {}
139
140 #[inline]
142 fn on_cache_check(&self, _span_id: SpanId, _query: TracerQueryKey, _valid: bool) {}
143
144 #[inline]
146 fn on_query_end(
147 &self,
148 _span_id: SpanId,
149 _query: TracerQueryKey,
150 _result: ExecutionResult,
151 _duration: Duration,
152 ) {
153 }
154
155 #[inline]
157 fn on_dependency_registered(
158 &self,
159 _span_id: SpanId,
160 _parent: TracerQueryKey,
161 _dependency: TracerQueryKey,
162 ) {
163 }
164
165 #[inline]
167 fn on_asset_dependency_registered(
168 &self,
169 _span_id: SpanId,
170 _parent: TracerQueryKey,
171 _asset: TracerAssetKey,
172 ) {
173 }
174
175 #[inline]
177 fn on_early_cutoff_check(
178 &self,
179 _span_id: SpanId,
180 _query: TracerQueryKey,
181 _output_changed: bool,
182 ) {
183 }
184
185 #[inline]
187 fn on_asset_requested(&self, _asset: TracerAssetKey, _state: TracerAssetState) {}
188
189 #[inline]
191 fn on_asset_resolved(&self, _asset: TracerAssetKey, _changed: bool) {}
192
193 #[inline]
195 fn on_asset_invalidated(&self, _asset: TracerAssetKey) {}
196
197 #[inline]
199 fn on_query_invalidated(&self, _query: TracerQueryKey, _reason: InvalidationReason) {}
200
201 #[inline]
203 fn on_cycle_detected(&self, _path: Vec<TracerQueryKey>) {}
204
205 #[inline]
207 fn on_missing_dependency(&self, _query: TracerQueryKey, _dependency_description: String) {}
208}
209
210pub struct NoopTracer;
214
215static NOOP_SPAN_COUNTER: AtomicU64 = AtomicU64::new(1);
217
218impl Tracer for NoopTracer {
219 #[inline(always)]
220 fn new_span_id(&self) -> SpanId {
221 SpanId(NOOP_SPAN_COUNTER.fetch_add(1, Ordering::Relaxed))
222 }
223 }
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use std::sync::atomic::AtomicUsize;
230 use std::sync::Arc;
231
232 struct CountingTracer {
233 start_count: AtomicUsize,
234 end_count: AtomicUsize,
235 }
236
237 impl CountingTracer {
238 fn new() -> Self {
239 Self {
240 start_count: AtomicUsize::new(0),
241 end_count: AtomicUsize::new(0),
242 }
243 }
244 }
245
246 impl Tracer for CountingTracer {
247 fn new_span_id(&self) -> SpanId {
248 SpanId(1)
249 }
250
251 fn on_query_start(&self, _span_id: SpanId, _query: TracerQueryKey) {
252 self.start_count.fetch_add(1, Ordering::Relaxed);
253 }
254
255 fn on_query_end(
256 &self,
257 _span_id: SpanId,
258 _query: TracerQueryKey,
259 _result: ExecutionResult,
260 _duration: Duration,
261 ) {
262 self.end_count.fetch_add(1, Ordering::Relaxed);
263 }
264 }
265
266 #[test]
267 fn test_noop_tracer_span_id() {
268 let tracer = NoopTracer;
269 let id1 = tracer.new_span_id();
270 let id2 = tracer.new_span_id();
271 assert_ne!(id1, id2);
272 }
273
274 #[test]
275 fn test_counting_tracer() {
276 let tracer = CountingTracer::new();
277 let key = TracerQueryKey::new("TestQuery", "()");
278
279 tracer.on_query_start(SpanId(1), key.clone());
280 tracer.on_query_start(SpanId(2), key.clone());
281 tracer.on_query_end(
282 SpanId(1),
283 key,
284 ExecutionResult::Changed,
285 Duration::from_millis(10),
286 );
287
288 assert_eq!(tracer.start_count.load(Ordering::Relaxed), 2);
289 assert_eq!(tracer.end_count.load(Ordering::Relaxed), 1);
290 }
291
292 #[test]
293 fn test_tracer_is_send_sync() {
294 fn assert_send_sync<T: Send + Sync>() {}
295 assert_send_sync::<NoopTracer>();
296 assert_send_sync::<Arc<CountingTracer>>();
297 }
298}