riscfetch_core/
lib.rs

1//! RISC-V system information library
2//!
3//! Provides functions to detect and query RISC-V specific system information
4//! including ISA extensions, hardware IDs, vector capabilities, and more.
5//!
6//! # Example
7//!
8//! ```no_run
9//! use riscfetch_core::*;
10//!
11//! if is_riscv() {
12//!     println!("ISA: {}", get_isa_string());
13//!     println!("Extensions: {}", get_extensions_compact());
14//! }
15//! ```
16
17mod extensions;
18mod hardware;
19mod parsing;
20mod system;
21mod types;
22
23// Re-export types
24pub use types::{CacheInfo, ExtensionEntry, HardwareIds, RiscvInfo, SystemInfo, VectorInfo};
25
26// Re-export extension definitions
27pub use extensions::{
28    STANDARD_EXTENSIONS, S_CATEGORY_NAMES, S_EXTENSIONS, Z_CATEGORY_NAMES, Z_EXTENSIONS,
29};
30
31// Re-export parsing functions and types
32pub use parsing::{
33    get_all_s_extensions_with_status, get_all_standard_extensions_with_status,
34    get_all_z_extensions_with_status, get_s_category_name, get_z_category_name, group_by_category,
35    parse_extensions_compact, parse_extensions_explained, parse_s_extensions,
36    parse_s_extensions_explained, parse_s_extensions_with_category, parse_vector_from_isa,
37    parse_z_extensions, parse_z_extensions_explained, parse_z_extensions_with_category,
38    ExtensionInfo,
39};
40
41// Re-export hardware functions
42pub use hardware::{
43    get_board_info, get_cache_info, get_hardware_ids, get_hart_count, get_hart_count_num,
44    get_isa_string, get_vector_detail,
45};
46
47// Re-export system functions
48pub use system::{
49    get_kernel_info, get_memory_bytes, get_memory_info, get_os_info, get_uptime, get_uptime_seconds,
50};
51
52use std::fs;
53use std::process::Command;
54use sysinfo::System;
55
56/// Check if the current system is RISC-V architecture
57#[must_use]
58pub fn is_riscv() -> bool {
59    if let Ok(output) = Command::new("uname").arg("-m").output() {
60        let arch = String::from_utf8_lossy(&output.stdout);
61        if arch.contains("riscv") {
62            return true;
63        }
64    }
65
66    if let Ok(content) = fs::read_to_string("/proc/cpuinfo") {
67        if content.contains("riscv") || content.contains("RISC-V") {
68            return true;
69        }
70    }
71
72    false
73}
74
75/// Get compact extension list (e.g., "I M A F D C V")
76#[must_use]
77pub fn get_extensions_compact() -> String {
78    parse_extensions_compact(&get_isa_string())
79}
80
81/// Get Z-extensions as compact string
82#[must_use]
83pub fn get_z_extensions() -> String {
84    parse_z_extensions(&get_isa_string())
85}
86
87/// Get extensions with explanations
88#[must_use]
89pub fn get_extensions_explained() -> Vec<(String, String)> {
90    parse_extensions_explained(&get_isa_string())
91}
92
93/// Get Z-extensions with explanations
94#[must_use]
95pub fn get_z_extensions_explained() -> Vec<(String, String)> {
96    parse_z_extensions_explained(&get_isa_string())
97}
98
99/// Get S-extensions as compact string
100#[must_use]
101pub fn get_s_extensions() -> String {
102    parse_s_extensions(&get_isa_string())
103}
104
105/// Get S-extensions with explanations
106#[must_use]
107pub fn get_s_extensions_explained() -> Vec<(String, String)> {
108    parse_s_extensions_explained(&get_isa_string())
109}
110
111/// Get Z-extensions with category info
112#[must_use]
113pub fn get_z_extensions_with_category() -> Vec<ExtensionInfo> {
114    parse_z_extensions_with_category(&get_isa_string())
115}
116
117/// Get S-extensions with category info
118#[must_use]
119pub fn get_s_extensions_with_category() -> Vec<ExtensionInfo> {
120    parse_s_extensions_with_category(&get_isa_string())
121}
122
123/// Collect RISC-V specific information only (excludes generic system info)
124#[must_use]
125pub fn collect_riscv_info() -> RiscvInfo {
126    use types::ExtensionEntry;
127
128    let mut sys = System::new();
129    sys.refresh_cpu_all();
130
131    let isa = get_isa_string();
132    let exts: Vec<ExtensionEntry> = get_extensions_explained()
133        .into_iter()
134        .map(|(name, description)| ExtensionEntry { name, description })
135        .collect();
136    let z_exts: Vec<ExtensionEntry> = get_z_extensions_explained()
137        .into_iter()
138        .map(|(name, description)| ExtensionEntry { name, description })
139        .collect();
140
141    let hw_ids = get_hardware_ids();
142    let isa_lower = isa.to_lowercase();
143    let base = isa_lower.split('_').next().unwrap_or(&isa_lower);
144
145    RiscvInfo {
146        isa,
147        extensions: exts,
148        z_extensions: z_exts,
149        vector: VectorInfo {
150            enabled: base.contains('v') || isa_lower.contains("zve"),
151            vlen: None,
152            elen: None,
153        },
154        hart_count: sys.cpus().len(),
155        hardware_ids: hw_ids,
156        cache: CacheInfo::default(),
157    }
158}
159
160/// Collect all information into a single struct
161#[must_use]
162pub fn collect_all_info() -> SystemInfo {
163    use types::ExtensionEntry;
164
165    let mut sys = System::new();
166    sys.refresh_memory();
167    sys.refresh_cpu_all();
168
169    let isa = get_isa_string();
170    let exts: Vec<ExtensionEntry> = get_extensions_explained()
171        .into_iter()
172        .map(|(name, description)| ExtensionEntry { name, description })
173        .collect();
174    let z_exts: Vec<ExtensionEntry> = get_z_extensions_explained()
175        .into_iter()
176        .map(|(name, description)| ExtensionEntry { name, description })
177        .collect();
178    let s_exts: Vec<ExtensionEntry> = get_s_extensions_explained()
179        .into_iter()
180        .map(|(name, description)| ExtensionEntry { name, description })
181        .collect();
182
183    let hw_ids = get_hardware_ids();
184    let isa_lower = isa.to_lowercase();
185    let base = isa_lower.split('_').next().unwrap_or(&isa_lower);
186
187    SystemInfo {
188        isa,
189        extensions: exts,
190        z_extensions: z_exts,
191        s_extensions: s_exts,
192        vector: VectorInfo {
193            enabled: base.contains('v') || isa_lower.contains("zve"),
194            vlen: None,
195            elen: None,
196        },
197        hart_count: sys.cpus().len(),
198        hardware_ids: hw_ids,
199        cache: CacheInfo::default(),
200        board: get_board_info(),
201        memory_used_bytes: sys.used_memory(),
202        memory_total_bytes: sys.total_memory(),
203        kernel: get_kernel_info(),
204        os: get_os_info(),
205        uptime_seconds: System::uptime(),
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    // === System Info Tests (work on any system) ===
214
215    #[test]
216    fn test_get_uptime() {
217        let uptime = get_uptime();
218        assert!(!uptime.is_empty());
219    }
220
221    #[test]
222    fn test_get_uptime_seconds() {
223        let secs = get_uptime_seconds();
224        assert!(secs > 0);
225    }
226
227    #[test]
228    fn test_get_memory_bytes() {
229        let (used, total) = get_memory_bytes();
230        assert!(total > 0);
231        assert!(used <= total);
232    }
233
234    #[test]
235    fn test_get_kernel_info() {
236        let kernel = get_kernel_info();
237        assert!(!kernel.is_empty());
238    }
239
240    #[test]
241    fn test_get_os_info() {
242        let os = get_os_info();
243        assert!(!os.is_empty());
244    }
245
246    #[test]
247    fn test_hardware_ids_default() {
248        let ids = HardwareIds::default();
249        assert!(ids.mvendorid.is_empty());
250        assert!(ids.marchid.is_empty());
251        assert!(ids.mimpid.is_empty());
252    }
253
254    // === RISC-V Hardware Tests (only run on actual RISC-V) ===
255
256    #[cfg(target_arch = "riscv64")]
257    mod riscv_hardware_tests {
258        use super::*;
259
260        #[test]
261        fn hw_is_riscv() {
262            assert!(is_riscv());
263        }
264
265        #[test]
266        fn hw_isa_string_valid() {
267            let isa = get_isa_string();
268            assert!(isa.starts_with("rv64") || isa.starts_with("rv32"));
269        }
270
271        #[test]
272        fn hw_isa_string_has_base() {
273            let isa = get_isa_string();
274            assert!(isa.contains('i') || isa.contains('e'));
275        }
276
277        #[test]
278        fn hw_extensions_not_empty() {
279            let ext = get_extensions_compact();
280            assert!(!ext.is_empty());
281            assert!(ext.contains('I') || ext.contains('E'));
282        }
283
284        #[test]
285        fn hw_hart_count_positive() {
286            let hart_str = get_hart_count();
287            assert!(hart_str.contains("hart"));
288            let num: String = hart_str
289                .chars()
290                .take_while(|c| c.is_ascii_digit())
291                .collect();
292            let count: usize = num.parse().unwrap_or(0);
293            assert!(count > 0);
294        }
295
296        #[test]
297        fn hw_hardware_ids_present() {
298            let ids = get_hardware_ids();
299            let has_any =
300                !ids.mvendorid.is_empty() || !ids.marchid.is_empty() || !ids.mimpid.is_empty();
301            assert!(has_any);
302        }
303
304        #[test]
305        fn hw_collect_all_info() {
306            let info = collect_all_info();
307            assert!(!info.isa.is_empty());
308            assert!(!info.extensions.is_empty());
309            assert!(info.hart_count > 0);
310        }
311
312        #[test]
313        fn hw_collect_riscv_info() {
314            let info = collect_riscv_info();
315            assert!(!info.isa.is_empty());
316            assert!(!info.extensions.is_empty());
317            assert!(info.hart_count > 0);
318        }
319
320        #[test]
321        fn hw_riscv_info_excludes_system_fields() {
322            let riscv_info = collect_riscv_info();
323            let all_info = collect_all_info();
324
325            // RiscvInfo should have the same RISC-V specific fields
326            assert_eq!(riscv_info.isa, all_info.isa);
327            assert_eq!(riscv_info.extensions, all_info.extensions);
328            assert_eq!(riscv_info.z_extensions, all_info.z_extensions);
329            assert_eq!(riscv_info.hart_count, all_info.hart_count);
330
331            // SystemInfo has additional fields that RiscvInfo doesn't have
332            // (board, memory_*, kernel, os, uptime_seconds)
333            // This is verified by the type system - RiscvInfo simply doesn't have these fields
334        }
335    }
336}