rocprofiler-sdk: attach: rocprofv3-attach py improvements (#1365)
* attach: rocprofv3-attach py improvements - Handle error status during detachment - Add detection and error for changing rocprofv3 configuration on reattachment - Add and improve console messages during attachment and detachment - Documentation update pass
This commit is contained in:
@@ -62,13 +62,22 @@ def main(
|
||||
print(f"Attaching to PID {pid} using library {attach_library} :: success")
|
||||
|
||||
def detach():
|
||||
print("Detaching. Please wait, this can take up to 1-2 minutes")
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
c_lib.detach()
|
||||
detach_status = c_lib.detach()
|
||||
except Exception as e:
|
||||
print(f"Exception during detachment: {e}")
|
||||
|
||||
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")
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
print("\nCaught signal SIGINT, detaching")
|
||||
print("\nCaught signal SIGINT")
|
||||
detach()
|
||||
sys.exit(0)
|
||||
|
||||
@@ -79,6 +88,8 @@ def main(
|
||||
sys.stdout.flush() # Force the prompt to appear immediately
|
||||
input() # Now wait for input
|
||||
else:
|
||||
print(f"Attaching for {duration} msec...\n")
|
||||
sys.stdout.flush()
|
||||
time.sleep(int(duration) / 1000)
|
||||
|
||||
detach()
|
||||
|
||||
@@ -964,7 +964,22 @@ def patch_args(data):
|
||||
return data
|
||||
|
||||
|
||||
def get_args(cmd_args, inp_args, filter=[]):
|
||||
def get_args(cmd_args, inp_args, filter=[], require_in_both=False):
|
||||
"""
|
||||
Merges key and values in dict cmd_args and inp_args and returns the merged dict. This is typically used to combine
|
||||
arguments from multiple sources (e.g. command line and an input file).
|
||||
|
||||
If a key has conflicting values in cmd_args and inp_args, a RuntimeError is thrown.
|
||||
If a filter is provided (as a list of regex strings), only keys matching at least one filter regex will throw a
|
||||
RuntimeError for conflicting values. If the key with conflicting values does not match a filter, a warning is
|
||||
generated instead, and the value from cmd_args is used in the merged dict.
|
||||
If require_in_both is True, all keys must be present in both cmd_args and inp_args or a RuntimeError is thrown.
|
||||
This is typically used when arguments must match exactly.
|
||||
If a filter is provided and require_in_both is True, only keys matching at least one filter regex will throw a
|
||||
RuntimeError if they are not present in both cmd_args and inp_args. If the key does not match a filter, a warning
|
||||
is generated instead, and the unique key is used in the merged dict.
|
||||
"""
|
||||
|
||||
def ensure_type(name, var, type_id):
|
||||
if not isinstance(var, type_id):
|
||||
raise TypeError(
|
||||
@@ -994,38 +1009,49 @@ def get_args(cmd_args, inp_args, filter=[]):
|
||||
return getattr(inp_args, key)
|
||||
return None
|
||||
|
||||
def is_filtered(key):
|
||||
if filter:
|
||||
for fitr in filter:
|
||||
import re
|
||||
|
||||
if re.match(fitr, key):
|
||||
return True
|
||||
else:
|
||||
# if there are no filters, all keys match
|
||||
return True
|
||||
return False
|
||||
|
||||
for itr in set(cmd_keys + inp_keys):
|
||||
# check for conflicting args between the two argument lists
|
||||
if (
|
||||
has_set_attr(cmd_args, itr)
|
||||
and has_set_attr(inp_args, itr)
|
||||
and getattr(cmd_args, itr) != getattr(inp_args, itr)
|
||||
):
|
||||
should_raise = True
|
||||
if filter:
|
||||
is_filtered = False
|
||||
for fitr in filter:
|
||||
import re
|
||||
|
||||
if re.match(fitr, itr):
|
||||
is_filtered = True
|
||||
break
|
||||
|
||||
if not is_filtered:
|
||||
warning(
|
||||
f"Option '{itr}' has been modified. {itr}={getattr(cmd_args, itr)} (previously {itr}={getattr(inp_args, itr)})"
|
||||
)
|
||||
should_raise = False
|
||||
|
||||
# should raise error if not in filter list
|
||||
if should_raise:
|
||||
if is_filtered(itr):
|
||||
raise RuntimeError(
|
||||
f"conflicting value for {itr} : {getattr(cmd_args, itr)} vs {getattr(inp_args, itr)}"
|
||||
f"Option '{itr}' has conflicting values: {getattr(cmd_args, itr)} vs {getattr(inp_args, itr)}"
|
||||
)
|
||||
else:
|
||||
# has preference towards command line args
|
||||
data[itr] = get_attr(itr)
|
||||
else:
|
||||
data[itr] = get_attr(itr)
|
||||
warning(
|
||||
f"Option '{itr}' has been modified. {itr}={getattr(cmd_args, itr)} (previously {itr}={getattr(inp_args, itr)})"
|
||||
)
|
||||
|
||||
# if require_in_both was set, check for keys unique to each argument list
|
||||
if require_in_both and (
|
||||
has_set_attr(cmd_args, itr) != has_set_attr(inp_args, itr)
|
||||
):
|
||||
if is_filtered(itr):
|
||||
raise RuntimeError(
|
||||
f"Option '{itr}' was only present in one argument list : {getattr(cmd_args, itr, None)} vs {getattr(inp_args, itr, None)}"
|
||||
)
|
||||
else:
|
||||
warning(
|
||||
f"Option '{itr}' was only present in one argument list, but will be used : {itr}={get_attr(itr)}"
|
||||
)
|
||||
|
||||
# has preference towards command line args
|
||||
data[itr] = get_attr(itr)
|
||||
|
||||
return patch_args(dotdict(data))
|
||||
|
||||
@@ -1748,22 +1774,36 @@ def main(argv=None):
|
||||
args = get_args(cmd_args, inp_args[0])
|
||||
|
||||
if args.pid:
|
||||
# For reattachment support, args must be the same as previous rocprofv3 sessions
|
||||
# Store args in a temporary file for future sessions. If the temporary file already exists,
|
||||
# compare those args and error out if the tracing options are not the same.
|
||||
import pickle
|
||||
|
||||
if args.collection_period:
|
||||
fatal_error("--collection-period is not compatible with attach mode")
|
||||
|
||||
fname = f"/tmp/rocprofv3_attach_{args.pid}.pkl"
|
||||
if os.path.exists(fname):
|
||||
if not os.path.exists(fname):
|
||||
# if this is the first attachment, write the temp configuration file for future attachments
|
||||
with open(fname, "wb") as ofs:
|
||||
if args.log_level in ("config", "info", "trace"):
|
||||
print(f"Saving attach configuration to {fname}...")
|
||||
pickle.dump(args, ofs)
|
||||
else:
|
||||
# if this is not the first attachment
|
||||
# load the configuration from the previous attachment
|
||||
with open(fname, "rb") as ifs:
|
||||
if args.log_level in ("config", "info", "trace"):
|
||||
print(f"Loading attach configuration from {fname}...")
|
||||
prev_args = pickle.load(ifs)
|
||||
|
||||
# get_args will compare the arguments used to the previous attachments's arguments
|
||||
# arguments matching the filter will throw an error if they are different
|
||||
args = get_args(
|
||||
args,
|
||||
dotdict(prev_args),
|
||||
# when updating this filter, please also update documentation on reattachment
|
||||
# in using-rocprofv3-process-attachment.rst
|
||||
filter=[
|
||||
".*_trace",
|
||||
"^pc_sampling_.*$",
|
||||
@@ -1771,14 +1811,9 @@ def main(argv=None):
|
||||
"^(pmc|pmc_groups|output_config|extra_counters)$",
|
||||
"^kernel_(include_regex|exclude_regex|iteration_range)$",
|
||||
],
|
||||
require_in_both=True,
|
||||
)
|
||||
|
||||
# write the configuration for future attachments
|
||||
with open(fname, "wb") as ofs:
|
||||
if args.log_level in ("config", "info", "trace"):
|
||||
print(f"Saving attach configuration to {fname}...")
|
||||
pickle.dump(args, ofs)
|
||||
|
||||
pass_idx = None
|
||||
if has_set_attr(args, "pmc") and len(args.pmc) > 0:
|
||||
pass_idx = 1
|
||||
|
||||
@@ -108,6 +108,27 @@ The following example attaches the profiler to a process with PID "12345", colle
|
||||
|
||||
rocprofv3 --pid 12345 --pmc SQ_WAVES GRBM_COUNT --output-format csv
|
||||
|
||||
Reattaching to the same process
|
||||
--------------------------------
|
||||
|
||||
The dynamic process attachment functionality supports reattachment, which can be used to attach multiple times to the same PID over a process's lifetime. Give the same PID to ``rocprofv3`` to reattach.
|
||||
|
||||
There are some restrictions on what options can change when reattaching. Typically, tracing, PC sampling, ATT, counter collection, and other options that change what data will be collected cannot be changed. ``rocprofv3`` will throw a ``RuntimeError`` if it detects a configuration change that is not supported.
|
||||
|
||||
.. class:: details
|
||||
|
||||
Full list of options that must not change
|
||||
- ALL options ending with ``trace``
|
||||
- ALL options starting with ``pc_sampling``
|
||||
- ALL options starting with ``att``
|
||||
- ``pmc``
|
||||
- ``pmc_groups``
|
||||
- ``output_config``
|
||||
- ``extra_counters``
|
||||
- ``kernel_include_regex``
|
||||
- ``kernel_exclude_regex``
|
||||
- ``kernel_iteration_range``
|
||||
|
||||
Key considerations
|
||||
-------------------
|
||||
|
||||
|
||||
Fai riferimento in un nuovo problema
Block a user