jetpwmon 0.1.2
A simple power monitor for Jetson
Requires Python: >=3.7
Keywords: jetson, power, monitor, nvidia, nvidia-jetson, jetson-power-monitor

# jetson-power-monitor

<p align="center">
  <a href="https://github.com/nerdneilsfield/jetson-power-monitor"><img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/nerdneilsfield/jetson-power-monitor?style=social" /></a>
  <a href="https://github.com/nerdneilsfield/jetson-power-monitor"><img alt="GitHub issues" src="https://img.shields.io/github/issues/nerdneilsfield/jetson-power-monitor.svg" /></a>
  <a href="https://github.com/nerdneilsfield/jetson-power-monitor"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/nerdneilsfield/jetson-power-monitor.svg" /></a>
  <a href="https://github.com/nerdneilsfield/jetson-power-monitor"><img alt="GitHub contributors" src="https://img.shields.io/github/contributors/nerdneilsfield/jetson-power-monitor.svg" /></a>
</p>

<p align="center">
        <a href=""><img alt="Build With C" src="https://img.shields.io/badge/Made%20with-C-1f425f.svg" /></a>
        <a href=""><img alt="Build With C++" src="https://img.shields.io/badge/Made%20with-C++-1f425f.svg" /></a>
        <a href=""><img alt="Build With Python" src="https://img.shields.io/badge/Made%20with-Python-1f425f.svg" /></a>
        <a href=""><img alt="Build With Rust" src="https://img.shields.io/badge/Made%20with-Rust-1f425f.svg" /></a>
<p align="center">

<p align="center">
  <a href="https://pypistats.org/packages/jetpwmon"><img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dw/jetpwmon.svg" /></a>
  <a href="https://badge.fury.io/py/jetpwmon"><img alt="PyPI version" src="https://badge.fury.io/py/jetpwmon.svg" /></a>
  <a href="https://www.python.org/"><img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/jetpwmon.svg" /></a>
  <a href="https://pypi.org/project/jetpwmon/"><img alt="PyPI - Format" src="https://img.shields.io/pypi/format/jetpwmon.svg" /></a>
  <a href="/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/nerdneilsfield/jetson-power-monitor" /></a>
  <a href="https://snyk.io/advisor/python/jetpwmon"><img alt="jetpwmon" src="https://snyk.io/advisor/python/jetpwmon/badge.svg" /></a>
  <a href="https://crates.io/crates/jetpwmon"><img src="https://img.shields.io/crates/v/jetpwmon.svg?colorB=319e8c" alt="Version info"></a><br>
  <a href="https://github.com/nerdneilsfield/jetson-power-monitor/actions?query=workflow%3A%22Build%20ARM%20wheels%22"><img alt="Python" src="https://github.com/nerdneilsfield/jetson-power-monitor/workflows/Build%20ARM%20wheels/badge.svg" /></a>
  <a href="https://github.com/nerdneilsfield/jetson-power-monitor/actions?query=workflow%3A%22Build%20Ubuntu%20Packages%22"><img alt="Deb" src="https://github.com/nerdneilsfield/jetson-power-monitor/workflows/Build%20Ubuntu%20Packages/badge.svg" /></a>
  <a href="https://github.com/nerdneilsfield/jetson-power-monitor/actions?query=workflow%3A%22Publish%20Rust%20Crate%22"><img alt="Rust" src="https://github.com/nerdneilsfield/jetson-power-monitor/workflows/Publish%20Rust%20Crate/badge.svg" /></a>
  <a href="https://github.com/nerdneilsfield/jetson-power-monitor/actions/workflows/github-code-scanning/codeql"><img alt="CodeQL" src="https://github.com/nerdneilsfield/jetson-power-monitor/actions/workflows/github-code-scanning/codeql/badge.svg?branch=master" /></a>
</p>

English | [中文](https://github.com/nerdneilsfield/jetson-power-monitor/blob/master/README_CN.md)

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

```bash
pip install jetpwmon
```

### Rust

Add to your `Cargo.toml`:

```toml
[dependencies]
jetpwmon = "0.1.2"
```

### C/C++

Download the pre-built `.deb` package from the [Releases](https://github.com/yourusername/jetson-power-monitor/releases) page:

```bash
sudo dpkg -i jetpwmon_0.1.2_amd64.deb
```

Or use CMake to find and link the library in your project:

```cmake
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.

```python
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?).")
```

<br/>

<details>
<summary><strong>Advanced: Monitor Power During a Task</strong></summary>

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.

```python
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)

```

</details>

<br/>

### Rust

First, add `jetpwmon` as a dependency in your `Cargo.toml`. Adjust the path or version as needed.

```toml
[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.

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

<br/>

<details>
<summary><strong>Advanced: Monitor Power During a Task</strong></summary>

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:

```toml
[dependencies]
jetpwmon = { version = "0.1.2" } # Adjust as needed
ndarray = "0.15"
rand = "0.8"
```

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

</details>

### C/C++

**Compilation**

1. **Include Header:** Add the following line to your C source files:

    ```c
    #include <jetpwmon/jetpwmon.h>
    // Or adjust the path based on your project structure:
    // #include "path/to/include/jetpwmon/jetpwmon.h"
    ```

2. **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`:

    ```bash
    # 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.

```c
#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, &current_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 = &current_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
}
```

<br/>

<details>
<summary><strong>Advanced: Monitor Power During a Task</strong></summary>

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` ...)

```c
#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
}
```

</details>

<br/>

### C++

**Compilation**

1. **Include Header:** Use the C++ wrapper header file in your source code:

    ```cpp
    #include <jetpwmon/jetpwmon++.hpp> // Use the C++ header
    #include <stdexcept> // For catching exceptions
    #include <iostream>  // For printing
    ```

2. **Link Library:** Compile your C++ code (ensuring C++14 or later standard is enabled) and link against the underlying `libjetpwmon` C library:

    ```bash
    # 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 using `try...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.

```cpp
#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
}
```

<br/>

<details>
<summary><strong>Advanced: Monitor Power During a Task (C++ Wrapper)</strong></summary>

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`).

```cpp
#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 S