2025-09-18 18:10:45 -05:00
#!/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.
2026-01-15 14:32:14 -06:00
import argparse
2025-09-18 18:10:45 -05:00
import ctypes
import os
import signal
import sys
import time
2025-11-18 10:52:17 -06:00
ROCPROF_ATTACH_DIR = os . path . dirname ( os . path . realpath ( __file__ ) )
ROCM_DIR = os . path . dirname ( ROCPROF_ATTACH_DIR )
2026-01-15 14:32:14 -06:00
ROCPROF_ATTACH_LIBRARY = f " { ROCM_DIR } /lib/librocprofiler-sdk-rocattach.so "
2025-09-18 18:10:45 -05:00
2026-01-15 14:32:14 -06:00
def parse_arguments ( args = None ) :
def format_help ( formatter , w = 120 , h = 40 ) :
""" Return a wider HelpFormatter, if possible. """
try :
kwargs = { " width " : w , " max_help_position " : h }
formatter ( None , * * kwargs )
return lambda prog : formatter ( prog , * * kwargs )
except TypeError :
return formatter
usage_examples = """
%(prog)s , e.g.
$ rocprof-attach -p <pid> -t <tool library> [-a <attach tool library> -d <msec duration>]
$ rocprof-attach -p 12345 -t path/to/your-tool-library.so -d 5000
"""
parser = argparse . ArgumentParser (
description = " rocprofiler-sdk attachment profiler for custom tool libraries " ,
usage = " %(prog)s [options] " ,
epilog = usage_examples ,
formatter_class = format_help ( argparse . RawTextHelpFormatter ) ,
)
parser . add_argument (
" -p " ,
" --pid " ,
" --attach " ,
help = """ Attachment target ' s process identifier (PID).
Can also be specified in environment variable ROCPROF_ATTACH_PID. This option overrides the environment variable if both are set. """ ,
type = int ,
required = False ,
default = os . environ . get ( " ROCPROF_ATTACH_PID " , None ) ,
)
parser . add_argument (
" -t " ,
" --attach-tool-library " ,
help = """ Comma delimited list of tool libraries to use during attachment.
Can also be specified in environment variable ROCPROF_ATTACH_TOOL_LIBRARY. This option overrides the environment variable if both are set. """ ,
type = str ,
required = False ,
default = os . environ . get ( " ROCPROF_ATTACH_TOOL_LIBRARY " , None ) ,
)
parser . add_argument (
" -d " ,
" --attach-duration-msec " ,
help = """ Sets the amount of time in milliseconds the profiler will be attached before detaching. When unset, the profiler will wait until Enter is pressed or SIGINT (Ctrl+C) to detach.
Can also be specified in environment variable ROCPROF_ATTACH_DURATION. This option overrides the environment variable if both are set. """ ,
type = int ,
required = False ,
default = os . environ . get ( " ROCPROF_ATTACH_DURATION " , None ) ,
)
advanced_options = parser . add_argument_group ( " Advanced options " )
advanced_options . add_argument (
" --attach-library " ,
help = """ Library used to attach and detach from the target process. Default will work for nearly all configurations.
Defaults to rocprofiler-sdk-rocattach.so from this ROCm install, i.e. <ROCmdirectory>/lib/rocprofiler-sdk-rocattach.so
Can also be specified in environment variable ROCPROF_ATTACH_LIBRARY. This option overrides the environment variable if both are set. """ ,
type = str ,
required = False ,
default = os . environ . get ( " ROCPROF_ATTACH_LIBRARY " , ROCPROF_ATTACH_LIBRARY ) ,
)
return parser . parse_args ( args )
def attach (
pid ,
attach_tool_library ,
attach_duration_msec ,
attach_library = ROCPROF_ATTACH_LIBRARY ,
2025-09-18 18:10:45 -05:00
) :
2026-01-15 14:32:14 -06:00
2025-09-18 18:10:45 -05:00
if pid is None :
2026-01-15 14:32:14 -06:00
raise RuntimeError ( " rocprof-attach called with no PID specified " )
if attach_tool_library is None :
raise RuntimeError ( " rocprof-attach called with no tool libraries specified " )
tool_libraries_tokens = attach_tool_library . split ( " : " )
for lib in tool_libraries_tokens :
if not os . path . exists ( lib ) :
raise RuntimeError ( f " rocprof-attach could not find tool library { lib } " )
# Program option overrides environment variable. This is consumed by rocprofiler-sdk on the target program side.
os . environ [ " ROCPROF_ATTACH_TOOL_LIBRARY " ] = attach_tool_library
2025-09-18 18:10:45 -05:00
print ( f " Attaching to PID { pid } using library { attach_library } " )
# Load the shared library into ctypes and attach
try :
c_lib = ctypes . CDLL ( attach_library )
2026-01-15 14:32:14 -06:00
c_lib . rocattach_attach . restype = ctypes . c_int
c_lib . rocattach_attach . argtypes = [ ctypes . c_int ]
c_lib . rocattach_detach . restype = ctypes . c_int
c_lib . rocattach_detach . argtypes = [ ctypes . c_int ]
attach_status = c_lib . rocattach_attach ( pid )
2025-09-18 18:10:45 -05:00
except Exception as e :
raise RuntimeError ( f " Exception during library load and attachment: { e } " )
if attach_status != 0 :
raise RuntimeError (
f " Calling attach in { attach_library } returned non-zero status { attach_status } "
)
print ( f " Attaching to PID { pid } using library { attach_library } :: success " )
def detach ( ) :
2025-11-10 09:43:00 -06:00
print ( " Detaching. Please wait, this can take up to 1-2 minutes " )
sys . stdout . flush ( )
2025-09-18 18:10:45 -05:00
try :
2026-01-15 14:32:14 -06:00
detach_status = c_lib . rocattach_detach ( int ( pid ) )
2025-09-18 18:10:45 -05:00
except Exception as e :
print ( f " Exception during detachment: { e } " )
2025-11-10 09:43:00 -06:00
if detach_status != 0 :
print (
f " Calling detach in { attach_library } returned non-zero status { detach_status } "
)
else :
print ( f " Detaching from PID { pid } using library { attach_library } :: success " )
2025-09-18 18:10:45 -05:00
def signal_handler ( sig , frame ) :
2025-11-10 09:43:00 -06:00
print ( " \n Caught signal SIGINT " )
2025-09-18 18:10:45 -05:00
detach ( )
sys . exit ( 0 )
signal . signal ( signal . SIGINT , signal_handler )
2026-01-15 14:32:14 -06:00
if attach_duration_msec is None :
2025-09-18 18:10:45 -05:00
sys . stdout . write ( " Press Enter to detach... " )
sys . stdout . flush ( ) # Force the prompt to appear immediately
input ( ) # Now wait for input
else :
2026-01-15 14:32:14 -06:00
print ( f " Attaching for { attach_duration_msec } msec... \n " )
2025-11-10 09:43:00 -06:00
sys . stdout . flush ( )
2026-01-15 14:32:14 -06:00
time . sleep ( int ( attach_duration_msec ) / 1000 )
2025-09-18 18:10:45 -05:00
detach ( )
2026-01-15 14:32:14 -06:00
def main ( cmd_args = None ) :
args = parse_arguments ( cmd_args )
attach (
pid = args . pid ,
attach_tool_library = args . attach_tool_library ,
attach_duration_msec = args . attach_duration_msec ,
attach_library = args . attach_library ,
)
return 0
2025-09-18 18:10:45 -05:00
if __name__ == " __main__ " :
2026-01-15 14:32:14 -06:00
ec = main ( sys . argv [ 1 : ] )
sys . exit ( ec )