Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.vs/
x64/
/target
.idea
Cargo.lock
17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 2 additions & 0 deletions Old_C++_version/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.vs/
x64/
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
32 changes: 32 additions & 0 deletions Old_C++_version/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# <a href="https://www.noureddine.org/research/joular/"><img src="https://raw.githubusercontent.com/joular/.github/main/profile/joular.png" alt="Joular Project" width="64" /></a> 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

File renamed without changes.
File renamed without changes.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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
258 changes: 258 additions & 0 deletions src/hubblo_rapl.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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<u64> {
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::<u64>() as u32,
Some(&mut reply_data as *mut u64 as *mut _),
std::mem::size_of::<u64>() 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<f64> {
match self.vendor {
CpuVendor::Intel => self.get_intel_energy(),
CpuVendor::AMD => self.get_amd_energy(),
}
}

fn get_intel_energy(&self) -> Result<f64> {
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<f64> {
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);
}
}
}
Loading