exeperimental rocprofiler support

This commit is contained in:
Radu-Mihai Diaconu
2025-05-08 17:31:49 +03:00
parent d07af1e8d8
commit e14359afda
19 changed files with 2788 additions and 17 deletions
Generated
+1
View File
@@ -266,6 +266,7 @@ version = "0.1.0"
dependencies = [
"bindgen 0.71.1",
"log",
"once_cell",
"rocm_smi_lib",
]
+1
View File
@@ -13,6 +13,7 @@ categories = ["api-bindings", "external-ffi-bindings"]
[dependencies]
log = "0.4.27"
rocm_smi_lib = { version = "0.3.1", optional = true }
once_cell = "1.21.3"
# Your dependencies here
[build-dependencies]
+28
View File
@@ -99,6 +99,34 @@ fn main() {
needs_stddef_stdint: false,
needs_cpp: true,
},
ModuleConfig {
name: "rocprofiler".to_string(),
lib_name: "rocprofiler64".to_string(),
extra_includes: vec![
// Include the current directory where your headers are located
".".to_string(),
"include".to_string(),
],
extra_args: vec![
"-D__HIP_PLATFORM_AMD__=1".to_string(), // Ensure AMD platform is defined
],
allowlist_prefixes: vec![
"rocprofiler_".to_string(),
"ROCPROFILER_".to_string(),
"ROCPROFILER_VERSION_".to_string(),
"ROCPROFILER_FEATURE_KIND_".to_string(),
"ROCPROFILER_DATA_KIND_".to_string(),
"ROCPROFILER_MODE_".to_string(),
"ROCPROFILER_TIME_ID_".to_string(),
"ROCPROFILER_INFO_KIND_".to_string(),
"ROCPROFILER_HSA_CB_ID_".to_string(),
"HSA_EVT_".to_string(), // Allow activity.h enum
"hsa_evt_".to_string(), // Allow activity.h typedef
],
dependencies: vec!["hip".to_string()],
needs_stddef_stdint: true, // ROCProfiler header requires stddef.h and stdint.h
needs_cpp: true, // C++ support needed for HSA includes
}
];
// Sort modules by dependency order
+22
View File
@@ -0,0 +1,22 @@
#ifndef _SRC_CORE_ACTIVITY_H
#define _SRC_CORE_ACTIVITY_H
#include "rocprofiler.h"
#include <stdint.h>
// HSA EVT ID enumeration
enum hsa_evt_id_t {
HSA_EVT_ID_ALLOCATE = ROCPROFILER_HSA_CB_ID_ALLOCATE,
HSA_EVT_ID_DEVICE = ROCPROFILER_HSA_CB_ID_DEVICE,
HSA_EVT_ID_MEMCOPY = ROCPROFILER_HSA_CB_ID_MEMCOPY,
HSA_EVT_ID_SUBMIT = ROCPROFILER_HSA_CB_ID_SUBMIT,
HSA_EVT_ID_KSYMBOL = ROCPROFILER_HSA_CB_ID_KSYMBOL,
HSA_EVT_ID_CODEOBJ = ROCPROFILER_HSA_CB_ID_CODEOBJ,
HSA_EVT_ID_NUMBER
};
// HSA EVT callback data type
typedef rocprofiler_hsa_callback_data_t hsa_evt_data_t;
#endif // _SRC_CORE_ACTIVITY_H
+14
View File
@@ -0,0 +1,14 @@
#ifndef ROCPROFILER_WRAPPER_H
#define ROCPROFILER_WRAPPER_H
#ifndef __HIP_PLATFORM_AMD__
#define __HIP_PLATFORM_AMD__ 1
#endif
// Include the main ROCProfiler header
// This should bring in all the necessary HSA dependencies
#include <rocprofiler/rocprofiler.h>
#include "activity.h"
#endif // ROCPROFILER_WRAPPER_H
View File
+10 -6
View File
@@ -47,7 +47,11 @@ where
index % 8
};
let mask = 1 << bit_index;
if val { byte | mask } else { byte & !mask }
if val {
byte | mask
} else {
byte & !mask
}
}
#[inline]
pub fn set_bit(&mut self, index: usize, val: bool) {
@@ -3624,7 +3628,7 @@ unsafe extern "C" {
unsafe extern "C" {
#[doc = " @brief Sets the current memory pool of a device\n\n The memory pool must be local to the specified device.\n @p hipMallocAsync allocates from the current mempool of the provided stream's device.\n By default, a device's current memory pool is its default memory pool.\n\n @note Use @p hipMallocFromPoolAsync for asynchronous memory allocations from a device\n different than the one the stream runs on.\n\n @param [in] device Device index for the update\n @param [in] mem_pool Memory pool for update as the current on the specified device\n\n @returns #hipSuccess, #hipErrorInvalidValue, #hipErrorInvalidDevice, #hipErrorNotSupported\n\n @see hipDeviceGetDefaultMemPool, hipMallocAsync, hipMemPoolTrimTo, hipMemPoolGetAttribute,\n hipDeviceSetMemPool, hipMemPoolSetAttribute, hipMemPoolSetAccess, hipMemPoolGetAccess\n\n @warning This API is marked as Beta. While this feature is complete, it can\n change and might have outstanding issues."]
pub fn hipDeviceSetMemPool(device: ::std::os::raw::c_int, mem_pool: hipMemPool_t)
-> hipError_t;
-> hipError_t;
}
unsafe extern "C" {
#[doc = " @brief Gets the current memory pool for the specified device\n\n Returns the last pool provided to @p hipDeviceSetMemPool for this device\n or the device's default memory pool if @p hipDeviceSetMemPool has never been called.\n By default the current mempool is the default mempool for a device,\n otherwise the returned pool must have been set with @p hipDeviceSetMemPool.\n\n @param [out] mem_pool Current memory pool on the specified device\n @param [in] device Device index to query the current memory pool\n\n @returns #hipSuccess, #hipErrorInvalidValue, #hipErrorNotSupported\n\n @see hipDeviceGetDefaultMemPool, hipMallocAsync, hipMemPoolTrimTo, hipMemPoolGetAttribute,\n hipDeviceSetMemPool, hipMemPoolSetAttribute, hipMemPoolSetAccess, hipMemPoolGetAccess\n\n @warning This API is marked as Beta. While this feature is complete, it can\n change and might have outstanding issues."]
@@ -3721,7 +3725,7 @@ unsafe extern "C" {
unsafe extern "C" {
#[doc = " @brief Opens an interprocess event handle.\n\n Opens an interprocess event handle exported from another process with ::hipIpcGetEventHandle.\n The returned #hipEvent_t behaves like a locally created event with the #hipEventDisableTiming\n flag specified. This event needs be freed with ::hipEventDestroy. After the exported event\n has been freed with ::hipEventDestroy, operations on the imported event will result in\n undefined behavior. If the input handle is from the same process, it will return\n #hipErrorInvalidContext.\n\n @param[out] event Pointer to hipEvent_t to return the imported event\n @param[in] handle The opaque interprocess handle to open\n\n @returns #hipSuccess, #hipErrorInvalidValue, #hipErrorInvalidContext\n\n @note This IPC event related feature API is currently applicable on Linux.\n"]
pub fn hipIpcOpenEventHandle(event: *mut hipEvent_t, handle: hipIpcEventHandle_t)
-> hipError_t;
-> hipError_t;
}
unsafe extern "C" {
#[doc = " @}\n/\n/**\n\n @defgroup Execution Execution Control\n @{\n This section describes the execution control functions of HIP runtime API.\n\n/\n/**\n @brief Set attribute for a specific function\n\n @param [in] func Pointer of the function\n @param [in] attr Attribute to set\n @param [in] value Value to set\n\n @returns #hipSuccess, #hipErrorInvalidDeviceFunction, #hipErrorInvalidValue\n\n Note: AMD devices and some Nvidia GPUS do not support shared cache banking, and the hint is\n ignored on those architectures.\n"]
@@ -3828,7 +3832,7 @@ unsafe extern "C" {
unsafe extern "C" {
#[doc = " @brief Return flags associated with this stream.\n\n @param[in] stream stream to be queried\n @param[in,out] flags Pointer to an unsigned integer in which the stream's flags are returned\n @returns #hipSuccess, #hipErrorInvalidValue, #hipErrorInvalidHandle\n\n @returns #hipSuccess #hipErrorInvalidValue #hipErrorInvalidHandle\n\n Return flags associated with this stream in *@p flags.\n\n @see hipStreamCreateWithFlags"]
pub fn hipStreamGetFlags(stream: hipStream_t, flags: *mut ::std::os::raw::c_uint)
-> hipError_t;
-> hipError_t;
}
unsafe extern "C" {
#[doc = " @brief Query the priority of a stream.\n\n @param[in] stream stream to be queried\n @param[in,out] priority Pointer to an unsigned integer in which the stream's priority is returned\n @returns #hipSuccess, #hipErrorInvalidValue, #hipErrorInvalidHandle\n\n @returns #hipSuccess #hipErrorInvalidValue #hipErrorInvalidHandle\n\n Query the priority of a stream. The priority is returned in in priority.\n\n @see hipStreamCreateWithFlags"]
@@ -4948,7 +4952,7 @@ unsafe extern "C" {
unsafe extern "C" {
#[doc = " @brief Returns the approximate HIP api version.\n\n @param [in] ctx Context to check [Deprecated]\n @param [out] apiVersion API version to get\n\n @returns #hipSuccess\n\n @warning The HIP feature set does not correspond to an exact CUDA SDK api revision.\n This function always set *apiVersion to 4 as an approximation though HIP supports\n some features which were introduced in later CUDA SDK revisions.\n HIP apps code should not rely on the api revision number here and should\n use arch feature flags to test device capabilities or conditional compilation.\n\n @see hipCtxCreate, hipCtxDestroy, hipCtxGetDevice, hipCtxGetFlags, hipCtxPopCurrent,\n hipCtxPushCurrent, hipCtxSetCacheConfig, hipCtxSynchronize, hipCtxGetDevice\n\n @warning This API is deprecated on the AMD platform, only for equivalent cuCtx driver API on the\n NVIDIA platform."]
pub fn hipCtxGetApiVersion(ctx: hipCtx_t, apiVersion: *mut ::std::os::raw::c_int)
-> hipError_t;
-> hipError_t;
}
unsafe extern "C" {
#[doc = " @brief Get Cache configuration for a specific function [Deprecated]\n\n @param [out] cacheConfig Cache configuration\n\n @returns #hipSuccess\n\n @warning AMD devices and some Nvidia GPUS do not support reconfigurable cache. This hint is\n ignored on those architectures.\n\n @see hipCtxCreate, hipCtxDestroy, hipCtxGetFlags, hipCtxPopCurrent, hipCtxGetCurrent,\n hipCtxSetCurrent, hipCtxPushCurrent, hipCtxSetCacheConfig, hipCtxSynchronize, hipCtxGetDevice\n\n @warning This API is deprecated on the AMD platform, only for equivalent cuCtx driver API on the\n NVIDIA platform."]
@@ -6926,7 +6930,7 @@ unsafe extern "C" {
}
unsafe extern "C" {
pub fn hipStreamBeginCapture_spt(stream: hipStream_t, mode: hipStreamCaptureMode)
-> hipError_t;
-> hipError_t;
}
unsafe extern "C" {
pub fn hipStreamEndCapture_spt(stream: hipStream_t, pGraph: *mut hipGraph_t) -> hipError_t;
+7
View File
@@ -128,6 +128,12 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "prettyplease"
version = "0.2.32"
@@ -191,6 +197,7 @@ version = "0.1.0"
dependencies = [
"bindgen",
"log",
"once_cell",
]
[[package]]
+5 -5
View File
@@ -1,14 +1,14 @@
// src/hip/mod.rs
// Private modules
mod device;
mod error;
pub(crate) mod device;
pub(crate) mod error;
mod event;
pub mod kernel;
mod memory;
pub mod memory;
pub mod module;
mod stream;
mod utils;
pub(crate) mod stream;
pub mod utils;
// We need to make this public for the rest of the crate
// but don't necessarily want to expose it to users
+1
View File
@@ -7,6 +7,7 @@ use crate::hip::ffi;
use std::ptr;
/// Safe wrapper for HIP streams
#[derive(Clone, Debug)]
pub struct Stream {
stream: hip::ffi::hipStream_t,
}
+378 -3
View File
@@ -12,14 +12,389 @@ use crate::rocfft::examples;
#[cfg(feature = "rocm_smi")]
pub mod rocmsmi;
pub mod rocsparse;
mod rocprofiler;
#[cfg(test)]
mod tests {
use crate::rocprofiler::types::{Feature, InfoData, Parameter, ParameterName, ProfilerMode};
use crate::hip::{device_synchronize, get_device_count, Device, DeviceMemory};
use crate::rocfft::examples::run_1d_complex_example;
use crate::rocprofiler::context::Properties;
use crate::rocprofiler::profiler::{get_metrics, version_string, Profiler};
use crate::rocrand::utils::{generate_normal_f32, generate_uniform_f64};
#[test]
fn test_rocrand() {
run_1d_complex_example().unwrap();
run_1d_complex_example().unwrap();
fn test_rocprofiler() {
// Import necessary items for panic handling
use std::panic::{self, AssertUnwindSafe};
println!("Starting ROCProfiler test");
// First check if this system supports all required features
let rocm_available = panic::catch_unwind(|| {
// A simple check to verify ROCm functionality is available
match get_device_count() {
Ok(_) => true,
Err(e) => {
println!("ROCm functionality appears unavailable: {:?}", e);
println!("Test will be skipped.");
false
}
}
}).unwrap_or_else(|_| {
println!("Panic while checking ROCm availability - test will be skipped.");
false
});
if !rocm_available {
println!("Skipping ROCProfiler test due to missing ROCm functionality.");
return;
}
// Start the main test
let result = panic::catch_unwind(AssertUnwindSafe(|| {
// Try to get device count with proper error handling
let device_count = get_device_count().unwrap_or_else(|e| {
println!("Warning: Could not get device count: {:?}", e);
println!("Test will try to continue with default device if available");
0
});
if device_count == 0 {
println!("No GPU devices found. Trying to proceed with default device.");
}
// Get the first device with better error handling
let device = match Device::new(0) {
Ok(dev) => dev,
Err(e) => {
println!("Warning: Could not get device 0: {:?}", e);
println!("Falling back to current device");
match Device::current() {
Ok(current_dev) => current_dev,
Err(e2) => {
println!("Error: Could not get current device: {:?}", e2);
println!("Test cannot proceed without a valid device");
return false; // Cannot proceed
}
}
}
};
// Safely access device properties
let device_name = match device.properties() {
Ok(props) => props.name,
Err(e) => {
println!("Warning: Could not get device properties: {:?}", e);
"Unknown device".to_string()
}
};
println!("Using device: {}", device_name);
// Get ROCProfiler version (if available)
println!("ROCProfiler version: {}", version_string());
// Try creating some GPU work, wrapped in a separate catch_unwind
let mem_result = panic::catch_unwind(AssertUnwindSafe(|| {
println!("Creating GPU workload...");
// Allocate device memory with proper error handling
let size = 100 * 1024 * 1024;
let mut d_memory = match DeviceMemory::<u8>::new(size) {
Ok(mem) => mem,
Err(e) => {
println!("Warning: Failed to allocate {} MB: {:?}", size / (1024 * 1024), e);
println!("Reducing memory size and trying again");
// Try with smaller size
let smaller_size = 10 * 1024 * 1024;
match DeviceMemory::<u8>::new(smaller_size) {
Ok(mem) => {
println!("Successfully allocated {} MB", smaller_size / (1024 * 1024));
mem
},
Err(e2) => {
println!("Error: Still failed to allocate device memory: {:?}", e2);
return None; // Memory allocation failed
}
}
}
};
// Set memory to 1 to ensure it's used
if let Err(e) = d_memory.memset(1) {
println!("Warning: Failed to set memory: {:?}", e);
println!("Continuing test, but profiling results may be affected");
}
// Ensure the operation is complete
if let Err(e) = device_synchronize() {
println!("Warning: Failed to synchronize device: {:?}", e);
println!("Continuing test, but profiling results may be affected");
}
Some(d_memory) // Return the memory object for later use
}));
// Check if memory operations were successful
let mut d_memory = match mem_result {
Ok(Some(mem)) => mem,
Ok(None) => {
println!("Test cannot proceed without device memory");
return false; // Cannot proceed
},
Err(_) => {
println!("Panic occurred during memory operations");
println!("Test cannot proceed");
return false; // Cannot proceed
}
};
// List available metrics with proper error handling
println!("\nQuerying available metrics...");
let metrics = get_metrics(Some(&device)).unwrap_or_else(|e| {
println!("Warning: Failed to get metrics: {:?}", e);
println!("Continuing test with default metrics");
Vec::new()
});
let mut metric_names = Vec::new();
for metric in &metrics {
if let crate::rocprofiler::types::InfoData::Metric(info) = metric {
metric_names.push(info.name.clone());
// Only show the first 10 metrics to avoid cluttering the output
if metric_names.len() <= 10 {
println!(" {}", info.name);
if let Some(desc) = &info.description {
println!(" Description: {}", desc);
}
}
}
}
if !metric_names.is_empty() {
if metric_names.len() > 10 {
println!(" ... and {} more", metric_names.len() - 10);
}
} else {
println!(" No metrics found or available");
}
// Select some metrics to profile
let mut features = Vec::new();
// Add common performance metrics
let common_metrics = [
"GRBM_COUNT", // Basic counter activity
"GRBM_GUI_ACTIVE", // Graphics engine activity
"SQ_WAVES", // Number of waves in flight
"SQ_INSTS_VALU", // Vector ALU instructions
"SQ_INSTS_SALU", // Scalar ALU instructions
"SQ_INSTS_SMEM", // Scalar memory instructions
"SQ_INSTS_VMEM", // Vector memory instructions
"SQ_WAIT_INST_LDS", // Cycles waiting for LDS instructions
"SQ_BUSY", // SQ is busy
"VALUInsts", // Vector ALU instructions issued
"SALUInsts", // Scalar ALU instructions issued
"LDSInsts", // LDS instructions issued
"MemUnitBusy", // Memory unit is busy
"GDSInsts", // GDS instructions issued
"FetchSize", // Fetch size
"CacheHit", // Cache hit rate
];
// Only add metrics that are available on this GPU
for &metric_name in &common_metrics {
if metric_names.contains(&metric_name.to_string()) {
println!("Adding metric: {}", metric_name);
let params = vec![Parameter::new(ParameterName::SeMask, 0xffffffff)];
match panic::catch_unwind(AssertUnwindSafe(|| {
features.push(Feature::new_metric(metric_name, params));
})) {
Ok(_) => println!("Successfully added metric: {}", metric_name),
Err(_) => println!("Warning: Failed to add metric: {}", metric_name)
}
}
}
if features.is_empty() {
println!("No common metrics were found available");
// If no common metrics were found, add the first available metric
if !metric_names.is_empty() {
println!("Trying first available metric: {}", metric_names[0]);
match panic::catch_unwind(AssertUnwindSafe(|| {
features.push(Feature::new_metric(&metric_names[0], vec![
Parameter::new(ParameterName::SeMask, 0xffffffff),
]));
})) {
Ok(_) => println!("Successfully added metric: {}", metric_names[0]),
Err(_) => println!("Warning: Failed to add metric: {}", metric_names[0])
}
}
}
if features.is_empty() {
println!("No metrics could be added. Test cannot proceed with profiling.");
// We'll continue to test the API without metrics
println!("Will attempt to initialize the profiler without metrics to test the API.");
// Create an empty feature list just to test the profiler creation
match panic::catch_unwind(AssertUnwindSafe(|| {
features.push(Feature::new_metric("GRBM_COUNT", vec![
Parameter::new(ParameterName::SeMask, 0xffffffff),
]));
})) {
Ok(_) => println!("Added placeholder metric for testing"),
Err(_) => {
println!("Failed to create even a placeholder metric");
println!("Test cannot proceed further");
return false;
}
}
}
// Create profiler with proper error handling
println!("\nCreating profiler...");
// Create properties
let properties = match panic::catch_unwind(AssertUnwindSafe(Properties::new)) {
Ok(props) => props,
Err(_) => {
println!("Panic occurred while creating Properties");
println!("Test cannot proceed");
return false;
}
};
let profiler_result = panic::catch_unwind(AssertUnwindSafe(|| Profiler::new(
device,
features,
&[ProfilerMode::Standalone, ProfilerMode::SingleGroup],
Some(properties)
)));
let mut profiler = match profiler_result {
Ok(Ok(p)) => p,
Ok(Err(e)) => {
println!("Error: Failed to create profiler: {:?}", e);
println!("Test cannot proceed without a profiler");
return false;
},
Err(_) => {
println!("Panic occurred while creating the profiler");
println!("Test cannot proceed");
return false;
}
};
// Run the profiling session
println!("\nStarting profiling...");
// Run the workload again while profiling
let workload_result = panic::catch_unwind(AssertUnwindSafe(|| {
// Set memory to 2 to ensure it's used during profiling
if let Err(e) = d_memory.memset(2) {
println!("Warning: Failed to set memory during profiling: {:?}", e);
println!("Continuing test, but profiling results may be affected");
}
// Synchronize to ensure the operation completes
if let Err(e) = device_synchronize() {
println!("Warning: Failed to synchronize device during profiling: {:?}", e);
println!("Continuing test, but profiling results may be affected");
}
true // Workload succeeded
}));
if workload_result.is_err() {
println!("Warning: Panic occurred during workload execution");
println!("Continuing with profiling, but results may be affected");
}
// Collect profiling data with proper error handling
println!("Collecting profiling data...");
let profile_result = panic::catch_unwind(AssertUnwindSafe(|| profiler.profile_all()));
match profile_result {
Ok(Ok(_)) => println!("Profiling completed successfully."),
Ok(Err(e)) => {
println!("Error: Failed to collect profiling data: {:?}", e);
println!("This could be due to incompatible metrics or hardware limitations");
println!("Test will continue but results may be incomplete");
},
Err(_) => {
println!("Panic occurred during profiling");
println!("This could indicate a serious issue with ROCProfiler");
println!("Will attempt to continue to examine any partial results");
}
}
// Display results
println!("\nResults:");
let features_result = panic::catch_unwind(AssertUnwindSafe(|| profiler.features()));
let features = match features_result {
Ok(f) => f,
Err(_) => {
println!("Panic occurred while accessing features");
println!("Cannot display results");
return true; // Continue test
}
};
if features.is_empty() {
println!(" No features were profiled");
} else {
for (i, feature) in features.iter().enumerate() {
println!("Metric {}: {}", i, feature.name());
let data_result = panic::catch_unwind(AssertUnwindSafe(|| feature.data()));
match data_result {
Ok(Some(data)) => {
match data {
crate::rocprofiler::types::Data::Uninit => {
println!(" Data: Uninitialized (metric may not be supported on this device)");
},
crate::rocprofiler::types::Data::Int32(val) => {
println!(" Value: {}", val);
},
crate::rocprofiler::types::Data::Int64(val) => {
println!(" Value: {}", val);
},
crate::rocprofiler::types::Data::Float(val) => {
println!(" Value: {:.2}", val);
},
crate::rocprofiler::types::Data::Double(val) => {
println!(" Value: {:.2}", val);
},
crate::rocprofiler::types::Data::Bytes(bytes, instances) => {
println!(" Data: {} bytes, {} instances", bytes.len(), instances);
// Print first few bytes for debugging if small enough
if bytes.len() <= 64 {
println!(" Bytes: {:?}", bytes);
}
},
}
},
Ok(None) => println!(" No data available (profiling may have failed)"),
Err(_) => println!(" Error: Panic occurred while accessing feature data")
}
}
}
true // Test succeeded
}));
match result {
Ok(true) => println!("\nROCProfiler test completed successfully!"),
Ok(false) => println!("\nROCProfiler test did not complete due to test preconditions not being met."),
Err(_) => println!("\nROCProfiler test failed due to an unhandled panic.")
}
}
}
+2 -2
View File
@@ -2487,7 +2487,7 @@ unsafe extern "C" {
unsafe extern "C" {
#[doc = " @brief Destroys a CTC loss function descriptor object\n\n @param ctcLossDesc CTC loss function descriptor type (input)\n @return miopenStatus_t"]
pub fn miopenDestroyCTCLossDescriptor(ctcLossDesc: miopenCTCLossDescriptor_t)
-> miopenStatus_t;
-> miopenStatus_t;
}
unsafe extern "C" {
#[doc = " @brief Set the details of a CTC loss function descriptor\n\n @param ctcLossDesc CTC loss function descriptor type (input)\n @param dataType Data type used in this CTC loss operation, only fp32 currently\n supported (input)\n @param blank_label_id User defined index for blank label, default 0 (input)\n @param apply_softmax_layer Boolean to toggle input layer property (input)\n @return miopenStatus_t"]
@@ -2543,7 +2543,7 @@ unsafe extern "C" {
unsafe extern "C" {
#[doc = " @brief Destroys the dropout descriptor object\n\n @param dropoutDesc Dropout descriptor type (input)\n @return miopenStatus_t"]
pub fn miopenDestroyDropoutDescriptor(dropoutDesc: miopenDropoutDescriptor_t)
-> miopenStatus_t;
-> miopenStatus_t;
}
unsafe extern "C" {
#[doc = " @brief Query the amount of memory required to run dropout\n\n This function calculates the amount of memory required to run dropout.\n @param xDesc Tensor descriptor for data tensor x (input)\n @param reserveSpaceSizeInBytes Number of bytes of reservespace required for executing dropout\n (Output)\n @return miopenStatus_t"]
+1 -1
View File
@@ -155,7 +155,7 @@ unsafe extern "C" {
unsafe extern "C" {
#[doc = " @brief Get library version string\n\n @param[in, out] buf buffer that receives the version string\n @param[in] len length of buf, minimum 30 characters"]
pub fn rocfft_get_version_string(buf: *mut ::std::os::raw::c_char, len: usize)
-> rocfft_status;
-> rocfft_status;
}
unsafe extern "C" {
#[doc = " @brief Set the communication library for distributed transforms.\n\n @details Set the multi-processing communication library for a plan.\n\n Multi-processing communication libraries require library-specific\n handle to also be specified. For MPI libraries, this is a\n pointer to an MPI communicator.\n\n @param[in] description description handle\n @param[in] comm_type communicator type\n @param[in] comm_handle handle to communication-library-specific state\n"]
+322
View File
@@ -0,0 +1,322 @@
// src/rocprofiler/context.rs
use std::ffi::c_void;
use std::marker::PhantomData;
use std::sync::Arc;
use crate::hip::device::Device;
use crate::hip::stream::Stream; // Your existing Stream type
use crate::rocprofiler::bindings;
use crate::rocprofiler::error::{Error, Result};
use crate::rocprofiler::types::{Feature, Group, ProfilerMode};
/// Handler type for ROCProfiler completion events
pub type Handler = Arc<dyn Fn(Group) -> bool + Send + Sync>;
/// Represents a ROCProfiler context for performance profiling
pub struct Context {
context: *mut bindings::rocprofiler_t,
device_id: i32,
features: Vec<Feature>,
owned: bool,
}
// Can't be automatically derived since we have a raw pointer
unsafe impl Send for Context {}
unsafe impl Sync for Context {}
impl Context {
/// Create a new profiling context
pub fn new(
device: Device,
features: Vec<Feature>,
modes: &[ProfilerMode],
queue: Option<&Stream>,
queue_depth: Option<u32>,
handler: Option<Handler>,
) -> Result<Self> {
let mode = ProfilerMode::combine(modes);
// Get the device ID for later reference
let device_id = device.id();
// Convert device to HSA agent handle for ROCProfiler
let agent_handle = bindings::hsa_agent_t {
handle: device_id as u64,
};
// Convert features to native format
let mut feature_handles = Vec::new();
let mut string_storage = Vec::new();
let mut param_storage = Vec::new();
for feature in &features {
let (native_feature, strings, params) = feature.to_native()?;
feature_handles.push(native_feature);
string_storage.push(strings);
param_storage.push(params);
}
// Prepare properties
let mut properties = bindings::rocprofiler_properties_t {
queue: if let Some(q) = queue {
q.as_raw() as *mut crate::rocprofiler::bindings::hsa_queue_t
} else {
std::ptr::null_mut()
},
queue_depth: queue_depth.unwrap_or(0),
handler: None,
handler_arg: std::ptr::null_mut(),
};
// TODO: If handler is provided, need to set up a trampoline function
// This is complex because we need to store the handler somewhere and
// make it accessible to the C callback
// Create the context
let mut context = std::ptr::null_mut();
let status = unsafe {
bindings::rocprofiler_open(
agent_handle,
if feature_handles.is_empty() {
std::ptr::null_mut()
} else {
feature_handles.as_mut_ptr()
},
feature_handles.len() as u32,
&mut context,
mode,
&mut properties,
)
};
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
return Err(Error::new(status));
}
// Return the context
Ok(Self {
context,
device_id,
features,
owned: true,
})
}
/// Create a context from an existing raw context pointer
pub unsafe fn from_raw(
context: *mut bindings::rocprofiler_t,
device_id: i32,
features: Vec<Feature>,
owned: bool,
) -> Self {
Self {
context,
device_id,
features,
owned,
}
}
/// Get the raw context pointer
pub fn as_raw(&self) -> *mut bindings::rocprofiler_t {
self.context
}
/// Get the device ID associated with this context
pub fn device_id(&self) -> i32 {
self.device_id
}
/// Get the device associated with this context
pub fn device(&self) -> crate::hip::error::Result<Device> {
Device::new(self.device_id)
}
/// Get the features associated with this context
pub fn features(&self) -> &[Feature] {
&self.features
}
/// Reset the context before reusing
pub fn reset(&self, group_index: u32) -> Result<()> {
let status = unsafe { bindings::rocprofiler_reset(self.context, group_index) };
Error::from_rocprofiler_error(status)
}
/// Start profiling
pub fn start(&self, group_index: u32) -> Result<()> {
let status = unsafe { bindings::rocprofiler_start(self.context, group_index) };
Error::from_rocprofiler_error(status)
}
/// Stop profiling
pub fn stop(&self, group_index: u32) -> Result<()> {
let status = unsafe { bindings::rocprofiler_stop(self.context, group_index) };
Error::from_rocprofiler_error(status)
}
/// Read profiling data
pub fn read(&self, group_index: u32) -> Result<()> {
let status = unsafe { bindings::rocprofiler_read(self.context, group_index) };
Error::from_rocprofiler_error(status)
}
/// Get profiling data
pub fn get_data(&self, group_index: u32) -> Result<()> {
let status = unsafe { bindings::rocprofiler_get_data(self.context, group_index) };
Error::from_rocprofiler_error(status)
}
/// Get the number of profiling groups
pub fn group_count(&self) -> Result<u32> {
let mut count = 0;
let status = unsafe { bindings::rocprofiler_group_count(self.context, &mut count) };
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
return Err(Error::new(status));
}
Ok(count)
}
/// Get a profiling group by index
pub fn get_group(&self, group_index: u32) -> Result<Group> {
let mut group = unsafe { std::mem::zeroed::<bindings::rocprofiler_group_t>() };
let status = unsafe {
bindings::rocprofiler_get_group(self.context, group_index, &mut group)
};
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
return Err(Error::new(status));
}
Ok(Group::from_native(group))
}
/// Get metrics data
pub fn get_metrics(&self) -> Result<()> {
let status = unsafe { bindings::rocprofiler_get_metrics(self.context) };
Error::from_rocprofiler_error(status)
}
/// Collect profiling data for all features
pub fn collect_data(&mut self) -> Result<()> {
// Get the number of groups
let group_count = self.group_count()?;
// For each group, get data
for i in 0..group_count {
self.get_data(i)?;
}
// Get metrics data
self.get_metrics()?;
// TODO: Update feature data from the native features
// This requires accessing the features inside the context
Ok(())
}
/// Iterate trace data with a callback
pub unsafe fn iterate_trace_data<F>(&self, mut callback: F) -> Result<()>
where
F: FnMut(bindings::hsa_ven_amd_aqlprofile_info_type_t, &bindings::hsa_ven_amd_aqlprofile_info_data_t) -> Result<()>,
{
unsafe {
// Create a wrapper for the callback
extern "C" fn callback_wrapper(
info_type: bindings::hsa_ven_amd_aqlprofile_info_type_t,
info_data: *mut bindings::hsa_ven_amd_aqlprofile_info_data_t,
callback_data: *mut c_void,
) -> bindings::hsa_status_t {
let callback_ptr = callback_data as *mut &mut dyn FnMut(
bindings::hsa_ven_amd_aqlprofile_info_type_t,
&bindings::hsa_ven_amd_aqlprofile_info_data_t,
) -> Result<()>;
unsafe {
match (*callback_ptr)(info_type, &*info_data) {
Ok(()) => bindings::hsa_status_t_HSA_STATUS_SUCCESS,
Err(e) => e.code(),
}
}
}
let mut callback_wrapper_data = &mut callback as &mut dyn FnMut(
bindings::hsa_ven_amd_aqlprofile_info_type_t,
&bindings::hsa_ven_amd_aqlprofile_info_data_t,
) -> Result<()>;
let status = bindings::rocprofiler_iterate_trace_data(
self.context,
Some(callback_wrapper),
&mut callback_wrapper_data as *mut _ as *mut c_void,
);
Error::from_rocprofiler_error(status)
}
}
}
impl Drop for Context {
fn drop(&mut self) {
if self.owned && !self.context.is_null() {
unsafe {
let _ = bindings::rocprofiler_close(self.context);
}
self.context = std::ptr::null_mut();
}
}
}
/// Properties for profiling context creation
#[derive(Clone)]
pub struct Properties {
/// Queue for STANDALONE mode
pub queue: Option<Stream>,
/// Created queue depth
pub queue_depth: u32,
/// Handler on completion
pub handler: Option<Handler>,
}
impl Properties {
/// Create new default properties
pub fn new() -> Self {
Self {
queue: None,
queue_depth: 0,
handler: None,
}
}
/// Set queue
pub fn with_queue(mut self, queue: Stream) -> Self {
self.queue = Some(queue);
self
}
/// Set queue depth
pub fn with_queue_depth(mut self, depth: u32) -> Self {
self.queue_depth = depth;
self
}
/// Set completion handler
pub fn with_handler<F>(mut self, handler: F) -> Self
where
F: Fn(Group) -> bool + Send + Sync + 'static,
{
self.handler = Some(Arc::new(handler));
self
}
}
impl Default for Properties {
fn default() -> Self {
Self::new()
}
}
+119
View File
@@ -0,0 +1,119 @@
// src/rocprofiler/error.rs
use std::fmt;
use std::error::Error as StdError;
use crate::rocprofiler::bindings;
/// Error type for ROCProfiler operations
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Error {
pub(crate) code: bindings::hsa_status_t,
}
/// Result type for ROCProfiler operations
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
/// Create a new error from a ROCProfiler error code
pub fn new(code: bindings::hsa_status_t) -> Self {
Self { code }
}
/// Get the raw error code
pub fn code(&self) -> bindings::hsa_status_t {
self.code
}
/// Returns true if the error code represents success
pub fn is_success(&self) -> bool {
self.code == bindings::hsa_status_t_HSA_STATUS_SUCCESS
}
/// Convert a ROCProfiler error code to a Result
pub fn from_rocprofiler_error<T>(error: bindings::hsa_status_t) -> Result<T>
where
T: Default,
{
if error == bindings::hsa_status_t_HSA_STATUS_SUCCESS {
Ok(T::default())
} else {
Err(Error::new(error))
}
}
/// Convert a ROCProfiler error code to a Result with a specific value
pub fn from_rocprofiler_error_with_value<T>(
error: bindings::hsa_status_t,
value: T,
) -> Result<T> {
if error == bindings::hsa_status_t_HSA_STATUS_SUCCESS {
Ok(value)
} else {
Err(Error::new(error))
}
}
/// Get a human-readable error description
pub fn description(&self) -> &'static str {
match self.code {
bindings::hsa_status_t_HSA_STATUS_SUCCESS => "Success",
bindings::hsa_status_t_HSA_STATUS_INFO_BREAK => "Break was requested in callback",
bindings::hsa_status_t_HSA_STATUS_ERROR => "Generic error",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_ARGUMENT => "Invalid argument",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_QUEUE_CREATION => "Invalid queue creation",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_ALLOCATION => "Invalid allocation",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_AGENT => "Invalid agent",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_REGION => "Invalid region",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_SIGNAL => "Invalid signal",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_QUEUE => "Invalid queue",
bindings::hsa_status_t_HSA_STATUS_ERROR_OUT_OF_RESOURCES => "Out of resources",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_PACKET_FORMAT => "Invalid packet format",
bindings::hsa_status_t_HSA_STATUS_ERROR_RESOURCE_FREE => "Error freeing resources",
bindings::hsa_status_t_HSA_STATUS_ERROR_NOT_INITIALIZED => "Not initialized",
bindings::hsa_status_t_HSA_STATUS_ERROR_REFCOUNT_OVERFLOW => "Reference count overflow",
bindings::hsa_status_t_HSA_STATUS_ERROR_INCOMPATIBLE_ARGUMENTS => "Incompatible arguments",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_INDEX => "Invalid index",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_ISA => "Invalid ISA",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_ISA_NAME => "Invalid ISA name",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_CODE_OBJECT => "Invalid code object",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_EXECUTABLE => "Invalid executable",
bindings::hsa_status_t_HSA_STATUS_ERROR_FROZEN_EXECUTABLE => "Executable is frozen",
bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_SYMBOL_NAME => "Invalid symbol name",
bindings::hsa_status_t_HSA_STATUS_ERROR_VARIABLE_ALREADY_DEFINED => "Variable already defined",
bindings::hsa_status_t_HSA_STATUS_ERROR_VARIABLE_UNDEFINED => "Variable undefined",
bindings::hsa_status_t_HSA_STATUS_ERROR_EXCEPTION => "HSAIL operation resulted in exception",
_ => "Unknown error",
}
}
/// Get an error string from the ROCProfiler library
pub fn error_string() -> Result<String> {
let mut str_ptr: *const ::std::os::raw::c_char = std::ptr::null();
let status = unsafe { bindings::rocprofiler_error_string(&mut str_ptr) };
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
return Err(Error::new(status));
}
if str_ptr.is_null() {
return Ok("No error string available".to_string());
}
let c_str = unsafe { std::ffi::CStr::from_ptr(str_ptr) };
Ok(c_str.to_string_lossy().into_owned())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ROCProfiler error {}: {}",
self.code,
self.description()
)
}
}
impl StdError for Error {}
@@ -0,0 +1,196 @@
// examples/rocprofiler_example.rs
//
// A simple example showing how to use the ROCProfiler API
use rocm_rs::hip::{Device, get_device_count};
use rocm_rs::rocprofiler::{
Profiler, Feature, Parameter, ParameterName, InfoKind, ProfilerMode, Properties,
get_metrics, get_metric_count, version_string, error_string
};
fn main() -> rocm_rs::rocprofiler::Result<()> {
// Print ROCProfiler version
println!("ROCProfiler version: {}", version_string());
// Get the number of available devices
let device_count = get_device_count().expect("Failed to get device count");
if device_count == 0 {
println!("No GPU devices found.");
return Ok(());
}
// Get the first device
let device = Device::new(0).expect("Failed to get device");
println!("Using device: {}", device.properties()?.name);
// Get the number of metrics
let metric_count = get_metric_count(Some(&device))?;
println!("Number of available metrics: {}", metric_count);
// List available metrics
let metrics = get_metrics(Some(&device))?;
println!("\nAvailable metrics:");
for metric in metrics {
match metric {
rocm_rs::rocprofiler::InfoData::Metric(info) => {
println!(" {}: instances={}, block={:?}",
info.name,
info.instances,
info.block_name.unwrap_or_default()
);
if let Some(desc) = info.description {
println!(" Description: {}", desc);
}
if let Some(expr) = info.expr {
println!(" Expression: {}", expr);
}
},
_ => {}
}
}
// Create a set of features to profile
let mut features = Vec::new();
// Add a simple metric
features.push(Feature::new_metric("GRBM_COUNT", vec![
Parameter::new(ParameterName::SeMask, 0xffffffff),
]));
// Add a counter
features.push(Feature::new_counter("GRBM", 0x1, vec![
Parameter::new(ParameterName::SeMask, 0xffffffff),
]));
// Create profiler
let properties = Properties::new()
.with_queue_depth(16)
.with_handler(|group| {
println!("Group {} completed", group.index());
true
});
let mut profiler = Profiler::new(
device,
features,
&[ProfilerMode::Standalone, ProfilerMode::SingleGroup],
Some(properties)
)?;
// Run the profiling session
println!("\nStarting profiling...");
profiler.profile_all()?;
println!("Profiling completed.");
// Display results
// examples/rocprofiler_example.rs (continued)
// Display results
println!("\nResults:");
for (i, feature) in profiler.features().iter().enumerate() {
println!("Feature {}: {}", i, feature.name());
if let Some(data) = feature.data() {
match data {
rocm_rs::rocprofiler::Data::Uninit => {
println!(" Data: Uninitialized");
},
rocm_rs::rocprofiler::Data::Int32(val) => {
println!(" Value (Int32): {}", val);
},
rocm_rs::rocprofiler::Data::Int64(val) => {
println!(" Value (Int64): {}", val);
},
rocm_rs::rocprofiler::Data::Float(val) => {
println!(" Value (Float): {}", val);
},
rocm_rs::rocprofiler::Data::Double(val) => {
println!(" Value (Double): {}", val);
},
rocm_rs::rocprofiler::Data::Bytes(bytes, instances) => {
println!(" Data: {} bytes, {} instances", bytes.len(), instances);
// If the data is small enough, print it
if bytes.len() <= 64 {
println!(" Bytes: {:?}", bytes);
}
},
}
} else {
println!(" No data available");
}
}
// Demonstrate using queue callbacks
println!("\nSetting up queue callbacks...");
let callbacks = rocm_rs::rocprofiler::QueueCallbacks::new()
.with_dispatch(|callback_data, group| {
// Extract kernel name if available
let kernel_name = if !callback_data.kernel_name.is_null() {
let c_str = unsafe { std::ffi::CStr::from_ptr(callback_data.kernel_name) };
Some(c_str.to_string_lossy().into_owned())
} else {
None
};
if let Some(name) = kernel_name {
println!("Kernel dispatch: {}", name);
} else {
println!("Non-kernel dispatch");
}
Ok(())
})
.with_create(|queue| {
println!("Queue created: {:?}", queue);
Ok(())
})
.with_destroy(|queue| {
println!("Queue destroyed: {:?}", queue);
Ok(())
});
// Set the callbacks
rocm_rs::rocprofiler::set_queue_callbacks(callbacks)?;
// Start the callbacks
rocm_rs::rocprofiler::start_queue_callbacks()?;
println!("Queue callbacks are active. To see them in action, create and use a queue.");
println!("Press Enter to continue...");
let mut input = String::new();
std::io::stdin().read_line(&mut input).expect("Failed to read line");
// Stop the callbacks
rocm_rs::rocprofiler::stop_queue_callbacks()?;
// Remove the callbacks
rocm_rs::rocprofiler::remove_queue_callbacks()?;
// Show how to use a profiled queue
println!("\nCreating a profiled queue...");
let profiled_queue = rocm_rs::rocprofiler::create_profiled_queue(
device,
256, // queue size
0, // private segment size
0, // group segment size
)?;
println!("Profiled queue created: {:?}", profiled_queue);
// Drop the queue
drop(profiled_queue);
// Example of how to handle HSA events
println!("\nExample of handling HSA events:");
println!("HsaEvtId::Allocate = {:?}", rocm_rs::rocprofiler::HsaEvtId::Allocate);
println!("HsaEvtId::Device = {:?}", rocm_rs::rocprofiler::HsaEvtId::Device);
println!("HsaEvtId::Memcopy = {:?}", rocm_rs::rocprofiler::HsaEvtId::Memcopy);
println!("HsaEvtId::Submit = {:?}", rocm_rs::rocprofiler::HsaEvtId::Submit);
println!("HsaEvtId::Ksymbol = {:?}", rocm_rs::rocprofiler::HsaEvtId::Ksymbol);
println!("HsaEvtId::Codeobj = {:?}", rocm_rs::rocprofiler::HsaEvtId::Codeobj);
println!("\nProfiling completed successfully!");
Ok(())
}
+5
View File
@@ -0,0 +1,5 @@
pub mod bindings;
pub mod error;
pub mod types;
pub mod context;
pub mod profiler;
+526
View File
@@ -0,0 +1,526 @@
// src/rocprofiler/profiler.rs
use std::ffi::{CStr, CString};
use std::ptr;
use std::sync::Arc;
use std::sync::RwLock;
use once_cell::sync::Lazy;
use crate::hip::device::Device;
use crate::hip::stream::Stream;
use crate::rocprofiler::bindings;
use crate::rocprofiler::context::{Context, Properties};
use crate::rocprofiler::error::{Error, Result};
use crate::rocprofiler::types::{Feature, InfoData, InfoKind, ProfilerMode};
/// Main ROCProfiler interface for performance profiling
pub struct Profiler {
context: Context,
}
impl Profiler {
/// Create a new profiler for the specified device with the given features
pub fn new(
device: Device,
features: Vec<Feature>,
modes: &[ProfilerMode],
properties: Option<Properties>,
) -> Result<Self> {
// Create the context
let context = if let Some(props) = properties {
Context::new(
device,
features,
modes,
props.queue.as_ref(),
Some(props.queue_depth),
props.handler,
)?
} else {
Context::new(device, features, modes, None, None, None)?
};
Ok(Self { context })
}
/// Get the underlying context
pub fn context(&self) -> &Context {
&self.context
}
/// Get the features being profiled
pub fn features(&self) -> &[Feature] {
self.context.features()
}
/// Start profiling
pub fn start(&self, group_index: u32) -> Result<()> {
self.context.start(group_index)
}
/// Stop profiling
pub fn stop(&self, group_index: u32) -> Result<()> {
self.context.stop(group_index)
}
/// Read profiling data
pub fn read(&self, group_index: u32) -> Result<()> {
self.context.read(group_index)
}
/// Get profiling data
pub fn get_data(&self, group_index: u32) -> Result<()> {
self.context.get_data(group_index)
}
/// Collect profiling data for all features
pub fn collect_data(&mut self) -> Result<()> {
self.context.collect_data()
}
/// Run a complete profiling session for a single group
pub fn profile_group(&mut self, group_index: u32) -> Result<()> {
// Start profiling
self.start(group_index)?;
// Stop profiling
self.stop(group_index)?;
// Read profiling data
self.read(group_index)?;
// Get profiling data
self.get_data(group_index)?;
Ok(())
}
/// Run a complete profiling session for all groups
pub fn profile_all(&mut self) -> Result<()> {
// Get the number of groups
let group_count = self.context.group_count()?;
// Profile each group
for i in 0..group_count {
self.profile_group(i)?;
}
// Collect all data
self.collect_data()?;
Ok(())
}
}
/// Create a profiled queue for ROCProfiler
pub fn create_profiled_queue(
device: Device,
size: u32,
private_segment_size: u32,
group_segment_size: u32,
) -> Result<Stream> {
let mut queue_ptr = ptr::null_mut();
// Create an HSA agent from the device ID for ROCProfiler
let agent_handle = bindings::hsa_agent_t {
handle: device.id() as u64
};
let status = unsafe {
bindings::rocprofiler_queue_create_profiled(
agent_handle,
size,
0, // Queue type (default)
None, // Callback
ptr::null_mut(), // Callback data
private_segment_size,
group_segment_size,
&mut queue_ptr,
)
};
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
return Err(Error::new(status));
}
// Create a Stream wrapper from the raw handle
unsafe { Ok(Stream::from_raw(queue_ptr as crate::hip::bindings::hipStream_t)) }
}
/// Get ROCProfiler version as a tuple of (major, minor)
pub fn version() -> (u32, u32) {
let major = unsafe { bindings::rocprofiler_version_major() };
let minor = unsafe { bindings::rocprofiler_version_minor() };
(major, minor)
}
/// Get ROCProfiler version as a formatted string
pub fn version_string() -> String {
let (major, minor) = version();
format!("{}.{}", major, minor)
}
/// Get error string from ROCProfiler
pub fn error_string() -> Result<String> {
let mut str_ptr: *const ::std::os::raw::c_char = ptr::null();
let status = unsafe { bindings::rocprofiler_error_string(&mut str_ptr) };
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
return Err(Error::new(status));
}
if str_ptr.is_null() {
return Ok("No error string available".to_string());
}
let c_str = unsafe { CStr::from_ptr(str_ptr) };
Ok(c_str.to_string_lossy().into_owned())
}
/// Get info for a specific info kind
pub fn get_info(device: Option<&Device>, kind: InfoKind) -> Result<Vec<InfoData>> {
let mut result = Vec::new();
// Callback function to collect info data
extern "C" fn callback(
info: bindings::rocprofiler_info_data_t,
data: *mut ::std::ffi::c_void,
) -> bindings::hsa_status_t {
let result_ptr = data as *mut Vec<InfoData>;
let info_data = unsafe { InfoData::from_native(&info) };
unsafe {
(*result_ptr).push(info_data);
}
bindings::hsa_status_t_HSA_STATUS_SUCCESS
}
// Convert device to agent handle for ROCProfiler
let agent_handle = device.map(|d| bindings::hsa_agent_t {
handle: d.id() as u64
});
// Call the ROCProfiler API
let status = unsafe {
bindings::rocprofiler_iterate_info(
if let Some(a) = agent_handle {
&a
} else {
ptr::null()
},
kind.to_native(),
Some(callback),
&mut result as *mut _ as *mut ::std::ffi::c_void,
)
};
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
return Err(Error::new(status));
}
Ok(result)
}
/// Get all available metrics for a specific device
pub fn get_metrics(device: Option<&Device>) -> Result<Vec<InfoData>> {
get_info(device, InfoKind::Metric)
}
/// Get the number of available metrics for a specific device
pub fn get_metric_count(device: Option<&Device>) -> Result<u32> {
let mut count = 0;
// Convert device to agent handle for ROCProfiler
let agent_handle = device.map(|d| bindings::hsa_agent_t {
handle: d.id() as u64
});
// Call the ROCProfiler API
let status = unsafe {
bindings::rocprofiler_get_info(
if let Some(a) = agent_handle {
&a
} else {
ptr::null()
},
bindings::rocprofiler_info_kind_t_ROCPROFILER_INFO_KIND_METRIC_COUNT,
&mut count as *mut _ as *mut ::std::ffi::c_void,
)
};
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
return Err(Error::new(status));
}
Ok(count)
}
/// Get all available traces for a specific device
pub fn get_traces(device: Option<&Device>) -> Result<Vec<InfoData>> {
get_info(device, InfoKind::Trace)
}
/// Get the number of available traces for a specific device
pub fn get_trace_count(device: Option<&Device>) -> Result<u32> {
let mut count = 0;
// Convert device to agent handle for ROCProfiler
let agent_handle = device.map(|d| bindings::hsa_agent_t {
handle: d.id() as u64
});
// Call the ROCProfiler API
let status = unsafe {
bindings::rocprofiler_get_info(
if let Some(a) = agent_handle {
&a
} else {
ptr::null()
},
bindings::rocprofiler_info_kind_t_ROCPROFILER_INFO_KIND_TRACE_COUNT,
&mut count as *mut _ as *mut ::std::ffi::c_void,
)
};
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
return Err(Error::new(status));
}
Ok(count)
}
/// Get all available trace parameters for a specific device
pub fn get_trace_parameters(device: Option<&Device>, trace_name: &str) -> Result<Vec<InfoData>> {
let mut result = Vec::new();
// Callback function to collect info data
extern "C" fn callback(
info: bindings::rocprofiler_info_data_t,
data: *mut ::std::ffi::c_void,
) -> bindings::hsa_status_t {
let result_ptr = data as *mut Vec<InfoData>;
let info_data = unsafe { InfoData::from_native(&info) };
unsafe {
(*result_ptr).push(info_data);
}
bindings::hsa_status_t_HSA_STATUS_SUCCESS
}
// Convert device to agent handle for ROCProfiler
let agent_handle = device.map(|d| bindings::hsa_agent_t {
handle: d.id() as u64
});
// Create trace name C string
let trace_name_cstr = CString::new(trace_name)
.map_err(|_| Error::new(bindings::hsa_status_t_HSA_STATUS_ERROR_INVALID_ARGUMENT))?;
// Create query
let query = bindings::rocprofiler_info_query_t {
trace_parameter: bindings::rocprofiler_info_query_t__bindgen_ty_1 {
trace_name: trace_name_cstr.as_ptr(),
},
};
// Call the ROCProfiler API
let status = unsafe {
bindings::rocprofiler_query_info(
if let Some(a) = agent_handle {
&a
} else {
ptr::null()
},
query,
Some(callback),
&mut result as *mut _ as *mut ::std::ffi::c_void,
)
};
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
return Err(Error::new(status));
}
Ok(result)
}
/// Enable queue callbacks for profiling
pub struct QueueCallbacks {
dispatch: Option<Box<dyn Fn(&bindings::rocprofiler_callback_data_t, &mut bindings::rocprofiler_group_t) -> Result<()> + Send + Sync + 'static>>,
create: Option<Box<dyn Fn(*mut bindings::hsa_queue_t) -> Result<()> + Send + Sync + 'static>>,
destroy: Option<Box<dyn Fn(*mut bindings::hsa_queue_t) -> Result<()> + Send + Sync + 'static>>,
data: Arc<()>, // Placeholder for user data
}
impl QueueCallbacks {
/// Create new empty callbacks
pub fn new() -> Self {
Self {
dispatch: None,
create: None,
destroy: None,
data: Arc::new(()),
}
}
/// Set dispatch callback
pub fn with_dispatch<F>(mut self, callback: F) -> Self
where
F: Fn(&bindings::rocprofiler_callback_data_t, &mut bindings::rocprofiler_group_t) -> Result<()> + Send + Sync + 'static,
{
self.dispatch = Some(Box::new(callback));
self
}
/// Set create callback
pub fn with_create<F>(mut self, callback: F) -> Self
where
F: Fn(*mut bindings::hsa_queue_t) -> Result<()> + Send + Sync + 'static,
{
self.create = Some(Box::new(callback));
self
}
/// Set destroy callback
pub fn with_destroy<F>(mut self, callback: F) -> Self
where
F: Fn(*mut bindings::hsa_queue_t) -> Result<()> + Send + Sync + 'static,
{
self.destroy = Some(Box::new(callback));
self
}
}
// Global storage for callbacks
// Trampoline functions for C callbacks
static QUEUE_CALLBACKS: Lazy<RwLock<Option<QueueCallbacks>>> = Lazy::new(|| RwLock::new(None));
// Trampoline functions for C callbacks
extern "C" fn dispatch_callback(
callback_data: *const bindings::rocprofiler_callback_data_t,
user_data: *mut ::std::ffi::c_void,
group: *mut bindings::rocprofiler_group_t,
) -> bindings::hsa_status_t {
// Try to acquire a read lock on the callbacks
if let Ok(callbacks_guard) = QUEUE_CALLBACKS.read() {
if let Some(callbacks) = &*callbacks_guard {
if let Some(dispatch) = &callbacks.dispatch {
// Safety: We're trusting that callback_data and group are valid pointers
// from the ROCProfiler library
let result = unsafe { dispatch(&*callback_data, &mut *group) };
match result {
Ok(()) => return bindings::hsa_status_t_HSA_STATUS_SUCCESS,
Err(e) => return e.code(),
}
}
}
}
// Default return if anything fails
bindings::hsa_status_t_HSA_STATUS_SUCCESS
}
extern "C" fn create_callback(
queue: *mut bindings::hsa_queue_t,
data: *mut ::std::ffi::c_void,
) -> bindings::hsa_status_t {
// Try to acquire a read lock on the callbacks
if let Ok(callbacks_guard) = QUEUE_CALLBACKS.read() {
if let Some(callbacks) = &*callbacks_guard {
if let Some(create) = &callbacks.create {
let result = unsafe { create(queue) };
match result {
Ok(()) => return bindings::hsa_status_t_HSA_STATUS_SUCCESS,
Err(e) => return e.code(),
}
}
}
}
// Default return if anything fails
bindings::hsa_status_t_HSA_STATUS_SUCCESS
}
extern "C" fn destroy_callback(
queue: *mut bindings::hsa_queue_t,
data: *mut ::std::ffi::c_void,
) -> bindings::hsa_status_t {
// Try to acquire a read lock on the callbacks
if let Ok(callbacks_guard) = QUEUE_CALLBACKS.read() {
if let Some(callbacks) = &*callbacks_guard {
if let Some(destroy) = &callbacks.destroy {
let result = unsafe { destroy(queue) };
return match result {
Ok(()) => bindings::hsa_status_t_HSA_STATUS_SUCCESS,
Err(e) => e.code(),
}
}
}
}
// Default return if anything fails
bindings::hsa_status_t_HSA_STATUS_SUCCESS
}
/// Set queue callbacks for profiling
pub fn set_queue_callbacks(callbacks: QueueCallbacks) -> Result<()> {
let native_callbacks = bindings::rocprofiler_queue_callbacks_t {
dispatch: Some(dispatch_callback),
create: Some(create_callback),
destroy: Some(destroy_callback),
};
// Store callbacks in the RwLock
if let Ok(mut callbacks_guard) = QUEUE_CALLBACKS.write() {
*callbacks_guard = Some(callbacks);
} else {
return Err(Error::new(bindings::hsa_status_t_HSA_STATUS_ERROR));
}
// Set callbacks
let status = unsafe {
bindings::rocprofiler_set_queue_callbacks(
native_callbacks,
ptr::null_mut(), // We're not using the data pointer
)
};
if status != bindings::hsa_status_t_HSA_STATUS_SUCCESS {
// If registration fails, remove our stored callbacks to maintain consistency
if let Ok(mut callbacks_guard) = QUEUE_CALLBACKS.write() {
*callbacks_guard = None;
}
return Err(Error::new(status));
}
Ok(())
}
/// Remove queue callbacks
pub fn remove_queue_callbacks() -> Result<()> {
// Remove callbacks from ROCProfiler
let status = unsafe { bindings::rocprofiler_remove_queue_callbacks() };
// Clear stored callbacks
if let Ok(mut callbacks_guard) = QUEUE_CALLBACKS.write() {
*callbacks_guard = None;
}
Error::from_rocprofiler_error(status)
}
/// Start queue callbacks
pub fn start_queue_callbacks() -> Result<()> {
let status = unsafe { bindings::rocprofiler_start_queue_callbacks() };
Error::from_rocprofiler_error(status)
}
/// Stop queue callbacks
pub fn stop_queue_callbacks() -> Result<()> {
let status = unsafe { bindings::rocprofiler_stop_queue_callbacks() };
Error::from_rocprofiler_error(status)
}
File diff suppressed because it is too large Load Diff