3 releases
new 0.1.2 | Mar 27, 2025 |
---|---|
0.1.1 | Mar 27, 2025 |
0.1.0 | Mar 26, 2025 |
#236 in Embedded development
52 downloads per month
155KB
1.5K
SLoC
jetson-power-monitor
English | 中文
A comprehensive power monitoring library for NVIDIA Jetson devices, available in multiple programming languages.
Features
- Real-time power consumption monitoring
- Support for multiple programming languages (C/C++, Rust, Python)
- Easy installation through package managers
- Low-level access to power metrics
- Cross-platform support for Jetson devices
Installation
Python
pip install jetpwmon
Rust
Add to your Cargo.toml
:
[dependencies]
jetpwmon = "0.1.2"
C/C++
Download the pre-built .deb
package from the Releases page:
sudo dpkg -i jetpwmon_0.1.2_amd64.deb
Or use CMake to find and link the library in your project:
find_package(jetpwmon REQUIRED)
target_link_libraries(your_target PRIVATE jetpwmon::jetpwmon) # Use shared library
# or
target_link_libraries(your_target PRIVATE jetpwmon::static) # Use static library
# For C++ bindings
target_link_libraries(your_target PRIVATE jetpwmon::jetpwmon_cpp) # Use shared library
# or
target_link_libraries(your_target PRIVATE jetpwmon::static_cpp) # Use static library
Usage
Python
Quick Start: Get Instantaneous Readings
This shows how to get the current total power consumption, voltage, and current readings directly from the device.
import jetpwmon
# Create a power monitor instance
monitor = jetpwmon.PowerMonitor()
# Get the latest data snapshot
try:
data = monitor.get_latest_data()
# Access total readings
total = data['total']
print(f"Current total power consumption: {total['power']:.2f} W")
print(f"Current bus voltage: {total['voltage']:.2f} V")
print(f"Current total current: {total['current']:.2f} A")
# Access individual sensor readings
print("\nIndividual Sensor Readings:")
for sensor in data['sensors']:
print(f"Sensor {sensor['name']}:")
print(f" Power: {sensor['power']:.2f} W")
print(f" Voltage: {sensor['voltage']:.2f} V")
print(f" Current: {sensor['current']:.2f} A")
except Exception as e:
print(f"Error reading power metrics: {e}")
print("Ensure the INA3221 device is connected and accessible (permissions?).")
Advanced: Monitor Power During a Task
This example demonstrates how to start background power sampling before a task, stop it afterwards, and retrieve detailed statistics (min, max, average power, total energy) for the monitoring period.
import jetpwmon
import time
import numpy as np # Using numpy for a sample CPU-intensive task
def cpu_intensive_task():
"""Simulate a CPU-intensive task"""
print("Starting CPU-intensive task...")
# Reduced size for a quicker example run
size = 2000
matrix1 = np.random.rand(size, size)
matrix2 = np.random.rand(size, size)
# Perform matrix multiplication
result = np.dot(matrix1, matrix2)
print("CPU-intensive task completed.")
def monitor_power_consumption(task_func):
"""Monitor power consumption during task execution"""
# Create a power monitor instance
monitor = jetpwmon.PowerMonitor()
try:
# Optional: Set the sampling frequency (e.g., 1000Hz)
# Higher frequencies provide more granular data but increase overhead.
# Check library documentation or device limits for valid/optimal values.
monitor.set_sampling_frequency(1000)
# Reset statistics before starting a new monitoring period
monitor.reset_statistics()
# Start background sampling
print("Starting power sampling...")
monitor.start_sampling()
# --- Execute the task you want to monitor ---
task_func()
# --- Task finished ---
# Optional: Wait briefly to ensure last samples are captured,
# depends on task duration and sampling frequency.
time.sleep(0.1)
# Stop background sampling
monitor.stop_sampling()
print("Stopped power sampling.")
# Get collected statistics
stats = monitor.get_statistics()
# --- Print the collected statistics ---
print("\n--- Power Consumption Statistics ---")
# Print total power consumption statistics
if 'total' in stats and 'power' in stats['total']:
total_stats = stats['total']['power']
print("Total Power Consumption:")
# Use .get() for safety in case some stats weren't computed
print(f" Minimum Value: {total_stats.get('min', float('nan')):.2f} W")
print(f" Maximum Value: {total_stats.get('max', float('nan')):.2f} W")
print(f" Average Value: {total_stats.get('avg', float('nan')):.2f} W")
print(f" Total Energy: {total_stats.get('total', float('nan')):.2f} J")
print(f" Sample Count: {total_stats.get('count', 0)}")
else:
print("Total power statistics not available.")
# Print power consumption information for each sensor/channel
if 'sensors' in stats:
print("\nPower Consumption Per Sensor:")
for sensor in stats['sensors']:
if 'power' in sensor:
sensor_stats = sensor['power']
print(f"\n Sensor: {sensor.get('name', 'Unknown')}")
print(f" Minimum Value: {sensor_stats.get('min', float('nan')):.2f} W")
print(f" Maximum Value: {sensor_stats.get('max', float('nan')):.2f} W")
print(f" Average Value: {sensor_stats.get('avg', float('nan')):.2f} W")
print(f" Total Energy: {sensor_stats.get('total', float('nan')):.2f} J")
print(f" Sample Count: {sensor_stats.get('count', 0)}")
else:
print("\nPer-sensor statistics not available.")
except Exception as e:
print(f"\nAn error occurred during monitoring: {e}")
print("Ensure the INA3221 device is connected and accessible.")
# --- Run the monitoring example ---
print("Jetson Power Monitor Example Program")
print("===================================")
monitor_power_consumption(cpu_intensive_task)
Rust
First, add jetpwmon
as a dependency in your Cargo.toml
. Adjust the path or version as needed.
[dependencies]
jetpwmon = "0.1.2"
# The examples also use these crates:
ndarray = "0.15" # For matrix example
rand = "0.8" # For matrix example
Quick Start: Get Latest Sensor Readings
This example shows how to initialize the monitor and get a single snapshot of the current power, voltage, and current, for both the total and individual sensors. Note the use of unsafe to access per-sensor data returned via raw pointers.
use jetpwmon::{PowerMonitor, PowerData, SensorData, Error};
use std::slice;
fn main() -> Result<(), Error> {
println!("Jetson Power Monitor - Rust Quick Start");
println!("======================================");
// Initialize the power monitor. This connects to the hardware.
// The '?' operator propagates any errors (like device not found).
let monitor = PowerMonitor::new()?;
println!("Power monitor initialized successfully.");
// Get the latest instantaneous data snapshot
let data: PowerData = monitor.get_latest_data()?;
// --- Access Total Aggregated Data (Safely) ---
// The 'total' field is a regular struct within PowerData.
println!("\n--- Total Readings ---");
println!("Total Power: {:.2} W", data.total.power);
println!("Bus Voltage: {:.2} V", data.total.voltage); // Often VIN
println!("Total Current: {:.2} A", data.total.current);
println!("Status: {}", String::from_utf8_lossy(&data.total.status).trim_matches('\0'));
// --- Access Individual Sensor Data (Requires Unsafe) ---
// 'data.sensors' is a raw pointer (*mut SensorData) from C.
// We need an unsafe block to dereference it and create a safe slice.
println!("\n--- Individual Sensor Readings ---");
if !data.sensors.is_null() && data.sensor_count > 0 {
// Create a safe slice from the raw pointer and count
// SAFETY: Assumes the C library guarantees that 'data.sensors' points to valid memory
// containing 'data.sensor_count' elements, and that this memory remains valid
// at least for the lifetime of the 'data' variable returned by get_latest_data().
let sensors_slice: &[SensorData] = unsafe {
slice::from_raw_parts(data.sensors, data.sensor_count as usize)
};
// Now iterate over the safe slice
for sensor in sensors_slice {
// Convert the fixed-size u8 array (C string) to a Rust String
// Using from_utf8_lossy is safer as it handles potential invalid UTF-8 bytes.
let name = String::from_utf8_lossy(&sensor.name)
.trim_matches('\0') // Remove null padding/terminator
.to_string();
let status = String::from_utf8_lossy(&sensor.status).trim_matches('\0').to_string();
println!(
" Sensor: {:<15} | Pwr: {:>6.2} W | V: {:>5.2} V | I: {:>6.2} A | Online: {} | Status: {}",
name, sensor.power, sensor.voltage, sensor.current, sensor.online, status
);
}
} else {
println!(" No individual sensor data available or pointer was null.");
}
// No explicit cleanup needed. The `PowerMonitor` struct implements the `Drop` trait,
// which automatically calls the C cleanup function when `monitor` goes out of scope.
println!("\nMonitor will be cleaned up automatically.");
Ok(())
}
Advanced: Monitor Power During a Task
This example demonstrates starting background sampling, running a CPU-intensive task (matrix multiplication across threads), stopping sampling, and retrieving detailed statistics. It highlights error handling with Result and the necessary unsafe block for accessing per-sensor statistics.
Dependencies needed for this example:
[dependencies]
jetpwmon = { version = "0.1.2" } # Adjust as needed
ndarray = "0.15"
rand = "0.8"
use jetpwmon::{PowerMonitor, PowerStats, SensorStats, Error};
use std::{thread, time::Duration, slice, error::Error as StdError};
use ndarray::Array2;
use rand::Rng;
// Example task parameters (adjust as needed)
const MATRIX_SIZE: usize = 1000; // Size of matrices
const NUM_THREADS: usize = 4; // Number of concurrent tasks
const NUM_ITERATIONS: usize = 5; // Workload per thread
/// Example CPU-intensive task using ndarray for matrix multiplication
fn matrix_multiply_task(thread_id: usize) {
// println!("Thread {} starting...", thread_id); // Optional logging
let mut rng = rand::thread_rng();
// Create large matrices filled with random data
let mut a: Array2<f64> = Array2::from_shape_fn((MATRIX_SIZE, MATRIX_SIZE), |_| rng.gen());
let b: Array2<f64> = Array2::from_shape_fn((MATRIX_SIZE, MATRIX_SIZE), |_| rng.gen());
// Perform repeated multiplications
for _ in 0..NUM_ITERATIONS {
a = a.dot(&b); // Matrix multiplication
}
// println!("Thread {} finished.", thread_id); // Optional logging
}
// Use Box<dyn StdError> for flexible error handling in main
fn main() -> Result<(), Box<dyn StdError>> {
println!("Jetson Power Monitor - Rust Monitoring Example");
println!("==========================================");
// Initialize the power monitor
let monitor = PowerMonitor::new()?; // Propagate errors using '?'
println!("Power monitor initialized.");
// Set the desired sampling frequency (e.g., 1000 Hz)
let frequency = 1000;
monitor.set_sampling_frequency(frequency)?;
println!("Set sampling frequency to {} Hz.", frequency);
// Reset any previously collected statistics
monitor.reset_statistics()?;
println!("Reset statistics.");
// Start background sampling in a separate thread (managed by the C library)
monitor.start_sampling()?;
println!("Started power sampling...");
// Record task start time
let task_start_time = std::time::Instant::now();
// --- Run the CPU-intensive task across multiple threads ---
let mut handles = vec![];
for i in 0..NUM_THREADS {
let handle = thread::spawn(move || {
matrix_multiply_task(i);
});
handles.push(handle);
}
// Wait for all threads to complete
for handle in handles {
handle.join().expect("Task thread panicked!");
}
// --- Task finished ---
let task_duration = task_start_time.elapsed();
println!("\nTask execution finished in: {:.2?}", task_duration);
// Allow a brief moment for the last samples to be collected by the background thread
thread::sleep(Duration::from_millis(100)); // Adjust if needed
// Stop the background sampling thread
monitor.stop_sampling()?;
println!("Stopped power sampling.");
// Retrieve the collected statistics
let stats: PowerStats = monitor.get_statistics()?;
// --- Print the Statistics ---
println!("\n--- Power Consumption Statistics ---");
// Print total aggregated statistics
println!("Total Power Consumption:");
println!(" Min Power: {:.2} W", stats.total.power.min);
println!(" Max Power: {:.2} W", stats.total.power.max);
println!(" Avg Power: {:.2} W", stats.total.power.avg);
println!(" Total Energy: {:.2} J", stats.total.power.total); // Energy = Avg Power * Duration
println!(" Sample Count: {}", stats.total.power.count);
// You can also access stats.total.voltage and stats.total.current if needed
// Print per-sensor statistics (requires unsafe)
println!("\nPer-Sensor Power Consumption:");
if !stats.sensors.is_null() && stats.sensor_count > 0 {
// Create a safe slice from the raw pointer and count
// SAFETY: Assumes C library guarantees pointer validity for the lifetime of 'stats'.
let sensor_stats_slice: &[SensorStats] = unsafe {
slice::from_raw_parts(stats.sensors, stats.sensor_count as usize)
};
// Iterate over the safe slice
for sensor_stat in sensor_stats_slice {
// Convert C char array name to Rust String
let name = String::from_utf8_lossy(&sensor_stat.name)
.trim_matches('\0')
.to_string();
println!("\n Sensor: {}", name);
println!(" Min Power: {:.2} W", sensor_stat.power.min);
println!(" Max Power: {:.2} W", sensor_stat.power.max);
println!(" Avg Power: {:.2} W", sensor_stat.power.avg);
println!(" Total Energy: {:.2} J", sensor_stat.power.total);
println!(" Sample Count: {}", sensor_stat.power.count);
// You can also access sensor_stat.voltage and sensor_stat.current if needed
}
} else {
println!(" No per-sensor statistics available or pointer was null.");
}
println!("\nMonitoring complete. Resources will be cleaned up.");
// `monitor` goes out of scope here, Drop trait calls pm_cleanup()
Ok(())
}
C/C++
Compilation
-
Include Header: Add the following line to your C source files:
#include <jetpwmon/jetpwmon.h> // Or adjust the path based on your project structure: // #include "path/to/include/jetpwmon/jetpwmon.h"
-
Link Library: When compiling, you need to link against the
libjetpwmon
library. Assuming the library and header files are installed in standard system paths or paths specified via-L
and-I
:# Basic compilation gcc your_program.c -o your_program -ljetpwmon # If library/includes are in custom locations: # gcc your_program.c -o your_program -I/path/to/jetpwmon/include -L/path/to/jetpwmon/lib -ljetpwmon # You can use pkg-config to find the library and include paths: # gcc your_program.c -o your_program -ljetpwmon `pkg-config --libs --cflags jetpwmon` # Add other libraries if needed (like pthread for threading, m for math, omp for OpenMP) # Example with OpenMP (like the advanced example below): # gcc your_program.c -o your_program -I/path/to/include -L/path/to/lib -ljetpwmon -fopenmp -lm
Quick Start: Get Latest Sensor Readings
This example demonstrates the basic lifecycle: initialize the library, get a single snapshot of current sensor readings, print them, and perform the mandatory cleanup.
#include <stdio.h>
#include <stdlib.h> // For EXIT_FAILURE
#include <jetpwmon/jetpwmon.h> // Adjust path if necessary
int main() {
pm_handle_t handle = NULL; // Opaque handle for the library instance
pm_power_data_t current_data; // Struct to hold the results
pm_error_t err; // Variable to store error codes
// 1. Initialize the library
// pm_init allocates resources and discovers sensors.
// It stores the handle needed for subsequent calls in 'handle'.
err = pm_init(&handle);
if (err != PM_SUCCESS) {
// Use pm_error_string to get a readable error message
fprintf(stderr, "ERROR: Failed to initialize jetpwmon: %s (code: %d)\n", pm_error_string(err), err);
return EXIT_FAILURE;
}
printf("Library initialized successfully.\n");
// 2. Get the latest data snapshot
// Pass the address of the struct; the library fills it.
// The 'current_data.sensors' pointer will point to an internal library buffer.
err = pm_get_latest_data(handle, ¤t_data);
if (err != PM_SUCCESS) {
fprintf(stderr, "ERROR: Failed to get latest data: %s (code: %d)\n", pm_error_string(err), err);
pm_cleanup(handle); // Clean up resources before exiting on error
return EXIT_FAILURE;
}
// 3. Print Total Aggregated Readings
printf("\n--- Total Readings ---\n");
printf("Total Power : %.2f W\n", current_data.total.power);
printf("Bus Voltage : %.2f V\n", current_data.total.voltage);
printf("Total Current: %.2f A\n", current_data.total.current);
// Assume 'status' is a null-terminated C string
printf("Status : %s\n", current_data.total.status);
// 4. Print Individual Sensor Readings
printf("\n--- Individual Sensor Readings ---\n");
// Check if the sensors pointer is valid and count is positive
// The 'current_data.sensors' pointer is managed by the library and points
// to 'current_data.sensor_count' elements. It's typically valid until
// the next call to a library function that modifies this data or pm_cleanup.
if (current_data.sensors != NULL && current_data.sensor_count > 0) {
for (int i = 0; i < current_data.sensor_count; ++i) {
// Access data using array indexing on the pointer
pm_sensor_data_t* sensor = ¤t_data.sensors[i];
printf(" Sensor: %-15s | Pwr: %6.2f W | V: %5.2f V | I: %6.2f A | Online: %s | Status: %s\n",
sensor->name, // Assumes null-terminated string
sensor->power,
sensor->voltage,
sensor->current,
sensor->online ? "Yes" : "No",
sensor->status); // Assumes null-terminated string
}
} else {
printf(" No individual sensor data available.\n");
}
// 5. Clean up library resources (MANDATORY)
// This releases memory and stops any running background threads.
err = pm_cleanup(handle);
if (err != PM_SUCCESS) {
// Log the error, but the program should still terminate.
fprintf(stderr, "ERROR: Failed to clean up jetpwmon cleanly: %s (code: %d)\n", pm_error_string(err), err);
return EXIT_FAILURE; // Indicate an error occurred during cleanup
}
printf("\nLibrary resources cleaned up successfully.\n");
return 0; // Success
}
Advanced: Monitor Power During a Task
This example demonstrates the complete workflow for monitoring power consumption during a specific task. It initializes the library, configures and starts sampling, executes a CPU-intensive task (using OpenMP for parallelization), stops sampling, retrieves the collected statistics, prints them, and cleans up.
Note: Compile this example with OpenMP support enabled (e.g., gcc -fopenmp
...)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // For usleep()
#include <time.h> // For timing task using clock_gettime
#include <omp.h> // For OpenMP parallel task example
#include <jetpwmon/jetpwmon.h> // Adjust path if necessary
// Example CPU-intensive task (Matrix Multiplication using OpenMP)
void cpu_intensive_task() {
printf("Starting CPU-intensive task...\n");
const int size = 1500; // Moderate size for example
double *matrix1 = NULL, *matrix2 = NULL, *result = NULL;
// Allocate memory for matrices
matrix1 = (double*)malloc(size * size * sizeof(double));
matrix2 = (double*)malloc(size * size * sizeof(double));
result = (double*)malloc(size * size * sizeof(double));
if (!matrix1 || !matrix2 || !result) {
fprintf(stderr, "Task ERROR: Failed to allocate memory for matrices.\n");
// Free any potentially allocated memory before returning
free(matrix1);
free(matrix2);
free(result);
return; // Exit the task function on allocation failure
}
// Initialize matrices with random data using OpenMP parallel for
#pragma omp parallel for
for (int i = 0; i < size * size; i++) {
// Note: rand() is not thread-safe, but for a demo it might be acceptable.
// For production, use thread-safe RNGs or seed per thread.
matrix1[i] = (double)rand() / RAND_MAX;
matrix2[i] = (double)rand() / RAND_MAX;
}
// Perform matrix multiplication using OpenMP parallel for with collapse
#pragma omp parallel for collapse(2)
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
double sum = 0.0;
for (int k = 0; k < size; k++) {
sum += matrix1[i * size + k] * matrix2[k * size + j];
}
result[i * size + j] = sum;
}
}
// Free allocated memory
free(matrix1);
free(matrix2);
free(result);
printf("CPU-intensive task completed.\n");
}
int main() {
pm_handle_t handle = NULL; // Library instance handle
pm_power_stats_t stats; // Struct to hold collected statistics
pm_error_t err; // Error code variable
struct timespec start_time, end_time; // For timing the task execution
printf("Jetson Power Monitor - C Monitoring Example\n");
printf("=========================================\n");
// 1. Initialize the library
err = pm_init(&handle);
if (err != PM_SUCCESS) {
fprintf(stderr, "ERROR: Initialization failed: %s\n", pm_error_string(err));
return EXIT_FAILURE;
}
printf("Library initialized.\n");
// 2. Set Sampling Frequency (e.g., 1000 Hz)
int target_frequency = 1000;
err = pm_set_sampling_frequency(handle, target_frequency);
if (err != PM_SUCCESS) {
fprintf(stderr, "ERROR: Failed to set sampling frequency: %s\n", pm_error_string(err));
pm_cleanup(handle); // Cleanup before exit
return EXIT_FAILURE;
}
printf("Set sampling frequency to %d Hz.\n", target_frequency);
// 3. Reset Statistics (Recommended before starting a measurement interval)
err = pm_reset_statistics(handle);
if (err != PM_SUCCESS) {
fprintf(stderr, "ERROR: Failed to reset statistics: %s\n", pm_error_string(err));
pm_cleanup(handle);
return EXIT_FAILURE;
}
printf("Statistics reset.\n");
// 4. Start Background Sampling
// This starts a thread managed by the library to collect data.
err = pm_start_sampling(handle);
if (err != PM_SUCCESS) {
fprintf(stderr, "ERROR: Failed to start sampling: %s\n", pm_error_string(err));
pm_cleanup(handle);
return EXIT_FAILURE;
}
printf("Started power sampling...\n");
// --- Execute the task to be monitored ---
clock_gettime(CLOCK_MONOTONIC, &start_time); // Get time before task
cpu_intensive_task(); // Run the actual task
clock_gettime(CLOCK_MONOTONIC, &end_time); // Get time after task
// --- Task Finished ---
// Calculate task duration
double task_duration_sec = (end_time.tv_sec - start_time.tv_sec) +
(end_time.tv_nsec - start_time.tv_nsec) / 1e9;
printf("\nTask execution finished in: %.3f seconds\n", task_duration_sec);
// Optional: Wait a short moment to ensure the sampling thread captures final moments.
// Adjust duration based on sampling frequency and task nature.
usleep(100 * 1000); // 100 milliseconds
// 5. Stop Background Sampling
err = pm_stop_sampling(handle);
if (err != PM_SUCCESS) {
// Log error, but proceed to get statistics if possible
fprintf(stderr, "WARNING: Failed to stop sampling cleanly: %s\n", pm_error_string(err));
} else {
printf("Stopped power sampling.\n");
}
// 6. Get Collected Statistics
// Pass address of 'stats' struct; library fills it.
// 'stats.sensors' will point to an internal buffer managed by the library.
err = pm_get_statistics(handle, &stats);
if (err != PM_SUCCESS) {
fprintf(stderr, "ERROR: Failed to get statistics: %s\n", pm_error_string(err));
pm_cleanup(handle);
return EXIT_FAILURE;
}
// --- Print Statistics ---
printf("\n--- Power Consumption Statistics ---\n");
printf("Total Power Consumption:\n");
printf(" Min Power : %.2f W\n", stats.total.power.min);
printf(" Max Power : %.2f W\n", stats.total.power.max);
printf(" Avg Power : %.2f W\n", stats.total.power.avg);
printf(" Total Energy: %.2f J (Avg Power * Duration)\n", stats.total.power.total);
printf(" Sample Count: %lu\n", stats.total.power.count);
// Access total voltage/current stats via stats.total.voltage.* etc.
printf("\nPer-Sensor Power Consumption:\n");
if (stats.sensors != NULL && stats.sensor_count > 0) {
for (int i = 0; i < stats.sensor_count; ++i) {
pm_sensor_stats_t* sensor_stat = &stats.sensors[i];
// Assume 'name' is a null-terminated C string
printf("\n Sensor: %s\n", sensor_stat->name);
printf(" Min Power : %.2f W\n", sensor_stat->power.min);
printf(" Max Power : %.2f W\n", sensor_stat->power.max);
printf(" Avg Power : %.2f W\n", sensor_stat->power.avg);
printf(" Total Energy: %.2f J\n", sensor_stat->power.total);
printf(" Sample Count: %lu\n", sensor_stat->power.count);
// Access per-sensor voltage/current stats via sensor_stat->voltage.* etc.
}
} else {
printf(" No per-sensor statistics available.\n");
}
// 7. Clean up library resources (MANDATORY)
err = pm_cleanup(handle);
if (err != PM_SUCCESS) {
fprintf(stderr, "ERROR: Cleanup failed: %s\n", pm_error_string(err));
return EXIT_FAILURE; // Indicate failure on cleanup error
}
printf("\nLibrary resources cleaned up successfully.\n");
return 0; // Success
}
C++
Compilation
-
Include Header: Use the C++ wrapper header file in your source code:
#include <jetpwmon/jetpwmon++.hpp> // Use the C++ header #include <stdexcept> // For catching exceptions #include <iostream> // For printing
-
Link Library: Compile your C++ code (ensuring C++14 or later standard is enabled) and link against the underlying
libjetpwmon
C library:# Compile using g++ with C++14 support g++ your_program.cpp -o your_program -std=c++14 -ljetpwmon # If library/includes are in custom locations: # g++ your_program.cpp -o your_program -std=c++14 -I/path/to/jetpwmon/include -L/path/to/jetpwmon/lib -ljetpwmon # Add other necessary flags (e.g., -pthread for std::thread, Eigen paths/libs) # g++ your_program.cpp -o your_program -std=c++14 -I/path/to/eigen -I/path/to/include -L/path/to/lib -ljetpwmon -pthread
Key Features of the C++ Wrapper:
- RAII (Resource Acquisition Is Initialization): The
jetpwmon::PowerMonitor
object automatically initializes the library (pm_init
) on creation and cleans up resources (pm_cleanup
) upon destruction (when it goes out of scope). No manual cleanup calls are needed. - Exception Safety: C API errors are converted into
std::runtime_error
exceptions, allowing for standard C++ error handling usingtry...catch
blocks.
Quick Start: Get Latest Sensor Readings (C++ Wrapper)
This example demonstrates initializing the monitor using the C++ wrapper, getting a snapshot of current readings, printing them, and letting RAII handle cleanup.
#include <jetpwmon/jetpwmon++.hpp> // C++ wrapper header
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept> // For std::runtime_error
#include <cstring> // For strnlen
// Helper function to safely convert C char array (potentially not null-terminated) to std::string
std::string c_char_to_string(const char* c_str, size_t max_len) {
// Find the actual length of the string or stop at max_len
size_t len = strnlen(c_str, max_len);
return std::string(c_str, len);
}
int main() {
try {
// 1. Initialize: Create PowerMonitor object.
// Constructor handles pm_init() and throws std::runtime_error on failure.
jetpwmon::PowerMonitor monitor;
std::cout << "Power monitor initialized successfully (RAII)." << std::endl;
// 2. Get Latest Data: Returns a jetpwmon::PowerData object.
// This object holds the data snapshot.
jetpwmon::PowerData data = monitor.getLatestData(); // Throws on C API error
// 3. Access and Print Total Readings
// Use the getTotal() method which returns a const reference to the C struct.
const pm_sensor_data_t& total_data = data.getTotal();
std::cout << "\n--- Total Readings ---" << std::endl;
std::cout << "Total Power : " << total_data.power << " W" << std::endl;
std::cout << "Bus Voltage : " << total_data.voltage << " V" << std::endl;
std::cout << "Total Current: " << total_data.current << " A" << std::endl;
// Safely convert C char array status field
std::cout << "Status : " << c_char_to_string(total_data.status, sizeof(total_data.status)) << std::endl;
// 4. Access and Print Individual Sensor Readings
std::cout << "\n--- Individual Sensor Readings ---" << std::endl;
const pm_sensor_data_t* sensors_ptr = data.getSensors(); // Get raw C pointer
int sensor_count = data.getSensorCount();
// SAFETY NOTE: The pointer from getSensors() points to memory managed
// by the underlying C library, assumed valid only temporarily. Access promptly.
if (sensors_ptr != nullptr && sensor_count > 0) {
for (int i = 0; i < sensor_count; ++i) {
const pm_sensor_data_t& sensor = sensors_ptr[i]; // Access via pointer
std::cout << " Sensor: " << c_char_to_string(sensor.name, sizeof(sensor.name))
<< " | Pwr: " << sensor.power << " W"
<< " | V: " << sensor.voltage << " V"
<< " | I: " << sensor.current << " A"
<< " | Online: " << (sensor.online ? "Yes" : "No")
<< " | Status: " << c_char_to_string(sensor.status, sizeof(sensor.status))
<< std::endl;
}
} else {
std::cout << " No individual sensor data available." << std::endl;
}
// 5. Cleanup is Automatic!
// When 'monitor' goes out of scope at the end of 'main' (or the try block),
// its destructor is called, which automatically calls pm_cleanup().
std::cout << "\nExiting scope. PowerMonitor destructor will handle cleanup." << std::endl;
} catch (const std::runtime_error& e) {
// Catch errors thrown by the PowerMonitor wrapper
std::cerr << "ERROR: " << e.what() << std::endl;
return 1; // Indicate failure
} catch (const std::exception& e) {
// Catch any other standard exceptions
std::cerr << "An unexpected error occurred: " << e.what() << std::endl;
return 1;
}
return 0; // Success
}
Advanced: Monitor Power During a Task (C++ Wrapper)
This example uses the jetpwmon::PowerMonitor
C++ wrapper, std::thread
, and exception handling to monitor power consumption during a parallel matrix multiplication task (using Eigen).
Example Dependencies: Eigen library, C++11 thread support (-pthread
).
#include <jetpwmon/jetpwmon++.hpp> // C++ wrapper
#include <iostream>
#include <vector>
#include <string>
#include <thread> // Use std::thread
#include <chrono> // For timing and sleep
#include <stdexcept> // For exception handling
#include <Eigen/Dense> // For Eigen matrix task
#include <cstring> // For strnlen
// Example Task Parameters
const int MATRIX_SIZE = 1000; // Adjust based on system memory/CPU
const int NUM_THREADS = 4; // Number of threads for the task
const int NUM_ITERATIONS = 5; // Workload per thread
// Helper function to safely convert C char array to std::string
std::string c_char_to_string(const char* c_str, size_t max_len) {
size_t len = strnlen(c_str, max_len);
return std::string(c_str, len);
}
// Example CPU-intensive task using Eigen library
void eigen_matrix_task(int thread_id) {
// std::cout << "Thread " << thread_id << " starting Eigen task..." << std::endl;
Eigen::MatrixXd a = Eigen::MatrixXd::Random(MATRIX_SIZE, MATRIX_SIZE);
Eigen::MatrixXd b = Eigen::MatrixXd::Random(MATRIX_SIZE, MATRIX_SIZE);
for (int i = 0; i < NUM_ITERATIONS; ++i) {
a = a * b; // Perform matrix multiplication
}
// std::cout << "Thread " << thread_id << " finished Eigen task." << std::endl;
}
int main() {
try {
// 1. Initialize PowerMonitor (RAII handles pm_init)
jetpwmon::PowerMonitor monitor;
std::cout << "Power monitor initialized." << std::endl;
// 2. Configure Sampling
int frequency = 1000;
monitor.setSamplingFrequency(frequency); // Throws on error
std::cout << "Set sampling frequency to " << frequency << " Hz." << std::endl;
// 3. Reset Statistics
monitor.resetStatistics(); // Throws on error
std::cout << "Statistics reset." << std::endl;
// 4. Start Background Sampling
monitor.startSampling(); // Throws on error
std::cout << "Started power sampling..." << std::endl;
// --- Execute the Parallel Task ---
auto task_start_time = std::chrono::high_resolution_clock::now();
std::vector<std::thread> task_threads;
task_threads.reserve(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; ++i) {
// Use std::thread for C++ concurrency
task_threads.emplace_back(eigen_matrix_task, i);
}
// Wait for all task threads to complete
for (auto& t : task_threads) {
if (t.joinable()) {
t.join();
}
}
auto task_end_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> task_duration = task_end_time - task_start_time;
std::cout << "\nTask execution finished in: " << task_duration.count() << " seconds" << std::endl;
// --- Task Finished ---
// Optional pause for final sample collection
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 5. Stop Background Sampling
monitor.stopSampling(); // Throws on error
std::cout << "Stopped power sampling." << std::endl;
// 6. Get Collected Statistics
// Returns a jetpwmon::PowerStats object.
jetpwmon::PowerStats stats = monitor.getStatistics(); // Throws on error
// --- Print Statistics ---
std::cout << "\n--- Power Consumption Statistics ---" << std::endl;
const pm_sensor_stats_t& total_stats = stats.getTotal();
std::cout << "Total Power Consumption:" << std::endl;
std::cout << " Min Power : " << total_stats.power.min << " W" << std::endl;
std::cout << " Max Power : " << total_stats.power.max << " W" << std::endl;
std::cout << " Avg Power : " << total_stats.power.avg << " W" << std::endl;
std::cout << " Total Energy: " << total_stats.power.total << " J" << std::endl;
std::cout << " Sample Count: " << total_stats.power.count << std::endl;
std::cout << "\nPer-Sensor Power Consumption:" << std::endl;
const pm_sensor_stats_t* sensors_stats_ptr = stats.getSensors();
int sensor_count = stats.getSensorCount();
// SAFETY NOTE: Access pointer promptly, assumes temporary validity.
if (sensors_stats_ptr != nullptr && sensor_count > 0) {
for (int i = 0; i < sensor_count; ++i) {
const pm_sensor_stats_t& sensor_stat = sensors_stats_ptr[i];
std::cout << "\n Sensor: " << c_char_to_string(sensor_stat.name, sizeof(sensor_stat.name)) << std::endl;
std::cout << " Min Power : " << sensor_stat.power.min << " W" << std::endl;
std::cout << " Max Power : " << sensor_stat.power.max << " W" << std::endl;
std::cout << " Avg Power : " << sensor_stat.power.avg << " W" << std::endl;
std::cout << " Total Energy: " << sensor_stat.power.total << " J" << std::endl;
std::cout << " Sample Count: " << sensor_stat.power.count << std::endl;
}
} else {
std::cout << " No per-sensor statistics available." << std::endl;
}
// 7. Cleanup is Automatic (RAII)
std::cout << "\nMonitoring complete. Resources automatically cleaned up." << std::endl;
// 'monitor' destructor called automatically when main returns.
} catch (const std::runtime_error& e) {
std::cerr << "ERROR: " << e.what() << std::endl;
return 1;
} catch (const std::exception& e) {
std::cerr << "An unexpected error occurred: " << e.what() << std::endl;
return 1;
}
return 0;
}
API Documentation
Python
API Reference
Here are the primary methods available on the PowerMonitor
class:
class PowerMonitor:
def __init__(self) -> None:
"""
Initializes the connection to the power monitor hardware (e.g., INA3221 via I2C).
May raise an exception if the device cannot be found or accessed.
"""
pass # Actual implementation omitted
def get_power_consumption(self) -> float:
"""
Reads the device for the current total power consumption across relevant channels.
Returns:
float: Instantaneous total power in Watts.
"""
pass
def get_voltage(self) -> float:
"""
Reads the device for the current bus voltage (typically from a specific channel like VIN).
Returns:
float: Instantaneous voltage in Volts.
"""
pass
def get_current(self) -> float:
"""
Reads the device for the current total shunt current across relevant channels.
Returns:
float: Instantaneous total current in Amperes.
"""
pass
def set_sampling_frequency(self, frequency_hz: int) -> None:
"""
Sets the target frequency for background sampling when monitoring.
Args:
frequency_hz (int): Desired samples per second (e.g., 100, 1000).
The actual achievable rate may be limited by hardware/system load.
"""
pass
def start_sampling(self) -> None:
"""
Starts a background thread or process to continuously sample power data
at the configured frequency. Statistics are accumulated internally.
Requires `stop_sampling()` to be called later.
"""
pass
def stop_sampling(self) -> None:
"""
Stops the background sampling process started by `start_sampling()`.
"""
pass
def reset_statistics(self) -> None:
"""
Clears all internally accumulated statistics (min, max, sum for average, energy, count).
Call this before `start_sampling()` to measure a specific interval.
"""
pass
def get_statistics(self) -> dict:
"""
Retrieves the power statistics collected since the last reset or initialization.
Best used after `start_sampling()` and `stop_sampling()`.
Returns:
dict: A dictionary containing aggregated ('total') and per-sensor ('sensors')
statistics. See the structure documented below. Returns empty or
partially filled dict if sampling didn't run or failed.
"""
pass
Statistics Data Structure (`get_statistics()` return value)
The get_statistics()
method returns a dictionary structured as follows:
{
'total': { # Statistics aggregated across relevant power-supplying sensors
'power': {
'min': float, # Minimum total power observed during sampling (Watts)
'max': float, # Maximum total power observed during sampling (Watts)
'avg': float, # Average total power over the sampling period (Watts)
'total': float, # Total energy consumed during the period (Joules)
# Calculated from average power and duration.
'count': int # Number of samples contributing to the total statistics.
}
# Note: May potentially include 'voltage' and 'current' keys
# if these are also aggregated and tracked.
},
'sensors': [ # A list containing statistics for each individual monitored sensor/channel
{
'name': str, # Name identifying the sensor (e.g., "VDD_GPU_SOC", "VDD_CPU_CV", "VIN_SYS_5V0")
# Names depend on Jetson model and INA3221 configuration.
'power': {
'min': float, # Minimum power for this specific sensor (Watts)
'max': float, # Maximum power for this specific sensor (Watts)
'avg': float, # Average power for this specific sensor (Watts)
'total': float, # Total energy for this specific sensor (Joules)
'count': int # Number of samples collected for this sensor.
},
# Note: May potentially include 'voltage' and 'current' keys
# if these are monitored per sensor.
},
# ... potentially more sensor dictionaries
]
}
Important Notes:
- The exact sensor names available in the
'sensors'
list depend on the specific Jetson board model and how the INA3221 channels are configured and named within the library. - The
'total'
energy is typically calculated based on the average power (avg
) and the duration of the sampling period (derived fromcount
and the sampling frequency). - If
start_sampling()
/stop_sampling()
were not used, or if data collection failed, the returned dictionary might be empty, partially filled, or contain default values like0
orNaN
. Robust code should handle potentially missing keys or non-numeric values (e.g., using.get()
with defaults as shown in the monitoring example).
Rust
Rust API Reference
Structs & Enums:
PowerMonitor
: The main interface to the library. Manages the C handle and ensures cleanup via theDrop
trait.SensorType
: Enum identifying the type of sensor (Unknown
,I2C
,System
).SensorData
: Holds instantaneous data for one sensor.name: [u8; 64]
: Sensor name (C string, needs conversion).type_: SensorType
: Type of the sensor.voltage: f64
,current: f64
,power: f64
: Measured values.online: bool
: Whether the sensor is currently readable.status: [u8; 32]
: Status message (C string, needs conversion).warning_threshold: f64
,critical_threshold: f64
: Thresholds in Watts.
Stats
: Holds statistics (min, max, avg, total, count) for a single metric (like power, voltage, or current).SensorStats
: Holds statistics for one sensor, containingStats
for voltage, current, and power.name: [u8; 64]
: Sensor name (C string, needs conversion).voltage: Stats
,current: Stats
,power: Stats
.
PowerData
: Holds instantaneous data snapshot.total: SensorData
: Aggregated data across relevant sensors.sensors: *mut SensorData
: Raw pointer to an array ofSensorData
. Requiresunsafe
to access.sensor_count: i32
: Number of elements in thesensors
array.
PowerStats
: Holds accumulated statistics.total: SensorStats
: Aggregated stats across relevant sensors.sensors: *mut SensorStats
: Raw pointer to an array ofSensorStats
. Requiresunsafe
to access.sensor_count: i32
: Number of elements in thesensors
array.
Error
: Enum representing possible error codes from the underlying C library (e.g.,InitFailed
,NotRunning
,NoSensors
). ImplementsFrom<i32>
andInto<i32>
.
PowerMonitor
Methods:
PowerMonitor::new() -> Result<Self, Error>
: Creates and initializes the monitor instance. Connects to hardware.set_sampling_frequency(&self, frequency_hz: i32) -> Result<(), Error>
: Sets the target sampling frequency in Hz for background monitoring.get_sampling_frequency(&self) -> Result<i32, Error>
: Gets the currently configured sampling frequency.start_sampling(&self) -> Result<(), Error>
: Starts background sampling thread. Statistics begin accumulating.stop_sampling(&self) -> Result<(), Error>
: Stops the background sampling thread.is_sampling(&self) -> Result<bool, Error>
: Returnstrue
if background sampling is currently active.get_latest_data(&self) -> Result<PowerData, Error>
: Fetches the most recent instantaneous readings. Return value (PowerData
) contains raw pointers requiringunsafe
access. See "Data Structures & Safety Notes".get_statistics(&self) -> Result<PowerStats, Error>
: Fetches the statistics accumulated since the lastreset_statistics()
or initialization. Return value (PowerStats
) contains raw pointers requiringunsafe
access. See "Data Structures & Safety Notes".reset_statistics(&self) -> Result<(), Error>
: Resets all internal statistics counters (min, max, avg, total, count) to zero.get_sensor_count(&self) -> Result<i32, Error>
: Returns the number of sensors detected by the library.get_sensor_names(&self) -> Result<Vec<String>, Error>
: Returns aVec<String>
containing the names of all detected sensors. Handles C string conversion internally.
Error Handling:
- All methods that interact with the C library return
Result<T, jetpwmon::Error>
. - Use standard Rust error handling (e.g.,
match
,if let Ok/Err
,?
operator) to check for and handle potential errors like device access failures, invalid states, etc.
Resource Management:
- The
PowerMonitor
struct implements theDrop
trait. When aPowerMonitor
instance goes out of scope, itsdrop
method is automatically called, which in turn calls the C library's cleanup function (pm_cleanup
). You do not need to call a cleanup function manually.
Data Structures & Safety Notes
Working with Raw Pointers in PowerData
and PowerStats
The C library returns arrays of sensor data/statistics via raw pointers (*mut SensorData
or *mut SensorStats
). The Rust wrapper exposes these directly within the PowerData
and PowerStats
structs.
Accessing this data requires unsafe
blocks in your code. The recommended way is to create a temporary, safe Rust slice from the raw pointer and count:
use std::slice;
use jetpwmon::{PowerStats, SensorStats, Error}; // Assuming these are defined
fn print_sensor_stats(stats: &PowerStats) -> Result<(), Error> {
// Check if the pointer is valid and count is positive
if !stats.sensors.is_null() && stats.sensor_count > 0 {
// SAFETY: This block assumes the C library guarantees that:
// 1. `stats.sensors` points to valid memory.
// 2. The memory contains exactly `stats.sensor_count` initialized `SensorStats` elements.
// 3. This memory remains valid for the lifetime of the `stats` reference.
// The caller must uphold these invariants.
let sensor_stats_slice: &[SensorStats] = unsafe {
slice::from_raw_parts(stats.sensors, stats.sensor_count as usize)
};
// Now 'sensor_stats_slice' is a safe slice you can iterate over
for sensor_stat in sensor_stats_slice {
// Process each sensor_stat safely here...
// Remember to handle the C string 'name' field (see below)
let name = String::from_utf8_lossy(&sensor_stat.name).trim_matches('\0').to_string();
println!("Sensor: {}, Avg Power: {:.2} W", name, sensor_stat.power.avg);
}
} else {
println!("No per-sensor statistics available.");
}
Ok(())
}
The same pattern applies when accessing sensors
within a PowerData
struct.
Working with C Strings (name
and status
fields)
Struct fields like name
([u8; 64]
) and status
([u8; 32]
) are fixed-size byte arrays intended to hold C-style null-terminated strings (or potentially just padded with nulls).
To safely convert them to a Rust String
:
- Use
String::from_utf8_lossy()
: This handles potential invalid UTF-8 sequences gracefully by replacing them with the character. - Use
.trim_matches('\0')
: This removes any leading/trailing null bytes used for padding or termination in the C buffer.
use jetpwmon::SensorData; // Assuming SensorData has a name: [u8; 64]
fn get_name(sensor_data: &SensorData) -> String {
String::from_utf8_lossy(&sensor_data.name) // Handles invalid UTF-8
.trim_matches('\0') // Removes null padding/terminator
.to_string() // Converts Cow<str> to String
}
Overall Safety
- This Rust wrapper aims to be safe where possible (using
Result
,Drop
for cleanup). - However, direct interaction with the C library via FFI inherently involves
unsafe
operations, especially when dealing with raw pointers returned from C (get_latest_data
,get_statistics
). - It is the user's responsibility to understand the memory management and lifetime guarantees provided by the underlying C library when working within
unsafe
blocks. Incorrect assumptions can lead to undefined behavior (crashes, memory corruption). Always consult the C library's documentation if available.
C/C++
API Reference
Handle Type:
pm_handle_t
: An opaque pointer (struct pm_handle_s*
) representing an initialized instance of the library. Returned bypm_init()
and required by most other functions. Must be passed topm_cleanup()
to release resources.
Enums:
pm_error_t
: Integer error codes.PM_SUCCESS
(0) indicates success. Negative values indicate errors. Seepm_error_string()
to get descriptions.PM_SUCCESS = 0
PM_ERROR_INIT_FAILED = -1
PM_ERROR_NOT_INITIALIZED = -2
PM_ERROR_ALREADY_RUNNING = -3
PM_ERROR_NOT_RUNNING = -4
PM_ERROR_INVALID_FREQUENCY = -5
PM_ERROR_NO_SENSORS = -6
PM_ERROR_FILE_ACCESS = -7
PM_ERROR_MEMORY = -8
PM_ERROR_THREAD = -9
pm_sensor_type_t
: Identifies the type of power sensor.PM_SENSOR_TYPE_UNKNOWN = 0
PM_SENSOR_TYPE_I2C = 1
(e.g., INA3221)PM_SENSOR_TYPE_SYSTEM = 2
(e.g., sysfs power supply class)
Data Structures:
pm_sensor_data_t
: Holds instantaneous data for a single sensor.char name[64]
: Null-terminated sensor name.pm_sensor_type_t type
: Sensor type.double voltage
,current
,power
: Measured values (V, A, W).bool online
: Indicates if the sensor is currently readable.char status[32]
: Null-terminated status string (e.g., "OK").double warning_threshold
,critical_threshold
: Power thresholds (W).
pm_stats_t
: Holds basic statistics for a metric.double min
,max
,avg
: Min, Max, Average values.double total
: Sum of values (can be used to calculate energy for power: Energy = Avg Power * Duration).uint64_t count
: Number of samples collected.
pm_sensor_stats_t
: Holds statistics for a single sensor.char name[64]
: Null-terminated sensor name.pm_stats_t voltage
,current
,power
: Statistics for each metric.
pm_power_data_t
: Structure filled bypm_get_latest_data
.pm_sensor_data_t total
: Aggregated instantaneous data.pm_sensor_data_t* sensors
: Pointer to an array of individual sensor data. Memory is managed by the library. The pointer is valid until the next relevant library call orpm_cleanup
. Do not free this pointer.int sensor_count
: Number of valid elements in thesensors
array.
pm_power_stats_t
: Structure filled bypm_get_statistics
.pm_sensor_stats_t total
: Aggregated statistics.pm_sensor_stats_t* sensors
: Pointer to an array of individual sensor statistics. Memory is managed by the library. The pointer is valid until the next relevant library call orpm_cleanup
. Do not free this pointer.int sensor_count
: Number of valid elements in thesensors
array.
Core Functions:
pm_error_t pm_init(pm_handle_t* handle)
:- Initializes the library, discovers sensors, allocates resources.
- Stores the opaque library instance handle at the address provided by
handle
. - Must be called first. Returns
PM_SUCCESS
on success.
pm_error_t pm_cleanup(pm_handle_t handle)
:- Stops sampling (if active) and frees all resources associated with the
handle
. - Must be called when finished with the library to prevent resource leaks.
- Stops sampling (if active) and frees all resources associated with the
const char* pm_error_string(pm_error_t error)
:- Returns a constant, human-readable string describing the given error code. Do not modify or free the returned string.
Sampling Control & Status:
pm_error_t pm_set_sampling_frequency(pm_handle_t handle, int frequency_hz)
:- Sets the target sampling frequency (in Hz) for the background monitoring thread. Must be > 0.
pm_error_t pm_get_sampling_frequency(pm_handle_t handle, int* frequency_hz)
:- Retrieves the currently configured sampling frequency, storing it at the address
frequency_hz
.
- Retrieves the currently configured sampling frequency, storing it at the address
pm_error_t pm_start_sampling(pm_handle_t handle)
:- Starts the background sampling thread. Statistics begin accumulating. Returns
PM_ERROR_ALREADY_RUNNING
if already started.
- Starts the background sampling thread. Statistics begin accumulating. Returns
pm_error_t pm_stop_sampling(pm_handle_t handle)
:- Stops the background sampling thread. Returns
PM_ERROR_NOT_RUNNING
if not running.
- Stops the background sampling thread. Returns
pm_error_t pm_is_sampling(pm_handle_t handle, bool* is_sampling)
:- Checks if the background sampling thread is active, storing the result (
true
orfalse
) at the addressis_sampling
.
- Checks if the background sampling thread is active, storing the result (
Data & Statistics Retrieval:
pm_error_t pm_get_latest_data(pm_handle_t handle, pm_power_data_t* data)
:- Fills the user-provided
data
structure with the most recent instantaneous sensor readings. - The
data->sensors
pointer will point to an internal library buffer.
- Fills the user-provided
pm_error_t pm_get_statistics(pm_handle_t handle, pm_power_stats_t* stats)
:- Fills the user-provided
stats
structure with statistics accumulated since the last reset. - The
stats->sensors
pointer will point to an internal library buffer.
- Fills the user-provided
pm_error_t pm_reset_statistics(pm_handle_t handle)
:- Resets all accumulated statistics (min, max, avg, total, count) to zero.
Sensor Information:
pm_error_t pm_get_sensor_count(pm_handle_t handle, int* count)
:- Gets the total number of sensors detected by the library.
pm_error_t pm_get_sensor_names(pm_handle_t handle, char** names, int* count)
:- Fills a caller-allocated array of C strings (
char* names[]
) with the names of detected sensors. names
: Pointer to an array ofchar*
. The caller must allocate this array. Eachchar*
in the array must also point to a caller-allocated buffer (e.g.,char name_buffer[64]
) large enough to hold a sensor name.count
:[inout]
parameter. On input, points to the allocated size of thenames
array. On output, points to the actual number of names written.- Note: This function requires careful memory management by the caller. Accessing names via
pm_get_latest_data
orpm_get_statistics
(using thesensors[i].name
field) is often simpler as the library manages those strings.
- Fills a caller-allocated array of C strings (
C++ Bindings
API Reference (C++ Wrapper)
Namespace: jetpwmon
Main Class: PowerMonitor
- Description: An RAII wrapper class for managing the
jetpwmon
C library. It handles initialization (pm_init
) in its constructor and cleanup (pm_cleanup
) in its destructor automatically. It converts C API error codes intostd::runtime_error
exceptions. - Resource Management: Non-copyable, but movable. Uses
std::unique_ptr
with a custom deleter for the C handle (pm_handle_t
). - Constructor:
PowerMonitor()
- Initializes the library connection.
- Throws:
std::runtime_error
ifpm_init
fails. The exception'swhat()
message contains the error description frompm_error_string
.
- Destructor:
~PowerMonitor()
- Automatically calls
pm_cleanup
on the managed C handle.
- Automatically calls
- Methods:
void setSamplingFrequency(int frequency_hz)
- Sets the background sampling frequency (Hz).
- Throws:
std::runtime_error
on C API failure.
int getSamplingFrequency() const
- Gets the current sampling frequency (Hz).
- Throws:
std::runtime_error
on C API failure.
void startSampling()
- Starts the background sampling thread.
- Throws:
std::runtime_error
on C API failure (e.g., already running).
void stopSampling()
- Stops the background sampling thread.
- Throws:
std::runtime_error
on C API failure (e.g., not running).
bool isSampling() const
- Checks if sampling is currently active.
- Throws:
std::runtime_error
on C API failure.
PowerData getLatestData() const
- Gets the most recent instantaneous sensor readings.
- Returns: A
PowerData
object containing the snapshot. - Throws:
std::runtime_error
on C API failure. - Note: See
PowerData
description and Safety Notes regarding pointer validity.
PowerStats getStatistics() const
- Gets the statistics accumulated since the last reset.
- Returns: A
PowerStats
object containing the statistics. - Throws:
std::runtime_error
on C API failure. - Note: See
PowerStats
description and Safety Notes regarding pointer validity.
void resetStatistics()
- Resets all internal accumulated statistics.
- Throws:
std::runtime_error
on C API failure.
int getSensorCount() const
- Gets the number of detected sensors.
- Throws:
std::runtime_error
on C API failure.
std::vector<std::string> getSensorNames() const
- Gets the names of all detected sensors. Handles C memory management and string conversion.
- Returns: A
std::vector<std::string>
containing the sensor names. - Throws:
std::runtime_error
on C API failure.
Data Wrapper Classes:
PowerData
/PowerStats
- Description: Thin wrappers around the C structs
pm_power_data_t
andpm_power_stats_t
, primarily returned bygetLatestData
andgetStatistics
. They are non-copyable but movable. - Memory: They hold a copy of the
total
C struct member and the raw C pointer (sensors
) along with thesensor_count
. They do NOT manage the memory pointed to by thesensors
pointer. That memory is owned by the underlying C library. - Getters:
const pm_sensor_data_t& getTotal() const
(forPowerData
)const pm_sensor_stats_t& getTotal() const
(forPowerStats
)- Returns a const reference to the copied
total
data/statistics struct.
- Returns a const reference to the copied
const pm_sensor_data_t* getSensors() const
(forPowerData
)const pm_sensor_stats_t* getSensors() const
(forPowerStats
)- Returns the raw C pointer to the array of per-sensor data/statistics. See Safety Notes.
int getSensorCount() const
: Returns the number of elements pointed to bygetSensors()
.
- Description: Thin wrappers around the C structs
Underlying C Structs:
- The C++ wrapper provides access to data via the C structs (
pm_sensor_data_t
,pm_stats_t
,pm_sensor_stats_t
). Refer to the C API documentation for detailed field descriptions within these structs.
C++ Wrapper Safety & Pointer Notes
- RAII & Exceptions: The
PowerMonitor
class significantly improves safety by automating resource cleanup (pm_cleanup
) through its destructor (RAII) and by converting C error codes into C++ exceptions (std::runtime_error
). Always usetry...catch
blocks when interacting withPowerMonitor
methods. - Pointer Validity (
getSensors()
): ThePowerData
andPowerStats
objects returned bygetLatestData()
andgetStatistics()
contain raw C pointers to arrays (sensors
). Crucially, the C++ wrapper classes (PowerData
,PowerStats
) do NOT manage the lifetime of the memory these pointers point to. This memory is managed by the C library.- Assumption: The memory pointed to by
getSensors()
is typically valid only temporarily, likely until the next non-const call to thePowerMonitor
object or until thePowerMonitor
object is destroyed. - Guideline: Access the data through the pointer returned by
getSensors()
immediately after the call togetLatestData()
orgetStatistics()
, within the same scope. Do not store this raw pointer for later use, as it may become invalid (dangling pointer).
- Assumption: The memory pointed to by
- C String Handling: Data structures contain C-style fixed-size character arrays (e.g.,
name[64]
,status[32]
). Use safe methods (like thec_char_to_string
helper in the examples usingstrnlen
) to convert these tostd::string
to avoid buffer over-reads, especially if null termination is not guaranteed within the fixed size.
Building from Source
Prerequisites
- CMake 3.10 or higher
- C++ compiler with C++17 support
- Python 3.8 or higher (for Python bindings)
- Rust toolchain (for Rust bindings)
Build Steps
C Library and C++ Bindings
git clone https://github.com/nerdneilsfield/jetson-power-monitor.git
cd jetson-power-monitor
mkdir build && cd build
cmake ..
make
sudo make install
Python Bindings
python3 -m pip install setuptools pybind11
python3 -m pip install -e .
# or you need to build wheel
python3 -m pip install build
python3 -m build --wheel
# the result will be in dist/
Rust Bindings
# copy c headers and sources to rust vendor directory
make copy-rust
# build rust crate
cd bindings/rust
cargo build
Contributing
We welcome contributions! Please see our CONTRIBUTING.md for detailed information about:
- Project architecture and implementation details
- Development setup and guidelines
- Code style and testing requirements
- Pull request process
- Common development tasks
- Release process
License
This project is licensed under the BSD 3-Clause License
License - see the LICENSE file for details.
Acknowledgments
- NVIDIA Jetson team for their excellent hardware
- All contributors who have helped with this project
- jetson_stats
Star History
Dependencies
~1–1.6MB
~24K SLoC