"""Contains objects that report different types of leaks to the
ContainerLeakDetector.
"""
from panda3d.core import *
from direct.showbase.DirectObject import DirectObject
from direct.showbase.PythonUtil import safeTypeName, typeName, uniqueName, serialNum
from direct.showbase.Job import Job
from direct.showbase.JobManagerGlobal import jobMgr
from direct.showbase.MessengerGlobal import messenger
from direct.task.TaskManagerGlobal import taskMgr
import gc
import 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 ConfigVariableBool('leak-scene-graph', False):
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 ConfigVariableBool('leak-message-types', False):
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 ConfigVariableBool('leak-message-listeners', False):
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)