166 lines
6.1 KiB
Python
166 lines
6.1 KiB
Python
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
|
|
#
|
|
# Use of this software is governed by the terms and conditions of the
|
|
# NVIDIA End User License Agreement (EULA), available at:
|
|
# https://docs.nvidia.com/cutlass/media/docs/pythonDSL/license.html
|
|
#
|
|
# Any use, reproduction, disclosure, or distribution of this software
|
|
# and related documentation outside the scope permitted by the EULA
|
|
# is strictly prohibited.
|
|
|
|
"""
|
|
This module provides stacktrace helper functions
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
|
|
|
|
def walk_to_top_module(start_path):
|
|
"""
|
|
Walk up from the start_path to find the top-level Python module.
|
|
|
|
:param start_path: The path to start from.
|
|
:return: The path of the top-level module.
|
|
"""
|
|
current_path = start_path
|
|
|
|
while True:
|
|
# Check if we are at the root directory
|
|
if os.path.dirname(current_path) == current_path:
|
|
break
|
|
|
|
# Check for __init__.py
|
|
init_file_path = os.path.join(current_path, "__init__.py")
|
|
if os.path.isfile(init_file_path):
|
|
# If __init__.py exists, move up one level
|
|
current_path = os.path.dirname(current_path)
|
|
else:
|
|
# If no __init__.py, we are not in a module; stop
|
|
break
|
|
|
|
# If we reached the root without finding a module, return None
|
|
if os.path.dirname(current_path) == current_path and not os.path.isfile(
|
|
os.path.join(current_path, "__init__.py")
|
|
):
|
|
return None
|
|
|
|
# Return the path of the top-level module
|
|
return current_path
|
|
|
|
|
|
def _filter_internal_frames(traceback, internal_path):
|
|
"""
|
|
Filter out stack frames from the traceback that belong to the specified module path.
|
|
|
|
This function removes stack frames from the traceback whose file paths start with
|
|
the given prefix_path, effectively hiding internal implementation details from
|
|
the error traceback shown to users.
|
|
"""
|
|
iter_prev = None
|
|
iter_tb = traceback
|
|
while iter_tb is not None:
|
|
if os.path.abspath(iter_tb.tb_frame.f_code.co_filename).startswith(
|
|
internal_path
|
|
):
|
|
if iter_tb.tb_next:
|
|
if iter_prev:
|
|
iter_prev.tb_next = iter_tb.tb_next
|
|
else:
|
|
traceback = iter_tb.tb_next
|
|
else:
|
|
iter_prev = iter_tb
|
|
iter_tb = iter_tb.tb_next
|
|
return traceback
|
|
|
|
|
|
_generated_function_names = re.compile(
|
|
r"^(loop_body|while_region|while_before_block|while_after_block|if_region|then_block|else_block|elif_region)_\d+$"
|
|
)
|
|
|
|
|
|
def _filter_duplicated_frames(traceback):
|
|
"""
|
|
Filter out duplicated stack frames from the traceback.
|
|
The function filters out consecutive frames that are in the same file and have the same line number.
|
|
In a sequence of consecutive frames, the logic prefers to keep the non-generated frame or the last frame.
|
|
"""
|
|
iter_prev = None
|
|
iter_tb = traceback
|
|
while iter_tb is not None:
|
|
skip_current = False
|
|
skip_next = False
|
|
if iter_tb.tb_next:
|
|
current_filename = os.path.abspath(iter_tb.tb_frame.f_code.co_filename)
|
|
next_filename = os.path.abspath(iter_tb.tb_next.tb_frame.f_code.co_filename)
|
|
# if in the same file, check if the line number is the same
|
|
if current_filename == next_filename:
|
|
current_lineno = iter_tb.tb_lineno
|
|
next_lineno = iter_tb.tb_next.tb_lineno
|
|
if current_lineno == next_lineno:
|
|
# Same file and line number, check name, if current is generated, skip current, otherwise skip next
|
|
name = iter_tb.tb_frame.f_code.co_name
|
|
is_generated = bool(_generated_function_names.match(name))
|
|
if is_generated:
|
|
# Skip current
|
|
skip_current = True
|
|
else:
|
|
# Skip next if it's generated, otherwise keep both
|
|
next_name = iter_tb.tb_next.tb_frame.f_code.co_name
|
|
skip_next = bool(_generated_function_names.match(next_name))
|
|
if skip_current:
|
|
if iter_prev:
|
|
iter_prev.tb_next = iter_tb.tb_next
|
|
else:
|
|
traceback = iter_tb.tb_next
|
|
elif skip_next:
|
|
# if next is last frame, don't skip
|
|
if iter_tb.tb_next.tb_next:
|
|
iter_tb.tb_next = iter_tb.tb_next.tb_next
|
|
iter_prev = iter_tb
|
|
else:
|
|
iter_prev = iter_tb
|
|
iter_tb = iter_tb.tb_next
|
|
|
|
return traceback
|
|
|
|
|
|
def filter_stackframe(traceback, prefix_path):
|
|
"""
|
|
Filter out stack frames from the traceback that belong to the specified module path.
|
|
|
|
This function removes stack frames from the traceback whose file paths start with
|
|
the given prefix_path, effectively hiding internal implementation details from
|
|
the error traceback shown to users.
|
|
|
|
:param traceback: The traceback object to filter.
|
|
:param prefix_path: The path prefix to filter out from the traceback.
|
|
:return: The filtered traceback with internal frames removed.
|
|
"""
|
|
# Step 1: filter internal frames
|
|
traceback = _filter_internal_frames(traceback, prefix_path)
|
|
|
|
# Step 2: consolidate duplicated frames
|
|
return _filter_duplicated_frames(traceback)
|
|
|
|
|
|
def filter_exception(value, module_dir):
|
|
"""
|
|
Filter out internal implementation details from exception traceback.
|
|
|
|
This function recursively processes an exception and its cause chain,
|
|
removing stack frames that belong to the specified module directory.
|
|
This helps to present cleaner error messages to users by hiding
|
|
implementation details.
|
|
|
|
:param value: The exception object to filter.
|
|
:param module_dir: The module directory path to filter out from tracebacks.
|
|
:return: The filtered exception with internal frames removed.
|
|
"""
|
|
if hasattr(value, "__cause__") and value.__cause__:
|
|
filter_exception(value.__cause__, module_dir)
|
|
|
|
if hasattr(value, "__traceback__"):
|
|
filter_stackframe(value.__traceback__, module_dir)
|