diff --git a/direct/src/showbase/GarbageReport.py b/direct/src/showbase/GarbageReport.py index 4b10ee9a69..6b7adf3f39 100755 --- a/direct/src/showbase/GarbageReport.py +++ b/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) + gc.set_debug(oldFlags) - 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) + self.numGarbage = len(self.garbage) - s = '\n===== GarbageReport: \'%s\' (%s items) =====' % (name, numGarbage) - if numGarbage > 0: + # 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]) + 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]) - self.notify.info(s) + 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 + """