stop using gc.DEBUG_SAVEALL (not necessary), GarbageReport output readability improvements

This commit is contained in:
Darren Ranalli 2008-12-05 01:50:55 +00:00
parent 04ed456dbe
commit fb1aa31b78
4 changed files with 70 additions and 46 deletions

View File

@ -98,10 +98,6 @@ class ConnectionRepository(
self._serverAddress = '' self._serverAddress = ''
if self.config.GetBool('want-debug-leak', 1):
import gc
gc.set_debug(gc.DEBUG_SAVEALL)
def generateGlobalObject(self, doId, dcname, values=None): def generateGlobalObject(self, doId, dcname, values=None):
def applyFieldValues(distObj, dclass, values): def applyFieldValues(distObj, dclass, values):
for i in range(dclass.getNumInheritedFields()): for i in range(dclass.getNumInheritedFields()):

View File

@ -3,13 +3,14 @@
__all__ = ['FakeObject', '_createGarbage', 'GarbageReport', 'GarbageLogger'] __all__ = ['FakeObject', '_createGarbage', 'GarbageReport', 'GarbageLogger']
from direct.directnotify.DirectNotifyGlobal import directNotify from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.showbase.PythonUtil import gcDebugOn, safeRepr, fastRepr, printListEnumGen, printNumberedTypesGen from direct.showbase.PythonUtil import safeRepr, fastRepr, printListEnumGen, printNumberedTypesGen
from direct.showbase.PythonUtil import AlphabetCounter from direct.showbase.PythonUtil import AlphabetCounter
from direct.showbase.Job import Job from direct.showbase.Job import Job
import gc import gc
import types import types
class FakeObject: class FakeObject:
def __del__(self):
pass pass
def _createGarbage(num=1): def _createGarbage(num=1):
@ -46,12 +47,39 @@ class GarbageReport(Job):
def run(self): def run(self):
# do the garbage collection # do the garbage collection
wasOn = gcDebugOn()
oldFlags = gc.get_debug() oldFlags = gc.get_debug()
if not wasOn:
# do a collect without SAVEALL, to get the instances that define __del__
# cycles that do not involve any instances that define __del__ are cleaned up
# automatically by Python, but they also appear in gc.garbage when SAVEALL is set
gc.set_debug(0)
gc.collect()
garbageInstances = gc.garbage[:]
del gc.garbage[:]
# only yield if there's more time-consuming work to do,
# if there's no garbage, give instant feedback
if len(garbageInstances) > 0:
yield None
# don't repr the garbage list if we don't have to
if self.notify.getDebug():
self.notify.debug('garbageInstances == %s' % fastRepr(garbageInstances))
self.numGarbageInstances = len(garbageInstances)
# grab the ids of the garbage instances (objects with __del__)
self.garbageInstanceIds = set()
for i in xrange(len(garbageInstances)):
self.garbageInstanceIds.add(id(garbageInstances[i]))
if not (i % 20):
yield None
# then release the list of instances so that it doesn't interfere with the gc.collect() below
del garbageInstances
# do a SAVEALL pass so that we have all of the objects involved in legitimate garbage cycles
# without SAVEALL, gc.garbage only contains objects with __del__ methods
gc.set_debug(gc.DEBUG_SAVEALL) gc.set_debug(gc.DEBUG_SAVEALL)
gc.collect() gc.collect()
self.garbage = list(gc.garbage) self.garbage = gc.garbage[:]
del gc.garbage[:]
# only yield if there's more time-consuming work to do, # only yield if there's more time-consuming work to do,
# if there's no garbage, give instant feedback # if there's no garbage, give instant feedback
if len(self.garbage) > 0: if len(self.garbage) > 0:
@ -59,8 +87,6 @@ class GarbageReport(Job):
# don't repr the garbage list if we don't have to # don't repr the garbage list if we don't have to
if self.notify.getDebug(): if self.notify.getDebug():
self.notify.debug('self.garbage == %s' % fastRepr(self.garbage)) self.notify.debug('self.garbage == %s' % fastRepr(self.garbage))
del gc.garbage[:]
if not wasOn:
gc.set_debug(oldFlags) gc.set_debug(oldFlags)
self.numGarbage = len(self.garbage) self.numGarbage = len(self.garbage)
@ -70,7 +96,7 @@ class GarbageReport(Job):
yield None yield None
if self._args.verbose: if self._args.verbose:
self.notify.info('found %s garbage items' % self.numGarbage) self.notify.info('found %s garbage instances' % self.numGarbageInstances)
""" spammy """ spammy
# print the types of the garbage first, in case the repr of an object # print the types of the garbage first, in case the repr of an object
@ -102,7 +128,7 @@ class GarbageReport(Job):
yield None yield None
# grab the referrers (pointing to garbage) # grab the referrers (pointing to garbage)
if self._args.fullReport and (self.numGarbage != 0): if self._args.fullReport and (self.numGarbageInstances != 0):
if self._args.verbose: if self._args.verbose:
self.notify.info('getting referrers...') self.notify.info('getting referrers...')
for i in xrange(self.numGarbage): for i in xrange(self.numGarbage):
@ -114,7 +140,7 @@ class GarbageReport(Job):
self.referrersByReference[i] = byRef self.referrersByReference[i] = byRef
# grab the referents (pointed to by garbage) # grab the referents (pointed to by garbage)
if self.numGarbage > 0: if self.numGarbageInstances > 0:
if self._args.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):
@ -126,7 +152,7 @@ class GarbageReport(Job):
self.referentsByReference[i] = byRef self.referentsByReference[i] = byRef
# find the cycles # find the cycles
if self._args.findCycles and self.numGarbage > 0: if self._args.findCycles and self.numGarbageInstances > 0:
if self._args.verbose: if self._args.verbose:
self.notify.info('detecting cycles...') self.notify.info('detecting cycles...')
for i in xrange(self.numGarbage): for i in xrange(self.numGarbage):
@ -221,12 +247,13 @@ class GarbageReport(Job):
self.cycleIds.update(set(cycle)) self.cycleIds.update(set(cycle))
if self._args.findCycles: if self._args.findCycles:
s = ['===== GarbageReport: \'%s\' (%s items, %s cycles) =====' % ( s = ['===== GarbageReport: \'%s\' (%s %s) =====' % (
self._args.name, self.numGarbage, len(self.cycles))] self._args.name, len(self.cycles),
choice(len(self.cycles) == 1, 'cycle', 'cycles'))]
else: else:
s = ['===== GarbageReport: \'%s\' (%s items) =====' % ( s = ['===== GarbageReport: \'%s\' =====' % (
self._args.name, self.numGarbage)] self._args.name)]
if self.numGarbage > 0: if self.numGarbageInstances > 0:
# make a list of the ids we will actually be printing # make a list of the ids we will actually be printing
if self._args.fullReport: if self._args.fullReport:
garbageIndices = range(self.numGarbage) garbageIndices = range(self.numGarbage)
@ -277,14 +304,14 @@ class GarbageReport(Job):
s.append(format % (idx, itype(self.garbage[idx]), objStr)) s.append(format % (idx, itype(self.garbage[idx]), objStr))
if self._args.findCycles: if self._args.findCycles:
s.append('===== Garbage Cycles By Index =====') s.append('===== Garbage Cycles (Garbage Item Numbers) =====')
ac = AlphabetCounter() ac = AlphabetCounter()
for i in xrange(len(self.cycles)): for i in xrange(len(self.cycles)):
yield None yield None
s.append('%s:%s' % (ac.next(), self.cycles[i])) s.append('%s:%s' % (ac.next(), self.cycles[i]))
if self._args.findCycles: if self._args.findCycles:
s.append('===== Garbage Cycles By Python Syntax =====') s.append('===== Garbage Cycles (Python Syntax) =====')
ac = AlphabetCounter() ac = AlphabetCounter()
for i in xrange(len(self.cyclesBySyntax)): for i in xrange(len(self.cyclesBySyntax)):
yield None yield None
@ -314,7 +341,7 @@ class GarbageReport(Job):
if self._args.log: if self._args.log:
self.printingBegin() self.printingBegin()
for i in xrange(len(self._report)): for i in xrange(len(self._report)):
if self.numGarbage > 0: if self.numGarbageInstances > 0:
yield None yield None
self.notify.info(self._report[i]) self.notify.info(self._report[i])
self.printingEnd() self.printingEnd()
@ -418,15 +445,18 @@ class GarbageReport(Job):
uniqueCycleSets = set() uniqueCycleSets = set()
stateStack = Stack() stateStack = Stack()
rootId = index rootId = index
stateStack.push(([rootId], rootId, 0)) # check if the root object is one of the garbage instances (has __del__)
objId = id(self.garbage[rootId])
numDelInstances = choice(objId in self.garbageInstanceIds, 1, 0)
stateStack.push(([rootId], rootId, numDelInstances, 0))
while True: while True:
yield None yield None
if len(stateStack) == 0: if len(stateStack) == 0:
break break
candidateCycle, curId, resumeIndex = stateStack.pop() candidateCycle, curId, numDelInstances, resumeIndex = stateStack.pop()
if self.notify.getDebug(): if self.notify.getDebug():
print 'restart: %s root=%s cur=%s resume=%s' % ( print 'restart: %s root=%s cur=%s numDelInstances=%s resume=%s' % (
candidateCycle, rootId, curId, resumeIndex) candidateCycle, rootId, curId, numDelInstances, resumeIndex)
for index in xrange(resumeIndex, len(self.referentsByNumber[curId])): for index in xrange(resumeIndex, len(self.referentsByNumber[curId])):
yield None yield None
refId = self.referentsByNumber[curId][index] refId = self.referentsByNumber[curId][index]
@ -437,6 +467,9 @@ class GarbageReport(Job):
normCandidateCycle = self._getNormalizedCycle(candidateCycle) normCandidateCycle = self._getNormalizedCycle(candidateCycle)
normCandidateCycleTuple = tuple(normCandidateCycle) normCandidateCycleTuple = tuple(normCandidateCycle)
if not normCandidateCycleTuple in uniqueCycleSets: if not normCandidateCycleTuple in uniqueCycleSets:
# cycles with no instances that define __del__ will be
# cleaned up by Python
if numDelInstances >= 1:
if self.notify.getDebug(): if self.notify.getDebug():
print ' FOUND: ', normCandidateCycle + [normCandidateCycle[0],] print ' FOUND: ', normCandidateCycle + [normCandidateCycle[0],]
cycles.append(normCandidateCycle + [normCandidateCycle[0],]) cycles.append(normCandidateCycle + [normCandidateCycle[0],])
@ -444,11 +477,14 @@ class GarbageReport(Job):
elif refId in candidateCycle: elif refId in candidateCycle:
pass pass
elif refId is not None: elif refId is not None:
# check if this object is one of the garbage instances (has __del__)
objId = id(self.garbage[refId])
numDelInstances += choice(objId in self.garbageInstanceIds, 1, 0)
# this refId does not complete a cycle. Mark down # this refId does not complete a cycle. Mark down
# where we are in this list of referents, then # where we are in this list of referents, then
# start looking through the referents of the new refId # start looking through the referents of the new refId
stateStack.push((list(candidateCycle), curId, index+1)) stateStack.push((list(candidateCycle), curId, numDelInstances, index+1))
stateStack.push((list(candidateCycle) + [refId], refId, 0)) stateStack.push((list(candidateCycle) + [refId], refId, numDelInstances, 0))
break break
yield cycles yield cycles

