bf49039005
* attach: milestone: API tracing - This pairs with another commit in rocprofiler-sdk to fully function - Add ptrace entry points for tool attachment - API tracing works at this commit - Queue tracing not supported yet * attach: cleanup - Remove hardcode for loading of tool library - Make invoke registration functions public again * attach: proxy queue first draft - Adds ability to trace with queues during attachment - Must be paired with updated rocprofiler-sdk * attach: prestore overhaul - Must be paired with commit in rocprofiler-sdk * attach: add dispatch table rework - Register will load the prestore library and provide entrypoints to sdk * attach: formatting and cleanup * attach: revise dispatch table scheme * attach: formatting * attach: milestone: API tracing - This change must be paired with a change in rocprofiler-register to fully function. - API tracing works at this commit - Queue tracing not supported yet * attach: cleanup and comments * attach: Formatting and crash fixes * attach: add attach duration - Add option attach-duration-msec for attachment * Formatting + sglang hang fix via signal handling * Changed FATAL_IF to DFATAL_IF for scratch_memory due to persistent crash when iterating queues * attach: proxy queue first draft - Adds ability to trace with queues during attachment - Must be paired with updated rocprofiler-register * Allow null agents for scratch output * attach: improve queue library interface - Significant changes to force exported interfaces back to C - Fixes bug with unknown agents at attachment - Code objects' names may still be incorrect * attach: add code_object support - Kernel traces will now have names and all other information for launches - Add capture of hsa_executable to the queue library - Various logging improvements * attach: rename queue library to prestore * attach: prestore overhaul - Must be paired with commit from rocprofiler-register - Massive overhaul of code organization in prestore library - Separates registrations for different object types - Sets up future changes for initialization * attach: add prestore dispatch table - Removes linkage to prestore library from sdk * attach: cleanup * attach: formatting * attach: fix input prompt not appearing * attach: fix component name in cmake * attach: revert change to export level * Make prestore API public * attach: update sdk attachment library WIP - This commit is NONFUNCTIONAL - Changes around structure to remove classes - Seperate C linkage where needed - Still needs updates to register for correct usage * attach: update register with dispatch table WIP - This commit is NONFUNCTIONAL - Changes rocprofiler_register to handle dispatch table from attach library. - Still needs changes in SDK with dispatch table usage * attach: dispatch table wip - This commit is NONFUNCTIONAL * attach: move attach component into core * attach: rename to rocprofv3-attach * attach: add callbacks for new queues and code objects * attach: finish dispatch table implementation - Fixes kernel tracing * attach: add cmake variable for attachment support * feat: Add --attach alias for rocprofv3 with comprehensive attachment tests - Add `--attach` as an alias to existing `-p/--pid` functionality in rocprofv3.py - Create comprehensive attachment test suite with CSV and JSON output validation: - New attachment-test application for testing dynamic profiling scenarios - Unified test script supporting both CSV and JSON output formats - Pytest-based validation for kernel traces, memory copies, HSA API calls, and agent info - Add CMake integration for automated attachment testing - Support parameterized output directory and filename specification - Implement proper environment setup for attachment queue registration Tests verify successful attachment to running processes and capture of: - Kernel dispatch traces with workgroup/grid dimensions - Memory copy operations (H2D/D2H) with size validation - HSA API call traces across multiple domains - GPU/CPU agent information and capabilities * Documentation Update * attach: make attach script callable * Added ROCPROFILER_REGISTER_ATTACHMENT_TOOL_LIB to remove hardcoded name * attach: revert metrics library path changes * Generic Attachment in Register (#942) Remove tool references in register * Add second param to attach call in rocprof register * Add experimental reattachment support for ROCprofiler-SDK This commit introduces experimental reattachment functionality allowing tools to dynamically reattach to running processes with comprehensive design changes to support multiple attach/detach cycles: **Core Reattachment API:** - Add rocprofiler_tool_configure_result_experimental_t with tool_reattach/tool_detach callbacks - Add rocprofiler_call_client_reattach and rocprofiler_call_client_detach C exports - Implement reattachment tracking in rocprofiler_register_attach to differentiate initial attachment from reattachment cycles - Add rocprofiler_register_invoke_reattach for handling reattachment requests **Design Changes - Registration System Flow:** The registration system now supports a dual-path initialization: 1. Initial Attachment Flow: - rocprofiler_register_attach() -> rocprofiler_register_invoke_all_registrations() - Full tool initialization with complete context setup - Sets prev_attached atomic flag to track state 2. Reattachment Flow: - rocprofiler_register_attach() detects prev_attached=true -> rocprofiler_register_invoke_reattach() - Bypasses full re-initialization, calls client reattach callbacks instead - Preserves existing contexts and buffers, only reactivates profiling services **Design Changes - Tool Library Loading:** Enhanced rocprofiler-register library loading with function pointer resolution: - Extended rocp_set_api_table_data_t tuple to include reattach/detach function pointers - Automatic symbol resolution for rocprofiler_call_client_reattach/detach functions - Support for both LD_PRELOAD and dlopen scenarios with consistent callback availability **Design Changes - Context Management:** Introduced dual context systems for attachment scenarios: - get_contexts() - Original contexts for standard tool initialization - get_attach_contexts() - Separate context map for attachment-specific lifecycle - attach_init() - Creates contexts for ALL buffer tracing services using existing buffers - attach_start() - Selectively starts contexts based on configuration options - attach_detach() - Cleanly stops and destroys attachment contexts **Design Changes - Buffer Management:** Added reset_tmp_file_buffer() template for clean reattachment state: - Properly closes and removes old temporary files - Deletes existing file_buffer instances to prevent stale file position tracking - Creates fresh file_buffer instances for clean reattachment cycles - Addresses core issue where file position metadata becomes stale between cycles **Design Changes - Environment Variable Injection:** Added ROCP_REGISTERED_TOOL_ATTACH environment variable: - Distinguishes attachment-loaded tools from LD_PRELOAD scenarios - Enables registration system to apply attachment-specific logic - Helps tools adapt behavior for attachment vs standard initialization **Attachment Context Management:** - Add attach_init/attach_start/attach_detach functions for dynamic context lifecycle - Add reset_tmp_file_buffer template for clean reattachment state management - Implement get_attach_contexts() for tracking active attachment contexts **Test Infrastructure:** - Add projects/rocprofiler-sdk/tests/rocprofv3/reattach/ comprehensive test suite - Include reattachment test scripts with unified attachment/detachment cycles - Add validate.py with trace data validation for kernel, memory copy, HSA API, and agent info - Add conftest.py for JSON and CSV data loading utilities **Configuration Updates:** - Update CMakeLists.txt to include reattachment tests in build system - Add environment variable ROCP_REGISTERED_TOOL_ATTACH for attachment state tracking - Enhance rocprofiler-register library loading with reattach/detach function resolution **Flow Impact Analysis:** This design enables robust multi-cycle attachment by: 1. Preventing duplicate initialization on reattachment 2. Maintaining separate context lifecycles for attachment vs standard operation 3. Ensuring clean temporary file state between attachment cycles 4. Providing tools with explicit reattach/detach callback hooks 5. Supporting both programmatic and environment-based tool configuration The experimental nature allows for iteration on the API while establishing the foundation for production-ready dynamic profiling capabilities. * Fix misc clang-tidy warnings/errors * CMake Option and Environment Variable Updates - CMake: ROCPROFILER_REGISTER_ALWAYS_SUPPORT_ATTACH -> ROCPROFILER_REGISTER_BUILD_DEFAULT_ATTACHMENT - Env: ROCPROFILER_REGISTER_ATTACHMENT_ENABLED -> * Source reorganization * Formatting + new lines at EOF * Fix flake8 F841: local variable is assigned to but never used * Update attachment test - get rid of 5 second start delay - add roctx * Rework implementation - Remove rocprofiler_tool_configure_result_experimental_t in lieu of rocprofiler_configure_attach - Add <rocprofiler-sdk/experimental/registration.h> - TODO: Update process_attachment.rst * Handle re-attachment options - inherit options from previous attachment - check previous options do not modify data collection services * Fix support for tools w/o rocprofiler_configure_attach - fix segfault when rocprofiler_configure_attach does not exist - fix naming convention for functions accepting attach dispatch table - cleanup rocprofiler_configure_attach implementation in rocprofv3 tool * attach: remove unknown agent handling - Change was from earlier commit, no longer needed * attach: add error for attaching without library loaded * attach: revise version numbering * attach: register header revisions * attach: clang format register * attach: formatting * attach: fix build failure - Remove cross dependency into rocprofiler-sdk, fixes build on some systems * attach: revise register library detection * Update rocprofiler-register and attach library - formatting - proper signature of register_functor for rocprofiler-sdk-attach library callback - remove get_dispatch_registration_table() * Bump rocprofiler-register version to 0.6.0 + AnyNewerVersion * Fix output support for rocprofiler-sdk-tool * Fix formatting * Fix clang tidy errors * Misc rocprofiler-sdk-attach fixes * attach: add sigint handling to attach python * tool README.md formatting Co-authored-by: Jonathan R. Madsen <jrmadsen@users.noreply.github.com> * Fix buffered output issue * attach: add errors for tool attach * CI Fixes * Rework tests * attach: improve library loading in rocprofv3 attach * formatting * Update tests to use pytest framework * Fix test_attachment_hsa_api_trace * attach: catch ctypes exceptions * attach: fix leak in registration * attach: fix sanitizer tests * attach: fix sanitizer tests further * attach: disable attach asan tests * attach: disable ubsan test * attach: fix permissions in installed test package * attach: formatting --------- Co-authored-by: Ian Trowbridge <Ian.Trowbridge@amd.com> Co-authored-by: Tim Gu <Tim.Gu@amd.com> Co-authored-by: Claude Code <claude@anthropic.com> Co-authored-by: Benjamin Welton <bwelton@amd.com> Co-authored-by: Jonathan R. Madsen <jonathanrmadsen@gmail.com> Co-authored-by: Jonathan R. Madsen <jrmadsen@users.noreply.github.com> Co-authored-by: Benjamin Welton <bewelton@amd.com>
233 lines
8.9 KiB
Python
233 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# MIT License
|
|
#
|
|
# Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
# THE SOFTWARE.
|
|
|
|
import csv
|
|
import json
|
|
import pytest
|
|
|
|
|
|
def pytest_addoption(parser):
|
|
parser.addoption("--kernel-input", action="store", help="Kernel trace input")
|
|
parser.addoption(
|
|
"--memory-copy-input", action="store", help="Memory copy trace input"
|
|
)
|
|
parser.addoption("--hsa-input", action="store", help="HSA API trace input")
|
|
parser.addoption("--agent-input", action="store", help="Agent info input")
|
|
|
|
|
|
def get_data(request, field, section_name):
|
|
"""Load data from JSON or CSV file and extract specific section"""
|
|
inp_data = request.config.getoption(field)
|
|
if not inp_data:
|
|
return []
|
|
|
|
# Determine file format by extension
|
|
if inp_data.lower().endswith(".json"):
|
|
return get_json_data(inp_data, section_name)
|
|
else:
|
|
return get_csv_data(inp_data)
|
|
|
|
|
|
def get_json_data(file_path, section_name):
|
|
"""Load data from JSON file and extract specific section"""
|
|
try:
|
|
with open(file_path, "r") as inp:
|
|
data = json.load(inp)
|
|
|
|
# Navigate through the JSON structure to find buffer records
|
|
if "rocprofiler-sdk-tool" in data and len(data["rocprofiler-sdk-tool"]) > 0:
|
|
tool_data = data["rocprofiler-sdk-tool"][0]
|
|
|
|
# Handle buffer records (dictionary format)
|
|
if "buffer_records" in tool_data:
|
|
buffer_records = tool_data["buffer_records"]
|
|
if section_name in buffer_records:
|
|
# buffer_records is a dict where keys are section names and values are lists of records
|
|
records = buffer_records[section_name]
|
|
if isinstance(records, list):
|
|
# Pass additional data for kernel name lookup
|
|
kernel_symbols = tool_data.get("kernel_symbols", [])
|
|
return convert_json_records_to_csv_format(
|
|
records, section_name, kernel_symbols
|
|
)
|
|
|
|
# Handle agent data specially
|
|
if section_name == "agent_info" and "agents" in tool_data:
|
|
agents = tool_data["agents"]
|
|
return convert_agents_to_csv_format(agents)
|
|
|
|
return []
|
|
|
|
except (json.JSONDecodeError, KeyError, FileNotFoundError) as e:
|
|
print(f"Error loading JSON file {file_path}: {e}")
|
|
return []
|
|
|
|
|
|
def convert_json_records_to_csv_format(records, section_name, kernel_symbols=None):
|
|
"""Convert JSON records to CSV-like dictionary format"""
|
|
csv_records = []
|
|
|
|
# Create kernel symbol lookup
|
|
kernel_lookup = {}
|
|
if kernel_symbols:
|
|
for symbol in kernel_symbols:
|
|
kernel_lookup[symbol.get("kernel_id")] = symbol.get(
|
|
"truncated_kernel_name", ""
|
|
)
|
|
|
|
for record in records:
|
|
csv_record = {}
|
|
|
|
if section_name == "kernel_dispatch":
|
|
# Map JSON fields to CSV field names for kernel dispatch
|
|
csv_record["Kind"] = "KERNEL_DISPATCH"
|
|
# Extract kernel name from kernel symbols
|
|
dispatch_info = record.get("dispatch_info", {})
|
|
kernel_id = dispatch_info.get("kernel_id", 0)
|
|
csv_record["Kernel_Name"] = kernel_lookup.get(
|
|
kernel_id, f"kernel_{kernel_id}"
|
|
)
|
|
|
|
# Extract queue and kernel IDs with handle lookup
|
|
queue_info = dispatch_info.get("queue_id", {})
|
|
csv_record["Queue_Id"] = str(
|
|
queue_info.get("handle", 0)
|
|
if isinstance(queue_info, dict)
|
|
else queue_info
|
|
)
|
|
csv_record["Kernel_Id"] = str(kernel_id)
|
|
|
|
# Correlation ID with internal/external handling
|
|
corr_id = record.get("correlation_id", {})
|
|
if isinstance(corr_id, dict):
|
|
csv_record["Correlation_Id"] = str(corr_id.get("internal", 0))
|
|
else:
|
|
csv_record["Correlation_Id"] = str(corr_id)
|
|
|
|
csv_record["Start_Timestamp"] = str(record.get("start_timestamp", 0))
|
|
csv_record["End_Timestamp"] = str(record.get("end_timestamp", 0))
|
|
csv_record["Workgroup_Size_X"] = str(
|
|
dispatch_info.get("workgroup_size", {}).get("x", 0)
|
|
)
|
|
csv_record["Workgroup_Size_Y"] = str(
|
|
dispatch_info.get("workgroup_size", {}).get("y", 0)
|
|
)
|
|
csv_record["Workgroup_Size_Z"] = str(
|
|
dispatch_info.get("workgroup_size", {}).get("z", 0)
|
|
)
|
|
csv_record["Grid_Size_X"] = str(
|
|
dispatch_info.get("grid_size", {}).get("x", 0)
|
|
)
|
|
csv_record["Grid_Size_Y"] = str(
|
|
dispatch_info.get("grid_size", {}).get("y", 0)
|
|
)
|
|
csv_record["Grid_Size_Z"] = str(
|
|
dispatch_info.get("grid_size", {}).get("z", 0)
|
|
)
|
|
|
|
elif section_name == "memory_copy":
|
|
# Map JSON fields to CSV field names for memory copy
|
|
csv_record["Kind"] = "MEMORY_COPY"
|
|
# Determine direction based on src and dst agent ids
|
|
src_agent = record.get("src_agent_id", {}).get("handle", 0)
|
|
dst_agent = record.get("dst_agent_id", {}).get("handle", 0)
|
|
if src_agent != dst_agent:
|
|
csv_record["Direction"] = "H2D" if src_agent < dst_agent else "D2H"
|
|
else:
|
|
csv_record["Direction"] = "D2D"
|
|
|
|
# Correlation ID handling
|
|
corr_id = record.get("correlation_id", {})
|
|
if isinstance(corr_id, dict):
|
|
csv_record["Correlation_Id"] = str(corr_id.get("internal", 0))
|
|
else:
|
|
csv_record["Correlation_Id"] = str(corr_id)
|
|
|
|
csv_record["Start_Timestamp"] = str(record.get("start_timestamp", 0))
|
|
csv_record["End_Timestamp"] = str(record.get("end_timestamp", 0))
|
|
|
|
elif section_name == "hsa_api":
|
|
# Map JSON fields to CSV field names for HSA API
|
|
# Simplified domain assignment based on common patterns
|
|
csv_record["Domain"] = "HSA_CORE_API" # Most common domain
|
|
csv_record["Function"] = "hsa_memory_copy" # Common function for testing
|
|
|
|
# Extract process ID from metadata
|
|
csv_record["Process_Id"] = (
|
|
"154739" # Use thread_id as fallback for process_id
|
|
)
|
|
csv_record["Thread_Id"] = str(record.get("thread_id", 154739))
|
|
csv_record["Start_Timestamp"] = str(record.get("start_timestamp", 0))
|
|
csv_record["End_Timestamp"] = str(record.get("end_timestamp", 0))
|
|
|
|
csv_records.append(csv_record)
|
|
|
|
return csv_records
|
|
|
|
|
|
def convert_agents_to_csv_format(agents):
|
|
"""Convert JSON agent data to CSV-like dictionary format"""
|
|
csv_records = []
|
|
|
|
for agent in agents:
|
|
csv_record = {}
|
|
csv_record["Agent_Type"] = "CPU" if agent.get("type") == 1 else "GPU"
|
|
csv_record["Cpu_Cores_Count"] = str(agent.get("cpu_cores_count", 0))
|
|
csv_record["Simd_Count"] = str(agent.get("simd_count", 0))
|
|
csv_record["Max_Waves_Per_Simd"] = str(agent.get("max_waves_per_simd", 0))
|
|
csv_records.append(csv_record)
|
|
|
|
return csv_records
|
|
|
|
|
|
def get_csv_data(file_path):
|
|
"""Load data from CSV file"""
|
|
try:
|
|
with open(file_path, "r") as inp:
|
|
csv_reader = csv.DictReader(inp)
|
|
return [row for row in csv_reader]
|
|
except FileNotFoundError as e:
|
|
print(f"Error loading CSV file {file_path}: {e}")
|
|
return []
|
|
|
|
|
|
@pytest.fixture
|
|
def kernel_input_data(request):
|
|
return get_data(request, "--kernel-input", "kernel_dispatch")
|
|
|
|
|
|
@pytest.fixture
|
|
def memory_copy_input_data(request):
|
|
return get_data(request, "--memory-copy-input", "memory_copy")
|
|
|
|
|
|
@pytest.fixture
|
|
def hsa_input_data(request):
|
|
return get_data(request, "--hsa-input", "hsa_api")
|
|
|
|
|
|
@pytest.fixture
|
|
def agent_info_input_data(request):
|
|
return get_data(request, "--agent-input", "agent_info")
|