Browse Source

ready for logout garbage detection

Darren Ranalli 19 years ago
parent
commit
ecb7a2f84e
1 changed files with 238 additions and 54 deletions
  1. 238 54
      direct/src/showbase/GarbageReport.py

+ 238 - 54
direct/src/showbase/GarbageReport.py

@@ -1,75 +1,259 @@
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import PythonUtil
+from direct.showbase.PythonUtil import POD
 import gc
 
+class FakeObject:
+    pass
+
+def _createGarbage():
+    a = FakeObject()
+    b = FakeObject()
+    a.other = b
+    b.other = a
+
+class CycleSearchState(POD):
+    DataSet = {
+        'curId': None,
+        'cycleByNumSoFar': [],
+        'cycleByRefSoFar': [],
+        'nonGarbageDepth': 0,
+        }
+
 class GarbageReport:
     notify = DirectNotifyGlobal.directNotify.newCategory("GarbageReport")
 
-    def __init__(self, name=None):
+    NotGarbage = 'NG'
+
+    def __init__(self, name=None, log=True, fullReport=False, findCycles=True):
+        # if log is True, GarbageReport will self-destroy after logging
+        # if false, caller is responsible for calling destroy()
         wasOn = PythonUtil.gcDebugOn()
+        oldFlags = gc.get_debug()
         if not wasOn:
-            gc.set_debug(gc.DEBUG_LEAK)
+            gc.set_debug(gc.DEBUG_SAVEALL)
         gc.collect()
-        garbage = list(gc.garbage)
+        self.notify.debug('gc.garbage == %s' % gc.garbage)
+        self.garbage = list(gc.garbage)
+        self.notify.debug('self.garbage == %s' % self.garbage)
         del gc.garbage[:]
         if not wasOn:
-            gc.set_debug(0)
-
-        numGarbage = len(garbage)
-        # grab the referrers
-        referrersByReference = {}
-        referrersByNumber = {}
-        for i in xrange(numGarbage):
-            referrers = gc.get_referrers(garbage[i])
-            referrersByReference[i] = list(referrers)
-            # look to see if each referrer is another garbage item
-            referrersByNumber[i] = list()
-            for referrer in referrers:
-                try:
-                    num = garbage.index(referrer)
-                except:
-                    num = 'NG' # Not Garbage
-                referrersByNumber[i].append(num)
-        # grab the referents
-        referentsByReference = {}
-        referentsByNumber = {}
-        for i in xrange(numGarbage):
-            referents = gc.get_referents(garbage[i])
-            referentsByReference[i] = list(referents)
-            # look to see if each referent is another garbage item
-            referentsByNumber[i] = list()
-            for referent in referents:
-                try:
-                    num = garbage.index(referent)
-                except:
-                    num = 'NG' # Not Garbage
-                referentsByNumber[i].append(num)
-
-        s = '\n===== GarbageReport: \'%s\' (%s items) =====' % (name, numGarbage)
-        if numGarbage > 0:
+            gc.set_debug(oldFlags)
+
+        self.numGarbage = len(self.garbage)
+
+        # grab the referrers (pointing to garbage)
+        self.referrersByReference = {}
+        self.referrersByNumber = {}
+        for i in xrange(self.numGarbage):
+            byNum, byRef = self._getReferrers(self.garbage[i])
+            self.referrersByNumber[i] = byNum
+            self.referrersByReference[i] = byRef
+
+        # grab the referents (pointed to by garbage)
+        self.referentsByReference = {}
+        self.referentsByNumber = {}
+        for i in xrange(self.numGarbage):
+            byNum, byRef = self._getReferents(self.garbage[i])
+            self.referentsByNumber[i] = byNum
+            self.referentsByReference[i] = byRef
+
+        """
+        # find the cycles
+        if findCycles:
+            self.cycles = self._getCycles()
+            """
+
+        s = '\n===== GarbageReport: \'%s\' (%s items) =====' % (name, self.numGarbage)
+        if self.numGarbage > 0:
             # log each individual item with a number in front of it
             s += '\n\n===== Garbage Items ====='
             digits = 0
