diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..47f8be7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,20 @@ +name: Build and release + +on: + push: + branches: [ "main", "develop" ] + pull_request: + branches: [ "main", "develop" ] + +jobs: + build: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Rust compiler and Cargo + run: rustup update + + - name: Build + run: cargo build --release \ No newline at end of file diff --git a/.gitignore b/.gitignore index c03028d..b169f3d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -.vs/ -x64/ \ No newline at end of file +/target +.idea +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b28ddd6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "WinPowerMonitor" +version = "0.1.0" +authors = ["Adel Noureddine"] +edition = "2024" +license = "GPL-3.0-only" +description = "Power Monitor for Windows is a command line tool that read CPU's power consumption through RAPL's MSRs, and provides the power consumption of the CPU every second" +repository = "https://github.com/joular/WinPowerMonitorRS" + +[dependencies] +windows = { version = "0.62.1", features = [ + "Win32_Foundation", + "Win32_Storage_FileSystem", + "Win32_System_IO", + "Win32_Security", +] } +ctrlc = "3.5.0" diff --git a/Old_C++_version/.gitignore b/Old_C++_version/.gitignore new file mode 100644 index 0000000..c03028d --- /dev/null +++ b/Old_C++_version/.gitignore @@ -0,0 +1,2 @@ +.vs/ +x64/ \ No newline at end of file diff --git a/Main.cpp b/Old_C++_version/Main.cpp similarity index 100% rename from Main.cpp rename to Old_C++_version/Main.cpp diff --git a/PowerMonitor.sln b/Old_C++_version/PowerMonitor.sln similarity index 100% rename from PowerMonitor.sln rename to Old_C++_version/PowerMonitor.sln diff --git a/PowerMonitor.vcxproj b/Old_C++_version/PowerMonitor.vcxproj similarity index 100% rename from PowerMonitor.vcxproj rename to Old_C++_version/PowerMonitor.vcxproj diff --git a/PowerMonitor.vcxproj.user b/Old_C++_version/PowerMonitor.vcxproj.user similarity index 100% rename from PowerMonitor.vcxproj.user rename to Old_C++_version/PowerMonitor.vcxproj.user diff --git a/Old_C++_version/README.md b/Old_C++_version/README.md new file mode 100644 index 0000000..28b28cb --- /dev/null +++ b/Old_C++_version/README.md @@ -0,0 +1,32 @@ +# Joular Project Power Monitor for Windows + +Power Monitor for Windows is a command line tool that read CPU's power consumption through RAPL's MSRs, and provides the power consumption of the CPU every second. + +Power Monitor for Windows depends on the [Windows RAPL driver by Hubblo](https://github.com/hubblo-org/windows-rapl-driver), and therefore require installing the driver first, and runs on Intel or AMD CPUs (since Ryzen). +The easiest way to install the driver is to install the windows version of [Scaphandre](https://github.com/hubblo-org/scaphandre) tool from [this link directly](https://github.com/hubblo-org/scaphandre/releases/download/v1.0.0/scaphandre_v1.0.0_installer.exe) (release 1.0.0). + +Power Monitor for Windows was initially developed as part of [JoularJX](https://github.com/joular/joularjx), but is now its separate project. + +## :bulb: Usage + +Just run the program in command line, or run the executable. +It'll print on the command line the power consumption of the CPU every second. + +## :floppy_disk: Compilation + +To compile the Power Monitor for Windows, open the project in Visual Studio and compile there. +Or open, Developer Command Prompt for VS (or Developer PowerShell for VS), and compile with this command: +``` +msbuild.exe PowerMonitor.sln /property:Configuration=Release +``` + +## :newspaper: License + +Power Monitor for Windows is licensed under the GNU GPL 3 license only (GPL-3.0-only). + +Copyright (c) 2024, Adel Noureddine, Université de Pau et des Pays de l'Adour. +All rights reserved. This program and the accompanying materials are made available under the terms of the GNU General Public License v3.0 only (GPL-3.0-only) which accompanies this distribution, and is available at: https://www.gnu.org/licenses/gpl-3.0.en.html + + +Author : Adel Noureddine + diff --git a/compiled-binary/PowerMonitor.exe b/Old_C++_version/compiled-binary/PowerMonitor.exe similarity index 100% rename from compiled-binary/PowerMonitor.exe rename to Old_C++_version/compiled-binary/PowerMonitor.exe diff --git a/hubbloRAPL.cpp b/Old_C++_version/hubbloRAPL.cpp similarity index 100% rename from hubbloRAPL.cpp rename to Old_C++_version/hubbloRAPL.cpp diff --git a/hubbloRAPL.h b/Old_C++_version/hubbloRAPL.h similarity index 100% rename from hubbloRAPL.h rename to Old_C++_version/hubbloRAPL.h diff --git a/README.md b/README.md index 5f47e8c..3fb907d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Power Monitor for Windows is a command line tool that read CPU's power consumption through RAPL's MSRs, and provides the power consumption of the CPU every second. Power Monitor for Windows depends on the [Windows RAPL driver by Hubblo](https://github.com/hubblo-org/windows-rapl-driver), and therefore require installing the driver first, and runs on Intel or AMD CPUs (since Ryzen). +The easiest way to install the driver is to install the windows version of [Scaphandre](https://github.com/hubblo-org/scaphandre) tool from [this link directly](https://github.com/hubblo-org/scaphandre/releases/download/v1.0.0/scaphandre_v1.0.0_installer.exe) (release 1.0.0). Power Monitor for Windows was initially developed as part of [JoularJX](https://github.com/joular/joularjx), but is now its separate project. @@ -13,17 +14,17 @@ It'll print on the command line the power consumption of the CPU every second. ## :floppy_disk: Compilation -To compile the Power Monitor for Windows, open the project in Visual Studio and compile there. -Or open, Developer Command Prompt for VS (or Developer PowerShell for VS), and compile with this command: +To compile the Power Monitor for Windows, compile it with the Rust compiler and Cargo: + ``` -msbuild.exe PowerMonitor.sln /property:Configuration=Release +cargo build --release ``` ## :newspaper: License Power Monitor for Windows is licensed under the GNU GPL 3 license only (GPL-3.0-only). -Copyright (c) 2024, Adel Noureddine, Université de Pau et des Pays de l'Adour. +Copyright (c) 2025, Adel Noureddine, Université Paris Nanterre. All rights reserved. This program and the accompanying materials are made available under the terms of the GNU General Public License v3.0 only (GPL-3.0-only) which accompanies this distribution, and is available at: https://www.gnu.org/licenses/gpl-3.0.en.html Author : Adel Noureddine \ No newline at end of file diff --git a/src/hubblo_rapl.rs b/src/hubblo_rapl.rs new file mode 100644 index 0000000..2a5393b --- /dev/null +++ b/src/hubblo_rapl.rs @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2025, Adel Noureddine, Université Paris Nanterre. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the + * GNU General Public License v3.0 only (GPL-3.0-only) + * which accompanies this distribution, and is available at + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * Author : Adel Noureddine + */ + +use windows::core::*; +use windows::Win32::Foundation::*; +use windows::Win32::Storage::FileSystem::*; +use windows::Win32::System::IO::DeviceIoControl; + +// RAPL MSR addresses for Intel +const MSR_INTEL_RAPL_POWER_UNIT: u64 = 0x606; +const MSR_INTEL_PKG_ENERGY_STATUS: u64 = 0x611; +const MSR_INTEL_DRAM_ENERGY_STATUS: u64 = 0x619; +const MSR_INTEL_PLATFORM_ENERGY_STATUS: u64 = 0x64d; + +// RAPL MSR addresses for AMD +const MSR_AMD_RAPL_POWER_UNIT: u64 = 0xc0010299; +const MSR_AMD_PKG_ENERGY_STATUS: u64 = 0xc001029b; + +// CTL_CODE macro implementation +const fn ctl_code(device_type: u32, function: u32, method: u32, access: u32) -> u32 { + (device_type << 16) | (access << 14) | (function << 2) | method +} + +const FILE_DEVICE_UNKNOWN: u32 = 0x00000022; +const METHOD_BUFFERED: u32 = 0; +const FILE_READ_DATA: u32 = 0x0001; +const FILE_WRITE_DATA: u32 = 0x0002; + +#[derive(Debug, Clone, Copy)] +enum CpuVendor { + Intel, + AMD, +} + +pub struct RaplDriver { + handle: HANDLE, + power_unit: f64, + energy_unit: f64, + time_unit: f64, + vendor: CpuVendor, + psys: bool, + pkg: bool, + dram: bool, +} + +impl RaplDriver { + pub fn new() -> Result { + let driver_path = w!("\\\\.\\ScaphandreDriver"); + + let handle = unsafe { + CreateFileW( + driver_path, + FILE_GENERIC_READ.0 | FILE_GENERIC_WRITE.0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + None, + )? + }; + + if handle == INVALID_HANDLE_VALUE { + return Err(Error::from_thread()); + } + + let mut driver = RaplDriver { + handle, + power_unit: 0.0, + energy_unit: 0.0, + time_unit: 0.0, + vendor: CpuVendor::Intel, // Default to Intel, will be detected + psys: false, + pkg: false, + dram: false, + }; + + driver.detect_cpu_vendor()?; + driver.get_energy_units()?; + driver.check_supported_platform()?; + + Ok(driver) + } + + fn get_rapl_ctl_code(&self) -> u32 { + let msr = match self.vendor { + CpuVendor::Intel => MSR_INTEL_RAPL_POWER_UNIT, + CpuVendor::AMD => MSR_AMD_RAPL_POWER_UNIT, + }; + + ctl_code( + FILE_DEVICE_UNKNOWN, + msr as u32, + METHOD_BUFFERED, + FILE_READ_DATA | FILE_WRITE_DATA, + ) + } + + fn get_data_from_driver(&self, msr: u64) -> Result { + let mut reply_data: u64 = 0; + let mut bytes_returned: u32 = 0; + + let ctl_code = self.get_rapl_ctl_code(); + + unsafe { + DeviceIoControl( + self.handle, + ctl_code, + Some(&msr as *const u64 as *const _), + std::mem::size_of::() as u32, + Some(&mut reply_data as *mut u64 as *mut _), + std::mem::size_of::() as u32, + Some(&mut bytes_returned), + None, + )?; + } + + Ok(reply_data) + } + + fn detect_cpu_vendor(&mut self) -> Result<()> { + // Try Intel first + if self.get_data_from_driver(MSR_INTEL_RAPL_POWER_UNIT).is_ok() { + self.vendor = CpuVendor::Intel; + return Ok(()); + } + + // Try AMD + if self.get_data_from_driver(MSR_AMD_RAPL_POWER_UNIT).is_ok() { + self.vendor = CpuVendor::AMD; + return Ok(()); + } + + Err(Error::from_thread()) + } + + fn get_energy_units(&mut self) -> Result<()> { + let msr = match self.vendor { + CpuVendor::Intel => MSR_INTEL_RAPL_POWER_UNIT, + CpuVendor::AMD => MSR_AMD_RAPL_POWER_UNIT, + }; + + let reply_data = self.get_data_from_driver(msr)?; + + // Time Units + const TIME_MASK: u64 = 0xF0000; + let time_val = reply_data & TIME_MASK; + self.time_unit = 1.0 / 2.0_f64.powi((time_val >> 16) as i32); + + // Energy Units + const ENERGY_MASK: u64 = 0x1F00; + let energy_val = reply_data & ENERGY_MASK; + self.energy_unit = 1.0 / 2.0_f64.powi((energy_val >> 8) as i32); + + // Power Units + const POWER_MASK: u64 = 0xF; + let power_val = reply_data & POWER_MASK; + self.power_unit = 1.0 / 2.0_f64.powi(power_val as i32); + + Ok(()) + } + + fn check_supported_platform(&mut self) -> Result<()> { + match self.vendor { + CpuVendor::Intel => { + // Check for PSYS (Platform) support + if let Ok(reply_data) = self.get_data_from_driver(MSR_INTEL_PLATFORM_ENERGY_STATUS) { + if reply_data != 0 { + self.psys = true; + return Ok(()); + } + } + + // If PSYS not supported, check PKG and DRAM + if let Ok(reply_data) = self.get_data_from_driver(MSR_INTEL_PKG_ENERGY_STATUS) { + if reply_data != 0 { + self.pkg = true; + } + } + + if let Ok(reply_data) = self.get_data_from_driver(MSR_INTEL_DRAM_ENERGY_STATUS) { + if reply_data != 0 { + self.dram = true; + } + } + } + CpuVendor::AMD => { + // AMD only supports PKG, no DRAM or PSYS + if let Ok(reply_data) = self.get_data_from_driver(MSR_AMD_PKG_ENERGY_STATUS) { + if reply_data != 0 { + self.pkg = true; + } + } + } + } + + Ok(()) + } + + pub fn get_rapl_energy(&self) -> Result { + match self.vendor { + CpuVendor::Intel => self.get_intel_energy(), + CpuVendor::AMD => self.get_amd_energy(), + } + } + + fn get_intel_energy(&self) -> Result { + if self.psys { + let reply_data = self.get_data_from_driver(MSR_INTEL_PLATFORM_ENERGY_STATUS)?; + let raw_psys_energy = (reply_data & 0xFFFFFFFF) as u32; + let psys_energy = raw_psys_energy as f64 * self.energy_unit; + return Ok(psys_energy); + } + + if self.pkg { + let reply_data = self.get_data_from_driver(MSR_INTEL_PKG_ENERGY_STATUS)?; + let raw_pkg_energy = (reply_data & 0xFFFFFFFF) as u32; + let pkg_energy = raw_pkg_energy as f64 * self.energy_unit; + + if self.dram { + let reply_data = self.get_data_from_driver(MSR_INTEL_DRAM_ENERGY_STATUS)?; + let raw_dram_energy = (reply_data & 0xFFFFFFFF) as u32; + let dram_energy = raw_dram_energy as f64 * self.energy_unit; + return Ok(pkg_energy + dram_energy); + } + + return Ok(pkg_energy); + } + + Ok(0.0) + } + + fn get_amd_energy(&self) -> Result { + if self.pkg { + let reply_data = self.get_data_from_driver(MSR_AMD_PKG_ENERGY_STATUS)?; + let raw_pkg_energy = (reply_data & 0xFFFFFFFF) as u32; + let pkg_energy = raw_pkg_energy as f64 * self.energy_unit; + return Ok(pkg_energy); + } + + Ok(0.0) + } +} + +impl Drop for RaplDriver { + fn drop(&mut self) { + unsafe { + let _ = CloseHandle(self.handle); + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..904b4c9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025, Adel Noureddine, Université Paris Nanterre. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the + * GNU General Public License v3.0 only (GPL-3.0-only) + * which accompanies this distribution, and is available at + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * Author : Adel Noureddine + */ + +mod hubblo_rapl; + +use std::{thread, time}; +use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; + +use hubblo_rapl::RaplDriver; + +fn main() { + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + ctrlc::set_handler(move || { + // Change value to stop main loop + r.store(false, Ordering::SeqCst); + }).expect("Error setting Ctrl-C"); + + let mut before_energy: f64 = 0.0; + let driver = RaplDriver::new().expect("Driver failed"); + let mut first_run = true; + + while running.load(Ordering::SeqCst) { + let after_energy = driver.get_rapl_energy().unwrap_or(0.0); + let energy = after_energy - before_energy; + before_energy = after_energy; + + if first_run { + first_run = false; + thread::sleep(time::Duration::from_secs(1)); + continue; + } + + println!("{:.3}", energy); + thread::sleep(time::Duration::from_secs(1)); + } +} \ No newline at end of file