jugar_probar/perf/
trace.rs1use super::span::{ActiveSpan, SharedTracerState, Span, SpanGuard, TracerState};
6use serde::{Deserialize, Serialize};
7use std::cell::RefCell;
8use std::rc::Rc;
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct TraceConfig {
14 pub sample_rate: u32,
16 pub capture_memory: bool,
18 pub capture_frames: bool,
20 pub max_spans: usize,
22}
23
24impl Default for TraceConfig {
25 fn default() -> Self {
26 Self {
27 sample_rate: super::DEFAULT_SAMPLE_RATE,
28 capture_memory: false,
29 capture_frames: true,
30 max_spans: 100_000,
31 }
32 }
33}
34
35impl TraceConfig {
36 #[must_use]
38 pub fn with_sample_rate(mut self, rate: u32) -> Self {
39 self.sample_rate = rate;
40 self
41 }
42
43 #[must_use]
45 pub fn with_memory_tracking(mut self, enabled: bool) -> Self {
46 self.capture_memory = enabled;
47 self
48 }
49
50 #[must_use]
52 pub fn with_frame_capture(mut self, enabled: bool) -> Self {
53 self.capture_frames = enabled;
54 self
55 }
56
57 #[must_use]
59 pub fn with_max_spans(mut self, max: usize) -> Self {
60 self.max_spans = max;
61 self
62 }
63}
64
65#[derive(Debug)]
67pub struct Tracer {
68 config: TraceConfig,
69 recording: bool,
70 state: SharedTracerState,
71}
72
73impl std::fmt::Debug for TracerState {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.debug_struct("TracerState")
76 .field("active_spans", &self.active_spans.len())
77 .field("completed_spans", &self.completed_spans.len())
78 .field("current_span", &self.current_span)
79 .finish()
80 }
81}
82
83impl Default for Tracer {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89impl Tracer {
90 #[must_use]
92 pub fn new() -> Self {
93 Self::with_config(TraceConfig::default())
94 }
95
96 #[must_use]
98 pub fn with_config(config: TraceConfig) -> Self {
99 Self {
100 config,
101 recording: false,
102 state: Rc::new(RefCell::new(TracerState::new())),
103 }
104 }
105
106 #[must_use]
108 pub fn config(&self) -> &TraceConfig {
109 &self.config
110 }
111
112 #[must_use]
114 pub fn is_recording(&self) -> bool {
115 self.recording
116 }
117
118 pub fn start(&mut self) {
120 self.recording = true;
121 let mut state = self.state.borrow_mut();
122 state.trace_start = Some(Instant::now());
123 state.active_spans.clear();
124 state.completed_spans.clear();
125 state.current_span = None;
126 }
127
128 pub fn stop(&mut self) -> Trace {
130 self.recording = false;
131
132 let mut state = self.state.borrow_mut();
133
134 let now = state.elapsed_ns();
136 let active: Vec<_> = state.active_spans.drain().collect();
137 for (_id, mut active_span) in active {
138 active_span.span.close(now);
139 state.completed_spans.push(active_span.span);
140 }
141
142 let duration = state.trace_start.map(|s| s.elapsed());
143
144 Trace {
145 spans: std::mem::take(&mut state.completed_spans),
146 duration,
147 config: self.config.clone(),
148 }
149 }
150
151 pub fn span(&self, name: &str) -> SpanGuard {
153 let mut state = self.state.borrow_mut();
154 let start_ns = state.elapsed_ns();
155 let start_instant = Instant::now();
156
157 let mut active = ActiveSpan::new(name, start_ns, start_instant);
158
159 if let Some(parent_id) = state.current_span {
161 active.span.parent = Some(parent_id);
162 }
163
164 let span_id = active.span.id;
165 state.active_spans.insert(span_id, active);
166 state.current_span = Some(span_id);
167
168 drop(state); SpanGuard::new(Rc::clone(&self.state), span_id, self.config.max_spans)
171 }
172
173 #[must_use]
175 pub fn active_span_count(&self) -> usize {
176 self.state.borrow().active_spans.len()
177 }
178
179 #[must_use]
181 pub fn completed_span_count(&self) -> usize {
182 self.state.borrow().completed_spans.len()
183 }
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct Trace {
189 pub spans: Vec<Span>,
191 #[serde(skip)]
193 pub duration: Option<Duration>,
194 pub config: TraceConfig,
196}
197
198impl Trace {
199 #[must_use]
201 pub fn span_count(&self) -> usize {
202 self.spans.len()
203 }
204
205 #[must_use]
207 pub fn duration(&self) -> Option<Duration> {
208 self.duration
209 }
210
211 #[must_use]
213 pub fn spans_by_name(&self, name: &str) -> Vec<&Span> {
214 self.spans.iter().filter(|s| s.name == name).collect()
215 }
216
217 #[must_use]
219 pub fn root_spans(&self) -> Vec<&Span> {
220 self.spans.iter().filter(|s| s.parent.is_none()).collect()
221 }
222
223 #[must_use]
225 pub fn statistics_for(&self, name: &str) -> Option<super::metrics::Statistics> {
226 let durations: Vec<f64> = self
227 .spans_by_name(name)
228 .iter()
229 .filter_map(|s| s.duration_ns())
230 .map(|ns| ns as f64 / 1_000_000.0) .collect();
232
233 if durations.is_empty() {
234 None
235 } else {
236 Some(super::metrics::Statistics::from_values(&durations))
237 }
238 }
239
240 #[must_use]
242 pub fn is_empty(&self) -> bool {
243 self.spans.is_empty()
244 }
245}
246
247#[cfg(test)]
248#[allow(clippy::unwrap_used, clippy::expect_used)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_trace_config_default() {
254 let config = TraceConfig::default();
255 assert_eq!(config.sample_rate, super::super::DEFAULT_SAMPLE_RATE);
256 assert!(!config.capture_memory);
257 assert!(config.capture_frames);
258 }
259
260 #[test]
261 fn test_trace_config_builders() {
262 let config = TraceConfig::default()
263 .with_sample_rate(500)
264 .with_memory_tracking(true)
265 .with_max_spans(1000);
266
267 assert_eq!(config.sample_rate, 500);
268 assert!(config.capture_memory);
269 assert_eq!(config.max_spans, 1000);
270 }
271
272 #[test]
273 fn test_tracer_new() {
274 let tracer = Tracer::new();
275 assert!(!tracer.is_recording());
276 }
277
278 #[test]
279 fn test_tracer_start_stop() {
280 let mut tracer = Tracer::new();
281
282 tracer.start();
283 assert!(tracer.is_recording());
284
285 let trace = tracer.stop();
286 assert!(!tracer.is_recording());
287 assert!(trace.spans.is_empty());
288 }
289
290 #[test]
291 fn test_tracer_span() {
292 let mut tracer = Tracer::new();
293 tracer.start();
294
295 {
296 let _guard = tracer.span("test");
297 assert_eq!(tracer.active_span_count(), 1);
298 }
299
300 assert_eq!(tracer.active_span_count(), 0);
301 assert_eq!(tracer.completed_span_count(), 1);
302 }
303
304 #[test]
305 fn test_tracer_nested_spans() {
306 let mut tracer = Tracer::new();
307 tracer.start();
308
309 {
310 let _outer = tracer.span("outer");
311 {
312 let _inner = tracer.span("inner");
313 }
314 }
315
316 let trace = tracer.stop();
317 assert_eq!(trace.span_count(), 2);
318
319 let inner = trace.spans_by_name("inner")[0];
321 let outer = trace.spans_by_name("outer")[0];
322 assert_eq!(inner.parent, Some(outer.id));
323 }
324
325 #[test]
326 fn test_trace_spans_by_name() {
327 let mut tracer = Tracer::new();
328 tracer.start();
329
330 {
331 let _s1 = tracer.span("a");
332 }
333 {
334 let _s2 = tracer.span("b");
335 }
336 {
337 let _s3 = tracer.span("a");
338 }
339
340 let trace = tracer.stop();
341 assert_eq!(trace.spans_by_name("a").len(), 2);
342 assert_eq!(trace.spans_by_name("b").len(), 1);
343 }
344
345 #[test]
346 fn test_trace_root_spans() {
347 let mut tracer = Tracer::new();
348 tracer.start();
349
350 {
351 let _outer = tracer.span("outer");
352 {
353 let _inner = tracer.span("inner");
354 }
355 }
356 {
357 let _other = tracer.span("other");
358 }
359
360 let trace = tracer.stop();
361 let roots = trace.root_spans();
362 assert_eq!(roots.len(), 2); }
364
365 #[test]
370 fn test_trace_config_with_frame_capture() {
371 let config = TraceConfig::default().with_frame_capture(false);
372 assert!(!config.capture_frames);
373
374 let config2 = TraceConfig::default().with_frame_capture(true);
375 assert!(config2.capture_frames);
376 }
377
378 #[test]
379 fn test_tracer_default() {
380 let tracer = Tracer::default();
381 assert!(!tracer.is_recording());
382 assert_eq!(tracer.active_span_count(), 0);
383 assert_eq!(tracer.completed_span_count(), 0);
384 }
385
386 #[test]
387 fn test_tracer_config_getter() {
388 let config = TraceConfig::default().with_sample_rate(1000);
389 let tracer = Tracer::with_config(config);
390 assert_eq!(tracer.config().sample_rate, 1000);
391 }
392
393 #[test]
394 fn test_trace_is_empty() {
395 let mut tracer = Tracer::new();
396 tracer.start();
397 let trace = tracer.stop();
398 assert!(trace.is_empty());
399
400 tracer.start();
401 {
402 let _span = tracer.span("test");
403 }
404 let trace2 = tracer.stop();
405 assert!(!trace2.is_empty());
406 }
407
408 #[test]
409 fn test_trace_duration() {
410 let mut tracer = Tracer::new();
411 tracer.start();
412 std::thread::sleep(std::time::Duration::from_millis(10));
413 let trace = tracer.stop();
414
415 let duration = trace.duration();
416 assert!(duration.is_some());
417 assert!(duration.unwrap().as_millis() >= 10);
418 }
419
420 #[test]
421 fn test_trace_statistics_for_existing() {
422 let mut tracer = Tracer::new();
423 tracer.start();
424
425 for _ in 0..5 {
426 let _span = tracer.span("repeated");
427 std::thread::sleep(std::time::Duration::from_micros(100));
428 }
429
430 let trace = tracer.stop();
431 let stats = trace.statistics_for("repeated");
432 assert!(stats.is_some());
433 let stats = stats.unwrap();
434 assert!(stats.count >= 5);
435 }
436
437 #[test]
438 fn test_trace_statistics_for_nonexistent() {
439 let mut tracer = Tracer::new();
440 tracer.start();
441 {
442 let _span = tracer.span("exists");
443 }
444 let trace = tracer.stop();
445
446 let stats = trace.statistics_for("does_not_exist");
447 assert!(stats.is_none());
448 }
449
450 #[test]
451 fn test_tracer_state_debug() {
452 let tracer = Tracer::new();
453 let state = tracer.state.borrow();
454 let debug_str = format!("{:?}", *state);
455 assert!(debug_str.contains("TracerState"));
456 assert!(debug_str.contains("active_spans"));
457 assert!(debug_str.contains("completed_spans"));
458 }
459
460 #[test]
461 fn test_tracer_stop_closes_active_spans() {
462 let mut tracer = Tracer::new();
463 tracer.start();
464
465 let _outer = tracer.span("outer");
467 let _inner = tracer.span("inner");
468 drop(_inner);
469 drop(_outer);
470
471 let trace = tracer.stop();
472 assert_eq!(trace.span_count(), 2);
474 }
475
476 #[test]
477 fn test_tracer_multiple_sessions() {
478 let mut tracer = Tracer::new();
479
480 tracer.start();
482 {
483 let _span = tracer.span("first_session");
484 }
485 let trace1 = tracer.stop();
486 assert_eq!(trace1.span_count(), 1);
487
488 tracer.start();
490 {
491 let _span = tracer.span("second_session");
492 }
493 let trace2 = tracer.stop();
494 assert_eq!(trace2.span_count(), 1);
495 assert_eq!(trace2.spans_by_name("first_session").len(), 0);
496 }
497
498 #[test]
499 fn test_trace_config_serialize_deserialize() {
500 let config = TraceConfig::default()
501 .with_sample_rate(500)
502 .with_memory_tracking(true)
503 .with_max_spans(5000);
504
505 let json = serde_json::to_string(&config).unwrap();
506 let deserialized: TraceConfig = serde_json::from_str(&json).unwrap();
507
508 assert_eq!(deserialized.sample_rate, 500);
509 assert!(deserialized.capture_memory);
510 assert_eq!(deserialized.max_spans, 5000);
511 }
512
513 #[test]
514 fn test_trace_serialize_deserialize() {
515 let mut tracer = Tracer::new();
516 tracer.start();
517 {
518 let _span = tracer.span("test_span");
519 }
520 let trace = tracer.stop();
521
522 let json = serde_json::to_string(&trace).unwrap();
523 let deserialized: Trace = serde_json::from_str(&json).unwrap();
524
525 assert_eq!(deserialized.span_count(), trace.span_count());
526 assert_eq!(deserialized.spans_by_name("test_span").len(), 1);
527 }
528
529 #[test]
530 fn test_deeply_nested_spans() {
531 let mut tracer = Tracer::new();
532 tracer.start();
533
534 {
535 let _l1 = tracer.span("level1");
536 {
537 let _l2 = tracer.span("level2");
538 {
539 let _l3 = tracer.span("level3");
540 {
541 let _l4 = tracer.span("level4");
542 }
543 }
544 }
545 }
546
547 let trace = tracer.stop();
548 assert_eq!(trace.span_count(), 4);
549
550 let l4_spans = trace.spans_by_name("level4");
552 assert_eq!(l4_spans.len(), 1);
553 let l4 = l4_spans[0];
554 assert!(l4.parent.is_some());
555
556 let l3_spans = trace.spans_by_name("level3");
557 let l3 = l3_spans[0];
558 assert_eq!(l4.parent, Some(l3.id));
559 }
560
561 #[test]
562 fn test_sibling_spans() {
563 let mut tracer = Tracer::new();
564 tracer.start();
565
566 {
567 let _parent = tracer.span("parent");
568 {
569 let _child1 = tracer.span("child");
570 }
571 {
572 let _child2 = tracer.span("child");
573 }
574 {
575 let _child3 = tracer.span("child");
576 }
577 }
578
579 let trace = tracer.stop();
580 let children = trace.spans_by_name("child");
581 assert_eq!(children.len(), 3);
582
583 let parent_spans = trace.spans_by_name("parent");
585 let parent_id = parent_spans[0].id;
586 for child in children {
587 assert_eq!(child.parent, Some(parent_id));
588 }
589 }
590
591 #[test]
592 fn test_span_count_after_stop() {
593 let mut tracer = Tracer::new();
594 tracer.start();
595 {
596 let _s = tracer.span("a");
597 }
598 assert_eq!(tracer.completed_span_count(), 1);
599 assert_eq!(tracer.active_span_count(), 0);
600
601 tracer.stop();
602 tracer.start();
604 assert_eq!(tracer.completed_span_count(), 0);
605 }
606}