| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- from direct.directnotify import DirectNotifyGlobal
- from direct.showbase import PythonUtil
- from direct.showbase.TaskThreaded import TaskThreaded
- import gc
- class FakeObject:
- pass
- def _createGarbage():
- a = FakeObject()
- b = FakeObject()
- a.other = b
- b.other = a
- class GarbageReport(TaskThreaded):
- """Detects leaked Python objects (via gc.collect()) and reports on garbage
- items, garbage-to-garbage references, and garbage cycles.
- If you just want to dump the report to the log, use GarbageLogger."""
- notify = DirectNotifyGlobal.directNotify.newCategory("GarbageReport")
- NotGarbage = 'NG'
- 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 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()
- oldFlags = gc.get_debug()
- if not wasOn:
- gc.set_debug(gc.DEBUG_SAVEALL)
- gc.collect()
- self.notify.debug('gc.garbage == %s' % gc.garbage)
- self.garbage = list(gc.garbage)
- self.notify.debug('self.garbage == %s' % self.garbage)
- del gc.garbage[:]
- if not wasOn:
- gc.set_debug(oldFlags)
- self.numGarbage = len(self.garbage)
- if self._args.verbose:
- self.notify.info('found %s garbage items' % self.numGarbage)
- self.scheduleNext(self.T_getReferrers)
- def T_getReferrers(self):
- # grab the referrers (pointing to garbage)
- self.referrersByReference = {}
- self.referrersByNumber = {}
- if self._args.fullReport:
- if self._args.verbose:
- self.notify.info('getting referrers...')
- # we need referents to detect cycles, but we don't need referrers
- for i in xrange(self.numGarbage):
- byNum, byRef = self._getReferrers(self.garbage[i])
- self.referrersByNumber[i] = byNum
- self.referrersByReference[i] = byRef
- self.scheduleNext(self.T_getReferents)
- def T_getReferents(self):
- # grab the referents (pointed to by garbage)
- self.referentsByReference = {}
- self.referentsByNumber = {}
- if self._args.verbose:
- self.notify.info('getting referents...')
- for i in xrange(self.numGarbage):
- byNum, byRef = self._getReferents(self.garbage[i])
- self.referentsByNumber[i] = byNum
- self.referentsByReference[i] = byRef
- self.scheduleNext(self.T_getCycles)
- def T_getCycles(self):
- # find the cycles
- if self._args.findCycles and self.numGarbage > 0:
- if self._args.verbose:
- self.notify.info('detecting cycles...')
- self.cycles = self._getCycles()
- self.scheduleNext(self.T_createReport)
- def T_createReport(self):
- s = '\n===== GarbageReport: \'%s\' (%s items) =====' % (self._args.name, self.numGarbage)
- if self.numGarbage > 0:
- # log each individual item with a number in front of it
- s += '\n\n===== Garbage Items ====='
- digits = 0
- n = self.numGarbage
- while n > 0:
- digits += 1
- n /= 10
- format = '\n%0' + '%s' % digits + 'i:%s \t%s'
- for i in range(len(self.garbage)):
- s += format % (i, type(self.garbage[i]), self.garbage[i])
- if self._args.findCycles:
- format = '\n%s'
- s += '\n\n===== Cycles ====='
- for cycle in self.cycles:
- s += format % cycle
- if self._args.fullReport:
- format = '\n%0' + '%s' % digits + 'i:%s'
- s += '\n\n===== Referrers By Number (what is referring to garbage item?) ====='
- for i in xrange(self.numGarbage):
- s += format % (i, self.referrersByNumber[i])
- s += '\n\n===== Referents By Number (what is garbage item referring to?) ====='
- for i in xrange(self.numGarbage):
- s += format % (i, self.referentsByNumber[i])
- s += '\n\n===== Referrers (what is referring to garbage item?) ====='
- for i in xrange(self.numGarbage):
- s += format % (i, self.referrersByReference[i])
- s += '\n\n===== Referents (what is garbage item referring to?) ====='
- for i in xrange(self.numGarbage):
- s += format % (i, self.referentsByReference[i])
- self._report = s
- self.scheduleNext(self.T_printReport)
- def T_printReport(self):
- if self._args.log:
- self.notify.info(self._report)
- self.scheduleNext(self.T_completed)
- def T_completed(self):
- if self._args.doneCallback:
- self._args.doneCallback(self)
- def destroy(self):
- del self._args
- del self.garbage
- del self.numGarbage
- del self.referrersByReference
- del self.referrersByNumber
- del self.referentsByReference
- del self.referentsByNumber
- if hasattr(self, 'cycles'):
- del self.cycles
- 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):
- # referrers (pointing to garbage)
- # returns two lists, first by index into gc.garbage, second by
- # direct reference
- byRef = gc.get_referrers(obj)
- # look to see if each referrer is another garbage item
- byNum = []
- for referrer in byRef:
- try:
- num = self.garbage.index(referrer)
- byNum.append(num)
- except:
- #num = GarbageReport.NotGarbage
- pass
- return byNum, byRef
- def _getReferents(self, obj):
- # referents (pointed to by garbage)
- # returns two lists, first by index into gc.garbage, second by
- # direct reference
- byRef = gc.get_referents(obj)
- # look to see if each referent is another garbage item
- byNum = []
- for referent in byRef:
- try:
- num = self.garbage.index(referent)
- byNum.append(num)
- except:
- #num = GarbageReport.NotGarbage
- pass
- return byNum, byRef
- def _getCycles(self):
- assert self.notify.debugCall()
- # returns list of lists, sublists are garbage reference cycles
- cycles = []
- # sets of cycle members, to avoid duplicates
- cycleSets = []
- stateStack = Stack()
- for rootId in xrange(len(self.garbage)):
- assert len(stateStack) == 0
- stateStack.push(([rootId], rootId, 0))
- while True:
- if len(stateStack) == 0:
- break
- candidateCycle, curId, resumeIndex = stateStack.pop()
- if self.notify.getDebug():
- print 'restart: %s root=%s cur=%s resume=%s' % (
- candidateCycle, rootId, curId, resumeIndex)
- for index in xrange(resumeIndex, len(self.referentsByNumber[curId])):
- refId = self.referentsByNumber[curId][index]
- if self.notify.getDebug():
- print ' : %s -> %s' % (curId, refId)
- if refId == rootId:
- # we found a cycle! mark it down and move on to the next refId
- if not set(candidateCycle) in cycleSets:
- if self.notify.getDebug():
- print ' FOUND: ', list(candidateCycle) + [refId]
- cycles.append(list(candidateCycle) + [refId])
- cycleSets.append(set(candidateCycle))
- elif refId in candidateCycle:
- pass
- else:
- # this refId does not complete a cycle. Mark down
- # where we are in this list of referents, then
- # start looking through the referents of the new refId
- stateStack.push((list(candidateCycle), curId, index+1))
- stateStack.push((list(candidateCycle) + [refId], refId, 0))
- break
- return cycles
- class GarbageLogger(GarbageReport):
- """If you just want to log the current garbage to the log file, make
- one of these. It automatically destroys itself after logging"""
- def __init__(self, *args, **kArgs):
- kArgs['log'] = True
- GarbageReport.__init__(self, *args, **kArgs)
- def T_completed(self):
- GarbageReport.T_completed(self)
- self.destroy()
|