Joular Core is a Rust library for measuring power and energy across systems and devices.
It measures CPU and GPU power consumption in real time, and can break that down to individual processes or applications. Joular Core runs on Linux, Windows, macOS, Raspberry Pi, and inside virtual machines.
It allows applications, telemetry services, benchmarks, and custom developer tools to monitor CPU, GPU, and total system power, as well as attribute energy usage to specific process IDs (PIDs) or multi-process applications. It can export data to CSV files, a shared-memory ring buffer, and an HTTP/WebSocket API.
Full documentation (user and reference guides) are available at: https://joular.github.io/joularcore/.
The crate exposes reusable monitoring, output, IPC, and API building blocks that can be embedded in command-line tools, graphical applications, telemetry services, benchmarks, or custom developer tools.
Currently, the library is used in these two multi-OS applications:
- Joular Core CLI: a command-line program, working on all OSes.
- Joular Core GUI: a graphical program written in Rust, and working on all OSes.
Joular Core is under active development and currently in beta quality. Expect rough edges and features still being worked on and polished.
- 💻 Supported Systems: 🐧 Linux, 🪟 Windows, 🍎 macOS, 🍓 Raspberry Pi, 💽 Virtual Machines.
- ⚙️ Supported Architectures: x86_64 (amd64), x86/i686, aarch64, arm, armv7, GPUs (Nvidia, Apple, AMD).
CPU:
| OS / Architecture | x86_64 | i686 | Apple Silicon | arm | armv7 | aarch64 |
|---|---|---|---|---|---|---|
| Linux | ✓ | ✓ | ||||
| Windows | ✓ | ✓ | ||||
| macOS | ✓ | ✓ | ||||
| SBC (Raspberry Pi) | ✓ | ✓ | ✓ | |||
| Virtual Machines | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
GPU:
| OS / Architecture | Nvidia | AMD | Apple GPU |
|---|---|---|---|
| Linux | ✓ | ✓ | |
| Windows | ✓ | ✓ | |
| macOS | ✓ | ||
| SBC (Raspberry Pi) | |||
| Virtual Machines | ✓ | ✓ | ✓ |
Supported SBC platforms: Raspberry Pi (models: Zero W, 1 B, 1 B+, 2 B, 3 B, 3 B+, 4 B, 400, 5 B), Asus Tinker Board S.
- 📊 Real-time CPU and GPU power monitoring on PCs, servers, and single-board computers
- 🌐 Monitor power from inside virtual machines using data from the hypervisor or an external meter
- 🔍 Per-process power monitoring: track the energy consumption of a specific PID
- 🔍 Per-application power monitoring: track a named application across all its processes, with variable refresh interval
- 📈 Export power data to CSV files (append or overwrite mode)
- 📈 Write power data to a shared-memory ring buffer for low-latency IPC with other programs
- 📈 Expose power data over HTTP (GET
/data) and WebSocket (/ws) endpoints - ⚙️ Filter output to only show CPU power, GPU power, or both
- ⚙️ CPU idle baseline calibration: automatically or manually subtract idle CPU power before attributing it to a process or application
Add joularcore to your Rust project's Cargo.toml:
[dependencies]
joularcore = "0.1.0"Or via the command line:
cargo add joularcoreYou can customize the features compiled into joularcore to minimize binary size and dependencies depending on your deployment target:
| Feature | Default | Description |
|---|---|---|
vm |
on | Monitor power inside virtual machines using files written by the hypervisor or an external meter |
api |
on | HTTP and WebSocket API server. CSV and ring buffer export work regardless of this feature. |
sbc |
off | Single-board computer support with SBC-specific power models. On Linux, this selects the SBC backend instead of the RAPL-based desktop/server backend. |
# Minimal core library (no VM file reading, no API server)
joularcore = { version = "0.1.0", default-features = false }
# Core library with VM support only
joularcore = { version = "0.1.0", default-features = false, features = ["vm"] }
# SBC build for Raspberry Pi target
joularcore = { version = "0.1.0", default-features = false, features = ["sbc"] }use joularcore::{logging, monitor::JoularCoreMonitor, platform};
use std::time::Duration;
fn main() {
// Initialize logging subscriber
logging::init();
// Setup the platform backend and sampling helpers
let platform = platform::current(false);
let cpu_usage = platform.cpu_usage();
let process_util = platform.process_cpu_usage();
let app_util = platform.app_cpu_usage(Duration::from_secs(3));
let cpu_energy = platform.cpu();
let gpu_energy = platform.gpu();
// Initialize the JoularCore monitoring engine
let mut monitor = JoularCoreMonitor::new(
platform,
cpu_energy,
gpu_energy,
cpu_usage,
process_util,
app_util,
0.0,
);
monitor.loop_init();
// Read current CPU, GPU, and Total System power in Watts
let reading = monitor.poll(None, None, None);
println!("CPU Power: {:.2} W", reading.cpu_power);
println!("GPU Power: {:.2} W", reading.gpu_power);
println!("Total Power: {:.2} W", reading.total_power);
println!("CPU Usage: {:.1} %", reading.cpu_usage);
}use joularcore::{monitor::JoularCoreMonitor, platform};
use std::time::Duration;
fn main() {
let platform = platform::current(false);
let cpu_usage = platform.cpu_usage();
let process_util = platform.process_cpu_usage();
let app_util = platform.app_cpu_usage(Duration::from_secs(3));
let cpu_energy = platform.cpu();
let gpu_energy = platform.gpu();
let mut monitor = JoularCoreMonitor::new(
platform,
cpu_energy,
gpu_energy,
cpu_usage,
process_util,
app_util,
0.0,
);
monitor.loop_init();
let target_pid = 1234;
let reading = monitor.poll(Some(target_pid), None, None);
println!(
"Process {} Power: {:.2} W",
target_pid,
reading.pid_app_power()
);
}use joularcore::output::{OutputBundle, OutputSink, OutputWriter};
use joularcore::ringbuffer::RingBufferWriter;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Enable shared-memory ring buffer output
let rb_writer = RingBufferWriter::new(true)?;
// 2. Bundle CSV, ring buffer, and optional API outputs
let mut outputs = OutputBundle::new(None, false, Some(rb_writer), None);
// false = CSV format, false = append mode
let mut writer = OutputWriter::new(Some("power_log.csv"), false, false)?;
writer.write_csv_header(None, false, false)?;
outputs.set_writer(writer);
// Send each MonitorSample with:
// outputs.send(&sample)?;
Ok(())
}On most platforms, no configuration is needed. Joular Core detects the platform, finds the right power interface, and starts measuring.
Linux (PC / servers)
Reads CPU power via the Intel RAPL package interface (/sys/class/powercap/intel-rapl/). These files are typically readable only by root, so either run Joular Core with sudo or grant read access to the RAPL files. See this issue for details. If RAPL is unavailable or unreadable, Joular Core warns and continues with CPU power reported as 0 W.
GPU power is read via nvidia-smi (Nvidia) or amd-smi / rocm-smi (AMD) if installed.
Windows
CPU power requires Hubblo's RAPL driver, used through the Scaphandre driver interface. The easiest way to install a signed version is through the Scaphandre installer. Once the driver is installed, Joular Core runs without administrator rights.
GPU power is read via nvidia-smi and amd-smi.
macOS
No additional dependencies. Uses powermetrics, which is installed by default on macOS, but requires elevated access to read power data. Library callers can pass true to platform::current(true) to allow GUI-style elevation with an askpass prompt, or false for normal sudo authentication. Apple Silicon GPU power is read through the same interface.
Raspberry Pi / SBC
No dependencies and no sudo required when built with the sbc feature. CPU power is calculated using regression models tuned for each supported board. Unsupported boards report 0 W. GPU is not supported on SBC platforms.
Set environment variables to point Joular Core at a file written by the hypervisor or an external power meter:
| Variable | Description |
|---|---|
VM_CPU_POWER_FILE |
Path to the CPU power data file |
VM_CPU_POWER_FORMAT |
Format of that file: joularcore, powerjoular, or watts (default: watts) |
VM_GPU_POWER_FILE |
Path to the GPU power data file |
VM_GPU_POWER_FORMAT |
Format of that file: joularcore, powerjoular, or watts (default: watts) |
Formats:
watts: a plain text file containing a single numeric value (watts).powerjoular: CSV with three columns; power is in the third column. This is the default output of PowerJoular.joularcore: CSV with a header row matching Joular Core CSV output. Joular Core reads the last data row. For CPU power it prefersApp Power (W), thenProcess Power (W), thenCPU Power (W); for GPU power it readsGPU Power (W).
By default, Joular Core ships with our built-in regression models for all supported Raspberry Pi boards. If you want to use your own model, set:
SBC_POWER_MODEL_JSON=/path/to/model.json
The JSON file format must match the one used in the Joular Power Models Database.
When RingBufferWriter is enabled, joularcore streams real-time readings into a zero-copy shared memory buffer accessible by separate native binaries:
Default paths:
| OS | Path |
|---|---|
| Linux | /dev/shm/joularcorering |
| macOS | /tmp/joularcorering |
| Windows | Local\\JoularCoreRing |
The shared memory region starts with an 8-byte native-endian u64 head counter, followed by 5 slots. Each slot is a C-compatible RingBufferStruct containing timestamp (u64, Unix seconds), CPU power (f64, watts), GPU power (f64, watts), Total power (f64, watts), CPU usage (f64, percent), and PID or app power (f64, watts). Fields that are not applicable to the current monitoring mode (for example, per-process power when not monitoring a process) are set to 0. Consumers can compare timestamp against the current time to detect stale or paused samples.
When built with feature = "api", Joular Core hosts an embedded web server:
Endpoints:
| Endpoint | Protocol | Description |
|---|---|---|
/data |
HTTP GET | Returns the latest power reading as JSON |
/ws |
WebSocket | Streams each new JSON reading published by the monitor loop |
JSON fields:
| Field | Type | Description |
|---|---|---|
timestamp |
integer | Unix timestamp (seconds) |
cpu_power |
float | CPU power in watts |
gpu_power |
float | GPU power in watts |
total_power |
float | Total (CPU + GPU) power in watts |
cpu_usage |
float | System CPU usage as a percentage |
pid_or_app_power |
float | Power for the monitored PID or application in watts (0 if not set) |
The server binds to 127.0.0.1 only. CORS is locked down to http://127.0.0.1:<port> and http://localhost:<port> by default, so no other website can read your power data through the browser. To allow an additional origin (for example, a self-hosted dashboard), pass extra origins to common::spawn_api_server or, when using Args, set api_allowed_origins. Passing * allows any origin.
When writing a CLI program using Joular Core library, the following options are already available to integrate and supported by the library:
| Option | Description |
|---|---|
-p, --pid <PID> |
Monitor a specific process by PID |
-a, --app <APP> |
Monitor an application by name (covers all its processes) |
-f, --file <FILE> |
Write output to a file. CSV is used by default; with -i, the file receives numeric-only values. |
-o, --overwrite |
With -f, truncate before each write so only the latest data row/value is kept |
-c, --component <cpu|gpu> |
Show only CPU or only GPU power |
-i, --numeric |
Output only the numeric value, no formatting or labels |
-s, --silent |
Suppress terminal output (file, ring buffer, and API still work) |
-g, --gui |
Start the graphical user interface |
-r, --ringbuffer |
Write power data to the shared-memory ring buffer |
--api-port <PORT> |
Start the HTTP and WebSocket API server on this port |
--api-allowed-origin <ORIGIN> |
Allow an extra CORS origin for the API (repeatable). Localhost is always allowed. |
--app-refresh-interval <SECONDS> |
How often to rescan for new PIDs belonging to an application (default: 3s; set to 0 to rescan every second) |
--cpu-idle-baseline <WATTS> |
Subtract a fixed idle CPU baseline before attributing power to a PID or application |
--calibrate-cpu-idle-baseline |
Measure idle CPU power automatically (5 samples, 1 second each) and use that as the baseline |
Notes:
--pidand--appare mutually exclusive.--cpu-idle-baselineand--calibrate-cpu-idle-baselineare mutually exclusive.-oonly has effect when used with-f.- In the CLI,
-g/--guiconflicts with--pid,--app,--file,--overwrite,--silent, and--numeric. - When
-fis used, the live terminal display is replaced by file output.
Joular Core CLI uses Joular Core library and provides a joularcore binary that uses the CLI options.
For example, you can use it and mix options:
# Monitor system power
joularcore
# Monitor a specific process
joularcore -p 1234
# Monitor an application by name, write to CSV
joularcore -a firefox -f power.csv
# Run silently, write to CSV, expose via API
joularcore -s -f power.csv --api-port 8080
# Only show CPU power, numeric output
joularcore -c cpu -i
# Launch the GUI (configure everything from inside the GUI)
joularcore -g
# Subtract idle CPU baseline when attributing process power
joularcore -p 1234 --calibrate-cpu-idle-baselineJoular Core is licensed under the GNU Lesser General Public License 3 license only (LGPL-3.0-only).
Copyright © 2025-2026, Adel Noureddine. All rights reserved. This program and the accompanying materials are made available under the terms of the GNU Lesser General Public License v3.0 (LGPL-3.0-only) which accompanies this distribution.
Author: Prof. Adel Noureddine

