# Copyright 2019 HTCondor Team, Computer Sciences Department,
# University of Wisconsin-Madison, WI.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import textwrap
[docs]
class ComponentError:
"""
Represents an error experienced by a map component during remote execution.
Attributes
----------
map : htmap.Map
The :class:`htmap.Map` the component is a part of.
component : int
The component index from the map.
exception_msg : str
The raw message string from the remote exception.
node_info : tuple
A tuple containing information about the HTCondor execute node the component ran on.
python_info : tuple
A tuple containing information about the Python installation on the execute node.
scratch_dir_contents : List[pathlib.Path]
A list of paths in the scratch directory on the execute node.
stack_summary : traceback.StackSummary
The Python stack frames at the time of execution, excluding HTMap's own stack frame.
"""
def __init__(
self,
*,
map,
component: int,
exception_msg: str,
node_info,
python_info,
scratch_dir_contents,
stack_summary,
):
self.map = map
self.component = component
self.exception_msg = exception_msg
self.node_info = node_info
self.python_info = python_info
self.scratch_dir_contents = scratch_dir_contents
self.stack_summary = stack_summary
def __repr__(self):
return f"<{self.__class__.__name__}(map = {self.map}, component = {self.component})>"
@classmethod
def _from_raw_error(cls, map, error):
"""Construct a :class:`ComponentError` from a raw component error returned from an execute node."""
return cls(
map=map,
component=error.component,
exception_msg=error.exception_msg,
node_info=error.node_info,
python_info=error.python_info,
scratch_dir_contents=error.scratch_dir_contents,
stack_summary=error.stack_summary,
)
def _indent(self, text, multiple=1):
return textwrap.indent(text, " " * 2 * multiple)
def _format_stack_trace(self):
# modified from https://github.com/python/cpython/blob/3.7/Lib/traceback.py
_RECURSIVE_CUTOFF = 3
result = []
last_file = None
last_line = None
last_name = None
count = 0
for frame in self.stack_summary:
if (
last_file is None
or last_file != frame.filename
or last_line is None
or last_line != frame.lineno
or last_name is None
or last_name != frame.name
):
if count > _RECURSIVE_CUTOFF:
count -= _RECURSIVE_CUTOFF
result.append(
self._indent(
f' [Previous line repeated {count} more time{"s" if count > 1 else ""}]\n'
)
)
last_file = frame.filename
last_line = frame.lineno
last_name = frame.name
count = 0
count += 1
if count > _RECURSIVE_CUTOFF:
continue
row = []
row.append(
self._indent(f'File "{frame.filename}", line {frame.lineno}, in {frame.name}\n')
)
if frame.line:
row.append(self._indent(f"{frame.line.strip()}\n", multiple=2))
row.append(self._indent("\nLocal variables:\n", multiple=2))
if frame.locals:
for name, value in sorted(frame.locals.items()):
v = str(value)
if len(v) > 45:
v = v[:20] + " ... " + v[-20:]
row.append(self._indent(f"{name} = {v}\n", multiple=3))
result.append("".join(row))
if count > _RECURSIVE_CUTOFF:
count -= _RECURSIVE_CUTOFF
result.append(
self._indent(
f'[Previous line repeated {count} more time{"s" if count > 1 else ""}]\n'
)
)
return result
[docs]
def report(self) -> str:
"""
Return a formatted error report.
The raw information in this report is available in the attributes of this class.
"""
lines = [
f" Start error report for component {self.component} of map {self.map.tag} ".center(
80, "="
),
"Landed on execute node {} ({}) at {}".format(*self.node_info),
]
if self.python_info is not None:
executable, version, packages = self.python_info
lines.append(f"\nPython executable is {executable} (version {version})")
lines.append(f"with installed packages")
lines.append(self._indent(packages))
else:
lines.append("\nPython executable information not available")
lines.append("\nScratch directory contents are")
for path in self.scratch_dir_contents:
lines.append(self._indent(path))
lines.append("\nException and traceback (most recent call last):")
lines.extend(self._format_stack_trace())
lines.append(self._indent(self.exception_msg, multiple=1))
lines.append("")
lines.append(
f" End error report for component {self.component} of map {self.map.tag} ".center(
80, "="
)
)
return "\n".join(lines)