|
@@ -1,6 +1,6 @@
|
|
|
from direct.directnotify import DirectNotifyGlobal
|
|
from direct.directnotify import DirectNotifyGlobal
|
|
|
from direct.showbase import PythonUtil
|
|
from direct.showbase import PythonUtil
|
|
|
-from direct.showbase.PythonUtil import POD
|
|
|
|
|
|
|
+from direct.showbase.TaskThreaded import TaskThreaded
|
|
|
import gc
|
|
import gc
|
|
|
|
|
|
|
|
class FakeObject:
|
|
class FakeObject:
|
|
@@ -12,7 +12,7 @@ def _createGarbage():
|
|
|
a.other = b
|
|
a.other = b
|
|
|
b.other = a
|
|
b.other = a
|
|
|
|
|
|
|
|
-class GarbageReport:
|
|
|
|
|
|
|
+class GarbageReport(TaskThreaded):
|
|
|
"""Detects leaked Python objects (via gc.collect()) and reports on garbage
|
|
"""Detects leaked Python objects (via gc.collect()) and reports on garbage
|
|
|
items, garbage-to-garbage references, and garbage cycles.
|
|
items, garbage-to-garbage references, and garbage cycles.
|
|
|
If you just want to dump the report to the log, use GarbageLogger."""
|
|
If you just want to dump the report to the log, use GarbageLogger."""
|
|
@@ -20,9 +20,23 @@ class GarbageReport:
|
|
|
|
|
|
|
|
NotGarbage = 'NG'
|
|
NotGarbage = 'NG'
|
|
|
|
|
|
|
|
- def __init__(self, name=None, log=True, verbose=False, fullReport=False, findCycles=True):
|
|
|
|
|
|
|
+ def __init__(self, name, log=True, verbose=False, fullReport=False, findCycles=True,
|
|
|
|
|
+ threaded=False, doneCallback=None):
|
|
|
# if log is True, GarbageReport will self-destroy after logging
|
|
# if log is True, GarbageReport will self-destroy after logging
|
|
|
# if false, caller is responsible for calling destroy()
|
|
# if false, caller is responsible for calling destroy()
|
|
|
|
|
+ # if threaded is True, processing will be performed over multiple frames
|
|
|
|
|
+ TaskThreaded.__init__(self, name, threaded)
|
|
|
|
|
+ # stick the arguments onto a ScratchPad so we can access them from the thread
|
|
|
|
|
+ # functions and delete them all at once
|
|
|
|
|
+ self._args = ScratchPad()
|
|
|
|
|
+ self._args.name = name
|
|
|
|
|
+ self._args.log = log
|
|
|
|
|
+ self._args.verbose = verbose
|
|
|
|
|
+ self._args.fullReport = fullReport
|
|
|
|
|
+ self._args.findCycles = findCycles
|
|
|
|
|
+ self._args.doneCallback = doneCallback
|
|
|
|
|
+
|
|
|
|
|
+ # do the garbage collection
|
|
|
wasOn = PythonUtil.gcDebugOn()
|
|
wasOn = PythonUtil.gcDebugOn()
|
|
|
oldFlags = gc.get_debug()
|
|
oldFlags = gc.get_debug()
|
|
|
if not wasOn:
|
|
if not wasOn:
|
|
@@ -37,14 +51,17 @@ class GarbageReport:
|
|
|
|
|
|
|
|
self.numGarbage = len(self.garbage)
|
|
self.numGarbage = len(self.garbage)
|
|
|
|
|
|
|
|
- if verbose:
|
|
|
|
|
|
|
+ if self._args.verbose:
|
|
|
self.notify.info('found %s garbage items' % self.numGarbage)
|
|
self.notify.info('found %s garbage items' % self.numGarbage)
|
|
|
|
|
|
|
|
|
|
+ self.scheduleNext(self.T_getReferrers)
|
|
|
|
|
+ def T_getReferrers(self):
|
|
|
|
|
+
|
|
|
# grab the referrers (pointing to garbage)
|
|
# grab the referrers (pointing to garbage)
|
|
|
self.referrersByReference = {}
|
|
self.referrersByReference = {}
|
|
|
self.referrersByNumber = {}
|
|
self.referrersByNumber = {}
|
|
|
- if fullReport:
|
|
|
|
|
- if verbose:
|
|
|
|
|
|
|
+ if self._args.fullReport:
|
|
|
|
|
+ if self._args.verbose:
|
|
|
self.notify.info('getting referrers...')
|
|
self.notify.info('getting referrers...')
|
|
|
# we need referents to detect cycles, but we don't need referrers
|
|
# we need referents to detect cycles, but we don't need referrers
|
|
|
for i in xrange(self.numGarbage):
|
|
for i in xrange(self.numGarbage):
|
|
@@ -52,23 +69,32 @@ class GarbageReport:
|
|
|
self.referrersByNumber[i] = byNum
|
|
self.referrersByNumber[i] = byNum
|
|
|
self.referrersByReference[i] = byRef
|
|
self.referrersByReference[i] = byRef
|
|
|
|
|
|
|
|
|
|
+ self.scheduleNext(self.T_getReferents)
|
|
|
|
|
+ def T_getReferents(self):
|
|
|
|
|
+
|
|
|
# grab the referents (pointed to by garbage)
|
|
# grab the referents (pointed to by garbage)
|
|
|
self.referentsByReference = {}
|
|
self.referentsByReference = {}
|
|
|
self.referentsByNumber = {}
|
|
self.referentsByNumber = {}
|
|
|
- if verbose:
|
|
|
|
|
|
|
+ if self._args.verbose:
|
|
|
self.notify.info('getting referents...')
|
|
self.notify.info('getting referents...')
|
|
|
for i in xrange(self.numGarbage):
|
|
for i in xrange(self.numGarbage):
|
|
|
byNum, byRef = self._getReferents(self.garbage[i])
|
|
byNum, byRef = self._getReferents(self.garbage[i])
|
|
|
self.referentsByNumber[i] = byNum
|
|
self.referentsByNumber[i] = byNum
|
|
|
self.referentsByReference[i] = byRef
|
|
self.referentsByReference[i] = byRef
|
|
|
|
|
|
|
|
|
|
+ self.scheduleNext(self.T_getCycles)
|
|
|
|
|
+ def T_getCycles(self):
|
|
|
|
|
+
|
|
|
# find the cycles
|
|
# find the cycles
|
|
|
- if findCycles and self.numGarbage > 0:
|
|
|
|
|
- if verbose:
|
|
|
|
|
|
|
+ if self._args.findCycles and self.numGarbage > 0:
|
|
|
|
|
+ if self._args.verbose:
|
|
|
self.notify.info('detecting cycles...')
|
|
self.notify.info('detecting cycles...')
|
|
|
self.cycles = self._getCycles()
|
|
self.cycles = self._getCycles()
|
|
|
|
|
|
|
|
- s = '\n===== GarbageReport: \'%s\' (%s items) =====' % (name, self.numGarbage)
|
|
|
|
|
|
|
+ self.scheduleNext(self.T_createReport)
|
|
|
|
|
+ def T_createReport(self):
|
|
|
|
|
+
|
|
|
|
|
+ s = '\n===== GarbageReport: \'%s\' (%s items) =====' % (self._args.name, self.numGarbage)
|
|
|
if self.numGarbage > 0:
|
|
if self.numGarbage > 0:
|
|
|
# log each individual item with a number in front of it
|
|
# log each individual item with a number in front of it
|
|
|
s += '\n\n===== Garbage Items ====='
|
|
s += '\n\n===== Garbage Items ====='
|
|
@@ -81,13 +107,13 @@ class GarbageReport:
|
|
|
for i in range(len(self.garbage)):
|
|
for i in range(len(self.garbage)):
|
|
|
s += format % (i, type(self.garbage[i]), self.garbage[i])
|
|
s += format % (i, type(self.garbage[i]), self.garbage[i])
|
|
|
|
|
|
|
|
- if findCycles:
|
|
|
|
|
|
|
+ if self._args.findCycles:
|
|
|
format = '\n%s'
|
|
format = '\n%s'
|
|
|
s += '\n\n===== Cycles ====='
|
|
s += '\n\n===== Cycles ====='
|
|
|
for cycle in self.cycles:
|
|
for cycle in self.cycles:
|
|
|
s += format % cycle
|
|
s += format % cycle
|
|
|
|
|
|
|
|
- if fullReport:
|
|
|
|
|
|
|
+ if self._args.fullReport:
|
|
|
format = '\n%0' + '%s' % digits + 'i:%s'
|
|
format = '\n%0' + '%s' % digits + 'i:%s'
|
|
|
s += '\n\n===== Referrers By Number (what is referring to garbage item?) ====='
|
|
s += '\n\n===== Referrers By Number (what is referring to garbage item?) ====='
|
|
|
for i in xrange(self.numGarbage):
|
|
for i in xrange(self.numGarbage):
|
|
@@ -103,19 +129,21 @@ class GarbageReport:
|
|
|
s += format % (i, self.referentsByReference[i])
|
|
s += format % (i, self.referentsByReference[i])
|
|
|
|
|
|
|
|
self._report = s
|
|
self._report = s
|
|
|
- if log:
|
|
|
|
|
- self.notify.info(self._report)
|
|
|
|
|
|
|
|
|
|
- def getNumItems(self):
|
|
|
|
|
- return self.numGarbage
|
|
|
|
|
|
|
+ self.scheduleNext(self.T_printReport)
|
|
|
|
|
+ def T_printReport(self):
|
|
|
|
|
|
|
|
- def getGarbage(self):
|
|
|
|
|
- return self.garbage
|
|
|
|
|
|
|
+ if self._args.log:
|
|
|
|
|
+ self.notify.info(self._report)
|
|
|
|
|
|
|
|
- def getReport(self):
|
|
|
|
|
- return self._report
|
|
|
|
|
|
|
+ self.scheduleNext(self.T_completed)
|
|
|
|
|
+ def T_completed(self):
|
|
|
|
|
+
|
|
|
|
|
+ if self._args.doneCallback:
|
|
|
|
|
+ self._args.doneCallback(self)
|
|
|
|
|
|
|
|
def destroy(self):
|
|
def destroy(self):
|
|
|
|
|
+ del self._args
|
|
|
del self.garbage
|
|
del self.garbage
|
|
|
del self.numGarbage
|
|
del self.numGarbage
|
|
|
del self.referrersByReference
|
|
del self.referrersByReference
|
|
@@ -126,6 +154,15 @@ class GarbageReport:
|
|
|
del self.cycles
|
|
del self.cycles
|
|
|
del self._report
|
|
del self._report
|
|
|
|
|
|
|
|
|
|
+ def getNumItems(self):
|
|
|
|
|
+ return self.numGarbage
|
|
|
|
|
+
|
|
|
|
|
+ def getGarbage(self):
|
|
|
|
|
+ return self.garbage
|
|
|
|
|
+
|
|
|
|
|
+ def getReport(self):
|
|
|
|
|
+ return self._report
|
|
|
|
|
+
|
|
|
def _getReferrers(self, obj):
|
|
def _getReferrers(self, obj):
|
|
|
# referrers (pointing to garbage)
|
|
# referrers (pointing to garbage)
|
|
|
# returns two lists, first by index into gc.garbage, second by
|
|
# returns two lists, first by index into gc.garbage, second by
|
|
@@ -203,4 +240,6 @@ class GarbageLogger(GarbageReport):
|
|
|
def __init__(self, *args, **kArgs):
|
|
def __init__(self, *args, **kArgs):
|
|
|
kArgs['log'] = True
|
|
kArgs['log'] = True
|
|
|
GarbageReport.__init__(self, *args, **kArgs)
|
|
GarbageReport.__init__(self, *args, **kArgs)
|
|
|
|
|
+ def T_completed(self):
|
|
|
|
|
+ GarbageReport.T_completed(self)
|
|
|
self.destroy()
|
|
self.destroy()
|