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, 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    let mut sys = System::new();
127    sys.refresh_cpu_all();
128
129    let isa = get_isa_string();
130    let exts: Vec<String> = get_extensions_explained()
131        .into_iter()
132        .map(|(name, _)| name)
133        .collect();
134    let z_exts: Vec<String> = get_z_extensions_explained()
135        .into_iter()
136        .map(|(name, _)| name)
137        .collect();
138
139    let hw_ids = get_hardware_ids();
140    let isa_lower = isa.to_lowercase();
141    let base = isa_lower.split('_').next().unwrap_or(&isa_lower);
142
143    RiscvInfo {
144        isa,
145        extensions: exts,
146        z_extensions: z_exts,
147        vector: VectorInfo {
148            enabled: base.contains('v') || isa_lower.contains("zve"),
149            vlen: None,
150            elen: None,
151        },
152        hart_count: sys.cpus().len(),
153        hardware_ids: hw_ids,
154        cache: CacheInfo::default(),
155    }
156}
157
158/// Collect all information into a single struct
159#[must_use]
160pub fn collect_all_info() -> SystemInfo {
161    let mut sys = System::new();
162    sys.refresh_memory();
163    sys.refresh_cpu_all();
164
165    let isa = get_isa_string();
166    let exts: Vec<String> = get_extensions_explained()
167        .into_iter()
168        .map(|(name, _)| name)
169        .collect();
170    let z_exts: Vec<String> = get_z_extensions_explained()
171        .into_iter()
172        .map(|(name, _)| name)
173        .collect();
174    let s_exts: Vec<String> = get_s_extensions_explained()
175        .into_iter()
176        .map(|(name, _)| name)
177        .collect();
178
179    let hw_ids = get_hardware_ids();
180    let isa_lower = isa.to_lowercase();
181    let base = isa_lower.split('_').next().unwrap_or(&isa_lower);
182
183    SystemInfo {
184        isa,
185        extensions: exts,
186        z_extensions: z_exts,
187        s_extensions: s_exts,
188        vector: VectorInfo {
189            enabled: base.contains('v') || isa_lower.contains("zve"),
190            vlen: None,
191            elen: None,
192        },
193        hart_count: sys.cpus().len(),
194        hardware_ids: hw_ids,
195        cache: CacheInfo::default(),
196        board: get_board_info(),
197        memory_used_bytes: sys.used_memory(),
198        memory_total_bytes: sys.total_memory(),
199        kernel: get_kernel_info(),
200        os: get_os_info(),
201        uptime_seconds: System::uptime(),
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    // === System Info Tests (work on any system) ===
210
211    #[test]
212    fn test_get_uptime() {
213        let uptime = get_uptime();
214        assert!(!uptime.is_empty());
215    }
216
217    #[test]
218    fn test_get_uptime_seconds() {
219        let secs = get_uptime_seconds();
220        assert!(secs > 0);
221    }
222
223    #[test]
224    fn test_get_memory_bytes() {
225        let (used, total) = get_memory_bytes();
226        assert!(total > 0);
227        assert!(used <= total);
228    }
229
230    #[test]
231    fn test_get_kernel_info() {
232        let kernel = get_kernel_info();
233        assert!(!kernel.is_empty());
234    }
235
236    #[test]
237    fn test_get_os_info() {
238        let os = get_os_info();
239        assert!(!os.is_empty());
240    }
241
242    #[test]
243    fn test_hardware_ids_default() {
244        let ids = HardwareIds::default();
245        assert!(ids.mvendorid.is_empty());
246        assert!(ids.marchid.is_empty());
247        assert!(ids.mimpid.is_empty());
248    }
249
250    // === RISC-V Hardware Tests (only run on actual RISC-V) ===
251
252    #[cfg(target_arch = "riscv64")]
253    mod riscv_hardware_tests {
254        use super::*;
255
256        #[test]
257        fn hw_is_riscv() {
258            assert!(is_riscv());
259        }
260
261        #[test]
262        fn hw_isa_string_valid() {
263            let isa = get_isa_string();
264            assert!(isa.starts_with("rv64") || isa.starts_with("rv32"));
265        }
266
267        #[test]
268        fn hw_isa_string_has_base() {
269            let isa = get_isa_string();
270            assert!(isa.contains('i') || isa.contains('e'));
271        }
272
273        #[test]
274        fn hw_extensions_not_empty() {
275            let ext = get_extensions_compact();
276            assert!(!ext.is_empty());
277            assert!(ext.contains('I') || ext.contains('E'));
278        }
279
280        #[test]
281        fn hw_hart_count_positive() {
282            let hart_str = get_hart_count();
283            assert!(hart_str.contains("hart"));
284            let num: String = hart_str
285                .chars()
286                .take_while(|c| c.is_ascii_digit())
287                .collect();
288            let count: usize = num.parse().unwrap_or(0);
289            assert!(count > 0);
290        }
291
292        #[test]
293        fn hw_hardware_ids_present() {
294            let ids = get_hardware_ids();
295            let has_any =
296                !ids.mvendorid.is_empty() || !ids.marchid.is_empty() || !ids.mimpid.is_empty();
297            assert!(has_any);
298        }
299
300        #[test]
301        fn hw_collect_all_info() {
302            let info = collect_all_info();
303            assert!(!info.isa.is_empty());
304            assert!(!info.extensions.is_empty());
305            assert!(info.hart_count > 0);
306        }
307
308        #[test]
309        fn hw_collect_riscv_info() {
310            let info = collect_riscv_info();
311            assert!(!info.isa.is_empty());
312            assert!(!info.extensions.is_empty());
313            assert!(info.hart_count > 0);
314        }
315
316        #[test]
317        fn hw_riscv_info_excludes_system_fields() {
318            let riscv_info = collect_riscv_info();
319            let all_info = collect_all_info();
320
321            // RiscvInfo should have the same RISC-V specific fields
322            assert_eq!(riscv_info.isa, all_info.isa);
323            assert_eq!(riscv_info.extensions, all_info.extensions);
324            assert_eq!(riscv_info.z_extensions, all_info.z_extensions);
325            assert_eq!(riscv_info.hart_count, all_info.hart_count);
326
327            // SystemInfo has additional fields that RiscvInfo doesn't have
328            // (board, memory_*, kernel, os, uptime_seconds)
329            // This is verified by the type system - RiscvInfo simply doesn't have these fields
330        }
331    }
332}