1use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8use std::time::SystemTime;
9use sysinfo::System;
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct SystemMetadata {
17 pub oxur_version: String,
19 pub rust_version: String,
21 pub cargo_version: String,
23 pub os_name: String,
25 pub os_version: String,
27 pub arch: String,
29 pub hostname: String,
31 pub pid: u32,
33 pub cwd: PathBuf,
35 pub started_at: SystemTime,
37}
38
39impl SystemMetadata {
40 pub fn capture() -> Self {
45 Self {
46 oxur_version: env!("CARGO_PKG_VERSION").to_string(),
47 rust_version: detect_rust_version(),
48 cargo_version: detect_cargo_version(),
49 os_name: System::name().unwrap_or_else(|| "unknown".to_string()),
50 os_version: System::os_version().unwrap_or_else(|| "unknown".to_string()),
51 arch: System::cpu_arch().unwrap_or_else(|| "unknown".to_string()),
52 hostname: System::host_name().unwrap_or_else(|| "unknown".to_string()),
53 pid: std::process::id(),
54 cwd: std::env::current_dir().unwrap_or_default(),
55 started_at: SystemTime::now(),
56 }
57 }
58
59 pub fn uptime_seconds(&self) -> f64 {
61 self.started_at.elapsed().map(|d| d.as_secs_f64()).unwrap_or(0.0)
62 }
63}
64
65fn detect_rust_version() -> String {
67 std::process::Command::new("rustc")
68 .arg("--version")
69 .output()
70 .ok()
71 .and_then(|o| String::from_utf8(o.stdout).ok())
72 .map(|s| s.trim().to_string())
73 .unwrap_or_else(|| "unknown".to_string())
74}
75
76fn detect_cargo_version() -> String {
78 std::process::Command::new("cargo")
79 .arg("--version")
80 .output()
81 .ok()
82 .and_then(|o| String::from_utf8(o.stdout).ok())
83 .map(|s| s.trim().to_string())
84 .unwrap_or_else(|| "unknown".to_string())
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn test_capture_metadata() {
93 let metadata = SystemMetadata::capture();
94
95 assert!(!metadata.oxur_version.is_empty());
97
98 assert!(!metadata.rust_version.is_empty());
100
101 assert!(!metadata.cargo_version.is_empty());
103
104 assert!(!metadata.os_name.is_empty());
106 assert!(!metadata.arch.is_empty());
107
108 assert!(metadata.pid > 0);
110
111 let _ = metadata.cwd;
114 }
115
116 #[test]
117 fn test_uptime_increases() {
118 let metadata = SystemMetadata::capture();
119
120 let uptime1 = metadata.uptime_seconds();
122 assert!(uptime1 < 1.0);
123
124 std::thread::sleep(std::time::Duration::from_millis(10));
126
127 let uptime2 = metadata.uptime_seconds();
129 assert!(uptime2 > uptime1);
130 }
131
132 #[test]
133 fn test_rust_version_detection() {
134 let version = detect_rust_version();
135 assert!(version.starts_with("rustc") || version == "unknown");
137 }
138
139 #[test]
140 fn test_cargo_version_detection() {
141 let version = detect_cargo_version();
142 assert!(version.starts_with("cargo") || version == "unknown");
144 }
145
146 #[test]
147 fn test_metadata_serialization() {
148 let metadata = SystemMetadata::capture();
149
150 let json = serde_json::to_string(&metadata).expect("Failed to serialize");
152 assert!(!json.is_empty());
153
154 let deserialized: SystemMetadata =
156 serde_json::from_str(&json).expect("Failed to deserialize");
157 assert_eq!(metadata.oxur_version, deserialized.oxur_version);
158 assert_eq!(metadata.pid, deserialized.pid);
159 }
160}