Allow ThreadTrace to save to a file (#1998)
* allow ThreadTrace to save to a file * make sure we close our output file * fix the egregious errors in the file - no mutable default args - import things before using them - variable named obj did not exist, but try/except ignored it * if it's dumb, and it works, it's not dumb * linux requires a flush; remove commented code * always close the file handle, even if its stdout * it may work for us, but just in case, protect with finally * turns out: lots of things like to use stdout! Co-authored-by: Luke Campagnola <lukec@alleninstitute.org> Co-authored-by: Ogi Moore <ognyan.moore@gmail.com>
This commit is contained in:
parent
e8b77a11e5
commit
a6bb2c6edd
|
@ -9,6 +9,7 @@ Distributed under MIT/X11 license. See license.txt for more information.
|
|||
from __future__ import print_function
|
||||
|
||||
import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile, threading
|
||||
import contextlib
|
||||
import warnings
|
||||
from time import perf_counter
|
||||
from numpy import ndarray
|
||||
|
@ -21,6 +22,17 @@ if sys.version.startswith("3.8") and QT_LIB == "PySide2":
|
|||
from .util.mutex import Mutex
|
||||
|
||||
|
||||
# credit to Wolph: https://stackoverflow.com/a/17603000
|
||||
@contextlib.contextmanager
|
||||
def open_maybe_console(filename=None):
|
||||
fh = sys.stdout if filename is None else open(filename, "w", encoding='utf-8')
|
||||
try:
|
||||
yield fh
|
||||
finally:
|
||||
if fh is not sys.stdout:
|
||||
fh.close()
|
||||
|
||||
|
||||
__ftraceDepth = 0
|
||||
def ftrace(func):
|
||||
"""Decorator used for marking the beginning and end of function calls.
|
||||
|
@ -167,13 +179,15 @@ def listObjs(regex='Q', typ=None):
|
|||
|
||||
|
||||
|
||||
def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ignore=None):
|
||||
def findRefPath(startObj, endObj, maxLen=8, restart=True, seen=None, path=None, ignore=None):
|
||||
"""Determine all paths of object references from startObj to endObj"""
|
||||
refs = []
|
||||
if path is None:
|
||||
path = [endObj]
|
||||
if ignore is None:
|
||||
ignore = {}
|
||||
if seen is None:
|
||||
seen = {}
|
||||
ignore[id(sys._getframe())] = None
|
||||
ignore[id(path)] = None
|
||||
ignore[id(seen)] = None
|
||||
|
@ -582,6 +596,7 @@ class Profiler(object):
|
|||
|
||||
def profile(code, name='profile_run', sort='cumulative', num=30):
|
||||
"""Common-use for cProfile"""
|
||||
import pstats
|
||||
cProfile.run(code, name)
|
||||
stats = pstats.Stats(name)
|
||||
stats.sort_stats(sort)
|
||||
|
@ -800,7 +815,7 @@ class ObjTracker(object):
|
|||
continue
|
||||
|
||||
try:
|
||||
ref = weakref.ref(obj)
|
||||
ref = weakref.ref(o)
|
||||
except:
|
||||
ref = None
|
||||
refs[oid] = ref
|
||||
|
@ -1132,10 +1147,11 @@ class ThreadTrace(object):
|
|||
Used to debug freezing by starting a new thread that reports on the
|
||||
location of other threads periodically.
|
||||
"""
|
||||
def __init__(self, interval=10.0):
|
||||
def __init__(self, interval=10.0, logFile=None):
|
||||
self.interval = interval
|
||||
self.lock = Mutex()
|
||||
self._stop = False
|
||||
self.logFile = logFile
|
||||
self.start()
|
||||
|
||||
def stop(self):
|
||||
|
@ -1151,35 +1167,41 @@ class ThreadTrace(object):
|
|||
self.thread.start()
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
with self.lock:
|
||||
if self._stop is True:
|
||||
return
|
||||
|
||||
print("\n============= THREAD FRAMES: ================")
|
||||
for id, frame in sys._current_frames().items():
|
||||
if id == threading.current_thread().ident:
|
||||
continue
|
||||
iter = 0
|
||||
with open_maybe_console(self.logFile) as printFile:
|
||||
while True:
|
||||
with self.lock:
|
||||
if self._stop is True:
|
||||
return
|
||||
|
||||
# try to determine a thread name
|
||||
try:
|
||||
name = threading._active.get(id, None)
|
||||
except:
|
||||
name = None
|
||||
if name is None:
|
||||
printFile.write(f"\n============= THREAD FRAMES {iter}: ================\n")
|
||||
for id, frame in sys._current_frames().items():
|
||||
if id == threading.current_thread().ident:
|
||||
continue
|
||||
|
||||
# try to determine a thread name
|
||||
try:
|
||||
# QThread._names must be manually set by thread creators.
|
||||
name = QtCore.QThread._names.get(id)
|
||||
name = threading._active.get(id, None)
|
||||
except:
|
||||
name = None
|
||||
if name is None:
|
||||
name = "???"
|
||||
if name is None:
|
||||
try:
|
||||
# QThread._names must be manually set by thread creators.
|
||||
name = QtCore.QThread._names.get(id)
|
||||
except:
|
||||
name = None
|
||||
if name is None:
|
||||
name = "???"
|
||||
|
||||
print("<< thread %d \"%s\" >>" % (id, name))
|
||||
traceback.print_stack(frame)
|
||||
print("===============================================\n")
|
||||
|
||||
time.sleep(self.interval)
|
||||
printFile.write("<< thread %d \"%s\" >>\n" % (id, name))
|
||||
tb = str(''.join(traceback.format_stack(frame)))
|
||||
printFile.write(tb)
|
||||
printFile.write("\n")
|
||||
printFile.write("===============================================\n\n")
|
||||
printFile.flush()
|
||||
|
||||
iter += 1
|
||||
time.sleep(self.interval)
|
||||
|
||||
|
||||
class ThreadColor(object):
|
||||
|
@ -1229,4 +1251,3 @@ def enableFaulthandler():
|
|||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
|
Loading…
Reference in New Issue