1use crate::crdt::{CRDT, Mergeable, ReplicaId};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct DevToolsConfig {
15 pub enable_crdt_inspection: bool,
17 pub enable_sync_monitoring: bool,
19 pub enable_transport_monitoring: bool,
21 pub max_events: usize,
23 pub enable_performance_metrics: bool,
25}
26
27impl Default for DevToolsConfig {
28 fn default() -> Self {
29 Self {
30 enable_crdt_inspection: true,
31 enable_sync_monitoring: true,
32 enable_transport_monitoring: true,
33 max_events: 1000,
34 enable_performance_metrics: true,
35 }
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub enum DevToolsEvent {
42 CrdtOperation {
44 crdt_id: String,
45 operation: String,
46 timestamp: u64,
47 replica_id: ReplicaId,
48 },
49 SyncOperation {
51 operation_id: String,
52 operation_type: String,
53 status: String,
54 timestamp: u64,
55 duration_ms: Option<u64>,
56 },
57 TransportEvent {
59 transport_type: String,
60 event_type: String,
61 timestamp: u64,
62 details: String,
63 },
64 PerformanceMetric {
66 metric_name: String,
67 value: f64,
68 timestamp: u64,
69 unit: String,
70 },
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct CrdtInspection {
76 pub id: String,
78 pub type_name: String,
80 pub replica_id: ReplicaId,
82 pub state_summary: String,
84 pub operation_count: u64,
86 pub last_operation_at: u64,
88 pub memory_usage_bytes: usize,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct SyncStats {
95 pub total_operations: u64,
97 pub successful_operations: u64,
99 pub failed_operations: u64,
101 pub avg_duration_ms: f64,
103 pub last_sync_at: u64,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct TransportStats {
110 pub transport_type: String,
112 pub messages_sent: u64,
114 pub messages_received: u64,
116 pub is_connected: bool,
118 pub last_activity_at: u64,
120 pub error_count: u64,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct PerformanceMetrics {
127 pub crdt_merges_per_second: f64,
129 pub sync_operations_per_second: f64,
131 pub memory_usage_bytes: usize,
133 pub cpu_usage_percent: f64,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct DevToolsExport {
140 pub events: Vec<DevToolsEvent>,
142 pub crdt_inspections: HashMap<String, CrdtInspection>,
144 pub sync_stats: SyncStats,
146 pub transport_stats: HashMap<String, TransportStats>,
148 pub performance_metrics: PerformanceMetrics,
150 pub config: DevToolsConfig,
152}
153
154pub struct DevTools {
156 config: DevToolsConfig,
157 events: Arc<RwLock<Vec<DevToolsEvent>>>,
158 crdt_inspections: Arc<RwLock<HashMap<String, CrdtInspection>>>,
159 sync_stats: Arc<RwLock<SyncStats>>,
160 transport_stats: Arc<RwLock<HashMap<String, TransportStats>>>,
161 performance_metrics: Arc<RwLock<PerformanceMetrics>>,
162}
163
164impl DevTools {
165 pub fn new(config: DevToolsConfig) -> Self {
167 Self {
168 config,
169 events: Arc::new(RwLock::new(Vec::new())),
170 crdt_inspections: Arc::new(RwLock::new(HashMap::new())),
171 sync_stats: Arc::new(RwLock::new(SyncStats {
172 total_operations: 0,
173 successful_operations: 0,
174 failed_operations: 0,
175 avg_duration_ms: 0.0,
176 last_sync_at: 0,
177 })),
178 transport_stats: Arc::new(RwLock::new(HashMap::new())),
179 performance_metrics: Arc::new(RwLock::new(PerformanceMetrics {
180 crdt_merges_per_second: 0.0,
181 sync_operations_per_second: 0.0,
182 memory_usage_bytes: 0,
183 cpu_usage_percent: 0.0,
184 })),
185 }
186 }
187
188 pub async fn record_crdt_operation(&self, crdt_id: String, operation: String, replica_id: ReplicaId) {
190 if !self.config.enable_crdt_inspection {
191 return;
192 }
193
194 let event = DevToolsEvent::CrdtOperation {
195 crdt_id: crdt_id.clone(),
196 operation,
197 timestamp: self.current_timestamp(),
198 replica_id,
199 };
200
201 self.add_event(event).await;
202
203 self.update_crdt_inspection(crdt_id, replica_id).await;
205 }
206
207 pub async fn record_sync_operation(&self, operation_id: String, operation_type: String, status: String, duration_ms: Option<u64>) {
209 if !self.config.enable_sync_monitoring {
210 return;
211 }
212
213 let is_success = status == "success";
214 let event = DevToolsEvent::SyncOperation {
215 operation_id,
216 operation_type,
217 status,
218 timestamp: self.current_timestamp(),
219 duration_ms,
220 };
221
222 self.add_event(event).await;
223 self.update_sync_stats(is_success, duration_ms).await;
224 }
225
226 pub async fn record_transport_event(&self, transport_type: String, event_type: String, details: String) {
228 if !self.config.enable_transport_monitoring {
229 return;
230 }
231
232 let event = DevToolsEvent::TransportEvent {
233 transport_type: transport_type.clone(),
234 event_type,
235 timestamp: self.current_timestamp(),
236 details,
237 };
238
239 self.add_event(event).await;
240 self.update_transport_stats(transport_type).await;
241 }
242
243 pub async fn record_performance_metric(&self, metric_name: String, value: f64, unit: String) {
245 if !self.config.enable_performance_metrics {
246 return;
247 }
248
249 let event = DevToolsEvent::PerformanceMetric {
250 metric_name,
251 value,
252 timestamp: self.current_timestamp(),
253 unit,
254 };
255
256 self.add_event(event).await;
257 }
258
259 pub async fn get_events(&self) -> Vec<DevToolsEvent> {
261 self.events.read().await.clone()
262 }
263
264 pub async fn get_crdt_inspections(&self) -> HashMap<String, CrdtInspection> {
266 self.crdt_inspections.read().await.clone()
267 }
268
269 pub async fn get_sync_stats(&self) -> SyncStats {
271 self.sync_stats.read().await.clone()
272 }
273
274 pub async fn get_transport_stats(&self) -> HashMap<String, TransportStats> {
276 self.transport_stats.read().await.clone()
277 }
278
279 pub async fn get_performance_metrics(&self) -> PerformanceMetrics {
281 self.performance_metrics.read().await.clone()
282 }
283
284 pub async fn clear_events(&self) {
286 let mut events = self.events.write().await;
287 events.clear();
288 }
289
290 pub fn config(&self) -> &DevToolsConfig {
292 &self.config
293 }
294
295 pub async fn get_events_by_type(&self, event_type: &str) -> Vec<DevToolsEvent> {
297 let events = self.events.read().await;
298 events.iter()
299 .filter(|event| match event {
300 DevToolsEvent::CrdtOperation { .. } => event_type == "crdt_operation",
301 DevToolsEvent::SyncOperation { .. } => event_type == "sync_operation",
302 DevToolsEvent::TransportEvent { .. } => event_type == "transport_event",
303 DevToolsEvent::PerformanceMetric { .. } => event_type == "performance_metric",
304 })
305 .cloned()
306 .collect()
307 }
308
309 pub async fn get_recent_events(&self, count: usize) -> Vec<DevToolsEvent> {
311 let events = self.events.read().await;
312 let start = if events.len() > count {
313 events.len() - count
314 } else {
315 0
316 };
317 events[start..].to_vec()
318 }
319
320 pub async fn get_event_counts(&self) -> HashMap<String, usize> {
322 let events = self.events.read().await;
323 let mut counts = HashMap::new();
324
325 for event in events.iter() {
326 let event_type = match event {
327 DevToolsEvent::CrdtOperation { .. } => "crdt_operation",
328 DevToolsEvent::SyncOperation { .. } => "sync_operation",
329 DevToolsEvent::TransportEvent { .. } => "transport_event",
330 DevToolsEvent::PerformanceMetric { .. } => "performance_metric",
331 };
332
333 *counts.entry(event_type.to_string()).or_insert(0) += 1;
334 }
335
336 counts
337 }
338
339 pub async fn export_data(&self) -> Result<String, serde_json::Error> {
341 let data = DevToolsExport {
342 events: self.get_events().await,
343 crdt_inspections: self.get_crdt_inspections().await,
344 sync_stats: self.get_sync_stats().await,
345 transport_stats: self.get_transport_stats().await,
346 performance_metrics: self.get_performance_metrics().await,
347 config: self.config().clone(),
348 };
349
350 serde_json::to_string_pretty(&data)
351 }
352
353 async fn add_event(&self, event: DevToolsEvent) {
355 let mut events = self.events.write().await;
356 events.push(event);
357
358 if events.len() > self.config.max_events {
360 let excess = events.len() - self.config.max_events;
361 events.drain(0..excess);
362 }
363 }
364
365 async fn update_crdt_inspection(&self, crdt_id: String, replica_id: ReplicaId) {
366 let mut inspections = self.crdt_inspections.write().await;
367 let inspection = inspections.entry(crdt_id.clone()).or_insert(CrdtInspection {
368 id: crdt_id,
369 type_name: "Unknown".to_string(),
370 replica_id,
371 state_summary: "Unknown".to_string(),
372 operation_count: 0,
373 last_operation_at: 0,
374 memory_usage_bytes: 0,
375 });
376
377 inspection.operation_count += 1;
378 inspection.last_operation_at = self.current_timestamp();
379 }
380
381 async fn update_sync_stats(&self, success: bool, duration_ms: Option<u64>) {
382 let mut stats = self.sync_stats.write().await;
383 stats.total_operations += 1;
384
385 if success {
386 stats.successful_operations += 1;
387 } else {
388 stats.failed_operations += 1;
389 }
390
391 if let Some(duration) = duration_ms {
392 let total_duration = stats.avg_duration_ms * (stats.total_operations - 1) as f64 + duration as f64;
394 stats.avg_duration_ms = total_duration / stats.total_operations as f64;
395 }
396
397 stats.last_sync_at = self.current_timestamp();
398 }
399
400 async fn update_transport_stats(&self, transport_type: String) {
401 let mut stats = self.transport_stats.write().await;
402 let transport_type_clone = transport_type.clone();
403 let transport_stats = stats.entry(transport_type).or_insert(TransportStats {
404 transport_type: transport_type_clone,
405 messages_sent: 0,
406 messages_received: 0,
407 is_connected: false,
408 last_activity_at: 0,
409 error_count: 0,
410 });
411
412 transport_stats.last_activity_at = self.current_timestamp();
413 }
414
415 fn current_timestamp(&self) -> u64 {
416 std::time::SystemTime::now()
417 .duration_since(std::time::UNIX_EPOCH)
418 .unwrap()
419 .as_millis() as u64
420 }
421}
422
423pub struct CrdtInspector {
425 devtools: Arc<DevTools>,
426}
427
428impl CrdtInspector {
429 pub fn new(devtools: Arc<DevTools>) -> Self {
431 Self { devtools }
432 }
433
434 pub async fn inspect_crdt<T: CRDT + Mergeable>(&self, crdt: &T, crdt_id: String) -> CrdtInspection {
436 let replica_id = crdt.replica_id().clone();
437 let type_name = std::any::type_name::<T>().to_string();
438
439 let state_summary = format!("CRDT of type {}", type_name);
441 let memory_usage_bytes = std::mem::size_of_val(crdt);
442
443 let inspection = CrdtInspection {
444 id: crdt_id.clone(),
445 type_name,
446 replica_id,
447 state_summary,
448 operation_count: 0, last_operation_at: 0,
450 memory_usage_bytes,
451 };
452
453 let mut inspections = self.devtools.crdt_inspections.write().await;
455 inspections.insert(crdt_id, inspection.clone());
456
457 inspection
458 }
459
460 pub async fn get_all_inspections(&self) -> HashMap<String, CrdtInspection> {
462 self.devtools.get_crdt_inspections().await
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469 use crate::crdt::{LwwRegister, ReplicaId};
470 use uuid::Uuid;
471
472 fn create_replica(id: u64) -> ReplicaId {
473 ReplicaId::from(Uuid::from_u64_pair(0, id))
474 }
475
476 #[tokio::test]
477 async fn test_devtools_creation() {
478 let config = DevToolsConfig::default();
479 let devtools = DevTools::new(config);
480
481 assert!(devtools.config().enable_crdt_inspection);
482 assert!(devtools.config().enable_sync_monitoring);
483 assert!(devtools.config().enable_transport_monitoring);
484 assert_eq!(devtools.config().max_events, 1000);
485 }
486
487 #[tokio::test]
488 async fn test_record_crdt_operation() {
489 let config = DevToolsConfig::default();
490 let devtools = DevTools::new(config);
491 let replica_id = create_replica(1);
492
493 devtools.record_crdt_operation("test-crdt".to_string(), "add".to_string(), replica_id).await;
494
495 let events = devtools.get_events().await;
496 assert_eq!(events.len(), 1);
497
498 match &events[0] {
499 DevToolsEvent::CrdtOperation { crdt_id, operation, replica_id: recorded_replica_id, .. } => {
500 assert_eq!(crdt_id, "test-crdt");
501 assert_eq!(operation, "add");
502 assert_eq!(recorded_replica_id, &replica_id);
503 }
504 _ => panic!("Expected CrdtOperation event"),
505 }
506 }
507
508 #[tokio::test]
509 async fn test_record_sync_operation() {
510 let config = DevToolsConfig::default();
511 let devtools = DevTools::new(config);
512
513 devtools.record_sync_operation(
514 "sync-1".to_string(),
515 "merge".to_string(),
516 "success".to_string(),
517 Some(150)
518 ).await;
519
520 let events = devtools.get_events().await;
521 assert_eq!(events.len(), 1);
522
523 match &events[0] {
524 DevToolsEvent::SyncOperation { operation_id, operation_type, status, duration_ms, .. } => {
525 assert_eq!(operation_id, "sync-1");
526 assert_eq!(operation_type, "merge");
527 assert_eq!(status, "success");
528 assert_eq!(duration_ms, &Some(150));
529 }
530 _ => panic!("Expected SyncOperation event"),
531 }
532 }
533
534 #[tokio::test]
535 async fn test_record_transport_event() {
536 let config = DevToolsConfig::default();
537 let devtools = DevTools::new(config);
538
539 devtools.record_transport_event(
540 "websocket".to_string(),
541 "connected".to_string(),
542 "Connected to server".to_string()
543 ).await;
544
545 let events = devtools.get_events().await;
546 assert_eq!(events.len(), 1);
547
548 match &events[0] {
549 DevToolsEvent::TransportEvent { transport_type, event_type, details, .. } => {
550 assert_eq!(transport_type, "websocket");
551 assert_eq!(event_type, "connected");
552 assert_eq!(details, "Connected to server");
553 }
554 _ => panic!("Expected TransportEvent"),
555 }
556 }
557
558 #[tokio::test]
559 async fn test_record_performance_metric() {
560 let config = DevToolsConfig::default();
561 let devtools = DevTools::new(config);
562
563 devtools.record_performance_metric(
564 "memory_usage".to_string(),
565 1024.0,
566 "bytes".to_string()
567 ).await;
568
569 let events = devtools.get_events().await;
570 assert_eq!(events.len(), 1);
571
572 match &events[0] {
573 DevToolsEvent::PerformanceMetric { metric_name, value, unit, .. } => {
574 assert_eq!(metric_name, "memory_usage");
575 assert_eq!(*value, 1024.0);
576 assert_eq!(unit, "bytes");
577 }
578 _ => panic!("Expected PerformanceMetric event"),
579 }
580 }
581
582 #[tokio::test]
583 async fn test_sync_stats_tracking() {
584 let config = DevToolsConfig::default();
585 let devtools = DevTools::new(config);
586
587 devtools.record_sync_operation(
589 "sync-1".to_string(),
590 "merge".to_string(),
591 "success".to_string(),
592 Some(100)
593 ).await;
594
595 devtools.record_sync_operation(
597 "sync-2".to_string(),
598 "merge".to_string(),
599 "failed".to_string(),
600 Some(200)
601 ).await;
602
603 let stats = devtools.get_sync_stats().await;
604 assert_eq!(stats.total_operations, 2);
605 assert_eq!(stats.successful_operations, 1);
606 assert_eq!(stats.failed_operations, 1);
607 assert_eq!(stats.avg_duration_ms, 150.0); }
609
610 #[tokio::test]
611 async fn test_event_limit() {
612 let config = DevToolsConfig {
613 max_events: 3,
614 ..Default::default()
615 };
616 let devtools = DevTools::new(config);
617
618 for i in 0..5 {
620 devtools.record_crdt_operation(
621 format!("crdt-{}", i),
622 "add".to_string(),
623 create_replica(1)
624 ).await;
625 }
626
627 let events = devtools.get_events().await;
628 assert_eq!(events.len(), 3); }
630
631 #[tokio::test]
632 async fn test_clear_events() {
633 let config = DevToolsConfig::default();
634 let devtools = DevTools::new(config);
635
636 devtools.record_crdt_operation("test".to_string(), "add".to_string(), create_replica(1)).await;
638 assert_eq!(devtools.get_events().await.len(), 1);
639
640 devtools.clear_events().await;
642 assert_eq!(devtools.get_events().await.len(), 0);
643 }
644
645 #[tokio::test]
646 async fn test_crdt_inspector() {
647 let config = DevToolsConfig::default();
648 let devtools = Arc::new(DevTools::new(config));
649 let inspector = CrdtInspector::new(devtools.clone());
650
651 let replica_id = create_replica(1);
652 let crdt = LwwRegister::new("test".to_string(), replica_id.clone());
653
654 let inspection = inspector.inspect_crdt(&crdt, "test-crdt".to_string()).await;
655
656 assert_eq!(inspection.id, "test-crdt");
657 assert!(inspection.type_name.contains("LwwRegister"));
658 assert_eq!(inspection.replica_id, replica_id);
659 assert!(inspection.memory_usage_bytes > 0);
660
661 let inspections = inspector.get_all_inspections().await;
663 assert_eq!(inspections.len(), 1);
664 assert!(inspections.contains_key("test-crdt"));
665 }
666
667 #[tokio::test]
668 async fn test_disabled_features() {
669 let config = DevToolsConfig {
670 enable_crdt_inspection: false,
671 enable_sync_monitoring: false,
672 enable_transport_monitoring: false,
673 enable_performance_metrics: false,
674 ..Default::default()
675 };
676 let devtools = DevTools::new(config);
677
678 devtools.record_crdt_operation("test".to_string(), "add".to_string(), create_replica(1)).await;
680 devtools.record_sync_operation("test".to_string(), "merge".to_string(), "success".to_string(), None).await;
681 devtools.record_transport_event("test".to_string(), "connected".to_string(), "test".to_string()).await;
682 devtools.record_performance_metric("test".to_string(), 1.0, "test".to_string()).await;
683
684 assert_eq!(devtools.get_events().await.len(), 0);
685 }
686
687 #[tokio::test]
688 async fn test_get_events_by_type() {
689 let config = DevToolsConfig::default();
690 let devtools = DevTools::new(config);
691
692 devtools.record_crdt_operation("test".to_string(), "add".to_string(), create_replica(1)).await;
694 devtools.record_sync_operation("test".to_string(), "merge".to_string(), "success".to_string(), None).await;
695 devtools.record_transport_event("test".to_string(), "connected".to_string(), "test".to_string()).await;
696 devtools.record_performance_metric("test".to_string(), 1.0, "test".to_string()).await;
697
698 let crdt_events = devtools.get_events_by_type("crdt_operation").await;
700 assert_eq!(crdt_events.len(), 1);
701
702 let sync_events = devtools.get_events_by_type("sync_operation").await;
703 assert_eq!(sync_events.len(), 1);
704
705 let transport_events = devtools.get_events_by_type("transport_event").await;
706 assert_eq!(transport_events.len(), 1);
707
708 let perf_events = devtools.get_events_by_type("performance_metric").await;
709 assert_eq!(perf_events.len(), 1);
710 }
711
712 #[tokio::test]
713 async fn test_get_recent_events() {
714 let config = DevToolsConfig::default();
715 let devtools = DevTools::new(config);
716
717 for i in 0..5 {
719 devtools.record_crdt_operation(format!("test-{}", i), "add".to_string(), create_replica(1)).await;
720 }
721
722 let recent = devtools.get_recent_events(3).await;
724 assert_eq!(recent.len(), 3);
725
726 let all_recent = devtools.get_recent_events(10).await;
728 assert_eq!(all_recent.len(), 5);
729 }
730
731 #[tokio::test]
732 async fn test_get_event_counts() {
733 let config = DevToolsConfig::default();
734 let devtools = DevTools::new(config);
735
736 devtools.record_crdt_operation("test".to_string(), "add".to_string(), create_replica(1)).await;
738 devtools.record_crdt_operation("test2".to_string(), "remove".to_string(), create_replica(1)).await;
739 devtools.record_sync_operation("test".to_string(), "merge".to_string(), "success".to_string(), None).await;
740 devtools.record_transport_event("test".to_string(), "connected".to_string(), "test".to_string()).await;
741
742 let counts = devtools.get_event_counts().await;
743 assert_eq!(counts.get("crdt_operation"), Some(&2));
744 assert_eq!(counts.get("sync_operation"), Some(&1));
745 assert_eq!(counts.get("transport_event"), Some(&1));
746 assert_eq!(counts.get("performance_metric"), None);
748 }
749
750 #[tokio::test]
751 async fn test_export_data() {
752 let config = DevToolsConfig::default();
753 let devtools = DevTools::new(config);
754
755 devtools.record_crdt_operation("test".to_string(), "add".to_string(), create_replica(1)).await;
757 devtools.record_sync_operation("test".to_string(), "merge".to_string(), "success".to_string(), None).await;
758
759 let export_json = devtools.export_data().await.unwrap();
761 assert!(export_json.contains("test"));
762 assert!(export_json.contains("CrdtOperation"));
763 assert!(export_json.contains("SyncOperation"));
764
765 let parsed: DevToolsExport = serde_json::from_str(&export_json).unwrap();
767 assert_eq!(parsed.events.len(), 2);
768 assert_eq!(parsed.config.max_events, 1000);
769 }
770}