kernel iteration filtering for counter collection (#911)

* kernel filtering for counter collection

* fixing trace tests

* removing print statements

* fix CI fail

* handling preload and updating docs

* minor fix

* misc fix

* misc fix

* Typo fix

* Update rocprofv3 + input schema

- "application_passes" -> "jobs"
- removed nesting in YAML/JSON inputs
- improved customAction (now booleanArgAction)
  - supports --<name> (defaults to true)
  - supports --<name>=<truth-value>
  - supports --<name> <truth-value>
- added --kernel-iteration-range to command-line
- automatically support new command-line options in YAML/JSON input
- standardized PMC return from text input to match PMC from YAML/JSON input
- added support for --log-level env
- updated various input*.(yml|json) to modified schema

* Update config.cpp

- added recommended code to get_kernel_filter_range

* Fixing iteration

* misc fix

* support only [-] for iteration

* bug fix

* Fix using-rocprofv3.rst

* Update config.cpp

- patch get_kernel_filter_range

---------

Co-authored-by: Jonathan R. Madsen <jonathanrmadsen@gmail.com>
This commit is contained in:
SrirakshaNag
2024-07-26 21:46:53 -05:00
committato da GitHub
parent dc671497da
commit ace34abd11
23 ha cambiato i file con 1303 aggiunte e 275 eliminazioni
+258 -106
Vedi File
@@ -6,12 +6,60 @@ import argparse
import subprocess
class dotdict(dict):
"""dot.notation access to dictionary attributes"""
__getattr__ = dict.get
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
def __init__(self, d):
super(dotdict, self).__init__(d)
for k, v in self.items():
if isinstance(v, dict):
self.__setitem__(k, dotdict(v))
elif isinstance(v, (list, tuple)):
self.__setitem__(
k,
[dotdict(i) if isinstance(i, (list, tuple, dict)) else i for i in v],
)
def fatal_error(msg, exit_code=1):
sys.stderr.write(f"Fatal error: {msg}\n")
sys.stderr.flush()
sys.exit(exit_code)
def strtobool(val):
"""Convert a string representation of truth to true or false.
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
'val' is anything else.
"""
if isinstance(val, (list, tuple)):
if len(val) > 1:
val_type = type(val).__name__
raise ValueError(f"invalid truth value {val} (type={val_type})")
else:
val = val[0]
if isinstance(val, bool):
return val
elif isinstance(val, str) and val.lower() in ("y", "yes", "t", "true", "on", "1"):
return True
elif isinstance(val, str) and val.lower() in ("n", "no", "f", "false", "off", "0"):
return False
else:
val_type = type(val).__name__
raise ValueError(f"invalid truth value {val} (type={val_type})")
class booleanArgAction(argparse.Action):
def __call__(self, parser, args, value, option_string=None):
setattr(args, self.dest, strtobool(value))
def parse_arguments(args=None):
usage_examples = """
@@ -35,112 +83,90 @@ For MPI applications (or other job launchers such as SLURM), place rocprofv3 ins
formatter_class=argparse.RawTextHelpFormatter,
)
def add_parser_bool_argument(*args, **kwargs):
parser.add_argument(
*args,
**kwargs,
action=booleanArgAction,
nargs="?",
const=True,
type=str,
required=False,
metavar="BOOL",
)
# Add the arguments
parser.add_argument(
add_parser_bool_argument(
"--hip-trace",
action="store_true",
help="For Collecting HIP Traces (runtime + compiler)",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--hip-runtime-trace",
action="store_true",
help="For Collecting HIP Runtime API Traces",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--hip-compiler-trace",
action="store_true",
help="For Collecting HIP Compiler generated code Traces",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--marker-trace",
action="store_true",
help="For Collecting Marker (ROCTx) Traces",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--kernel-trace",
action="store_true",
help="For Collecting Kernel Dispatch Traces",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--memory-copy-trace",
action="store_true",
help="For Collecting Memory Copy Traces",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--scratch-memory-trace",
action="store_true",
help="For Collecting Scratch Memory operations Traces",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--stats",
action="store_true",
help="For Collecting statistics of enabled tracing types",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--hsa-trace",
action="store_true",
help="For Collecting HSA Traces (core + amd + image + finalizer)",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--hsa-core-trace",
action="store_true",
help="For Collecting HSA API Traces (core API)",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--hsa-amd-trace",
action="store_true",
help="For Collecting HSA API Traces (AMD-extension API)",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--hsa-image-trace",
action="store_true",
help="For Collecting HSA API Traces (Image-extenson API)",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"--hsa-finalizer-trace",
action="store_true",
help="For Collecting HSA API Traces (Finalizer-extension API)",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"-s",
"--sys-trace",
action="store_true",
help="For Collecting HIP, HSA, Marker (ROCTx), Memory copy, Scratch memory, and Kernel dispatch traces",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"-M",
"--mangled-kernels",
action="store_true",
help="Do not demangle the kernel names",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"-T",
"--truncate-kernels",
action="store_true",
help="Truncate the demangled kernel names",
required=False,
)
parser.add_argument(
add_parser_bool_argument(
"-L",
"--list-metrics",
action="store_true",
help="List metrics for counter collection",
required=False,
)
parser.add_argument(
"-i",
@@ -168,7 +194,7 @@ For MPI applications (or other job launchers such as SLURM), place rocprofv3 ins
"--output-format",
help="For adding output format (supported formats: csv, json, pftrace)",
nargs="+",
default=["csv"],
default=None,
choices=("csv", "json", "pftrace"),
type=str.lower,
)
@@ -176,15 +202,29 @@ For MPI applications (or other job launchers such as SLURM), place rocprofv3 ins
"--log-level",
help="Set the log level",
default=None,
choices=("fatal", "error", "warning", "info", "trace"),
choices=("fatal", "error", "warning", "info", "trace", "env"),
type=str.lower,
)
parser.add_argument(
"--kernel-names",
help="Filter kernel names",
"--kernel-include-regex",
help="Include the kernels matching this filter",
default=None,
type=str,
metavar="REGULAR_EXPRESSION",
)
parser.add_argument(
"--kernel-exclude-regex",
help="Exclude the kernels matching this filter",
default=None,
type=str,
metavar="REGULAR_EXPRESSION",
)
parser.add_argument(
"--kernel-iteration-range",
help="Iteration range",
nargs="+",
default=None,
type=str,
)
parser.add_argument(
"--preload",
@@ -197,6 +237,7 @@ For MPI applications (or other job launchers such as SLURM), place rocprofv3 ins
args = sys.argv[1:]
rocp_args = args[:]
app_args = []
for idx, itr in enumerate(args):
@@ -215,11 +256,19 @@ def parse_yaml(yaml_file):
fatal_error(
f"{e}\n\nYAML package is not installed. Run '{sys.executable} -m pip install pyyaml' or use JSON or text format"
)
try:
lst = []
with open(yaml_file, "r") as file:
data = yaml.safe_load(file)
return [" ".join(itr["pmc"]) for itr in data["metrics"]]
for itr in data["jobs"]:
# TODO: support naming jobs
# if isinstance(itr, str):
# itr = data["jobs"][itr]
itr["sub_directory"] = "pass_"
lst.append(itr)
return [dotdict(itr) for itr in lst]
except yaml.YAMLError as exc:
fatal_error(f"{exc}")
@@ -230,9 +279,15 @@ def parse_json(json_file):
import json
try:
lst = []
with open(json_file, "r") as file:
data = json.load(file)
return [" ".join(itr["pmc"]) for itr in data["metrics"]]
for itr in data["jobs"]:
itr["sub_directory"] = "pass_"
lst.append(itr)
return [dotdict(itr) for itr in lst]
except Exception as e:
fatal_error(f"{e}")
@@ -252,10 +307,10 @@ def parse_text(text_file):
def _dedup(_line, _sep):
for itr in _sep:
_line = " ".join(_line.split(itr))
return _line
return _line.strip()
# remove tabs and duplicate spaces
return _dedup(line.replace("pmc:", ""), ["\t", " "]).strip()
return _dedup(line.replace("pmc:", ""), ["\n", "\t", " "]).split(" ")
try:
with open(text_file, "r") as file:
@@ -271,25 +326,81 @@ def parse_text(text_file):
def parse_input(input_file):
pmc_lines = []
_, extension = os.path.splitext(input_file)
if extension == ".txt":
pmc_lines = parse_text(input_file)
text_input = parse_text(input_file)
text_input_lst = [{"pmc": itr, "sub_directory": "pmc_"} for itr in text_input]
return [dotdict(itr) for itr in text_input_lst]
elif extension in (".yaml", ".yml"):
pmc_lines = parse_yaml(input_file)
return parse_yaml(input_file)
elif extension == ".json":
pmc_lines = parse_json(input_file)
return parse_json(input_file)
else:
fatal_error(
f"Input file '{input_file}' does not have a recognized extension (.txt, .json, .yaml, .yml)\n"
)
return pmc_lines
return None
def main(argv=None):
def has_set_attr(obj, key):
if obj and hasattr(obj, key) and getattr(obj, key) is not None:
return True
else:
return False
def patch_args(data):
"""Used to handle certain fields which might be specified as a string instead of an array or vice-versa"""
if hasattr(data, "kernel_iteration_range") and isinstance(
data.kernel_iteration_range, str
):
data.kernel_iteration_range = [data.kernel_iteration_range]
return data
def get_args(cmd_args, inp_args):
def ensure_type(name, var, type_id):
if not isinstance(var, type_id):
raise TypeError(
f"{name} is of type {type(var).__name__}, expected {type(type_id).__name__}"
)
ensure_type("cmd_args", cmd_args, argparse.Namespace)
ensure_type("inp_args", inp_args, dotdict)
cmd_keys = list(cmd_args.__dict__.keys())
inp_keys = list(inp_args.keys())
data = {}
def get_attr(key):
if has_set_attr(cmd_args, key):
return getattr(cmd_args, key)
elif has_set_attr(inp_args, key):
return getattr(inp_args, key)
return None
for itr in set(cmd_keys + inp_keys):
if (
has_set_attr(cmd_args, itr)
and has_set_attr(inp_args, itr)
and getattr(cmd_args, itr) != getattr(inp_args, itr)
):
raise RuntimeError(f"conflicting value for {itr}")
else:
data[itr] = get_attr(itr)
return patch_args(dotdict(data))
def run(app_args, args, **kwargs):
app_env = dict(os.environ)
use_execv = kwargs.get("use_execv", True)
app_pass = kwargs.get("pass_id", None)
def update_env(env_var, env_val, **kwargs):
"""Local function for updating application environment which supports
@@ -361,12 +472,12 @@ def main(argv=None):
ROCPROF_TOOL_LIBRARY = f"{ROCM_DIR}/lib/rocprofiler-sdk/librocprofiler-sdk-tool.so"
ROCPROF_SDK_LIBRARY = f"{ROCM_DIR}/lib/librocprofiler-sdk.so"
args, app_args = parse_arguments(argv)
args.preload = [itr for itr in args.preload if itr]
if args.preload:
update_env("LD_PRELOAD", ":".join(args.preload), prepend=True)
_preload = ":".join(args.preload) if args.preload else None
update_env("LD_PRELOAD", _preload, prepend=True)
update_env("LD_PRELOAD", f"{ROCPROF_TOOL_LIBRARY}:{ROCPROF_SDK_LIBRARY}", append=True)
update_env(
"ROCP_TOOL_LIBRARIES",
f"{ROCPROF_TOOL_LIBRARY}",
@@ -385,17 +496,21 @@ def main(argv=None):
update_env("ROCPROF_OUTPUT_FILE_NAME", _output_file)
update_env("ROCPROF_OUTPUT_PATH", _output_path)
if app_pass is not None:
app_env["ROCPROF_OUTPUT_PATH"] = os.path.join(
f"{_output_path}", f"{args.sub_directory}{app_pass}"
)
if args.output_file is not None or args.output_directory is not None:
update_env("ROCPROF_OUTPUT_LIST_METRICS_FILE", True)
if not args.output_format:
args.output_format = ["csv"]
update_env(
"ROCPROF_OUTPUT_FORMAT", ",".join(args.output_format), append=True, join_char=","
)
_kernel_names = ",".join(args.kernel_names) if args.kernel_names else None
update_env("ROCPROF_KERNEL_NAMES", _kernel_names, append=True, join_char=",")
if args.sys_trace:
for itr in (
"hip_trace",
@@ -445,7 +560,9 @@ def main(argv=None):
update_env("ROCPROF_STATS", args.stats, overwrite_if_true=True)
update_env(
"ROCPROF_DEMANGLE_KERNELS", not args.mangled_kernels, overwrite_if_false=True
"ROCPROF_DEMANGLE_KERNELS",
not args.mangled_kernels,
overwrite_if_false=True,
)
update_env(
"ROCPROF_TRUNCATE_KERNELS",
@@ -458,61 +575,96 @@ def main(argv=None):
overwrite_if_true=True,
)
for itr in ("ROCPROF", "ROCPROFILER", "ROCTX"):
update_env(
f"{itr}_LOG_LEVEL",
args.log_level,
)
if args.log_level and args.log_level not in ("env"):
for itr in ("ROCPROF", "ROCPROFILER", "ROCTX"):
update_env(
f"{itr}_LOG_LEVEL",
args.log_level,
)
def log_config(_env):
existing_env = dict(os.environ)
init_message = "- rocprofv3 configuration:\n"
init_message = "\n- rocprofv3 configuration{}:\n".format(
"" if app_pass is None else f" (pass {app_pass})"
)
for key, itr in _env.items():
if key not in existing_env.keys():
if init_message:
sys.stderr.write(init_message)
init_message = None
sys.stderr.write(f"\t- {key}={itr}\n")
if init_message is None:
sys.stderr.write("\n")
sys.stderr.flush()
if args.list_metrics:
app_args = [f"{ROCM_DIR}/lib/rocprofiler-sdk/rocprofv3-trigger-list-metrics"]
elif not app_args:
log_config(app_env)
fatal_error("No application provided")
pmc_lines = []
if args.input:
pmc_lines = parse_input(args.input)
if args.kernel_include_regex:
update_env(
"ROCPROF_KERNEL_FILTER_INCLUDE_REGEX",
args.kernel_include_regex,
)
if pmc_lines:
exit_code = 0
update_env("ROCPROF_COUNTER_COLLECTION", True, overwrite_if_true=True)
if args.kernel_exclude_regex:
update_env(
"ROCPROF_KERNEL_FILTER_EXCLUDE_REGEX",
args.kernel_exclude_regex,
)
for idx, pmc_line in enumerate(pmc_lines):
COUNTER = idx + 1
pmc_env = dict(app_env)
pmc_env["ROCPROF_COUNTERS"] = f"pmc: {pmc_line}"
pmc_env["ROCPROF_OUTPUT_PATH"] = os.path.join(
f"{_output_path}", f"pmc_{COUNTER}"
)
if args.kernel_iteration_range:
update_env("ROCPROF_KERNEL_FILTER_RANGE", ", ".join(args.kernel_iteration_range))
if args.log_level in ("info", "trace"):
log_config(pmc_env)
try:
exit_code = subprocess.check_call(app_args, env=pmc_env)
if exit_code != 0:
fatal_error("Application exited with non-zero exit code", exit_code)
except Exception as e:
fatal_error(f"{e}\n")
return exit_code
if args.pmc:
update_env("ROCPROF_COUNTER_COLLECTION", True, overwrite=True)
update_env(
"ROCPROF_COUNTERS", "pmc: {}".format(" ".join(args.pmc)), overwrite=True
)
else:
if args.log_level in ("info", "trace"):
log_config(app_env)
update_env("ROCPROF_COUNTER_COLLECTION", False, overwrite=True)
if args.log_level in ("info", "trace", "env"):
log_config(app_env)
if use_execv:
# does not return
os.execvpe(app_args[0], app_args, env=app_env)
else:
try:
exit_code = subprocess.check_call(app_args, env=app_env)
if exit_code != 0:
fatal_error("Application exited with non-zero exit code", exit_code)
except Exception as e:
fatal_error(f"{e}\n")
return exit_code
def main(argv=None):
cmd_args, app_args = parse_arguments(argv)
inp_args = (
parse_input(cmd_args.input) if getattr(cmd_args, "input") else [dotdict({})]
)
if len(inp_args) == 1:
args = get_args(cmd_args, inp_args[0])
pass_idx = None
if hasattr(args, "pmc") and args.pmc is not None and len(args.pmc) > 0:
pass_idx = 1
run(app_args, args, pass_id=pass_idx)
else:
for idx, itr in enumerate(inp_args):
args = get_args(cmd_args, itr)
run(
app_args,
args,
pass_id=(idx + 1),
use_execv=False,
)
if __name__ == "__main__":