diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/apple_silicon/src/main.rs | 7 | ||||
| -rw-r--r-- | src/apple_silicon/src/soc.rs | 190 |
2 files changed, 186 insertions, 11 deletions
diff --git a/src/apple_silicon/src/main.rs b/src/apple_silicon/src/main.rs index a1b6ba1..18c9747 100644 --- a/src/apple_silicon/src/main.rs +++ b/src/apple_silicon/src/main.rs @@ -4,7 +4,10 @@ mod soc; fn main() { let cpu_info = soc::SocInfo::new().unwrap(); println!( - "our CPU is an {}, and we have {} CPU cores, and {} GPU cores", - cpu_info.cpu_brand_name, cpu_info.num_cpu_cores, cpu_info.num_gpu_cores, + "our CPU is an {}, and we have {} CPU cores, and {} GPU cores. The TDP is {}.", + cpu_info.cpu_brand_name, + cpu_info.num_cpu_cores, + cpu_info.num_gpu_cores, + cpu_info.cpu_max_power.unwrap(), ); } diff --git a/src/apple_silicon/src/soc.rs b/src/apple_silicon/src/soc.rs index 43a29ba..8db2e1f 100644 --- a/src/apple_silicon/src/soc.rs +++ b/src/apple_silicon/src/soc.rs @@ -1,29 +1,118 @@ use crate::error::Error; -use std::process::Command; +use std::process::{Command, Output}; pub type Result<T> = std::result::Result<T, Error>; +pub type Watts = u32; +pub type Bandwidth = u32; +pub type CoreCount = u16; /// Information about the Silicon chip pub struct SocInfo { /// The CPU brand name string pub cpu_brand_name: String, /// Number of CPU cores - pub num_cpu_cores: u16, + pub num_cpu_cores: CoreCount, /// Number of GPU cores - pub num_gpu_cores: u16, + pub num_gpu_cores: CoreCount, + /// Maximum CPU power in watts (if available) + pub cpu_max_power: Option<Watts>, + /// Maximum GPU power in watts (if available) + pub gpu_max_power: Option<Watts>, + /// Maximum CPU bandwidth in GB/s (if available) + pub cpu_max_bw: Option<Bandwidth>, + /// Maximum GPU bandwidth in GB/s (if available) + pub gpu_max_bw: Option<Bandwidth>, + /// Number of efficiency cores + pub e_core_count: Option<CoreCount>, + /// Number of performance cores + pub p_core_count: Option<CoreCount>, +} + +#[derive(Debug, PartialEq)] +enum AppleChip { + M1, + M1Pro, + M1Max, + M1Ultra, + M2, + M2Pro, + M2Max, + M2Ultra, + M3, + M3Pro, + M3Max, + Unknown, +} + +struct ChipSpecs { + cpu_tdp: Watts, + gpu_tdp: Watts, +} + +impl AppleChip { + fn from_brand_string(brand: &str) -> Self { + match brand { + s if s.contains("M1 Pro") => AppleChip::M1Pro, + s if s.contains("M1 Max") => AppleChip::M1Max, + s if s.contains("M1 Ultra") => AppleChip::M1Ultra, + s if s.contains("M1") => AppleChip::M1, + s if s.contains("M2 Pro") => AppleChip::M2Pro, + s if s.contains("M2 Max") => AppleChip::M2Max, + s if s.contains("M2 Ultra") => AppleChip::M2Ultra, + s if s.contains("M2") => AppleChip::M2, + s if s.contains("M3 Pro") => AppleChip::M3Pro, + s if s.contains("M3 Max") => AppleChip::M3Max, + s if s.contains("M3") => AppleChip::M3, + _ => AppleChip::Unknown, + } + } + + fn get_specs(&self) -> ChipSpecs { + match self { + AppleChip::M1 => ChipSpecs { + cpu_tdp: 20, + gpu_tdp: 20, + }, + AppleChip::M2 => ChipSpecs { + cpu_tdp: 20, + gpu_tdp: 22, + }, + AppleChip::M2Pro => ChipSpecs { + cpu_tdp: 30, + gpu_tdp: 35, + }, + AppleChip::M2Max => ChipSpecs { + cpu_tdp: 30, + gpu_tdp: 40, + }, + // Add more variants as needed + _ => ChipSpecs { + cpu_tdp: 0, + gpu_tdp: 0, + }, + } + } } impl SocInfo { pub fn new() -> Result<SocInfo> { - let (cpu_brand_name, num_cpu_cores) = cpu_info()?; + let (cpu_brand_name, num_cpu_cores) = cpu_info(&RealCommand)?; + let num_gpu_cores = gpu_info(&RealCommand)?; - let num_gpu_cores = gpu_info()?; + let chip = AppleChip::from_brand_string(&cpu_brand_name); + let specs = chip.get_specs(); Ok(SocInfo { cpu_brand_name, num_cpu_cores, num_gpu_cores, + cpu_max_power: Some(specs.cpu_tdp), + gpu_max_power: Some(specs.gpu_tdp), + cpu_max_bw: None, + gpu_max_bw: None, + e_core_count: None, + p_core_count: None, }) } } @@ -31,7 +120,7 @@ impl SocInfo { // https://github.com/tlkh/asitop/blob/74ebe2cbc23d5b1eec874aebb1b9bacfe0e670cd/asitop/utils.py#L94 const SYSCTL_PATH: &str = "/usr/sbin/sysctl"; -fn cpu_info() -> Result<(String, u16)> { +fn cpu_info(cmd: &impl SystemCommand) -> Result<(String, u16)> { let binary = SYSCTL_PATH; let args = &[ // don't display the variable name @@ -40,7 +129,7 @@ fn cpu_info() -> Result<(String, u16)> { "machdep.cpu.core_count", ]; - let output = Command::new(binary).args(args).output()?; + let output = cmd.execute(binary, args)?; let buffer = String::from_utf8(output.stdout)?; let mut iter = buffer.split('\n'); @@ -58,11 +147,11 @@ fn cpu_info() -> Result<(String, u16)> { } // https://github.com/tlkh/asitop/blob/74ebe2cbc23d5b1eec874aebb1b9bacfe0e670cd/asitop/utils.py#L120 -fn gpu_info() -> Result<u16> { +fn gpu_info(cmd: &impl SystemCommand) -> Result<u16> { let binary = "/usr/sbin/system_profiler"; let args = &["-detailLevel", "basic", "SPDisplaysDataType"]; - let output = Command::new(binary).args(args).output()?; + let output = cmd.execute(binary, args)?; let buffer = String::from_utf8(output.stdout)?; let num_gpu_cores_line = buffer @@ -79,3 +168,86 @@ fn gpu_info() -> Result<u16> { Ok(num_gpu_cores) } + +/// Trait for system command execution +pub trait SystemCommand { + fn execute(&self, binary: &str, args: &[&str]) -> Result<Output>; +} + +/// Real command executor +pub struct RealCommand; + +impl SystemCommand for RealCommand { + fn execute(&self, binary: &str, args: &[&str]) -> Result<Output> { + Ok(Command::new(binary).args(args).output()?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::os::unix::process::ExitStatusExt; + + struct MockCommand { + output: Vec<u8>, + } + + impl MockCommand { + fn new(output: &str) -> Self { + Self { + output: output.as_bytes().to_vec(), + } + } + } + + impl SystemCommand for MockCommand { + fn execute(&self, _binary: &str, _args: &[&str]) -> Result<Output> { + Ok(Output { + status: std::process::ExitStatus::from_raw(0), + stdout: self.output.clone(), + stderr: Vec::new(), + }) + } + } + + #[test] + fn test_gpu_info() { + let mock_output = r#"Graphics/Displays: + Apple M2: + Total Number of Cores: 10"#; + let cmd = MockCommand::new(mock_output); + + let result = gpu_info(&cmd); + assert_eq!(result.unwrap(), 10); + } + + #[test] + fn test_cpu_info_success() { + let mock_output = "Apple M2\n8\n"; + let cmd = MockCommand::new(mock_output); + + let result = cpu_info(&cmd); + assert!(result.is_ok()); + let (brand, cores) = result.unwrap(); + assert_eq!(brand, "Apple M2"); + assert_eq!(cores, 8); + } + + #[test] + fn test_cpu_info_missing_core_count() { + let mock_output = "Apple M2\n"; + let cmd = MockCommand::new(mock_output); + + let result = cpu_info(&cmd); + assert!(matches!(result, Err(Error::ParseInt { .. }))); + } + + #[test] + fn test_cpu_info_invalid_core_count() { + let mock_output = "Apple M2\ninvalid\n"; + let cmd = MockCommand::new(mock_output); + + let result = cpu_info(&cmd); + assert!(matches!(result, Err(Error::ParseInt { .. }))); + } +} |
