importgcimportinspectimportosimportsysimporttimetry:importobjgraphexceptImportError:objgraph=Noneimportcherrypyfromcherrypyimport_cprequest,_cpwsgifromcherrypy.process.pluginsimportSimplePluginclassReferrerTree(object):"""An object which gathers all referrers of an object to a given depth."""peek_length=40def__init__(self,ignore=None,maxdepth=2,maxparents=10):self.ignore=ignoreor[]self.ignore.append(inspect.currentframe().f_back)self.maxdepth=maxdepthself.maxparents=maxparentsdefascend(self,obj,depth=1):"""Return a nested list containing referrers of the given object."""depth+=1parents=[]# Gather all referrers in one step to minimize# cascading references due to repr() logic.refs=gc.get_referrers(obj)self.ignore.append(refs)iflen(refs)>self.maxparents:return[("[%s referrers]"%len(refs),[])]try:ascendcode=self.ascend.__code__exceptAttributeError:ascendcode=self.ascend.im_func.func_codeforparentinrefs:ifinspect.isframe(parent)andparent.f_codeisascendcode:continueifparentinself.ignore:continueifdepth<=self.maxdepth:parents.append((parent,self.ascend(parent,depth)))else:parents.append((parent,[]))returnparentsdefpeek(self,s):"""Return s, restricted to a sane length."""iflen(s)>(self.peek_length+3):half=self.peek_length//2returns[:half]+'...'+s[-half:]else:returnsdef_format(self,obj,descend=True):"""Return a string representation of a single object."""ifinspect.isframe(obj):filename,lineno,func,context,index=inspect.getframeinfo(obj)return"<frame of function '%s'>"%funcifnotdescend:returnself.peek(repr(obj))ifisinstance(obj,dict):return"{"+", ".join(["%s: %s"%(self._format(k,descend=False),self._format(v,descend=False))fork,vinobj.items()])+"}"elifisinstance(obj,list):return"["+", ".join([self._format(item,descend=False)foriteminobj])+"]"elifisinstance(obj,tuple):return"("+", ".join([self._format(item,descend=False)foriteminobj])+")"r=self.peek(repr(obj))ifisinstance(obj,(str,int,float)):returnrreturn"%s: %s"%(type(obj),r)defformat(self,tree):"""Return a list of string reprs from a nested list of referrers."""output=[]defascend(branch,depth=1):forparent,grandparentsinbranch:output.append((" "*depth)+self._format(parent))ifgrandparents:ascend(grandparents,depth+1)ascend(tree)returnoutputdefget_instances(cls):return[xforxingc.get_objects()ifisinstance(x,cls)]classRequestCounter(SimplePlugin):defstart(self):self.count=0defbefore_request(self):self.count+=1defafter_request(self):self.count-=1request_counter=RequestCounter(cherrypy.engine)request_counter.subscribe()defget_context(obj):ifisinstance(obj,_cprequest.Request):return"path=%s;stage=%s"%(obj.path_info,obj.stage)elifisinstance(obj,_cprequest.Response):return"status=%s"%obj.statuselifisinstance(obj,_cpwsgi.AppResponse):return"PATH_INFO=%s"%obj.environ.get('PATH_INFO','')elifhasattr(obj,"tb_lineno"):return"tb_lineno=%s"%obj.tb_linenoreturn""classGCRoot(object):"""A CherryPy page handler for testing reference leaks."""classes=[(_cprequest.Request,2,2,"Should be 1 in this request thread and 1 in the main thread."),(_cprequest.Response,2,2,"Should be 1 in this request thread and 1 in the main thread."),(_cpwsgi.AppResponse,1,1,"Should be 1 in this request thread only."),]defindex(self):return"Hello, world!"index.exposed=Truedefstats(self):output=["Statistics:"]fortrialinrange(10):ifrequest_counter.count>0:breaktime.sleep(0.5)else:output.append("\nNot all requests closed properly.")# gc_collect isn't perfectly synchronous, because it may# break reference cycles that then take time to fully# finalize. Call it thrice and hope for the best.gc.collect()gc.collect()unreachable=gc.collect()ifunreachable:ifobjgraphisnotNone:final=objgraph.by_type('Nondestructible')iffinal:objgraph.show_backrefs(final,filename='finalizers.png')trash={}forxingc.garbage:trash[type(x)]=trash.get(type(x),0)+1iftrash:output.insert(0,"\n%s unreachable objects:"%unreachable)trash=[(v,k)fork,vintrash.items()]trash.sort()forpairintrash:output.append(" "+repr(pair))# Check declared classes to verify uncollected instances.# These don't have to be part of a cycle; they can be# any objects that have unanticipated referrers that keep# them from being collected.allobjs={}forcls,minobj,maxobj,msginself.classes:allobjs[cls]=get_instances(cls)forcls,minobj,maxobj,msginself.classes:objs=allobjs[cls]lenobj=len(objs)iflenobj<minobjorlenobj>maxobj:ifminobj==maxobj:output.append("\nExpected %s%r references, got %s."%(minobj,cls,lenobj))else:output.append("\nExpected %s to %s%r references, got %s."%(minobj,maxobj,cls,lenobj))forobjinobjs:ifobjgraphisnotNone:ig=[id(objs),id(inspect.currentframe())]fname="graph_%s_%s.png"%(cls.__name__,id(obj))objgraph.show_backrefs(obj,extra_ignore=ig,max_depth=4,too_many=20,filename=fname,extra_info=get_context)output.append("\nReferrers for %s (refcount=%s):"%(repr(obj),sys.getrefcount(obj)))t=ReferrerTree(ignore=[objs],maxdepth=3)tree=t.ascend(obj)output.extend(t.format(tree))return"\n".join(output)stats.exposed=True