vuio/platform/
diagnostics.rs

1use std::collections::HashMap;
2
3use std::path::{Path, PathBuf};
4use serde::{Deserialize, Serialize};
5use crate::platform::{PlatformInfo, PlatformError, OsType};
6
7/// Comprehensive diagnostic information for troubleshooting
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct DiagnosticInfo {
10    /// Platform information
11    pub platform: PlatformDiagnostics,
12    
13    /// Network configuration and status
14    pub network: NetworkDiagnostics,
15    
16    /// Database status and configuration
17    pub database: DatabaseDiagnostics,
18    
19    /// File system and directory information
20    pub filesystem: FilesystemDiagnostics,
21    
22    /// Application configuration status
23    pub configuration: ConfigurationDiagnostics,
24    
25    /// System resource information
26    pub system: SystemDiagnostics,
27    
28    /// Timestamp when diagnostics were collected
29    pub timestamp: chrono::DateTime<chrono::Utc>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct PlatformDiagnostics {
34    pub os_type: String,
35    pub os_version: String,
36    pub architecture: String,
37    pub hostname: String,
38    pub capabilities: PlatformCapabilitiesDiag,
39    pub platform_specific: HashMap<String, String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct PlatformCapabilitiesDiag {
44    pub case_sensitive_fs: bool,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct NetworkDiagnostics {
49    pub interfaces: Vec<NetworkInterfaceDiag>,
50    pub primary_interface: Option<String>,
51    pub multicast_support: bool,
52    pub port_availability: HashMap<u16, bool>,
53    pub connectivity_tests: HashMap<String, bool>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct NetworkInterfaceDiag {
58    pub name: String,
59    pub ip_address: String,
60    pub is_loopback: bool,
61    pub is_up: bool,
62    pub supports_multicast: bool,
63    pub interface_type: String,
64    pub mtu: Option<u32>,
65    pub speed: Option<u64>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub enum FirewallStatus {
70    Enabled,
71    Disabled,
72    Unknown,
73    Configured { rules: Vec<String> },
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct DatabaseDiagnostics {
78    pub database_path: Option<PathBuf>,
79    pub database_exists: bool,
80    pub database_size: Option<u64>,
81    pub database_accessible: bool,
82    pub schema_version: Option<u32>,
83    pub media_file_count: Option<u64>,
84    pub last_scan_time: Option<chrono::DateTime<chrono::Utc>>,
85    pub integrity_status: DatabaseIntegrityStatus,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub enum DatabaseIntegrityStatus {
90    Healthy,
91    Corrupted { details: String },
92    Unknown,
93    NotChecked,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct FilesystemDiagnostics {
98    pub monitored_directories: Vec<DirectoryDiag>,
99    pub config_directory: DirectoryDiag,
100    pub cache_directory: DirectoryDiag,
101    pub log_directory: DirectoryDiag,
102    pub temp_directory: DirectoryDiag,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct DirectoryDiag {
107    pub path: PathBuf,
108    pub exists: bool,
109    pub accessible: bool,
110    pub readable: bool,
111    pub writable: bool,
112    pub file_count: Option<u64>,
113    pub total_size: Option<u64>,
114    pub free_space: Option<u64>,
115    pub permissions: Option<String>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct ConfigurationDiagnostics {
120    pub config_file_path: Option<PathBuf>,
121    pub config_file_exists: bool,
122    pub config_file_valid: bool,
123    pub config_errors: Vec<String>,
124    pub hot_reload_enabled: bool,
125    pub default_values_used: Vec<String>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct SystemDiagnostics {
130    pub uptime: Option<u64>,
131    pub memory_total: Option<u64>,
132    pub memory_available: Option<u64>,
133    pub cpu_count: Option<u32>,
134    pub load_average: Option<f64>,
135    pub disk_usage: HashMap<String, DiskUsage>,
136    pub process_info: ProcessInfo,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct DiskUsage {
141    pub total: u64,
142    pub used: u64,
143    pub available: u64,
144    pub percentage: f64,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct ProcessInfo {
149    pub pid: u32,
150    pub memory_usage: Option<u64>,
151    pub cpu_usage: Option<f64>,
152    pub thread_count: Option<u32>,
153    pub file_descriptors: Option<u32>,
154}
155
156impl DiagnosticInfo {
157    /// Collect comprehensive diagnostic information
158    pub async fn collect() -> Result<Self, PlatformError> {
159        tracing::info!("Collecting diagnostic information...");
160        
161        let platform_info = PlatformInfo::detect().await?;
162        
163        Ok(DiagnosticInfo {
164            platform: Self::collect_platform_diagnostics(&platform_info).await?,
165            network: Self::collect_network_diagnostics(&platform_info).await?,
166            database: Self::collect_database_diagnostics().await?,
167            filesystem: Self::collect_filesystem_diagnostics().await?,
168            configuration: Self::collect_configuration_diagnostics().await?,
169            system: Self::collect_system_diagnostics().await?,
170            timestamp: chrono::Utc::now(),
171        })
172    }
173    
174    /// Collect platform-specific diagnostic information
175    async fn collect_platform_diagnostics(platform_info: &PlatformInfo) -> Result<PlatformDiagnostics, PlatformError> {
176        let hostname = hostname::get()
177            .map(|h| h.to_string_lossy().to_string())
178            .unwrap_or_else(|_| "unknown".to_string());
179        
180        let capabilities = PlatformCapabilitiesDiag {
181            case_sensitive_fs: platform_info.capabilities.case_sensitive_fs,
182        };
183        
184        Ok(PlatformDiagnostics {
185            os_type: platform_info.os_type.display_name().to_string(),
186            os_version: platform_info.version.clone(),
187            architecture: std::env::consts::ARCH.to_string(),
188            hostname,
189            capabilities,
190            platform_specific: platform_info.metadata.clone(),
191        })
192    }
193    
194    /// Collect network diagnostic information
195    async fn collect_network_diagnostics(platform_info: &PlatformInfo) -> Result<NetworkDiagnostics, PlatformError> {
196        let interfaces: Vec<NetworkInterfaceDiag> = platform_info.network_interfaces
197            .iter()
198            .map(|iface| NetworkInterfaceDiag {
199                name: iface.name.clone(),
200                ip_address: iface.ip_address.to_string(),
201                is_loopback: iface.is_loopback,
202                is_up: iface.is_up,
203                supports_multicast: iface.supports_multicast,
204                interface_type: format!("{:?}", iface.interface_type),
205                mtu: None, // TODO: Implement MTU detection
206                speed: None, // TODO: Implement speed detection
207            })
208            .collect();
209        
210        let primary_interface = platform_info.get_primary_interface()
211            .map(|iface| iface.name.clone());
212        
213        let multicast_support = true; // Simplified - assume multicast support
214        
215        // Test port availability
216        let mut port_availability = HashMap::new();
217        for port in &[1900, 8080, 8081, 8082] {
218            port_availability.insert(*port, Self::test_port_availability(*port).await);
219        }
220        
221        // Test basic connectivity
222        let mut connectivity_tests = HashMap::new();
223        connectivity_tests.insert("localhost".to_string(), Self::test_connectivity("127.0.0.1", 80).await);
224        connectivity_tests.insert("internet".to_string(), Self::test_connectivity("8.8.8.8", 53).await);
225        
226        
227        Ok(NetworkDiagnostics {
228            interfaces,
229            primary_interface,
230            multicast_support,
231            port_availability,
232            connectivity_tests,
233        })
234    }
235    
236    /// Test if a port is available for binding
237    async fn test_port_availability(port: u16) -> bool {
238        use tokio::net::TcpListener;
239        
240        (TcpListener::bind(format!("127.0.0.1:{}", port)).await).is_ok()
241    }
242    
243    /// Test basic network connectivity
244    async fn test_connectivity(host: &str, port: u16) -> bool {
245        use tokio::net::TcpStream;
246        use tokio::time::{timeout, Duration};
247        
248        matches!(timeout(Duration::from_secs(5), TcpStream::connect(format!("{}:{}", host, port))).await, Ok(Ok(_)))
249    }
250    
251    
252async fn collect_database_diagnostics() -> Result<DatabaseDiagnostics, PlatformError> {
253        // TODO: Implement database diagnostics collection
254        Ok(DatabaseDiagnostics {
255            database_path: None,
256            database_exists: true,
257            database_size: None,
258            database_accessible: false,
259            schema_version: None,
260            media_file_count: None,
261            last_scan_time: None,
262            integrity_status: DatabaseIntegrityStatus::NotChecked,
263        })
264    }
265    
266    /// Collect filesystem diagnostic information
267    async fn collect_filesystem_diagnostics() -> Result<FilesystemDiagnostics, PlatformError> {
268        // TODO: Implement filesystem diagnostics collection
269        Ok(FilesystemDiagnostics {
270            monitored_directories: vec![],
271            config_directory: Self::diagnose_directory(&PathBuf::from(".")).await,
272            cache_directory: Self::diagnose_directory(&PathBuf::from(".")).await,
273            log_directory: Self::diagnose_directory(&PathBuf::from(".")).await,
274            temp_directory: Self::diagnose_directory(&std::env::temp_dir()).await,
275        })
276    }
277    
278    /// Diagnose a specific directory
279    async fn diagnose_directory(path: &PathBuf) -> DirectoryDiag {
280        let exists = path.exists();
281        let accessible = path.is_dir();
282        let readable = path.metadata().map(|m| !m.permissions().readonly()).unwrap_or(false);
283        let writable = std::fs::OpenOptions::new()
284            .write(true)
285            .create(true)
286            .truncate(false)
287            .open(path.join(".test_write"))
288            .and_then(|_| std::fs::remove_file(path.join(".test_write")))
289            .is_ok();
290        
291        let (file_count, total_size) = if accessible {
292            Self::count_directory_contents(path).await
293        } else {
294            (None, None)
295        };
296        
297        let free_space = Self::get_free_space(path).await;
298        
299        DirectoryDiag {
300            path: path.clone(),
301            exists,
302            accessible,
303            readable,
304            writable,
305            file_count,
306            total_size,
307            free_space,
308            permissions: None, // TODO: Implement permission string formatting
309        }
310    }
311    
312    /// Count files and calculate total size in a directory
313    async fn count_directory_contents(path: &PathBuf) -> (Option<u64>, Option<u64>) {
314        match std::fs::read_dir(path) {
315            Ok(entries) => {
316                let mut count = 0;
317                let mut total_size = 0;
318                
319                for entry in entries.flatten() {
320                    count += 1;
321                    if let Ok(metadata) = entry.metadata() {
322                        total_size += metadata.len();
323                    }
324                }
325                
326                (Some(count), Some(total_size))
327            }
328            Err(_) => (None, None),
329        }
330    }
331    
332    /// Get available free space for a path
333    async fn get_free_space(_path: &Path) -> Option<u64> {
334        // TODO: Implement cross-platform free space detection
335        None
336    }
337    
338    /// Collect configuration diagnostic information
339    async fn collect_configuration_diagnostics() -> Result<ConfigurationDiagnostics, PlatformError> {
340        // TODO: Implement configuration diagnostics collection
341        Ok(ConfigurationDiagnostics {
342            config_file_path: None,
343            config_file_exists: false,
344            config_file_valid: false,
345            config_errors: vec![],
346            hot_reload_enabled: false,
347            default_values_used: vec![],
348        })
349    }
350    
351    /// Collect system diagnostic information
352    async fn collect_system_diagnostics() -> Result<SystemDiagnostics, PlatformError> {
353        let process_info = ProcessInfo {
354            pid: std::process::id(),
355            memory_usage: None, // TODO: Implement memory usage detection
356            cpu_usage: None,    // TODO: Implement CPU usage detection
357            thread_count: None, // TODO: Implement thread count detection
358            file_descriptors: None, // TODO: Implement FD count detection
359        };
360        
361        Ok(SystemDiagnostics {
362            uptime: None,           // TODO: Implement system uptime detection
363            memory_total: None,     // TODO: Implement total memory detection
364            memory_available: None, // TODO: Implement available memory detection
365            cpu_count: std::thread::available_parallelism()
366                .map(|n| n.get() as u32)
367                .ok(),
368            load_average: None,     // TODO: Implement load average detection
369            disk_usage: HashMap::new(), // TODO: Implement disk usage detection
370            process_info,
371        })
372    }
373    
374    /// Log diagnostic information at startup
375    pub fn log_startup_diagnostics(&self) {
376        tracing::info!("=== VuIO Startup Diagnostics ===");
377        tracing::info!("Platform: {} {} ({})", 
378            self.platform.os_type, 
379            self.platform.os_version, 
380            self.platform.architecture
381        );
382        tracing::info!("Hostname: {}", self.platform.hostname);
383        
384        // Log platform capabilities
385        tracing::info!("Platform Capabilities:");
386        tracing::info!("  - Case-sensitive FS: {}", self.platform.capabilities.case_sensitive_fs);
387        
388        // Log network information
389        tracing::info!("Network Configuration:");
390        tracing::info!("  - Interfaces found: {}", self.network.interfaces.len());
391        if let Some(primary) = &self.network.primary_interface {
392            tracing::info!("  - Primary interface: {}", primary);
393        }
394        tracing::info!("  - Multicast support: {}", self.network.multicast_support);
395        
396        // Log port availability
397        tracing::info!("Port Availability:");
398        for (port, available) in &self.network.port_availability {
399            let status = if *available { "Available" } else { "In use" };
400            tracing::info!("  - Port {}: {}", port, status);
401        }
402        
403        // Log connectivity tests
404        tracing::info!("Connectivity Tests:");
405        for (test, result) in &self.network.connectivity_tests {
406            let status = if *result { "Success" } else { "Failed" };
407            tracing::info!("  - {}: {}", test, status);
408        }
409        
410        // Log database status
411        tracing::info!("Database Status:");
412        tracing::info!("  - Database exists: {}", self.database.database_exists);
413        tracing::info!("  - Database accessible: {}", self.database.database_accessible);
414        tracing::info!("  - Integrity status: {:?}", self.database.integrity_status);
415        if let Some(count) = self.database.media_file_count {
416            tracing::info!("  - Media files: {}", count);
417        }
418        
419        // Log filesystem status
420        tracing::info!("Filesystem Status:");
421        tracing::info!("  - Config directory: {} (accessible: {})", 
422            self.filesystem.config_directory.path.display(),
423            self.filesystem.config_directory.accessible
424        );
425        tracing::info!("  - Monitored directories: {}", self.filesystem.monitored_directories.len());
426        
427        // Log configuration status
428        tracing::info!("Configuration Status:");
429        tracing::info!("  - Config file exists: {}", self.configuration.config_file_exists);
430        tracing::info!("  - Config file valid: {}", self.configuration.config_file_valid);
431        tracing::info!("  - Hot reload enabled: {}", self.configuration.hot_reload_enabled);
432        if !self.configuration.config_errors.is_empty() {
433            tracing::warn!("  - Configuration errors: {:?}", self.configuration.config_errors);
434        }
435        
436        // Log system information
437        tracing::info!("System Information:");
438        tracing::info!("  - Process ID: {}", self.system.process_info.pid);
439        if let Some(cpu_count) = self.system.cpu_count {
440            tracing::info!("  - CPU cores: {}", cpu_count);
441        }
442        
443        tracing::info!("=== End Diagnostics ===");
444    }
445    
446    /// Log diagnostic information for debugging
447    pub fn log_debug_diagnostics(&self) {
448        tracing::debug!("=== Detailed Diagnostic Information ===");
449        
450        // Log all network interfaces
451        tracing::debug!("Network Interfaces:");
452        for iface in &self.network.interfaces {
453            tracing::debug!("  - {} ({}): {} [{}] up={} multicast={}", 
454                iface.name, 
455                iface.interface_type,
456                iface.ip_address,
457                if iface.is_loopback { "loopback" } else { "physical" },
458                iface.is_up,
459                iface.supports_multicast
460            );
461        }
462        
463        // Log platform-specific metadata
464        tracing::debug!("Platform Metadata:");
465        for (key, value) in &self.platform.platform_specific {
466            tracing::debug!("  - {}: {}", key, value);
467        }
468        
469        // Log directory details
470        tracing::debug!("Directory Details:");
471        for dir in &self.filesystem.monitored_directories {
472            tracing::debug!("  - {}: exists={} readable={} writable={} files={:?}", 
473                dir.path.display(),
474                dir.exists,
475                dir.readable,
476                dir.writable,
477                dir.file_count
478            );
479        }
480        
481        tracing::debug!("=== End Detailed Diagnostics ===");
482    }
483    
484    /// Export diagnostics to JSON for support purposes
485    pub fn to_json(&self) -> Result<String, serde_json::Error> {
486        serde_json::to_string_pretty(self)
487    }
488    
489    /// Save diagnostics to a file
490    pub async fn save_to_file(&self, path: &PathBuf) -> Result<(), std::io::Error> {
491        let json = self.to_json().map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
492        tokio::fs::write(path, json).await
493    }
494}
495
496/// Startup diagnostic checks that can prevent application startup
497pub struct StartupDiagnostics;
498
499impl StartupDiagnostics {
500    /// Perform critical startup checks
501    pub async fn perform_startup_checks() -> Result<(), PlatformError> {
502        tracing::info!("Performing startup diagnostic checks...");
503        
504        // Check platform compatibility
505        Self::check_platform_compatibility().await?;
506        
507        // Check network requirements
508        Self::check_network_requirements().await?;
509        
510        // Check filesystem requirements
511        Self::check_filesystem_requirements().await?;
512        
513        // Check system resources
514        Self::check_system_resources().await?;
515        
516        tracing::info!("All startup diagnostic checks passed");
517        Ok(())
518    }
519    
520    async fn check_platform_compatibility() -> Result<(), PlatformError> {
521        let platform_info = PlatformInfo::detect().await?;
522        
523        // Check if platform is supported
524        match platform_info.os_type {
525            OsType::Windows | OsType::MacOS | OsType::Linux | OsType::Bsd => {
526                tracing::info!("Platform {} is supported", platform_info.os_type.display_name());
527            }
528        }
529        
530        
531        Ok(())
532    }
533    
534    async fn check_network_requirements() -> Result<(), PlatformError> {
535        let platform_info = PlatformInfo::detect().await?;
536        
537        // Check if we have at least one usable network interface
538        let usable_interfaces: Vec<_> = platform_info.network_interfaces
539            .iter()
540            .filter(|iface| !iface.is_loopback && iface.is_up)
541            .collect();
542        
543        if usable_interfaces.is_empty() {
544            tracing::error!("No usable network interfaces found");
545            return Err(PlatformError::NetworkConfig(
546                "No active network interfaces available for DLNA service".to_string()
547            ));
548        }
549        
550        tracing::info!("Found {} usable network interface(s)", usable_interfaces.len());
551        
552        // Test port availability
553        if !DiagnosticInfo::test_port_availability(1900).await {
554            tracing::warn!("Port 1900 is not available - will use alternative port");
555        }
556        
557        Ok(())
558    }
559    
560    async fn check_filesystem_requirements() -> Result<(), PlatformError> {
561        // Check if we can create necessary directories
562        let temp_dir = std::env::temp_dir();
563        let test_dir = temp_dir.join("vuio_startup_test");
564        
565        match std::fs::create_dir_all(&test_dir) {
566            Ok(_) => {
567                tracing::debug!("Filesystem write test passed");
568                let _ = std::fs::remove_dir_all(&test_dir);
569            }
570            Err(e) => {
571                tracing::error!("Filesystem write test failed: {}", e);
572                return Err(PlatformError::FileSystemAccess(
573                    format!("Cannot create directories: {}", e)
574                ));
575            }
576        }
577        
578        Ok(())
579    }
580    
581    async fn check_system_resources() -> Result<(), PlatformError> {
582        // Check available memory (basic check)
583        // TODO: Implement more comprehensive resource checks
584        
585        tracing::debug!("System resource checks passed");
586        Ok(())
587    }
588}
589
590#[cfg(test)]
591mod tests {
592    use super::*;
593    
594    #[tokio::test]
595    async fn test_diagnostic_collection() {
596        let diagnostics = DiagnosticInfo::collect().await;
597        assert!(diagnostics.is_ok());
598        
599        let diag = diagnostics.unwrap();
600        assert!(!diag.platform.os_type.is_empty());
601        assert!(!diag.platform.hostname.is_empty());
602    }
603    
604    #[tokio::test]
605    async fn test_port_availability() {
606        // Test a port that should be available
607        let available = DiagnosticInfo::test_port_availability(0).await; // Port 0 should always be available
608        assert!(available);
609    }
610    
611    #[tokio::test]
612    async fn test_startup_diagnostics() {
613        let result = StartupDiagnostics::perform_startup_checks().await;
614        // This might fail in some test environments, so we just check it doesn't panic
615        match result {
616            Ok(_) => tracing::info!("Startup diagnostics passed"),
617            Err(e) => tracing::warn!("Startup diagnostics failed: {}", e),
618        }
619    }
620    
621    #[test]
622    fn test_diagnostic_serialization() {
623        let diag = DiagnosticInfo {
624            platform: PlatformDiagnostics {
625                os_type: "Linux".to_string(),
626                os_version: "5.4.0".to_string(),
627                architecture: "x86_64".to_string(),
628                hostname: "test-host".to_string(),
629                capabilities: PlatformCapabilitiesDiag {
630                    case_sensitive_fs: true,
631                },
632                platform_specific: HashMap::new(),
633            },
634            network: NetworkDiagnostics {
635                interfaces: vec![],
636                primary_interface: None,
637                multicast_support: true,
638                port_availability: HashMap::new(),
639                connectivity_tests: HashMap::new(),
640            },
641            database: DatabaseDiagnostics {
642                database_path: None,
643                database_exists: false,
644                database_size: None,
645                database_accessible: false,
646                schema_version: None,
647                media_file_count: None,
648                last_scan_time: None,
649                integrity_status: DatabaseIntegrityStatus::NotChecked,
650            },
651            filesystem: FilesystemDiagnostics {
652                monitored_directories: vec![],
653                config_directory: DirectoryDiag {
654                    path: PathBuf::from("/tmp"),
655                    exists: true,
656                    accessible: true,
657                    readable: true,
658                    writable: true,
659                    file_count: None,
660                    total_size: None,
661                    free_space: None,
662                    permissions: None,
663                },
664                cache_directory: DirectoryDiag {
665                    path: PathBuf::from("/tmp"),
666                    exists: true,
667                    accessible: true,
668                    readable: true,
669                    writable: true,
670                    file_count: None,
671                    total_size: None,
672                    free_space: None,
673                    permissions: None,
674                },
675                log_directory: DirectoryDiag {
676                    path: PathBuf::from("/tmp"),
677                    exists: true,
678                    accessible: true,
679                    readable: true,
680                    writable: true,
681                    file_count: None,
682                    total_size: None,
683                    free_space: None,
684                    permissions: None,
685                },
686                temp_directory: DirectoryDiag {
687                    path: PathBuf::from("/tmp"),
688                    exists: true,
689                    accessible: true,
690                    readable: true,
691                    writable: true,
692                    file_count: None,
693                    total_size: None,
694                    free_space: None,
695                    permissions: None,
696                },
697            },
698            configuration: ConfigurationDiagnostics {
699                config_file_path: None,
700                config_file_exists: false,
701                config_file_valid: false,
702                config_errors: vec![],
703                hot_reload_enabled: false,
704                default_values_used: vec![],
705            },
706            system: SystemDiagnostics {
707                uptime: None,
708                memory_total: None,
709                memory_available: None,
710                cpu_count: Some(4),
711                load_average: None,
712                disk_usage: HashMap::new(),
713                process_info: ProcessInfo {
714                    pid: 1234,
715                    memory_usage: None,
716                    cpu_usage: None,
717                    thread_count: None,
718                    file_descriptors: None,
719                },
720            },
721            timestamp: chrono::Utc::now(),
722        };
723        
724        let json = diag.to_json();
725        assert!(json.is_ok());
726        assert!(json.unwrap().contains("Linux"));
727    }
728}