print_exc_plus.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. import inspect
  2. import os
  3. import re
  4. import sys
  5. import traceback
  6. from itertools import islice
  7. from pickle import PickleError
  8. from typing import Sized, Dict, Tuple, Type
  9. from types import FrameType
  10. from tool_lib.threading_timer_decorator import exit_after
  11. try:
  12. import numpy
  13. except ImportError:
  14. numpy = None
  15. FORMATTING_OPTIONS = {
  16. 'MAX_LINE_LENGTH': 1024,
  17. 'SHORT_LINE_THRESHOLD': 128,
  18. 'MAX_NEWLINES': 20,
  19. }
  20. ID = int
  21. # noinspection PyPep8Naming
  22. def name_or_str(X):
  23. try:
  24. return re.search(r"<class '?(.*?)'?>", str(X))[1]
  25. except TypeError: # if not found
  26. return str(X)
  27. @exit_after(2)
  28. def type_string(x):
  29. if numpy is not None and isinstance(x, numpy.ndarray):
  30. return name_or_str(type(x)) + str(x.shape)
  31. elif isinstance(x, Sized):
  32. return name_or_str(type(x)) + f'({len(x)})'
  33. else:
  34. return name_or_str(type(x))
  35. @exit_after(2)
  36. def to_string_with_timeout(x):
  37. return str(x)
  38. def nth_index(iterable, value, n):
  39. matches = (idx for idx, val in enumerate(iterable) if val == value)
  40. return next(islice(matches, n - 1, n), None)
  41. class DumpingException(Exception):
  42. pass
  43. class SubclassNotFound(ImportError):
  44. pass
  45. def subclass_by_name(name: str, base: Type):
  46. candidates = [t for t in base.__subclasses__() if t.__name__ == name]
  47. if len(candidates) != 1:
  48. raise SubclassNotFound()
  49. return candidates[0]
  50. def dont_import():
  51. raise ImportError
  52. loaded_custom_picklers = False
  53. def load_custom_picklers():
  54. global loaded_custom_picklers
  55. if loaded_custom_picklers:
  56. return
  57. print('Loading custom picklers...')
  58. typing_types = ['Dict', 'List', 'Set', 'Tuple', 'Callable', 'Optional']
  59. for unpicklable_type in [
  60. 'from zmq import Socket as unpicklable',
  61. 'from zmq import Context as unpicklable',
  62. 'from sqlite3 import Connection as unpicklable',
  63. 'from sqlite3 import Cursor as unpicklable',
  64. 'from socket import socket as unpicklable',
  65. 'from tensorflow import Tensor as unpicklable',
  66. 'from tensorflow.python.types.core import Tensor as unpicklable',
  67. # 'from tensorflow.keras import Model as unpicklable',
  68. 'from tensorflow.python.eager.def_function import Function as unpicklable',
  69. 'from tensorflow.python.keras.utils.object_identity import _ObjectIdentityWrapper as unpicklable',
  70. # Next line: pybind11_builtins.pybind11_object
  71. 'from tensorflow.python._pywrap_tfe import TFE_MonitoringBoolGauge0;unpicklable=TFE_MonitoringBoolGauge0.__base__',
  72. 'unpicklable = subclass_by_name(\\"PyCapsule\\", object)',
  73. 'unpicklable = subclass_by_name(\\"_CData\\", object)',
  74. 'from h5py import HLObject as unpicklable',
  75. 'from builtins import memoryview as unpicklable',
  76. # can't pickle type annotations from typing in python <= 3.6 (Next line: typing._TypingBase)
  77. 'import sys;dont_import() if sys.version >=\\"3.7\\" else None;from typing import Optional;unpicklable = type(Optional).__base__.__base__',
  78. *[f'import sys;dont_import() if sys.version >=\\"3.7\\" else None;from typing import {t};unpicklable = type({t})' for t in typing_types],
  79. 'import sys;dont_import() if sys.version >=\\"3.7\\" else None;from typing import Dict;unpicklable = type(Dict).__base__',
  80. 'import inspect;unpicklable = type(inspect.stack()[0].frame)',
  81. # can't pickle thread-local data
  82. 'from threading import local as unpicklable',
  83. # can't pickle generator objects
  84. 'unpicklable = type(_ for _ in [])',
  85. ]:
  86. try:
  87. unpicklable = eval(f'exec("{unpicklable_type}") or unpicklable')
  88. except ImportError:
  89. pass
  90. except TypeError as e :
  91. if 'Descriptors cannot not be created directly' in str(e):
  92. pass
  93. else:
  94. raise
  95. else:
  96. register_unpicklable(unpicklable, also_subclasses=True)
  97. finally:
  98. unpicklable = None
  99. loaded_custom_picklers = True
  100. def register_unpicklable(unpicklable: Type, also_subclasses=False):
  101. import dill
  102. @dill.register(unpicklable)
  103. def save_unpicklable(pickler, obj):
  104. def recreate_unpicklable():
  105. return f'This was something that could not be pickled and instead was replaced with this string'
  106. recreate_unpicklable.__name__ = f'unpicklable_{unpicklable.__name__}'
  107. pickler.save_reduce(recreate_unpicklable, (), obj=obj)
  108. if also_subclasses:
  109. if unpicklable.__subclasses__ is type.__subclasses__:
  110. subclasses = []
  111. else:
  112. subclasses = unpicklable.__subclasses__()
  113. for subclass in subclasses:
  114. register_unpicklable(subclass, also_subclasses=True)
  115. def dump_stack_to_file(serialize_to, print=print, stack=None):
  116. if stack is None:
  117. stack = inspect.stack()[1:]
  118. try:
  119. import dill
  120. except ModuleNotFoundError:
  121. print('Dill not available. Not dumping stack.')
  122. else:
  123. print('Dumping stack...')
  124. load_custom_picklers()
  125. serializable_stack = []
  126. for frame in stack:
  127. if isinstance(frame, inspect.FrameInfo):
  128. frame = frame.frame
  129. serializable_stack.append({
  130. k: frame.__getattribute__(k)
  131. for k in ['f_globals', 'f_locals', 'f_code', 'f_lineno', 'f_lasti']
  132. })
  133. with open(serialize_to, 'wb') as serialize_to_file:
  134. try:
  135. print(f'Dumping stack...')
  136. dill.dump(serializable_stack, serialize_to_file)
  137. except (PickleError, RecursionError) as e:
  138. print(f'Was not able to dump the stack. Error {type(e)}: {e}')
  139. unpicklable_frames = []
  140. for frame_idx in range(len(serializable_stack)):
  141. try:
  142. dill.dumps(serializable_stack[frame_idx])
  143. except (PickleError, RecursionError):
  144. unpicklable_frames.append(frame_idx)
  145. print(f'Unpicklable frames (top=0, bottom={len(serializable_stack)}):', unpicklable_frames)
  146. if 'typing.' in str(e):
  147. print('This might be fixed by upgrading to python 3.7 or above.')
  148. else:
  149. print(f'Dumped stack. Can be loaded with:')
  150. print(f'with open(r"{os.path.abspath(serialize_to)}", "rb") as f: import dill; dill._dill._reverse_typemap["ClassType"] = type; stack = dill.load(f)')
  151. if os.path.isfile(serialize_to):
  152. from lib.util import backup_file
  153. backup_file(serialize_to)
  154. def print_exc_plus(print=print, serialize_to=None, print_trace=True):
  155. """
  156. Print the usual traceback information, followed by a listing of all the
  157. local variables in each frame.
  158. """
  159. limit = FORMATTING_OPTIONS['MAX_LINE_LENGTH']
  160. max_newlines = FORMATTING_OPTIONS['MAX_NEWLINES']
  161. tb = sys.exc_info()[2]
  162. if numpy is not None:
  163. options = numpy.get_printoptions()
  164. numpy.set_printoptions(precision=3, edgeitems=2, floatmode='maxprec', threshold=20, linewidth=120)
  165. else:
  166. options = {}
  167. stack = []
  168. long_printed_objs: Dict[ID, Tuple[str, FrameType]] = {}
  169. while tb:
  170. stack.append(tb.tb_frame)
  171. tb = tb.tb_next
  172. if print_trace:
  173. for frame in stack:
  174. if frame is not stack[0]:
  175. print('-' * 40)
  176. try:
  177. print("Frame %s in %s at line %s" % (frame.f_code.co_name,
  178. os.path.relpath(frame.f_code.co_filename),
  179. frame.f_lineno))
  180. except ValueError: # if path is not relative
  181. print("Frame %s in %s at line %s" % (frame.f_code.co_name,
  182. frame.f_code.co_filename,
  183. frame.f_lineno))
  184. for key, value in frame.f_locals.items():
  185. # We have to be careful not to cause a new error in our error
  186. # printer! Calling str() on an unknown object could cause an
  187. # error we don't want.
  188. # noinspection PyBroadException
  189. try:
  190. key_string = to_string_with_timeout(key)
  191. except KeyboardInterrupt:
  192. key_string = "<TIMEOUT WHILE PRINTING KEY>"
  193. except Exception:
  194. key_string = "<ERROR WHILE PRINTING KEY>"
  195. # noinspection PyBroadException
  196. try:
  197. type_as_string = type_string(value)
  198. except KeyboardInterrupt:
  199. type_as_string = "<TIMEOUT WHILE PRINTING TYPE>"
  200. except Exception as e:
  201. # noinspection PyBroadException
  202. try:
  203. type_as_string = f"<{type(e).__name__} WHILE PRINTING TYPE>"
  204. except Exception:
  205. type_as_string = "<ERROR WHILE PRINTING TYPE>"
  206. if id(value) in long_printed_objs:
  207. prev_key_string, prev_frame = long_printed_objs[id(value)]
  208. if prev_frame is frame:
  209. print("\t%s is the same as '%s'" %
  210. (key_string + ' : ' + type_as_string,
  211. prev_key_string))
  212. else:
  213. print("\t%s is the same as '%s' in frame %s in %s at line %s." %
  214. (key_string + ' : ' + type_as_string,
  215. prev_key_string,
  216. prev_frame.f_code.co_name,
  217. os.path.relpath(prev_frame.f_code.co_filename),
  218. prev_frame.f_lineno))
  219. continue
  220. # noinspection PyBroadException
  221. try:
  222. value_string = to_string_with_timeout(value)
  223. except KeyboardInterrupt:
  224. value_string = "<TIMEOUT WHILE PRINTING VALUE>"
  225. except Exception:
  226. value_string = "<ERROR WHILE PRINTING VALUE>"
  227. line: str = '\t' + key_string + ' : ' + type_as_string + ' = ' + value_string
  228. if limit is not None and len(line) > limit:
  229. line = line[:limit - 1] + '...'
  230. if max_newlines is not None and line.count('\n') > max_newlines:
  231. line = line[:nth_index(line, '\n', max_newlines)].strip() + '... (' + str(
  232. line[nth_index(line, '\n', max_newlines):].count('\n')) + ' more lines)'
  233. if len(line) > FORMATTING_OPTIONS['SHORT_LINE_THRESHOLD']:
  234. long_printed_objs[id(value)] = key_string, frame
  235. print(line)
  236. traceback.print_exc()
  237. etype, value, tb = sys.exc_info()
  238. for line in traceback.TracebackException(type(value), value, tb, limit=limit).format(chain=True):
  239. print(line)
  240. if serialize_to is not None:
  241. dump_stack_to_file(stack=stack, serialize_to=serialize_to, print=print)
  242. if numpy is not None:
  243. numpy.set_printoptions(**options)
  244. def main():
  245. def fun1(c, d, e):
  246. return fun2(c, d + e)
  247. def fun2(g, h):
  248. raise RuntimeError
  249. def fun3(z):
  250. return numpy.zeros(shape=z)
  251. try:
  252. import numpy as np
  253. fun1(numpy.random.normal(size=(3, 4, 5, 6)), '12321', '123')
  254. data = '???' * 100
  255. fun3(data)
  256. except:
  257. print_exc_plus()
  258. if __name__ == '__main__':
  259. main()