aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/apple_silicon/src/main.rs7
-rw-r--r--src/apple_silicon/src/soc.rs190
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 { .. })));
+ }
+}