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 @@
+#
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