Source code for direct.showbase.LeakDetectors

"""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)