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