"""Contains objects that report different types of leaks to the
ContainerLeakDetector.
"""
from panda3d.core import *
from direct.showbase.DirectObject import DirectObject
from direct.showbase.Job import Job
import gc, sys
if sys.version_info >= (3, 0):
import builtins
else:
import __builtin__ as builtins
[docs]class LeakDetector:
[docs] def __init__(self):
# put this object just under __builtins__ where the
# ContainerLeakDetector will find it quickly
if not hasattr(builtins, "leakDetectors"):
builtins.leakDetectors = {}
self._leakDetectorsKey = self.getLeakDetectorKey()
if __dev__:
assert self._leakDetectorsKey not in leakDetectors
leakDetectors[self._leakDetectorsKey] = self
[docs] def destroy(self):
del leakDetectors[self._leakDetectorsKey]
[docs] def getLeakDetectorKey(self):
# this string will be shown to the end user and should ideally contain enough information to
# point to what is leaking
return '%s-%s' % (self.__class__.__name__, id(self))
[docs]class ObjectTypeLeakDetector(LeakDetector):
[docs] def __init__(self, otld, objType, generation):
self._otld = otld
self._objType = objType
self._generation = generation
LeakDetector.__init__(self)
[docs] def destroy(self):
self._otld = None
LeakDetector.destroy(self)
[docs] def getLeakDetectorKey(self):
return '%s-%s' % (self._objType, self.__class__.__name__)
def __len__(self):
num = self._otld._getNumObjsOfType(self._objType, self._generation)
self._generation = self._otld._getGeneration()
return num
[docs]class ObjectTypesLeakDetector(LeakDetector):
# are we accumulating any particular Python object type?
[docs] def __init__(self):
LeakDetector.__init__(self)
self._type2ld = {}
self._type2count = {}
self._generation = 0
self._thisLdGen = 0
[docs] def destroy(self):
for ld in self._type2ld.values():
ld.destroy()
LeakDetector.destroy(self)
def _recalc(self):
objs = gc.get_objects()
self._type2count = {}
for obj in objs:
objType = safeTypeName(obj)
if objType not in self._type2ld:
self._type2ld[objType] = ObjectTypeLeakDetector(self, objType, self._generation)
self._type2count.setdefault(objType, 0)
self._type2count[objType] += 1
self._generation += 1
def _getGeneration(self):
return self._generation
def _getNumObjsOfType(self, objType, otherGen):
if self._generation == otherGen:
self._recalc()
return self._type2count.get(objType, 0)
def __len__(self):
if self._generation == self._thisLdGen:
self._recalc()
self._thisLdGen = self._generation
return len(self._type2count)
[docs]class GarbageLeakDetector(LeakDetector):
# are we accumulating Python garbage?
def __len__(self):
# do a garbage collection
oldFlags = gc.get_debug()
gc.set_debug(0)
gc.collect()
numGarbage = len(gc.garbage)
del gc.garbage[:]
gc.set_debug(oldFlags)
return numGarbage
[docs]class SceneGraphLeakDetector(LeakDetector):
# is a scene graph leaking nodes?
[docs] def __init__(self, render):
LeakDetector.__init__(self)
self._render = render
if config.GetBool('leak-scene-graph', 0):
self._leakTaskName = 'leakNodes-%s' % serialNum()
self._leakNode()
[docs] def destroy(self):
if hasattr(self, '_leakTaskName'):
taskMgr.remove(self._leakTaskName)
del self._render
LeakDetector.destroy(self)
def __len__(self):
try:
# this will be available when the build server finishes
return self._render.countNumDescendants()
except:
return self._render.getNumDescendants()
def __repr__(self):
return 'SceneGraphLeakDetector(%s)' % self._render
def _leakNode(self, task=None):
self._render.attachNewNode('leakNode-%s' % serialNum())
taskMgr.doMethodLater(10, self._leakNode, self._leakTaskName)
[docs]class CppMemoryUsage(LeakDetector):
def __len__(self):
haveMemoryUsage = True
try:
MemoryUsage
except:
haveMemoryUsage = False
if haveMemoryUsage:
return int(MemoryUsage.getCurrentCppSize())
else:
return 0
[docs]class TaskLeakDetectorBase:
def _getTaskNamePattern(self, taskName):
# get a generic string pattern from a task name by removing numeric characters
for i in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9):
taskName = taskName.replace('%s' % i, '')
return taskName
class _TaskNamePatternLeakDetector(LeakDetector, TaskLeakDetectorBase):
# tracks the number of each individual task type
# e.g. are we leaking 'examine-<doId>' tasks
def __init__(self, taskNamePattern):
self._taskNamePattern = taskNamePattern
LeakDetector.__init__(self)
def __len__(self):
# count the number of tasks that match our task name pattern
numTasks = 0
for task in taskMgr.getTasks():
if self._getTaskNamePattern(task.name) == self._taskNamePattern:
numTasks += 1
for task in taskMgr.getDoLaters():
if self._getTaskNamePattern(task.name) == self._taskNamePattern:
numTasks += 1
return numTasks
def getLeakDetectorKey(self):
return '%s-%s' % (self._taskNamePattern, self.__class__.__name__)
[docs]class TaskLeakDetector(LeakDetector, TaskLeakDetectorBase):
# tracks the number task 'types' and creates leak detectors for each task type
[docs] def __init__(self):
LeakDetector.__init__(self)
self._taskName2collector = {}
[docs] def destroy(self):
for taskName, collector in self._taskName2collector.items():
collector.destroy()
del self._taskName2collector
LeakDetector.destroy(self)
def _processTaskName(self, taskName):
# if this is a new task name pattern, create a leak detector for that pattern
namePattern = self._getTaskNamePattern(taskName)
if namePattern not in self._taskName2collector:
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)
for task in taskMgr.getDoLaters():
self._processTaskName(task.name)
# are we leaking task types?
return len(self._taskName2collector)
[docs]class MessageLeakDetectorBase:
def _getMessageNamePattern(self, msgName):
# get a generic string pattern from a message name by removing numeric characters
for i in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9):
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
[docs]class MessageTypesLeakDetector(LeakDetector, MessageLeakDetectorBase):
[docs] 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
[docs] 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.items():
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
[docs]class MessageListenerTypesLeakDetector(LeakDetector):
[docs] 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
[docs] 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.items():
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)