View File

@ -1,7 +1,6 @@
# objects that report different types of leaks to the ContainerLeakDetector # objects that report different types of leaks to the ContainerLeakDetector
from pandac.PandaModules import * from pandac.PandaModules import *
from direct.showbase.PythonUtil import gcDebugOn
import __builtin__, gc import __builtin__, gc
class LeakDetector: class LeakDetector:
@ -24,14 +23,11 @@ class GarbageLeakDetector(LeakDetector):
# are we accumulating Python garbage? # are we accumulating Python garbage?
def __len__(self): def __len__(self):
# do a garbage collection # do a garbage collection
wasOn = gcDebugOn()
oldFlags = gc.get_debug() oldFlags = gc.get_debug()
if not wasOn: gc.set_debug(0)
gc.set_debug(gc.DEBUG_SAVEALL)
gc.collect() gc.collect()
numGarbage = len(gc.garbage) numGarbage = len(gc.garbage)
del gc.garbage[:] del gc.garbage[:]
if not wasOn:
gc.set_debug(oldFlags) gc.set_debug(oldFlags)
return numGarbage return numGarbage

View File

@ -18,7 +18,7 @@ __all__ = ['enumerate', 'unique', 'indent', 'nonRepeatingRandomList',
'clampScalar', 'weightedChoice', 'randFloat', 'normalDistrib', 'clampScalar', 'weightedChoice', 'randFloat', 'normalDistrib',
'weightedRand', 'randUint31', 'randInt32', 'randUint32', 'weightedRand', 'randUint31', 'randInt32', 'randUint32',
'SerialNumGen', 'serialNum', 'uniqueName', 'Enum', 'Singleton', 'SerialNumGen', 'serialNum', 'uniqueName', 'Enum', 'Singleton',
'SingletonError', 'printListEnum', 'gcDebugOn', 'safeRepr', 'SingletonError', 'printListEnum', 'safeRepr',
'fastRepr', 'tagRepr', 'tagWithCaller', 'isDefaultValue', 'set_trace', 'pm', 'fastRepr', 'tagRepr', 'tagWithCaller', 'isDefaultValue', 'set_trace', 'pm',
'ScratchPad', 'Sync', 'RefCounter', 'itype', 'getNumberedTypedString', 'ScratchPad', 'Sync', 'RefCounter', 'itype', 'getNumberedTypedString',
'getNumberedTypedSortedString', 'getNumberedTypedSortedStringWithReferrers', 'getNumberedTypedSortedString', 'getNumberedTypedSortedStringWithReferrers',
@ -2444,10 +2444,6 @@ def printListEnum(l):
for result in printListEnumGen(l): for result in printListEnumGen(l):
pass pass
def gcDebugOn():
import gc
return (gc.get_debug() & gc.DEBUG_SAVEALL) == gc.DEBUG_SAVEALL
# base class for all Panda C++ objects # base class for all Panda C++ objects
# libdtoolconfig doesn't seem to have this, grab it off of PandaNode # libdtoolconfig doesn't seem to have this, grab it off of PandaNode
dtoolSuperBase = None dtoolSuperBase = None