1use serde::{Deserialize, Serialize};
29use std::sync::atomic::{AtomicU64, Ordering};
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub struct SpanId(pub u64);
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub struct TracerQueryKey {
38 pub query_type: &'static str,
40 pub cache_key_debug: String,
42}
43
44impl TracerQueryKey {
45 #[inline]
47 pub fn new(query_type: &'static str, cache_key_debug: impl Into<String>) -> Self {
48 Self {
49 query_type,
50 cache_key_debug: cache_key_debug.into(),
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash)]
57pub struct TracerAssetKey {
58 pub asset_type: &'static str,
60 pub key_debug: String,
62}
63
64impl TracerAssetKey {
65 #[inline]
67 pub fn new(asset_type: &'static str, key_debug: impl Into<String>) -> Self {
68 Self {
69 asset_type,
70 key_debug: key_debug.into(),
71 }
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum ExecutionResult {
78 Changed,
80 Unchanged,
82 CacheHit,
84 Suspended,
86 CycleDetected,
88 Error { message: String },
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum TracerAssetState {
95 Loading,
97 Ready,
99 NotFound,
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
105pub enum InvalidationReason {
106 DependencyChanged { dep: TracerQueryKey },
108 AssetChanged { asset: TracerAssetKey },
110 ManualInvalidation,
112 AssetRemoved { asset: TracerAssetKey },
114}
115
116pub trait Tracer: Send + Sync + 'static {
130 fn new_span_id(&self) -> SpanId;
134
135 #[inline]
137 fn on_query_start(&self, _span_id: SpanId, _query: TracerQueryKey) {}
138
139 #[inline]
141 fn on_cache_check(&self, _span_id: SpanId, _query: TracerQueryKey, _valid: bool) {}
142
143 #[inline]
145 fn on_query_end(&self, _span_id: SpanId, _query: TracerQueryKey, _result: ExecutionResult) {}
146
147 #[inline]
149 fn on_dependency_registered(
150 &self,
151 _span_id: SpanId,
152 _parent: TracerQueryKey,
153 _dependency: TracerQueryKey,
154 ) {
155 }
156
157 #[inline]
159 fn on_asset_dependency_registered(
160 &self,
161 _span_id: SpanId,
162 _parent: TracerQueryKey,
163 _asset: TracerAssetKey,
164 ) {
165 }
166
167 #[inline]
169 fn on_early_cutoff_check(
170 &self,
171 _span_id: SpanId,
172 _query: TracerQueryKey,
173 _output_changed: bool,
174 ) {
175 }
176
177 #[inline]
179 fn on_asset_requested(&self, _asset: TracerAssetKey, _state: TracerAssetState) {}
180
181 #[inline]
183 fn on_asset_resolved(&self, _asset: TracerAssetKey, _changed: bool) {}
184
185 #[inline]
187 fn on_asset_invalidated(&self, _asset: TracerAssetKey) {}
188
189 #[inline]
191 fn on_query_invalidated(&self, _query: TracerQueryKey, _reason: InvalidationReason) {}
192
193 #[inline]
195 fn on_cycle_detected(&self, _path: Vec<TracerQueryKey>) {}
196
197 #[inline]
199 fn on_missing_dependency(&self, _query: TracerQueryKey, _dependency_description: String) {}
200}
201
202pub struct NoopTracer;
206
207static NOOP_SPAN_COUNTER: AtomicU64 = AtomicU64::new(1);
209
210impl Tracer for NoopTracer {
211 #[inline(always)]
212 fn new_span_id(&self) -> SpanId {
213 SpanId(NOOP_SPAN_COUNTER.fetch_add(1, Ordering::Relaxed))
214 }
215 }
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use std::sync::atomic::AtomicUsize;
222 use std::sync::Arc;
223
224 struct CountingTracer {
225 start_count: AtomicUsize,
226 end_count: AtomicUsize,
227 }
228
229 impl CountingTracer {
230 fn new() -> Self {
231 Self {
232 start_count: AtomicUsize::new(0),
233 end_count: AtomicUsize::new(0),
234 }
235 }
236 }
237
238 impl Tracer for CountingTracer {
239 fn new_span_id(&self) -> SpanId {
240 SpanId(1)
241 }
242
243 fn on_query_start(&self, _span_id: SpanId, _query: TracerQueryKey) {
244 self.start_count.fetch_add(1, Ordering::Relaxed);
245 }
246
247 fn on_query_end(&self, _span_id: SpanId, _query: TracerQueryKey, _result: ExecutionResult) {
248 self.end_count.fetch_add(1, Ordering::Relaxed);
249 }
250 }
251
252 #[test]
253 fn test_noop_tracer_span_id() {
254 let tracer = NoopTracer;
255 let id1 = tracer.new_span_id();
256 let id2 = tracer.new_span_id();
257 assert_ne!(id1, id2);
258 }
259
260 #[test]
261 fn test_counting_tracer() {
262 let tracer = CountingTracer::new();
263 let key = TracerQueryKey::new("TestQuery", "()");
264
265 tracer.on_query_start(SpanId(1), key.clone());
266 tracer.on_query_start(SpanId(2), key.clone());
267 tracer.on_query_end(SpanId(1), key, ExecutionResult::Changed);
268
269 assert_eq!(tracer.start_count.load(Ordering::Relaxed), 2);
270 assert_eq!(tracer.end_count.load(Ordering::Relaxed), 1);
271 }
272
273 #[test]
274 fn test_tracer_is_send_sync() {
275 fn assert_send_sync<T: Send + Sync>() {}
276 assert_send_sync::<NoopTracer>();
277 assert_send_sync::<Arc<CountingTracer>>();
278 }
279}