Browse Source

first rev

Darren Ranalli 19 years ago
parent
commit
ce8589e0fa
2 changed files with 266 additions and 0 deletions
  1. 115 0
      direct/src/showbase/ObjectPool.py
  2. 151 0
      direct/src/showbase/ObjectReport.py

+ 115 - 0
direct/src/showbase/ObjectPool.py

@@ -0,0 +1,115 @@
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from direct.showbase.PythonUtil import invertDict, makeList, safeRepr
+from direct.showbase.PythonUtil import getNumberedTypedString
+import types
+import gc
+
+class Diff:
+    def __init__(self, lost, gained):
+        self.lost=lost
+        self.gained=gained
+    def __repr__(self):
+        s  = 'lost %s objects, gained %s objects' % (len(self.lost), len(self.gained))
+        s += '\n\nself.lost\n'
+        s += self.lost.typeFreqStr()
+        s += '\n\nself.gained\n'
+        s += self.gained.typeFreqStr()
+        s += '\n\nGAINED-OBJECT REFERRERS\n'
+        s += self.gained.referrersStr(1)
+        return s
+
+class ObjectPool:
+    """manipulate a pool of Python objects"""
+    notify = directNotify.newCategory('ObjectPool')
+
+    def __init__(self, objects):
+        self._objs = list(objects)
+        self._type2objs = {}
+        self._count2types = {}
+        type2count = {}
+        for obj in self._objs:
+            typ = type(obj)
+            # <type 'instance'> isn't all that useful
+            if typ is types.InstanceType:
+                typ = '%s of %s' % (typ, repr(obj.__class__))
+            type2count.setdefault(typ, 0)
+            type2count[typ] += 1
+            self._type2objs.setdefault(typ, [])
+            self._type2objs[typ].append(obj)
+        self._count2types = invertDict(type2count)
+
+    def _getInternalObjs(self):
+        return (self._objs, self._type2objs, self._count2types)
+
+    def destroy(self):
+        del self._objs
+        del self._type2objs
+        del self._count2types
+
+    def getTypes(self):
+        return self._type2objs.keys()
+
+    def getObjsOfType(self, type):
+        return self._type2objs.get(type, [])
+
+    def printObjsOfType(self, type):
+        for obj in self._type2objs.get(type, []):
+            print repr(obj)
+
+    def diff(self, other):
+        """print difference between this pool and 'other' pool"""
+        thisId2obj = {}
+        otherId2obj = {}
+        for obj in self._objs:
+            thisId2obj[id(obj)] = obj
+        for obj in other._objs:
+            otherId2obj[id(obj)] = obj
+        thisIds = set(thisId2obj.keys())
+        otherIds = set(otherId2obj.keys())
+        lostIds = thisIds.difference(otherIds)
+        print 'lost: %s' % lostIds
+        gainedIds = otherIds.difference(thisIds)
+        del thisIds
+        del otherIds
+        lostObjs = []
+        for i in lostIds:
+            lostObjs.append(thisId2obj[i])
+        gainedObjs = []
+        for i in gainedIds:
+            gainedObjs.append(otherId2obj[i])
+        return Diff(self.__class__(lostObjs), self.__class__(gainedObjs))
+
+    def typeFreqStr(self):
+        s  =   'Object Pool: Type Frequencies'
+        s += '\n============================='
+        counts = list(set(self._count2types.keys()))
+        counts.sort()
+        counts.reverse()
+        for count in counts:
+            types = makeList(self._count2types[count])
+            for typ in types:
+                s += '\n%s\t%s' % (count, typ)
+        return s
+
+    def referrersStr(self, numEach=3):
+        """referrers of the first few of each type of object"""
+        s = ''
+        counts = list(set(self._count2types.keys()))
+        counts.sort()
+        counts.reverse()
+        for count in counts:
+            types = makeList(self._count2types[count])
+            for typ in types:
+                s += '\n\nTYPE: %s' % typ
+                for i in xrange(min(numEach,len(self._type2objs[typ]))):
+                    obj = self._type2objs[typ][i]
+                    s += '\nOBJ: %s\n' % safeRepr(obj)
+                    referrers = gc.get_referrers(obj)
+                    if len(referrers):
+                        s += getNumberedTypedString(referrers)
+                    else:
+                        s += '<No Referrers>'
+        return s
+
+    def __len__(self):
+        return len(self._objs)

+ 151 - 0
direct/src/showbase/ObjectReport.py

