Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/runtime/observatory.rs
Line
Count
Source
1
//! Actor observatory for live system introspection (RUCHY-0817)
2
//!
3
//! Provides comprehensive monitoring and debugging capabilities for the actor system,
4
//! including message tracing, deadlock detection, and performance analysis.
5
6
#[cfg(test)]
7
mod tests {
8
    use super::*;
9
    use crate::runtime::actor::{ActorSystem, Message};
10
    use std::sync::{Arc, Mutex};
11
    use std::time::Duration;
12
13
    // Helper functions for consistent test setup
14
17
    fn create_test_actor_system() -> Arc<Mutex<ActorSystem>> {
15
17
        ActorSystem::new()
16
17
    }
17
18
17
    fn create_test_config() -> ObservatoryConfig {
19
17
        ObservatoryConfig {
20
17
            max_traces: 100,
21
17
            trace_retention_seconds: 3600,
22
17
            enable_deadlock_detection: true,
23
17
            deadlock_check_interval_ms: 1000,
24
17
            enable_metrics: true,
25
17
            metrics_interval_ms: 5000,
26
17
            max_snapshots: 50,
27
17
        }
28
17
    }
29
30
13
    fn create_test_observatory() -> ActorObservatory {
31
13
        let system = create_test_actor_system();
32
13
        let config = create_test_config();
33
13
        ActorObservatory::new(system, config)
34
13
    }
35
36
25
    fn create_test_message_trace() -> MessageTrace {
37
25
        MessageTrace {
38
25
            trace_id: 12345,
39
25
            timestamp: current_timestamp(),
40
25
            source: Some(ActorId(1)),
41
25
            destination: ActorId(2),
42
25
            message: Message::User("test_message".to_string(), vec![]),
43
25
            status: MessageStatus::Queued,
44
25
            processing_duration_us: None,
45
25
            error: None,
46
25
            stack_depth: 1,
47
25
            correlation_id: Some("corr-123".to_string()),
48
25
        }
49
25
    }
50
51
3
    fn create_test_actor_snapshot() -> ActorSnapshot {
52
3
        ActorSnapshot {
53
3
            actor_id: ActorId(1),
54
3
            name: "test_actor".to_string(),
55
3
            timestamp: current_timestamp(),
56
3
            state: ActorState::Running,
57
3
            mailbox_size: 5,
58
3
            parent: Some(ActorId(0)),
59
3
            children: vec![ActorId(2), ActorId(3)],
60
3
            message_stats: MessageStats::default(),
61
3
            memory_usage: Some(1024),
62
3
        }
63
3
    }
64
65
3
    fn create_test_message_filter() -> MessageFilter {
66
3
        MessageFilter {
67
3
            name: "test_filter".to_string(),
68
3
            actor_id: Some(ActorId(1)),
69
3
            actor_name_pattern: Some("test_actor".to_string()),
70
3
            message_type_pattern: Some(".*message.*".to_string()),
71
3
            min_processing_time_us: None,
72
3
            failed_only: false,
73
3
            max_stack_depth: Some(10),
74
3
        }
75
3
    }
76
77
    // ========== Observatory Configuration Tests ==========
78
79
    #[test]
80
1
    fn test_observatory_config_default() {
81
1
        let config = ObservatoryConfig::default();
82
1
        assert_eq!(config.max_traces, 10000);
83
1
        assert_eq!(config.trace_retention_seconds, 3600);
84
1
        assert!(config.enable_deadlock_detection);
85
1
        assert_eq!(config.deadlock_check_interval_ms, 1000);
86
1
        assert!(config.enable_metrics);
87
1
        assert_eq!(config.metrics_interval_ms, 5000);
88
1
        assert_eq!(config.max_snapshots, 1000);
89
1
    }
90
91
    #[test]
92
1
    fn test_observatory_config_clone() {
93
1
        let config1 = create_test_config();
94
1
        let config2 = config1.clone();
95
1
        assert_eq!(config1.max_traces, config2.max_traces);
96
1
        assert_eq!(config1.enable_deadlock_detection, config2.enable_deadlock_detection);
97
1
    }
98
99
    #[test]
100
1
    fn test_observatory_config_debug() {
101
1
        let config = create_test_config();
102
1
        let debug_str = format!("{:?}", config);
103
1
        assert!(debug_str.contains("ObservatoryConfig"));
104
1
        assert!(debug_str.contains("max_traces"));
105
1
        assert!(debug_str.contains("enable_deadlock_detection"));
106
1
    }
107
108
    #[test]
109
1
    fn test_observatory_config_serialization() {
110
1
        let config = create_test_config();
111
1
        let json = serde_json::to_string(&config).unwrap();
112
1
        let deserialized: ObservatoryConfig = serde_json::from_str(&json).unwrap();
113
1
        assert_eq!(config.max_traces, deserialized.max_traces);
114
1
        assert_eq!(config.enable_metrics, deserialized.enable_metrics);
115
1
    }
116
117
    // ========== Observatory Creation and Setup Tests ==========
118
119
    #[test]
120
1
    fn test_observatory_creation() {
121
1
        let system = create_test_actor_system();
122
1
        let config = create_test_config();
123
1
        let observatory = ActorObservatory::new(system, config.clone());
124
        
125
1
        assert_eq!(observatory.config.max_traces, config.max_traces);
126
1
        assert!(observatory.filters.is_empty());
127
1
        assert!(observatory.start_time.elapsed() < Duration::from_secs(1));
128
1
    }
129
130
    #[test]
131
1
    fn test_observatory_with_default_config() {
132
1
        let system = create_test_actor_system();
133
1
        let config = ObservatoryConfig::default();
134
1
        let observatory = ActorObservatory::new(system, config);
135
        
136
1
        assert_eq!(observatory.config.max_traces, 10000);
137
1
        assert!(observatory.config.enable_deadlock_detection);
138
1
    }
139
140
    #[test]
141
1
    fn test_observatory_initialization_state() {
142
1
        let observatory = create_test_observatory();
143
        
144
        // Should start with empty state
145
1
        assert!(observatory.get_filters().is_empty());
146
1
        let metrics = observatory.metrics.lock().unwrap();
147
1
        assert_eq!(metrics.active_actors, 0);
148
1
        assert_eq!(metrics.total_messages_processed, 0);
149
1
    }
150
151
    // ========== Message Filter Management Tests ==========
152
153
    #[test]
154
1
    fn test_add_message_filter() {
155
1
        let mut observatory = create_test_observatory();
156
1
        let filter = create_test_message_filter();
157
        
158
1
        observatory.add_filter(filter.clone());
159
        
160
1
        assert_eq!(observatory.get_filters().len(), 1);
161
1
        assert_eq!(observatory.get_filters()[0].name, filter.name);
162
1
    }
163
164
    #[test]
165
1
    fn test_add_multiple_filters() {
166
1
        let mut observatory = create_test_observatory();
167
        
168
1
        let filter1 = MessageFilter {
169
1
            name: "filter1".to_string(),
170
1
            actor_id: None,
171
1
            actor_name_pattern: None,
172
1
            message_type_pattern: Some("type1".to_string()),
173
1
            min_processing_time_us: None,
174
1
            failed_only: false,
175
1
            max_stack_depth: None,
176
1
        };
177
        
178
1
        let filter2 = MessageFilter {
179
1
            name: "filter2".to_string(),
180
1
            actor_id: Some(ActorId(2)),
181
1
            actor_name_pattern: Some("actor2".to_string()),
182
1
            message_type_pattern: None,
183
1
            min_processing_time_us: Some(1000),
184
1
            failed_only: true,
185
1
            max_stack_depth: Some(5),
186
1
        };
187
        
188
1
        observatory.add_filter(filter1);
189
1
        observatory.add_filter(filter2);
190
        
191
1
        assert_eq!(observatory.get_filters().len(), 2);
192
1
        assert_eq!(observatory.get_filters()[0].name, "filter1");
193
1
        assert_eq!(observatory.get_filters()[1].name, "filter2");
194
1
    }
195
196
    #[test]
197
1
    fn test_remove_message_filter() {
198
1
        let mut observatory = create_test_observatory();
199
1
        let filter = create_test_message_filter();
200
        
201
1
        observatory.add_filter(filter);
202
1
        assert_eq!(observatory.get_filters().len(), 1);
203
        
204
1
        let removed = observatory.remove_filter("test_filter");
205
1
        assert!(removed);
206
1
        assert!(observatory.get_filters().is_empty());
207
1
    }
208
209
    #[test]
210
1
    fn test_remove_nonexistent_filter() {
211
1
        let mut observatory = create_test_observatory();
212
1
        let removed = observatory.remove_filter("nonexistent");
213
1
        assert!(!removed);
214
1
    }
215
216
    #[test]
217
1
    fn test_get_filters_empty() {
218
1
        let observatory = create_test_observatory();
219
1
        assert!(observatory.get_filters().is_empty());
220
1
    }
221
222
    // ========== Message Tracing Tests ==========
223
224
    #[test]
225
1
    fn test_trace_message() {
226
1
        let observatory = create_test_observatory();
227
1
        let trace = create_test_message_trace();
228
        
229
1
        let result = observatory.trace_message(trace.clone());
230
1
        assert!(result.is_ok());
231
        
232
1
        let traces = observatory.get_traces(None, None).unwrap();
233
1
        assert_eq!(traces.len(), 1);
234
1
        assert_eq!(traces[0].trace_id, trace.trace_id);
235
1
    }
236
237
    #[test]
238
1
    fn test_trace_message_with_limit() {
239
1
        let mut observatory = create_test_observatory();
240
1
        observatory.config.max_traces = 2;
241
        
242
        // Add 3 traces, should only keep 2 most recent
243
4
        for 
i3
in 0..3 {
244
3
            let mut trace = create_test_message_trace();
245
3
            trace.trace_id = i as u64;
246
3
            observatory.trace_message(trace).unwrap();
247
3
        }
248
        
249
1
        let traces = observatory.get_traces(None, None).unwrap();
250
1
        assert_eq!(traces.len(), 2);
251
1
        assert_eq!(traces[0].trace_id, 1); // First trace should be evicted
252
1
        assert_eq!(traces[1].trace_id, 2);
253
1
    }
254
255
    #[test]
256
1
    fn test_trace_message_age_retention() {
257
1
        let mut observatory = create_test_observatory();
258
1
        observatory.config.trace_retention_seconds = 1; // 1 second retention
259
        
260
        // Add an old trace
261
1
        let mut old_trace = create_test_message_trace();
262
1
        old_trace.timestamp = current_timestamp() - 3600; // 1 hour ago
263
1
        observatory.trace_message(old_trace).unwrap();
264
        
265
        // Add a recent trace
266
1
        let recent_trace = create_test_message_trace();
267
1
        observatory.trace_message(recent_trace).unwrap();
268
        
269
1
        let traces = observatory.get_traces(None, None).unwrap();
270
        // Only recent trace should remain
271
1
        assert_eq!(traces.len(), 1);
272
1
    }
273
274
    #[test]
275
1
    fn test_get_traces_with_limit() {
276
1
        let observatory = create_test_observatory();
277
        
278
        // Add 5 traces
279
6
        for 
i5
in 0..5 {
280
5
            let mut trace = create_test_message_trace();
281
5
            trace.trace_id = i as u64;
282
5
            observatory.trace_message(trace).unwrap();
283
5
        }
284
        
285
1
        let traces = observatory.get_traces(Some(3), None).unwrap();
286
1
        assert_eq!(traces.len(), 3);
287
1
    }
288
289
    // ========== Message Status Tests ==========
290
291
    #[test]
292
1
    fn test_message_status_variants() {
293
1
        let statuses = vec![
294
1
            MessageStatus::Queued,
295
1
            MessageStatus::Processing,
296
1
            MessageStatus::Completed,
297
1
            MessageStatus::Failed,
298
1
            MessageStatus::Dropped,
299
        ];
300
        
301
1
        assert_eq!(statuses.len(), 5);
302
1
        assert_eq!(statuses[0], MessageStatus::Queued);
303
1
        assert_ne!(statuses[0], MessageStatus::Processing);
304
1
    }
305
306
    #[test]
307
1
    fn test_message_status_serialization() {
308
1
        let status = MessageStatus::Processing;
309
1
        let json = serde_json::to_string(&status).unwrap();
310
1
        let deserialized: MessageStatus = serde_json::from_str(&json).unwrap();
311
1
        assert_eq!(status, deserialized);
312
1
    }
313
314
    #[test]
315
1
    fn test_message_status_debug() {
316
1
        let status = MessageStatus::Failed;
317
1
        let debug_str = format!("{:?}", status);
318
1
        assert!(debug_str.contains("Failed"));
319
1
    }
320
321
    // ========== Actor State Tests ==========
322
323
    #[test]
324
1
    fn test_actor_state_variants() {
325
1
        let states = vec![
326
1
            ActorState::Starting,
327
1
            ActorState::Running,
328
1
            ActorState::Processing("test_message".to_string()),
329
1
            ActorState::Restarting,
330
1
            ActorState::Stopping,
331
1
            ActorState::Stopped,
332
1
            ActorState::Failed("test_error".to_string()),
333
        ];
334
        
335
1
        assert_eq!(states.len(), 7);
336
1
        assert_eq!(states[0], ActorState::Starting);
337
1
        assert_ne!(states[0], ActorState::Running);
338
1
    }
339
340
    #[test]
341
1
    fn test_actor_state_processing() {
342
1
        let state = ActorState::Processing("handle_request".to_string());
343
1
        if let ActorState::Processing(message_type) = state {
344
1
            assert_eq!(message_type, "handle_request");
345
        } else {
346
0
            panic!("Expected Processing state");
347
        }
348
1
    }
349
350
    #[test]
351
1
    fn test_actor_state_failed() {
352
1
        let state = ActorState::Failed("connection_timeout".to_string());
353
1
        if let ActorState::Failed(reason) = state {
354
1
            assert_eq!(reason, "connection_timeout");
355
        } else {
356
0
            panic!("Expected Failed state");
357
        }
358
1
    }
359
360
    #[test]
361
1
    fn test_actor_state_serialization() {
362
1
        let state = ActorState::Processing("test".to_string());
363
1
        let json = serde_json::to_string(&state).unwrap();
364
1
        let deserialized: ActorState = serde_json::from_str(&json).unwrap();
365
1
        assert_eq!(state, deserialized);
366
1
    }
367
368
    // ========== Message Statistics Tests ==========
369
370
    #[test]
371
1
    fn test_message_stats_default() {
372
1
        let stats = MessageStats::default();
373
1
        assert_eq!(stats.total_processed, 0);
374
1
        assert_eq!(stats.messages_per_second, 0.0);
375
1
        assert_eq!(stats.avg_processing_time_us, 0.0);
376
1
        assert_eq!(stats.max_processing_time_us, 0);
377
1
        assert_eq!(stats.failed_messages, 0);
378
1
        assert!(stats.last_processed.is_none());
379
1
    }
380
381
    #[test]
382
1
    fn test_message_stats_clone() {
383
1
        let mut stats1 = MessageStats::default();
384
1
        stats1.total_processed = 100;
385
1
        stats1.messages_per_second = 10.5;
386
        
387
1
        let stats2 = stats1.clone();
388
1
        assert_eq!(stats1.total_processed, stats2.total_processed);
389
1
        assert_eq!(stats1.messages_per_second, stats2.messages_per_second);
390
1
    }
391
392
    #[test]
393
1
    fn test_message_stats_serialization() {
394
1
        let mut stats = MessageStats::default();
395
1
        stats.total_processed = 500;
396
1
        stats.avg_processing_time_us = 1500.0;
397
        
398
1
        let json = serde_json::to_string(&stats).unwrap();
399
1
        let deserialized: MessageStats = serde_json::from_str(&json).unwrap();
400
1
        assert_eq!(stats.total_processed, deserialized.total_processed);
401
1
    }
402
403
    // ========== System Metrics Tests ==========
404
405
    #[test]
406
1
    fn test_system_metrics_default() {
407
1
        let metrics = SystemMetrics::default();
408
1
        assert_eq!(metrics.active_actors, 0);
409
1
        assert_eq!(metrics.total_messages_processed, 0);
410
1
        assert_eq!(metrics.system_messages_per_second, 0.0);
411
1
        assert_eq!(metrics.total_memory_usage, 0);
412
1
        assert_eq!(metrics.total_queued_messages, 0);
413
1
        assert_eq!(metrics.avg_mailbox_size, 0.0);
414
1
        assert_eq!(metrics.recent_restarts, 0);
415
1
        assert!(metrics.last_updated > 0); // Uses current timestamp
416
1
    }
417
418
    #[test]
419
1
    fn test_system_metrics_update() {
420
1
        let mut metrics = SystemMetrics::default();
421
1
        metrics.active_actors = 10;
422
1
        metrics.total_messages_processed = 1000;
423
1
        metrics.system_messages_per_second = 50.5;
424
1
        metrics.last_updated = current_timestamp();
425
        
426
1
        assert_eq!(metrics.active_actors, 10);
427
1
        assert_eq!(metrics.total_messages_processed, 1000);
428
1
        assert!(metrics.last_updated > 0);
429
1
    }
430
431
    #[test]
432
1
    fn test_system_metrics_serialization() {
433
1
        let mut metrics = SystemMetrics::default();
434
1
        metrics.active_actors = 5;
435
1
        metrics.total_memory_usage = 1024000;
436
        
437
1
        let json = serde_json::to_string(&metrics).unwrap();
438
1
        let deserialized: SystemMetrics = serde_json::from_str(&json).unwrap();
439
1
        assert_eq!(metrics.active_actors, deserialized.active_actors);
440
1
        assert_eq!(metrics.total_memory_usage, deserialized.total_memory_usage);
441
1
    }
442
443
    // ========== Actor Snapshot Tests ==========
444
445
    #[test]
446
1
    fn test_actor_snapshot_creation() {
447
1
        let snapshot = create_test_actor_snapshot();
448
        
449
1
        assert_eq!(snapshot.actor_id, ActorId(1));
450
1
        assert_eq!(snapshot.name, "test_actor");
451
1
        assert_eq!(snapshot.state, ActorState::Running);
452
1
        assert_eq!(snapshot.mailbox_size, 5);
453
1
        assert_eq!(snapshot.children.len(), 2);
454
1
        assert!(snapshot.memory_usage.is_some());
455
1
    }
456
457
    #[test]
458
1
    fn test_actor_snapshot_with_no_parent() {
459
1
        let mut snapshot = create_test_actor_snapshot();
460
1
        snapshot.parent = None;
461
        
462
1
        assert!(snapshot.parent.is_none());
463
1
        assert_eq!(snapshot.children.len(), 2);
464
1
    }
465
466
    #[test]
467
1
    fn test_actor_snapshot_serialization() {
468
1
        let snapshot = create_test_actor_snapshot();
469
        
470
1
        let json = serde_json::to_string(&snapshot).unwrap();
471
1
        let deserialized: ActorSnapshot = serde_json::from_str(&json).unwrap();
472
        
473
1
        assert_eq!(snapshot.actor_id, deserialized.actor_id);
474
1
        assert_eq!(snapshot.name, deserialized.name);
475
1
        assert_eq!(snapshot.state, deserialized.state);
476
1
    }
477
478
    // ========== Message Filter Tests ==========
479
480
    #[test]
481
1
    fn test_message_filter_creation() {
482
1
        let filter = create_test_message_filter();
483
        
484
1
        assert_eq!(filter.name, "test_filter");
485
1
        assert!(filter.actor_id.is_some());
486
1
        assert!(filter.actor_name_pattern.is_some());
487
1
        assert!(filter.message_type_pattern.is_some());
488
1
        assert!(!filter.failed_only);
489
1
    }
490
491
    #[test]
492
1
    fn test_message_filter_with_duration_limits() {
493
1
        let filter = MessageFilter {
494
1
            name: "duration_filter".to_string(),
495
1
            actor_id: None,
496
1
            actor_name_pattern: None,
497
1
            message_type_pattern: None,
498
1
            min_processing_time_us: Some(1000),
499
1
            failed_only: false,
500
1
            max_stack_depth: None,
501
1
        };
502
        
503
1
        assert_eq!(filter.min_processing_time_us, Some(1000));
504
1
    }
505
506
    #[test]
507
1
    fn test_message_filter_errors_only() {
508
1
        let filter = MessageFilter {
509
1
            name: "error_filter".to_string(),
510
1
            actor_id: None,
511
1
            actor_name_pattern: None,
512
1
            message_type_pattern: None,
513
1
            min_processing_time_us: None,
514
1
            failed_only: true,
515
1
            max_stack_depth: None,
516
1
        };
517
        
518
1
        assert!(filter.failed_only);
519
1
    }
520
521
    // ========== Message Trace Tests ==========
522
523
    #[test]
524
1
    fn test_message_trace_creation() {
525
1
        let trace = create_test_message_trace();
526
        
527
1
        assert_eq!(trace.trace_id, 12345);
528
1
        assert!(trace.source.is_some());
529
1
        assert_eq!(trace.destination, ActorId(2));
530
1
        assert_eq!(trace.status, MessageStatus::Queued);
531
1
        assert_eq!(trace.stack_depth, 1);
532
1
        assert!(trace.correlation_id.is_some());
533
1
    }
534
535
    #[test]
536
1
    fn test_message_trace_with_processing() {
537
1
        let mut trace = create_test_message_trace();
538
1
        trace.status = MessageStatus::Processing;
539
1
        trace.processing_duration_us = Some(1500);
540
        
541
1
        assert_eq!(trace.status, MessageStatus::Processing);
542
1
        assert_eq!(trace.processing_duration_us, Some(1500));
543
1
    }
544
545
    #[test]
546
1
    fn test_message_trace_with_error() {
547
1
        let mut trace = create_test_message_trace();
548
1
        trace.status = MessageStatus::Failed;
549
1
        trace.error = Some("timeout_error".to_string());
550
        
551
1
        assert_eq!(trace.status, MessageStatus::Failed);
552
1
        assert_eq!(trace.error, Some("timeout_error".to_string()));
553
1
    }
554
555
    #[test]
556
1
    fn test_message_trace_serialization() {
557
1
        let trace = create_test_message_trace();
558
        
559
1
        let json = serde_json::to_string(&trace).unwrap();
560
1
        let deserialized: MessageTrace = serde_json::from_str(&json).unwrap();
561
        
562
1
        assert_eq!(trace.trace_id, deserialized.trace_id);
563
1
        assert_eq!(trace.status, deserialized.status);
564
1
    }
565
566
    // ========== Utility Function Tests ==========
567
568
    #[test]
569
1
    fn test_current_timestamp() {
570
1
        let ts1 = current_timestamp();
571
1
        std::thread::sleep(Duration::from_millis(10)); // Increase sleep time for more reliable test
572
1
        let ts2 = current_timestamp();
573
        
574
1
        assert!(ts2 >= ts1); // May be equal due to precision
575
1
    }
576
577
    // ========== Integration Tests ==========
578
579
    #[test]
580
1
    fn test_observatory_full_workflow() {
581
1
        let mut observatory = create_test_observatory();
582
        
583
        // Add filter that matches our test message
584
1
        let filter = MessageFilter {
585
1
            name: "permissive_filter".to_string(),
586
1
            actor_id: None, // Allow all actors
587
1
            actor_name_pattern: None,
588
1
            message_type_pattern: None, // Allow all message types 
589
1
            min_processing_time_us: None,
590
1
            failed_only: false,
591
1
            max_stack_depth: None,
592
1
        };
593
1
        observatory.add_filter(filter);
594
1
        assert_eq!(observatory.get_filters().len(), 1);
595
        
596
        // Add trace
597
1
        let trace = create_test_message_trace();
598
1
        observatory.trace_message(trace.clone()).unwrap();
599
        
600
        // Verify trace was recorded 
601
1
        let traces = observatory.get_traces(None, None).unwrap();
602
1
        assert_eq!(traces.len(), 1);
603
1
        assert_eq!(traces[0].trace_id, trace.trace_id);
604
        
605
        // Remove filter
606
1
        let removed = observatory.remove_filter("permissive_filter");
607
1
        assert!(removed);
608
1
        assert!(observatory.get_filters().is_empty());
609
1
    }
610
611
    #[test]
612
1
    fn test_observatory_multiple_traces() {
613
1
        let observatory = create_test_observatory();
614
        
615
        // Add multiple traces with different statuses
616
1
        let statuses = vec![
617
1
            MessageStatus::Queued,
618
1
            MessageStatus::Processing,
619
1
            MessageStatus::Completed,
620
1
            MessageStatus::Failed,
621
        ];
622
        
623
4
        for (i, status) in 
statuses.iter()1
.
enumerate1
() {
624
4
            let mut trace = create_test_message_trace();
625
4
            trace.trace_id = i as u64;
626
4
            trace.status = status.clone();
627
4
            observatory.trace_message(trace).unwrap();
628
4
        }
629
        
630
1
        let traces = observatory.get_traces(None, None).unwrap();
631
1
        assert_eq!(traces.len(), 4);
632
        
633
        // Verify different statuses were recorded - count unique statuses differently
634
1
        let mut status_counts = std::collections::HashMap::new();
635
5
        for 
trace4
in &traces {
636
4
            *status_counts.entry(&trace.status).or_insert(0) += 1;
637
4
        }
638
1
        assert_eq!(status_counts.len(), 4);
639
1
    }
640
641
    #[test]
642
1
    fn test_observatory_config_variations() {
643
1
        let configs = vec![
644
1
            ObservatoryConfig {
645
1
                max_traces: 50,
646
1
                enable_deadlock_detection: false,
647
1
                enable_metrics: false,
648
1
                ..ObservatoryConfig::default()
649
1
            },
650
1
            ObservatoryConfig {
651
1
                trace_retention_seconds: 7200,
652
1
                deadlock_check_interval_ms: 500,
653
1
                metrics_interval_ms: 2000,
654
1
                ..ObservatoryConfig::default()
655
1
            },
656
        ];
657
        
658
3
        for 
config2
in configs {
659
2
            let system = create_test_actor_system();
660
2
            let observatory = ActorObservatory::new(system, config.clone());
661
2
            assert_eq!(observatory.config.max_traces, config.max_traces);
662
2
            assert_eq!(observatory.config.enable_deadlock_detection, config.enable_deadlock_detection);
663
        }
664
1
    }
665
666
    #[test]
667
1
    fn test_observatory_concurrent_access() {
668
        use std::thread;
669
        
670
1
        let observatory = Arc::new(create_test_observatory());
671
1
        let mut handles = vec![];
672
        
673
        // Spawn multiple threads to add traces concurrently
674
6
        for 
i5
in 0..5 {
675
5
            let obs = observatory.clone();
676
5
            let handle = thread::spawn(move || {
677
5
                let mut trace = create_test_message_trace();
678
5
                trace.trace_id = i;
679
5
                obs.trace_message(trace).unwrap();
680
5
            });
681
5
            handles.push(handle);
682
        }
683
        
684
        // Wait for all threads to complete
685
6
        for 
handle5
in handles {
686
5
            handle.join().unwrap();
687
5
        }
688
        
689
        // Verify all traces were recorded
690
1
        let traces = observatory.get_traces(None, None).unwrap();
691
1
        assert_eq!(traces.len(), 5);
692
1
    }
693
}
694
695
use crate::runtime::actor::{ActorId, ActorSystem, Message};
696
use anyhow::Result;
697
use serde::{Deserialize, Serialize};
698
use std::collections::{HashMap, HashSet, VecDeque};
699
use std::sync::{Arc, Mutex};
700
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
701
702
/// Actor system observatory for live introspection and monitoring
703
pub struct ActorObservatory {
704
    /// Reference to the actor system being observed
705
    actor_system: Arc<Mutex<ActorSystem>>,
706
    
707
    /// Message trace storage
708
    message_traces: Arc<Mutex<VecDeque<MessageTrace>>>,
709
    
710
    /// Actor state snapshots
711
    actor_snapshots: Arc<Mutex<HashMap<ActorId, ActorSnapshot>>>,
712
    
713
    /// Deadlock detection state
714
    deadlock_detector: Arc<Mutex<DeadlockDetector>>,
715
    
716
    /// Observatory configuration
717
    config: ObservatoryConfig,
718
    
719
    /// Active filters for message tracing
720
    filters: Vec<MessageFilter>,
721
    
722
    /// Performance metrics
723
    metrics: Arc<Mutex<SystemMetrics>>,
724
    
725
    /// Observatory start time
726
    start_time: Instant,
727
}
728
729
/// Configuration for the actor observatory
730
#[derive(Debug, Clone, Serialize, Deserialize)]
731
pub struct ObservatoryConfig {
732
    /// Maximum number of message traces to keep
733
    pub max_traces: usize,
734
    
735
    /// Maximum age for message traces (in seconds)
736
    pub trace_retention_seconds: u64,
737
    
738
    /// Enable deadlock detection
739
    pub enable_deadlock_detection: bool,
740
    
741
    /// Deadlock detection interval (in milliseconds)
742
    pub deadlock_check_interval_ms: u64,
743
    
744
    /// Enable performance metrics collection
745
    pub enable_metrics: bool,
746
    
747
    /// Metrics collection interval (in milliseconds)
748
    pub metrics_interval_ms: u64,
749
    
750
    /// Maximum number of actor snapshots to keep
751
    pub max_snapshots: usize,
752
}
753
754
impl Default for ObservatoryConfig {
755
36
    fn default() -> Self {
756
36
        Self {
757
36
            max_traces: 10000,
758
36
            trace_retention_seconds: 3600, // 1 hour
759
36
            enable_deadlock_detection: true,
760
36
            deadlock_check_interval_ms: 1000, // 1 second
761
36
            enable_metrics: true,
762
36
            metrics_interval_ms: 5000, // 5 seconds
763
36
            max_snapshots: 1000,
764
36
        }
765
36
    }
766
}
767
768
/// Message trace entry for debugging and analysis
769
#[derive(Debug, Clone, Serialize, Deserialize)]
770
pub struct MessageTrace {
771
    /// Unique trace ID
772
    pub trace_id: u64,
773
    
774
    /// Timestamp when the message was traced
775
    pub timestamp: u64,
776
    
777
    /// Source actor ID (None for external messages)
778
    pub source: Option<ActorId>,
779
    
780
    /// Destination actor ID
781
    pub destination: ActorId,
782
    
783
    /// The traced message
784
    pub message: Message,
785
    
786
    /// Message processing status
787
    pub status: MessageStatus,
788
    
789
    /// Processing duration in microseconds
790
    pub processing_duration_us: Option<u64>,
791
    
792
    /// Error information if message processing failed
793
    pub error: Option<String>,
794
    
795
    /// Stack depth for nested message calls
796
    pub stack_depth: usize,
797
    
798
    /// Correlation ID for tracking message chains
799
    pub correlation_id: Option<String>,
800
}
801
802
/// Status of a traced message
803
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
804
pub enum MessageStatus {
805
    /// Message is queued for processing
806
    Queued,
807
    /// Message is currently being processed
808
    Processing,
809
    /// Message was processed successfully
810
    Completed,
811
    /// Message processing failed
812
    Failed,
813
    /// Message was dropped due to actor failure
814
    Dropped,
815
}
816
817
/// Snapshot of an actor's state at a point in time
818
#[derive(Debug, Clone, Serialize, Deserialize)]
819
pub struct ActorSnapshot {
820
    /// Actor ID
821
    pub actor_id: ActorId,
822
    
823
    /// Actor name
824
    pub name: String,
825
    
826
    /// Snapshot timestamp
827
    pub timestamp: u64,
828
    
829
    /// Current state of the actor
830
    pub state: ActorState,
831
    
832
    /// Number of messages in the actor's mailbox
833
    pub mailbox_size: usize,
834
    
835
    /// Actor's supervision parent (if any)
836
    pub parent: Option<ActorId>,
837
    
838
    /// Actor's supervised children
839
    pub children: Vec<ActorId>,
840
    
841
    /// Recent message processing statistics
842
    pub message_stats: MessageStats,
843
    
844
    /// Memory usage estimate (in bytes)
845
    pub memory_usage: Option<usize>,
846
}
847
848
/// Current state of an actor
849
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
850
pub enum ActorState {
851
    /// Actor is starting up
852
    Starting,
853
    /// Actor is running normally
854
    Running,
855
    /// Actor is processing a message
856
    Processing(String), // Message type being processed
857
    /// Actor is restarting due to failure
858
    Restarting,
859
    /// Actor is stopping
860
    Stopping,
861
    /// Actor has stopped
862
    Stopped,
863
    /// Actor has failed
864
    Failed(String), // Failure reason
865
}
866
867
/// Message processing statistics for an actor
868
#[derive(Debug, Clone, Serialize, Deserialize)]
869
pub struct MessageStats {
870
    /// Total messages processed
871
    pub total_processed: u64,
872
    
873
    /// Messages processed per second (recent average)
874
    pub messages_per_second: f64,
875
    
876
    /// Average message processing time in microseconds
877
    pub avg_processing_time_us: f64,
878
    
879
    /// Maximum message processing time in microseconds
880
    pub max_processing_time_us: u64,
881
    
882
    /// Number of failed message processings
883
    pub failed_messages: u64,
884
    
885
    /// Last processing timestamp
886
    pub last_processed: Option<u64>,
887
}
888
889
impl Default for MessageStats {
890
7
    fn default() -> Self {
891
7
        Self {
892
7
            total_processed: 0,
893
7
            messages_per_second: 0.0,
894
7
            avg_processing_time_us: 0.0,
895
7
            max_processing_time_us: 0,
896
7
            failed_messages: 0,
897
7
            last_processed: None,
898
7
        }
899
7
    }
900
}
901
902
/// System-wide performance metrics
903
#[derive(Debug, Clone, Serialize, Deserialize)]
904
pub struct SystemMetrics {
905
    /// Total number of active actors
906
    pub active_actors: usize,
907
    
908
    /// Total messages processed across all actors
909
    pub total_messages_processed: u64,
910
    
911
    /// System-wide messages per second
912
    pub system_messages_per_second: f64,
913
    
914
    /// Total memory usage estimate (in bytes)
915
    pub total_memory_usage: usize,
916
    
917
    /// Number of currently queued messages across all actors
918
    pub total_queued_messages: usize,
919
    
920
    /// Average actor mailbox size
921
    pub avg_mailbox_size: f64,
922
    
923
    /// Number of actor restarts in the last period
924
    pub recent_restarts: u64,
925
    
926
    /// Last metrics update timestamp
927
    pub last_updated: u64,
928
}
929
930
impl Default for SystemMetrics {
931
52
    fn default() -> Self {
932
52
        Self {
933
52
            active_actors: 0,
934
52
            total_messages_processed: 0,
935
52
            system_messages_per_second: 0.0,
936
52
            total_memory_usage: 0,
937
52
            total_queued_messages: 0,
938
52
            avg_mailbox_size: 0.0,
939
52
            recent_restarts: 0,
940
52
            last_updated: current_timestamp(),
941
52
        }
942
52
    }
943
}
944
945
/// Filter for message tracing
946
#[derive(Debug, Clone, Serialize, Deserialize)]
947
pub struct MessageFilter {
948
    /// Filter name for identification
949
    pub name: String,
950
    
951
    /// Actor ID to filter by (None for all actors)
952
    pub actor_id: Option<ActorId>,
953
    
954
    /// Actor name pattern to filter by
955
    pub actor_name_pattern: Option<String>,
956
    
957
    /// Message type pattern to filter by
958
    pub message_type_pattern: Option<String>,
959
    
960
    /// Minimum message processing time to include (microseconds)
961
    pub min_processing_time_us: Option<u64>,
962
    
963
    /// Only include failed messages
964
    pub failed_only: bool,
965
    
966
    /// Maximum stack depth to include
967
    pub max_stack_depth: Option<usize>,
968
}
969
970
/// Deadlock detection system
971
#[derive(Debug)]
972
pub struct DeadlockDetector {
973
    /// Graph of actor message dependencies
974
    dependency_graph: HashMap<ActorId, HashSet<ActorId>>,
975
    
976
    /// Currently blocked actors waiting for responses
977
    blocked_actors: HashMap<ActorId, Vec<BlockedRequest>>,
978
    
979
    /// Last deadlock check timestamp
980
    last_check: Instant,
981
    
982
    /// Detected deadlocks
983
    detected_deadlocks: Vec<DeadlockCycle>,
984
}
985
986
/// Information about a blocked request
987
#[derive(Debug, Clone)]
988
pub struct BlockedRequest {
989
    /// Actor making the request
990
    pub requester: ActorId,
991
    
992
    /// Actor being requested from
993
    pub target: ActorId,
994
    
995
    /// When the request was made
996
    pub timestamp: Instant,
997
    
998
    /// Timeout for the request
999
    pub timeout: Duration,
1000
    
1001
    /// Message correlation ID
1002
    pub correlation_id: Option<String>,
1003
}
1004
1005
/// A detected deadlock cycle
1006
#[derive(Debug, Clone, Serialize, Deserialize)]
1007
pub struct DeadlockCycle {
1008
    /// Actors involved in the deadlock
1009
    pub actors: Vec<ActorId>,
1010
    
1011
    /// When the deadlock was detected
1012
    pub detected_at: u64,
1013
    
1014
    /// Estimated duration of the deadlock
1015
    pub duration_estimate_ms: u64,
1016
    
1017
    /// Suggested resolution strategy
1018
    pub resolution_suggestion: String,
1019
}
1020
1021
impl ActorObservatory {
1022
    /// Create a new actor observatory
1023
49
    pub fn new(actor_system: Arc<Mutex<ActorSystem>>, config: ObservatoryConfig) -> Self {
1024
49
        Self {
1025
49
            actor_system,
1026
49
            message_traces: Arc::new(Mutex::new(VecDeque::new())),
1027
49
            actor_snapshots: Arc::new(Mutex::new(HashMap::new())),
1028
49
            deadlock_detector: Arc::new(Mutex::new(DeadlockDetector::new())),
1029
49
            config,
1030
49
            filters: Vec::new(),
1031
49
            metrics: Arc::new(Mutex::new(SystemMetrics::default())),
1032
49
            start_time: Instant::now(),
1033
49
        }
1034
49
    }
1035
    
1036
    /// Add a message filter for tracing
1037
5
    pub fn add_filter(&mut self, filter: MessageFilter) {
1038
5
        self.filters.push(filter);
1039
5
    }
1040
    
1041
    /// Remove a message filter by name
1042
3
    pub fn remove_filter(&mut self, name: &str) -> bool {
1043
3
        let initial_len = self.filters.len();
1044
3
        self.filters.retain(|f| 
f.name2
!=
name2
);
1045
3
        self.filters.len() != initial_len
1046
3
    }
1047
    
1048
    /// Get current list of filters
1049
11
    pub fn get_filters(&self) -> &[MessageFilter] {
1050
11
        &self.filters
1051
11
    }
1052
    
1053
    /// Record a message trace
1054
22
    pub fn trace_message(&self, trace: MessageTrace) -> Result<()> {
1055
22
        let mut traces = self.message_traces
1056
22
            .lock()
1057
22
            .map_err(|_| anyhow::anyhow!(
"Failed to acquire message traces lock"0
))
?0
;
1058
        
1059
        // Apply filters
1060
22
        if !self.message_matches_filters(&trace) {
1061
0
            return Ok(());
1062
22
        }
1063
        
1064
22
        traces.push_back(trace);
1065
        
1066
        // Enforce retention limits
1067
23
        while traces.len() > self.config.max_traces {
1068
1
            traces.pop_front();
1069
1
        }
1070
        
1071
        // Remove old traces based on age
1072
22
        let retention_threshold = current_timestamp() - self.config.trace_retention_seconds;
1073
24
        while let Some(
front22
) = traces.front() {
1074
22
            if front.timestamp < retention_threshold {
1075
2
                traces.pop_front();
1076
2
            } else {
1077
20
                break;
1078
            }
1079
        }
1080
        
1081
22
        Ok(())
1082
22
    }
1083
    
1084
    /// Get recent message traces with optional filtering
1085
8
    pub fn get_traces(&self, limit: Option<usize>, filter_name: Option<&str>) -> Result<Vec<MessageTrace>> {
1086
8
        let traces = self.message_traces
1087
8
            .lock()
1088
8
            .map_err(|_| anyhow::anyhow!(
"Failed to acquire message traces lock"0
))
?0
;
1089
        
1090
8
        let mut result: Vec<MessageTrace> = if let Some(
filter_name0
) = filter_name {
1091
0
            traces.iter()
1092
0
                .filter(|trace| self.trace_matches_filter(trace, filter_name))
1093
0
                .cloned()
1094
0
                .collect()
1095
        } else {
1096
8
            traces.iter().cloned().collect()
1097
        };
1098
        
1099
8
        if let Some(
limit2
) = limit {
1100
2
            result.truncate(limit);
1101
6
        }
1102
        
1103
8
        Ok(result)
1104
8
    }
1105
    
1106
    /// Update actor snapshot
1107
1
    pub fn update_actor_snapshot(&self, snapshot: ActorSnapshot) -> Result<()> {
1108
1
        let mut snapshots = self.actor_snapshots
1109
1
            .lock()
1110
1
            .map_err(|_| anyhow::anyhow!(
"Failed to acquire actor snapshots lock"0
))
?0
;
1111
        
1112
1
        snapshots.insert(snapshot.actor_id, snapshot);
1113
        
1114
        // Enforce snapshot limits
1115
1
        if snapshots.len() > self.config.max_snapshots {
1116
            // Remove oldest snapshots
1117
0
            let mut oldest_actors: Vec<_> = snapshots.iter()
1118
0
                .map(|(&id, snapshot)| (id, snapshot.timestamp))
1119
0
                .collect();
1120
0
            oldest_actors.sort_by_key(|(_, timestamp)| *timestamp);
1121
            
1122
0
            let to_remove = snapshots.len() - self.config.max_snapshots;
1123
0
            for i in 0..to_remove {
1124
0
                if let Some((actor_id, _)) = oldest_actors.get(i) {
1125
0
                    snapshots.remove(actor_id);
1126
0
                }
1127
            }
1128
1
        }
1129
        
1130
1
        Ok(())
1131
1
    }
1132
    
1133
    /// Get current actor snapshots
1134
0
    pub fn get_actor_snapshots(&self) -> Result<HashMap<ActorId, ActorSnapshot>> {
1135
0
        Ok(self.actor_snapshots
1136
0
            .lock()
1137
0
            .map_err(|_| anyhow::anyhow!("Failed to acquire actor snapshots lock"))?
1138
0
            .clone())
1139
0
    }
1140
    
1141
    /// Get specific actor snapshot
1142
0
    pub fn get_actor_snapshot(&self, actor_id: ActorId) -> Result<Option<ActorSnapshot>> {
1143
0
        Ok(self.actor_snapshots
1144
0
            .lock()
1145
0
            .map_err(|_| anyhow::anyhow!("Failed to acquire actor snapshots lock"))?
1146
0
            .get(&actor_id)
1147
0
            .cloned())
1148
0
    }
1149
    
1150
    /// Perform deadlock detection
1151
0
    pub fn detect_deadlocks(&self) -> Result<Vec<DeadlockCycle>> {
1152
0
        if !self.config.enable_deadlock_detection {
1153
0
            return Ok(Vec::new());
1154
0
        }
1155
        
1156
0
        let mut detector = self.deadlock_detector
1157
0
            .lock()
1158
0
            .map_err(|_| anyhow::anyhow!("Failed to acquire deadlock detector lock"))?;
1159
        
1160
0
        detector.detect_cycles()
1161
0
    }
1162
    
1163
    /// Update system metrics
1164
0
    pub fn update_metrics(&self) -> Result<()> {
1165
0
        if !self.config.enable_metrics {
1166
0
            return Ok(());
1167
0
        }
1168
        
1169
0
        let _system = self.actor_system
1170
0
            .lock()
1171
0
            .map_err(|_| anyhow::anyhow!("Failed to acquire actor system lock"))?;
1172
        
1173
0
        let mut metrics = self.metrics
1174
0
            .lock()
1175
0
            .map_err(|_| anyhow::anyhow!("Failed to acquire metrics lock"))?;
1176
        
1177
0
        let snapshots = self.actor_snapshots
1178
0
            .lock()
1179
0
            .map_err(|_| anyhow::anyhow!("Failed to acquire actor snapshots lock"))?;
1180
        
1181
        // Update metrics based on current system state
1182
0
        metrics.active_actors = snapshots.len();
1183
0
        metrics.total_messages_processed = snapshots.values()
1184
0
            .map(|s| s.message_stats.total_processed)
1185
0
            .sum();
1186
        
1187
0
        metrics.total_queued_messages = snapshots.values()
1188
0
            .map(|s| s.mailbox_size)
1189
0
            .sum();
1190
        
1191
0
        metrics.avg_mailbox_size = if snapshots.is_empty() {
1192
0
            0.0
1193
        } else {
1194
0
            metrics.total_queued_messages as f64 / snapshots.len() as f64
1195
        };
1196
        
1197
0
        metrics.total_memory_usage = snapshots.values()
1198
0
            .filter_map(|s| s.memory_usage)
1199
0
            .sum();
1200
        
1201
0
        metrics.last_updated = current_timestamp();
1202
        
1203
0
        Ok(())
1204
0
    }
1205
    
1206
    /// Get current system metrics
1207
0
    pub fn get_metrics(&self) -> Result<SystemMetrics> {
1208
0
        Ok(self.metrics
1209
0
            .lock()
1210
0
            .map_err(|_| anyhow::anyhow!("Failed to acquire metrics lock"))?
1211
0
            .clone())
1212
0
    }
1213
    
1214
    /// Get observatory uptime
1215
0
    pub fn uptime(&self) -> Duration {
1216
0
        self.start_time.elapsed()
1217
0
    }
1218
    
1219
    /// Check if a message matches the configured filters
1220
22
    fn message_matches_filters(&self, trace: &MessageTrace) -> bool {
1221
22
        if self.filters.is_empty() {
1222
21
            return true; // No filters means include all messages
1223
1
        }
1224
        
1225
1
        self.filters.iter().any(|filter| self.message_matches_filter(trace, filter))
1226
22
    }
1227
    
1228
    /// Check if a message matches a specific filter
1229
1
    fn message_matches_filter(&self, trace: &MessageTrace, filter: &MessageFilter) -> bool {
1230
        // Filter by actor ID
1231
1
        if let Some(
filter_actor_id0
) = filter.actor_id {
1232
0
            if trace.destination != filter_actor_id {
1233
0
                return false;
1234
0
            }
1235
1
        }
1236
        
1237
        // Filter by processing time
1238
1
        if let Some(
min_time0
) = filter.min_processing_time_us {
1239
0
            if let Some(duration) = trace.processing_duration_us {
1240
0
                if duration < min_time {
1241
0
                    return false;
1242
0
                }
1243
            } else {
1244
0
                return false;
1245
            }
1246
1
        }
1247
        
1248
        // Filter by failed messages only
1249
1
        if filter.failed_only && 
trace.status != MessageStatus::Failed0
{
1250
0
            return false;
1251
1
        }
1252
        
1253
        // Filter by stack depth
1254
1
        if let Some(
max_depth0
) = filter.max_stack_depth {
1255
0
            if trace.stack_depth > max_depth {
1256
0
                return false;
1257
0
            }
1258
1
        }
1259
        
1260
1
        true
1261
1
    }
1262
    
1263
    /// Check if a trace matches a filter by name
1264
0
    fn trace_matches_filter(&self, trace: &MessageTrace, filter_name: &str) -> bool {
1265
0
        self.filters.iter()
1266
0
            .find(|f| f.name == filter_name)
1267
0
            .is_some_and(|filter| self.message_matches_filter(trace, filter))
1268
0
    }
1269
}
1270
1271
impl Default for DeadlockDetector {
1272
0
    fn default() -> Self {
1273
0
        Self::new()
1274
0
    }
1275
}
1276
1277
impl DeadlockDetector {
1278
    /// Create a new deadlock detector
1279
49
    pub fn new() -> Self {
1280
49
        Self {
1281
49
            dependency_graph: HashMap::new(),
1282
49
            blocked_actors: HashMap::new(),
1283
49
            last_check: Instant::now(),
1284
49
            detected_deadlocks: Vec::new(),
1285
49
        }
1286
49
    }
1287
    
1288
    /// Add a blocked request to track
1289
0
    pub fn add_blocked_request(&mut self, request: BlockedRequest) {
1290
0
        self.blocked_actors
1291
0
            .entry(request.requester)
1292
0
            .or_default()
1293
0
            .push(request.clone());
1294
        
1295
        // Update dependency graph
1296
0
        self.dependency_graph
1297
0
            .entry(request.requester)
1298
0
            .or_default()
1299
0
            .insert(request.target);
1300
0
    }
1301
    
1302
    /// Remove a blocked request (when resolved)
1303
0
    pub fn remove_blocked_request(&mut self, requester: ActorId, target: ActorId) {
1304
0
        if let Some(requests) = self.blocked_actors.get_mut(&requester) {
1305
0
            requests.retain(|r| r.target != target);
1306
0
            if requests.is_empty() {
1307
0
                self.blocked_actors.remove(&requester);
1308
0
            }
1309
0
        }
1310
        
1311
        // Update dependency graph
1312
0
        if let Some(dependencies) = self.dependency_graph.get_mut(&requester) {
1313
0
            dependencies.remove(&target);
1314
0
            if dependencies.is_empty() {
1315
0
                self.dependency_graph.remove(&requester);
1316
0
            }
1317
0
        }
1318
0
    }
1319
    
1320
    /// Detect cycles in the dependency graph (potential deadlocks)
1321
0
    pub fn detect_cycles(&mut self) -> Result<Vec<DeadlockCycle>> {
1322
0
        let mut cycles = Vec::new();
1323
0
        let mut visited = HashSet::new();
1324
0
        let mut path = Vec::new();
1325
        
1326
0
        for &actor in self.dependency_graph.keys() {
1327
0
            if !visited.contains(&actor) {
1328
0
                self.dfs_detect_cycle(actor, &mut visited, &mut path, &mut cycles)?;
1329
0
            }
1330
        }
1331
        
1332
0
        self.detected_deadlocks.extend(cycles.clone());
1333
0
        self.last_check = Instant::now();
1334
        
1335
0
        Ok(cycles)
1336
0
    }
1337
    
1338
    /// Depth-first search to detect cycles
1339
0
    fn dfs_detect_cycle(
1340
0
        &self,
1341
0
        actor: ActorId,
1342
0
        visited: &mut HashSet<ActorId>,
1343
0
        path: &mut Vec<ActorId>,
1344
0
        cycles: &mut Vec<DeadlockCycle>,
1345
0
    ) -> Result<()> {
1346
0
        visited.insert(actor);
1347
0
        path.push(actor);
1348
        
1349
0
        if let Some(dependencies) = self.dependency_graph.get(&actor) {
1350
0
            for &dependent_actor in dependencies {
1351
0
                if let Some(cycle_start_index) = path.iter().position(|&a| a == dependent_actor) {
1352
0
                    // Found a cycle
1353
0
                    let cycle_actors = path[cycle_start_index..].to_vec();
1354
0
                    let duration_estimate = self.estimate_cycle_duration(&cycle_actors);
1355
0
                    
1356
0
                    cycles.push(DeadlockCycle {
1357
0
                        actors: cycle_actors.clone(),
1358
0
                        detected_at: current_timestamp(),
1359
0
                        duration_estimate_ms: duration_estimate,
1360
0
                        resolution_suggestion: self.suggest_resolution(&cycle_actors),
1361
0
                    });
1362
0
                } else if !visited.contains(&dependent_actor) {
1363
0
                    self.dfs_detect_cycle(dependent_actor, visited, path, cycles)?;
1364
0
                }
1365
            }
1366
0
        }
1367
        
1368
0
        path.pop();
1369
0
        Ok(())
1370
0
    }
1371
    
1372
    /// Estimate how long a deadlock cycle has been active
1373
0
    fn estimate_cycle_duration(&self, actors: &[ActorId]) -> u64 {
1374
0
        let now = Instant::now();
1375
0
        actors.iter()
1376
0
            .filter_map(|&actor| self.blocked_actors.get(&actor))
1377
0
            .flatten()
1378
0
            .map(|request| now.duration_since(request.timestamp).as_millis() as u64)
1379
0
            .max()
1380
0
            .unwrap_or(0)
1381
0
    }
1382
    
1383
    /// Suggest a resolution strategy for a deadlock cycle
1384
0
    fn suggest_resolution(&self, actors: &[ActorId]) -> String {
1385
0
        match actors.len() {
1386
0
            1 => "Self-deadlock: Check for recursive message sending".to_string(),
1387
0
            2 => "Binary deadlock: Consider using ask with timeout or redesign interaction pattern".to_string(),
1388
0
            3..=5 => "Multi-actor deadlock: Implement hierarchical message ordering or use supervision".to_string(),
1389
0
            _ => "Complex deadlock: Consider breaking into smaller subsystems or using event sourcing".to_string(),
1390
        }
1391
0
    }
1392
}
1393
1394
/// Get current timestamp in seconds since Unix epoch
1395
106
fn current_timestamp() -> u64 {
1396
106
    SystemTime::now()
1397
106
        .duration_since(UNIX_EPOCH)
1398
106
        .unwrap_or_default()
1399
106
        .as_secs()
1400
106
}
1401
1402
/// Create a simple message filter for testing
1403
impl MessageFilter {
1404
0
    pub fn new(name: &str) -> Self {
1405
0
        Self {
1406
0
            name: name.to_string(),
1407
0
            actor_id: None,
1408
0
            actor_name_pattern: None,
1409
0
            message_type_pattern: None,
1410
0
            min_processing_time_us: None,
1411
0
            failed_only: false,
1412
0
            max_stack_depth: None,
1413
0
        }
1414
0
    }
1415
    
1416
    /// Create a filter for a specific actor
1417
0
    pub fn for_actor(name: &str, actor_id: ActorId) -> Self {
1418
0
        Self {
1419
0
            name: name.to_string(),
1420
0
            actor_id: Some(actor_id),
1421
0
            actor_name_pattern: None,
1422
0
            message_type_pattern: None,
1423
0
            min_processing_time_us: None,
1424
0
            failed_only: false,
1425
0
            max_stack_depth: None,
1426
0
        }
1427
0
    }
1428
    
1429
    /// Create a filter for failed messages only
1430
0
    pub fn failed_messages_only(name: &str) -> Self {
1431
0
        Self {
1432
0
            name: name.to_string(),
1433
0
            actor_id: None,
1434
0
            actor_name_pattern: None,
1435
0
            message_type_pattern: None,
1436
0
            min_processing_time_us: None,
1437
0
            failed_only: true,
1438
0
            max_stack_depth: None,
1439
0
        }
1440
0
    }
1441
    
1442
    /// Create a filter for delayed messages
1443
0
    pub fn slow_messages(name: &str, min_time_us: u64) -> Self {
1444
0
        Self {
1445
0
            name: name.to_string(),
1446
0
            actor_id: None,
1447
0
            actor_name_pattern: None,
1448
0
            message_type_pattern: None,
1449
0
            min_processing_time_us: Some(min_time_us),
1450
0
            failed_only: false,
1451
0
            max_stack_depth: None,
1452
0
        }
1453
0
    }
1454
}