mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-01 17:35:34 -04:00
more leak detection
This commit is contained in:
parent
a764c4e37c
commit
6f5a7d0bc6
@ -1,6 +1,8 @@
|
||||
# objects that report different types of leaks to the ContainerLeakDetector
|
||||
|
||||
from pandac.PandaModules import *
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
from direct.showbase.Job import Job
|
||||
import __builtin__, gc
|
||||
|
||||
class LeakDetector:
|
||||
@ -10,6 +12,8 @@ class LeakDetector:
|
||||
if not hasattr(__builtin__, "leakDetectors"):
|
||||
__builtin__.leakDetectors = {}
|
||||
self._leakDetectorsKey = self.getLeakDetectorKey()
|
||||
if __dev__:
|
||||
assert self._leakDetectorsKey not in leakDetectors
|
||||
leakDetectors[self._leakDetectorsKey] = self
|
||||
def destroy(self):
|
||||
del leakDetectors[self._leakDetectorsKey]
|
||||
@ -89,7 +93,7 @@ class _TaskNamePatternLeakDetector(LeakDetector, TaskLeakDetectorBase):
|
||||
return numTasks
|
||||
|
||||
def getLeakDetectorKey(self):
|
||||
return '%s-%s' % (self._taskNamePattern, LeakDetector.getLeakDetectorKey(self))
|
||||
return '%s-%s' % (self._taskNamePattern, self.__class__.__name__)
|
||||
|
||||
class TaskLeakDetector(LeakDetector, TaskLeakDetectorBase):
|
||||
# tracks the number task 'types' and creates leak detectors for each task type
|
||||
@ -110,6 +114,7 @@ class TaskLeakDetector(LeakDetector, TaskLeakDetectorBase):
|
||||
self._taskName2collector[namePattern] = _TaskNamePatternLeakDetector(namePattern)
|
||||
|
||||
def __len__(self):
|
||||
self._taskName2collector = {}
|
||||
# update our table of task leak detectors
|
||||
for task in taskMgr.getTasks():
|
||||
self._processTaskName(task.name)
|
||||
@ -117,3 +122,174 @@ class TaskLeakDetector(LeakDetector, TaskLeakDetectorBase):
|
||||
self._processTaskName(task.name)
|
||||
# are we leaking task types?
|
||||
return len(self._taskName2collector)
|
||||
|
||||
class MessageLeakDetectorBase:
|
||||
def _getMessageNamePattern(self, msgName):
|
||||
# get a generic string pattern from a message name by removing numeric characters
|
||||
for i in xrange(10):
|
||||
msgName = msgName.replace('%s' % i, '')
|
||||
return msgName
|
||||
|
||||
class _MessageTypeLeakDetector(LeakDetector, MessageLeakDetectorBase):
|
||||
# tracks the number of objects that are listening to each message
|
||||
def __init__(self, msgNamePattern):
|
||||
self._msgNamePattern = msgNamePattern
|
||||
self._msgNames = set()
|
||||
LeakDetector.__init__(self)
|
||||
|
||||
def addMsgName(self, msgName):
|
||||
# for efficiency, we keep the actual message names around
|
||||
# for queries on the messenger
|
||||
self._msgNames.add(msgName)
|
||||
|
||||
def __len__(self):
|
||||
toRemove = set()
|
||||
num = 0
|
||||
for msgName in self._msgNames:
|
||||
n = messenger._getNumListeners(msgName)
|
||||
if n == 0:
|
||||
toRemove.add(msgName)
|
||||
else:
|
||||
num += n
|
||||
# remove message names that are no longer in the messenger
|
||||
self._msgNames.difference_update(toRemove)
|
||||
return num
|
||||
|
||||
def getLeakDetectorKey(self):
|
||||
return '%s-%s' % (self._msgNamePattern, self.__class__.__name__)
|
||||
|
||||
class _MessageTypeLeakDetectorCreator(Job):
|
||||
def __init__(self, creator):
|
||||
Job.__init__(self, uniqueName(typeName(self)))
|
||||
self._creator = creator
|
||||
|
||||
def destroy(self):
|
||||
self._creator = None
|
||||
Job.destroy(self)
|
||||
|
||||
def finished(self):
|
||||
Job.finished(self)
|
||||
|
||||
def run(self):
|
||||
for msgName in messenger._getEvents():
|
||||
yield None
|
||||
namePattern = self._creator._getMessageNamePattern(msgName)
|
||||
if namePattern not in self._creator._msgName2detector:
|
||||
self._creator._msgName2detector[namePattern] = _MessageTypeLeakDetector(namePattern)
|
||||
self._creator._msgName2detector[namePattern].addMsgName(msgName)
|
||||
yield Job.Done
|
||||
|
||||
class MessageTypesLeakDetector(LeakDetector, MessageLeakDetectorBase):
|
||||
def __init__(self):
|
||||
LeakDetector.__init__(self)
|
||||
self._msgName2detector = {}
|
||||
self._createJob = None
|
||||
if config.GetBool('leak-message-types', 0):
|
||||
self._leakers = []
|
||||
self._leakTaskName = uniqueName('leak-message-types')
|
||||
taskMgr.add(self._leak, self._leakTaskName)
|
||||
|
||||
def _leak(self, task):
|
||||
self._leakers.append(DirectObject())
|
||||
self._leakers[-1].accept('leak-msg', self._leak)
|
||||
return task.cont
|
||||
|
||||
def destroy(self):
|
||||
if hasattr(self, '_leakTaskName'):
|
||||
taskMgr.remove(self._leakTaskName)
|
||||
for leaker in self._leakers:
|
||||
leaker.ignoreAll()
|
||||
self._leakers = None
|
||||
if self._createJob:
|
||||
self._createJob.destroy()
|
||||
self._createJob = None
|
||||
for msgName, detector in self._msgName2detector.iteritems():
|
||||
detector.destroy()
|
||||
del self._msgName2detector
|
||||
LeakDetector.destroy(self)
|
||||
|
||||
def __len__(self):
|
||||
if self._createJob:
|
||||
if self._createJob.isFinished():
|
||||
self._createJob.destroy()
|
||||
self._createJob = None
|
||||
self._createJob = _MessageTypeLeakDetectorCreator(self)
|
||||
jobMgr.add(self._createJob)
|
||||
# are we leaking message types?
|
||||
return len(self._msgName2detector)
|
||||
|
||||
class _MessageListenerTypeLeakDetector(LeakDetector):
|
||||
# tracks the number of each object type that is listening for events
|
||||
def __init__(self, typeName):
|
||||
self._typeName = typeName
|
||||
LeakDetector.__init__(self)
|
||||
|
||||
def __len__(self):
|
||||
numObjs = 0
|
||||
for obj in messenger._getObjects():
|
||||
if typeName(obj) == self._typeName:
|
||||
numObjs += 1
|
||||
return numObjs
|
||||
|
||||
def getLeakDetectorKey(self):
|
||||
return '%s-%s' % (self._typeName, self.__class__.__name__)
|
||||
|
||||
class _MessageListenerTypeLeakDetectorCreator(Job):
|
||||
def __init__(self, creator):
|
||||
Job.__init__(self, uniqueName(typeName(self)))
|
||||
self._creator = creator
|
||||
|
||||
def destroy(self):
|
||||
self._creator = None
|
||||
Job.destroy(self)
|
||||
|
||||
def finished(self):
|
||||
Job.finished(self)
|
||||
|
||||
def run(self):
|
||||
for obj in messenger._getObjects():
|
||||
yield None
|
||||
tName = typeName(obj)
|
||||
if tName not in self._creator._typeName2detector:
|
||||
self._creator._typeName2detector[tName] = (
|
||||
_MessageListenerTypeLeakDetector(tName))
|
||||
yield Job.Done
|
||||
|
||||
class MessageListenerTypesLeakDetector(LeakDetector):
|
||||
def __init__(self):
|
||||
LeakDetector.__init__(self)
|
||||
self._typeName2detector = {}
|
||||
self._createJob = None
|
||||
if config.GetBool('leak-message-listeners', 0):
|
||||
self._leakers = []
|
||||
self._leakTaskName = uniqueName('leak-message-listeners')
|
||||
taskMgr.add(self._leak, self._leakTaskName)
|
||||
|
||||
def _leak(self, task):
|
||||
self._leakers.append(DirectObject())
|
||||
self._leakers[-1].accept(uniqueName('leak-msg-listeners'), self._leak)
|
||||
return task.cont
|
||||
|
||||
def destroy(self):
|
||||
if hasattr(self, '_leakTaskName'):
|
||||
taskMgr.remove(self._leakTaskName)
|
||||
for leaker in self._leakers:
|
||||
leaker.ignoreAll()
|
||||
self._leakers = None
|
||||
if self._createJob:
|
||||
self._createJob.destroy()
|
||||
self._createJob = None
|
||||
for typeName, detector in self._typeName2detector.iteritems():
|
||||
detector.destroy()
|
||||
del self._typeName2detector
|
||||
LeakDetector.destroy(self)
|
||||
|
||||
def __len__(self):
|
||||
if self._createJob:
|
||||
if self._createJob.isFinished():
|
||||
self._createJob.destroy()
|
||||
self._createJob = None
|
||||
self._createJob = _MessageListenerTypeLeakDetectorCreator(self)
|
||||
jobMgr.add(self._createJob)
|
||||
# are we leaking listener types?
|
||||
return len(self._typeName2detector)
|
||||
|
@ -61,7 +61,7 @@ class Messenger:
|
||||
# accept/ignore more than once over their lifetime)
|
||||
# get unique messenger id for this object
|
||||
if not hasattr(object, '_messengerId'):
|
||||
object._messengerId = self._messengerIdGen
|
||||
object._messengerId = (object.__class__.__name__, self._messengerIdGen)
|
||||
self._messengerIdGen += 1
|
||||
return object._messengerId
|
||||
|
||||
@ -77,6 +77,18 @@ class Messenger:
|
||||
def _getObject(self, id):
|
||||
return self._id2object[id][1]
|
||||
|
||||
def _getObjects(self):
|
||||
objs = []
|
||||
for refCount, obj in self._id2object.itervalues():
|
||||
objs.append(obj)
|
||||
return objs
|
||||
|
||||
def _getNumListeners(self, event):
|
||||
return len(self.__callbacks.get(event, {}))
|
||||
|
||||
def _getEvents(self):
|
||||
return self.__callbacks.keys()
|
||||
|
||||
def _releaseObject(self, object):
|
||||
# assumes lock is held.
|
||||
id = self._getMessengerId(object)
|
||||
|
@ -31,7 +31,8 @@ __all__ = ['enumerate', 'unique', 'indent', 'nonRepeatingRandomList',
|
||||
'printStack', 'printReverseStack', 'listToIndex2item', 'listToItem2index',
|
||||
'pandaBreak','pandaTrace','formatTimeCompact','DestructiveScratchPad',
|
||||
'deeptype','getProfileResultString','StdoutCapture','StdoutPassthrough',
|
||||
'Averager', 'getRepository', 'formatTimeExact', 'startSuperLog', 'endSuperLog' ]
|
||||
'Averager', 'getRepository', 'formatTimeExact', 'startSuperLog', 'endSuperLog',
|
||||
'typeName', ]
|
||||
|
||||
import types
|
||||
import string
|
||||
@ -3852,6 +3853,12 @@ def configIsToday(configName):
|
||||
return True
|
||||
return False
|
||||
|
||||
def typeName(o):
|
||||
if hasattr(o, '__class__'):
|
||||
return o.__class__.__name__
|
||||
else:
|
||||
return o.__name__
|
||||
|
||||
import __builtin__
|
||||
__builtin__.Functor = Functor
|
||||
__builtin__.Stack = Stack
|
||||
@ -3905,3 +3912,4 @@ __builtin__.deeptype = deeptype
|
||||
__builtin__.Default = Default
|
||||
__builtin__.isInteger = isInteger
|
||||
__builtin__.configIsToday = configIsToday
|
||||
__builtin__.typeName = typeName
|
||||
|
Loading…
x
Reference in New Issue
Block a user