@@ -0,0 +1,151 @@
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from direct.showbase import DirectObject, ObjectPool
+from direct.showbase.PythonUtil import makeList, Sync
+import gc
+import sys
+
+"""
+>>> from direct.showbase import ObjectReport
+
+>>> o=ObjectReport.ObjectReport('baseline')
+>>> run()
+...
+
+>>> o2=ObjectReport.ObjectReport('')
+>>> o.diff(o2)
+<wait a loooong time>
+"""
+
+class ExclusiveObjectPool(DirectObject.DirectObject):
+    """ObjectPool specialization that excludes particular objects"""
+    # IDs of objects to globally exclude from reporting
+    _ExclObjIds = {}
+    _SyncMaster = Sync('ExclusiveObjectPool.ExcludedObjectList')
+    _SerialNumGen = SerialNumGen()
+
+    @classmethod
+    def addExclObjs(cls, *objs):
+        for obj in makeList(objs):
+            cls._ExclObjIds.setdefault(id(obj), 0)
+            cls._ExclObjIds[id(obj)] += 1
+        cls._SyncMaster.change()
+    @classmethod
+    def removeExclObjs(cls, *objs):
+        for obj in makeList(objs):
+            assert id(obj) in cls._ExclObjIds
+            del cls._ExclObjIds[id(obj)]
+        cls._SyncMaster.change()
+
+    def __init__(self, objects):
+        self._objects = list(objects)
+        self._postFilterObjs = []
+        self._sync = Sync('%s-%s' % (self.__class__.__name__,
+                                     self._SerialNumGen.next()),
+                          self._SyncMaster)
+        self._sync.invalidate()
+        ExclusiveObjectPool.addExclObjs(self._objects, self._postFilterObjs,
+                                        self._sync)
+
+    def destroy(self):
+        self.ignoreAll()
+        ExclusiveObjectPool.removeExclObjs(self._objects, self._postFilterObjs,
+                                           self._sync)
+        del self._objects
+        del self._postFilterObjs
+        del self._sync
+
+    def _resync(self):
+        if self._sync.isSynced(self._SyncMaster):
+            return
+        if hasattr(self, '_filteredPool'):
+            ExclusiveObjectPool.removeExclObjs(*self._filteredPool._getInternalObjs())
+            ExclusiveObjectPool.removeExclObjs(self._filteredPool)
+            del self._filteredPool
+        del self._postFilterObjs[:]
+        for obj in self._objects:
+            if id(obj) not in ExclusiveObjectPool._ExclObjIds:
+                self._postFilterObjs.append(obj)
+        self._filteredPool = ObjectPool.ObjectPool(self._postFilterObjs)
+        ExclusiveObjectPool.addExclObjs(self._filteredPool)
+        ExclusiveObjectPool.addExclObjs(*self._filteredPool._getInternalObjs())
+        self._sync.sync(self._SyncMaster)
+
+    def getObjsOfType(self, type):
+        self._resync()
+        return self._filteredPool.getObjsOfType(type)
+    def printObjsOfType(self, type):
+        self._resync()
+        return self._filteredPool.printObjsOfType(type)
+    def diff(self, other):
+        self._resync()
+        return self._filteredPool.diff(other._filteredPool)
+    def typeFreqStr(self):
+        self._resync()
+        return self._filteredPool.typeFreqStr()
+    def __len__(self):
+        self._resync()
+        return len(self._filteredPool)
+
+class ObjectReport:
+    """report on every Python object in the current process"""
+    notify = directNotify.newCategory('ObjectReport')
+
+    def __init__(self, name, log=True, garbageCollect=True):
+        if not hasattr(sys, 'getobjects'):
+            self.notify.error(
+                '%s only works in debug Python (pyd-shell)' % self.__class__.__name__)
+        if garbageCollect:
+            import gc
+            gc.collect()
+        self._name = name
+        self._pool = ExclusiveObjectPool(self._getObjectList())
+        ExclusiveObjectPool.addExclObjs(self, self._pool, self._name)
+        if log:
+            self.notify.info('===== ObjectReport: \'%s\' =====\n%s' % (self._name, self.typeFreqStr()))
+
+    def destroy(self):
+        ExclusiveObjectPool.removeExclObjs(self, self._pool, self._name)
+        self._pool.destroy()
+        del self._pool
+        del self._name
+
+    def typeFreqStr(self):
+        return self._pool.typeFreqStr()
+
+    def diff(self, other):
+        return self._pool.diff(other._pool)
+
+    def _getObjectList(self):
+        if hasattr(sys, 'getobjects'):
+            return sys.getobjects(0)
+        else:
+            objs = []
+            stateStack = Stack()
+            root = __builtins__
+            objIds = set([id(root)])
+            stateStack.push((root, None, 0))
+            while True:
+                if len(stateStack) == 0:
+                    break
+                obj, adjacents, resumeIndex = stateStack.pop()
+                objs.append(obj)
+                print id(obj)
+                if adjacents is None:
+                    adjacents = gc.get_referents(obj)
+                    adjacents.extend(gc.get_referrers(obj))
+                    # pare the list down to the ones that have not already been visited
+                    # to minimize calls to get_referents/get_referrers
+                    newObjs = []
+                    for adj in adjacents:
+                        adjId = id(adj)
+                        if adjId not in objIds:
+                            objIds.add(adjId)
+                            newObjs.append(adj)
+                    adjacents = newObjs
+                    if len(adjacents) == 0:
+                        print 'DEAD END'
+                for i in xrange(resumeIndex, len(adjacents)):
+                    adj = adjacents[i]
+                    stateStack.push((obj, adjacents, i+1))
+                    stateStack.push((adj, None, 0))
+                    continue