-            n = numGarbage
+            n = self.numGarbage
             while n > 0:
                 digits += 1
                 n /= 10
             format = '\n%0' + '%s' % digits + 'i:%s \t%s'
-            for i in range(len(garbage)):
-                s += format % (i, type(garbage[i]), garbage[i])
+            for i in range(len(self.garbage)):
+                s += format % (i, type(self.garbage[i]), self.garbage[i])
+
+            """
+            if findCycles:
+                format = '\n%s'
+                s += '\n\n===== Cycles ====='
+                for cycle in self.cycles:
+                    s += format % cycle
+                    """
 
             format = '\n%0' + '%s' % digits + 'i:%s'
-            s += '\n\n===== Referrers By Number (what is referring to garbage item?) NG=NonGarbage ====='
-            for i in xrange(numGarbage):
-                s += format % (i, referrersByNumber[i])
-            s += '\n\n===== Referents By Number (what is garbage item referring to?) NG=NonGarbage ====='
-            for i in xrange(numGarbage):
-                s += format % (i, referentsByNumber[i])
-            s += '\n\n===== Referrers (what is referring to garbage item?) ====='
-            for i in xrange(numGarbage):
-                s += format % (i, referrersByReference[i])
-            s += '\n\n===== Referents (what is garbage item referring to?) ====='
-            for i in xrange(numGarbage):
-                s += format % (i, referentsByReference[i])
-
-        self.notify.info(s)
+            s += '\n\n===== Referrers By Number (what is referring to garbage item?) ====='
+            for i in xrange(self.numGarbage):
+                s += format % (i, self.referrersByNumber[i])
+            s += '\n\n===== Referents By Number (what is garbage item referring to?) ====='
+            for i in xrange(self.numGarbage):
+                s += format % (i, self.referentsByNumber[i])
+
+            if fullReport:
+                s += '\n\n===== Referrers (what is referring to garbage item?) ====='
+                for i in xrange(self.numGarbage):
+                    s += format % (i, self.referrersByReference[i])
+                s += '\n\n===== Referents (what is garbage item referring to?) ====='
+                for i in xrange(self.numGarbage):
+                    s += format % (i, self.referentsByReference[i])
+
+        self._report = s
+        if log:
+            self.notify.info(self._report)
+            self.destroy()
+
+    def getNumItems(self):
+        return self.numGarbage
+
+    def getGarbage(self):
+        return self.garbage
+
+    def getReport(self):
+        return self._report
+
+    def destroy(self):
+        del self.garbage
+        del self.numGarbage
+        del self.referrersByReference
+        del self.referrersByNumber
+        del self.referentsByReference
+        del self.referentsByNumber
+        if hasattr(self, 'cycles'):
+            del self.cycles
+        del self._report
+
+    def _getReferrers(self, obj):
+        # referrers (pointing to garbage)
+        # returns two lists, first by index into gc.garbage, second by
+        # direct reference
+        byRef = gc.get_referrers(obj)
+        # look to see if each referrer is another garbage item
+        byNum = []
+        for referrer in byRef:
+            try:
+                num = self.garbage.index(referrer)
+                byNum.append(num)
+            except:
+                #num = GarbageReport.NotGarbage
+                pass
+        return byNum, byRef
+
+    def _getReferents(self, obj):
+        # referents (pointed to by garbage)
+        # returns two lists, first by index into gc.garbage, second by
+        # direct reference
+        byRef = gc.get_referents(obj)
+        # look to see if each referent is another garbage item
+        byNum = []
+        for referent in byRef:
+            try:
+                num = self.garbage.index(referent)
+                byNum.append(num)
+            except:
+                #num = GarbageReport.NotGarbage
+                pass
+        return byNum, byRef
+
+    def _getCycles(self):
+        assert self.notify.debugCall()
+        # returns list of lists, sublists are garbage reference cycles
+        cycles = []
+        # dict of already-found cycles as sets so we can avoid duplicate cycle reports
+        cycleSets = {}
+        # set of garbage item IDs involved in already-discovered cycles
+        visited = set()
+        self.stateStack = Stack()
+        for rootId in xrange(len(self.garbage)):
+            cycleSoFar = []
+            self.stateStack.push((rootId, rootId, cycles, cycleSoFar, cycleSets, visited))
+            self._findCycles()
+            self.stateStack.pop()
+        assert len(self.stateStack) == 0
+        return cycles
+
+    def _findCycles(self):
+        # rootId: id of first garbage item in potential cycle
+        # curId: id of current garbage item under examination
+        # cycles: list of complete cycles we have already found
+        # cycleSoFar: list of IDs leading from rootId up to curId
+        # cycleSets: dict of ID to list of cycle ID sets
+        # visited: set of garbage item IDs from existing (already-found) cycles
+        assert self.notify.debugCall()
+        rootId, curId, cycles, cycleSoFar, cycleSets, visited = self.stateStack.top()
+        cycleSoFar.append(curId)
+        # make sure this is not a cycle that is already in the list of cycles
+        if curId in cycleSets:
+            cycleSet = set(cycleSoFar)
+            cycleSet.add(curId)
+            if cycleSet in cycleSets[curId]:
+                return
+        for refId in self.referentsByNumber[curId]:
+            if refId == rootId:
+                # we found a cycle!
+                cycle = list(cycleSoFar) + [refId]
+                cycleSet = set(cycle)
+                # make sure this is not a duplicate of a previously-found cycle
+                if cycleSet not in cycleSets.get(rootId, []):
+                    cycles.append(cycle)
+                    for id in cycle:
+                        visited.add(id)
+                        cycleSets.setdefault(id, [])
+                        if cycleSet not in cycleSets[id]:
+                            cycleSets[id].append(cycleSet)
+            elif refId not in visited and refId not in cycleSoFar and refId != GarbageReport.NotGarbage:
+                #print rootId, curId, refId, visited, cycles, cycleSoFar, cycleSets
+                self.stateStack.push((rootId, refId, cycles, list(cycleSoFar), cycleSets, visited))
+                self._findCycles()
+                self.stateStack.pop()
+
+"""
+    def _getCycles(self):
+        # returns list of lists, sublists are garbage reference cycles by number
+        cycles = []
+        for rootId in xrange(len(self.garbage)):
+            curId = rootId
+            stateStack = Stack()
+            initialState = CycleSearchState(curId=curId, cycleByNumSoFar=[rootId],
+                                            cycleByRefSoFar=[self.garbage[rootId]])
+            stateStack.push(initialState)
+            curState = stateStack.top()
+            while not stateStack.isEmpty():
+                for index in xrange(len(self.referentsByNumber[curId])):
+                    refId = self.referentsByNumber[curId][index]
+                    if refId is rootId:
+                        # we found a cycle!
+                        cyclesByNumber.append(curState.cycleByNumSoFar + [refId])
+                        cyclesByReference.append(curState.cycleByRefSoFar + [
+
+    def _getCycles(self):
+        assert self.notify.debugCall()
+        # returns list of lists, sublists are garbage reference cycles
+        cycles = []
+        # dict of already-found cycles as sets so we can avoid duplicate cycle reports
+        cycleSets = {}
+        # set of garbage item IDs involved in already-discovered cycles
+        visited = set()
+        for rootId in xrange(len(self.garbage)):
+            for index in xrange(len(self.referentsByNumber[rootId])):
+                refId = self.referentsByNumber[rootId][index]
+                if refId is rootId:
+                    # we found a cycle!
+                    cycle = [rootId, rootId]
+                    cycleSet = set(cycle)
+                    # make sure this is not a duplicate of a previously-found cycle
+                    if cycleSet not in cycleSets.get(rootId, []):
+                        cycles.append(cycle)
+                        for id in cycle:
+                            visited.add(id)
+                            cycleSets.setdefault(id, [])
+                            if cycleSet not in cycleSets[id]:
+                                cycleSets[id].append(cycleSet)
+                elif refId is not GarbageReport.NotGarbage:
+                    cycleSoFar = [rootId]
+                    self._findCycles(rootId, refId, cycles, cycleSoFar, cycleSets, visited)
+        return cycles
+    """