Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

230 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Joular Project Joular Core ⚡

License: LGPL v3 Made with Rust

Joular Core Logo

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 is under active development and currently in beta quality. Expect rough edges and features still being worked on and polished.

📡 Supported platforms

  • 💻 Supported Systems: 🐧 Linux, 🪟 Windows, 🍎 macOS, 🍓 Raspberry Pi, 💽 Virtual Machines.
  • ⚙️ Supported Architectures: x86_64 (amd64), x86/i686, aarch64, arm, armv7, GPUs (Nvidia, Apple, AMD).

🌍 Universal Compatibility:

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.


🚀 Features

  • 📊 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

📦 Installation & Setup

Add joularcore to your Rust project's Cargo.toml:

[dependencies]
joularcore = "0.1.0"

Or via the command line:

cargo add joularcore

⚙️ Cargo Features

You 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.

Feature Examples in Cargo.toml:

# 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"] }

💡 Quickstart & Code Examples

1. Simple System Power Sampling

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);
}

2. Monitoring a Specific Process ID (PID)

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()
    );
}

3. Writing Output to CSV, Shared Memory, or API

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(())
}

💡 Configuration

On most platforms, no configuration is needed. Joular Core detects the platform, finds the right power interface, and starts measuring.

Platform requirements

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.

Virtual Machines

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 prefers App Power (W), then Process Power (W), then CPU Power (W); for GPU power it reads GPU Power (W).

Single-Board Computers (SBC)

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.


🛠️ Low-Level IPC Interfaces

Shared memory ring buffer

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.

HTTP and WebSockets API

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.


CLI Options

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:

  • --pid and --app are mutually exclusive.
  • --cpu-idle-baseline and --calibrate-cpu-idle-baseline are mutually exclusive.
  • -o only has effect when used with -f.
  • In the CLI, -g / --gui conflicts with --pid, --app, --file, --overwrite, --silent, and --numeric.
  • When -f is used, the live terminal display is replaced by file output.

Examples

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-baseline

📜 License

Joular 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

About

Joular Core is a platform to measure power and energy across all systems, OSes and devices

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages