Skip to content

Commit

Permalink
- catch up with Django 1.9 deprecation notices
Browse files Browse the repository at this point in the history
- fix for issue #11 (missing import patterns)
  note dowser is not compatible with Django 1.3 templating engine
- pep8
  • Loading branch information
munhitsu committed Mar 15, 2016
1 parent db480ea commit b78a359
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 148 deletions.
10 changes: 2 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@ About

Based on: `Dowser <http://www.aminus.net/wiki/Dowser>`__

The original Dowser is WSGI enabled. Unfortunately, not all Django
hosting providers use WSGI. In daily development the most common usage
pattern is ./manage.py runserver which is not using WSGI. That's the
story why this fork was created.

In the other words the target of this project is to provide easy to use
and install Django app to debug your memory leaks.
A Django specific Dowser port.

Following enhancements have been implemented on top of original Dowser:

Expand Down Expand Up @@ -51,7 +45,7 @@ urls.py

::

urlpatterns += [url(r'^dowser2/', include('django_dowser.urls'))]
urlpatterns += [url(r'^dowser/', include('django_dowser.urls'))]

Example buildout recipe
-----------------------
Expand Down
44 changes: 18 additions & 26 deletions django_dowser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import gc
import threading
import time
from django.utils.html import escape

from collections import deque
import itertools

#maximum from all old periods is being promoted to next one
DOWSER_MAXENTRIES = [12,120,60,60]
DOWSER_TICKS = [5,6,48,28]
DOWSER_NAMES = ["1m","1h","1d","4w"]

#DOWSER_MAXENTRIES = [2,2,4]
#DOWSER_TICKS = [2,2,2]
# maximum from all old periods is being promoted to next one
DOWSER_MAXENTRIES = [12, 120, 60, 60]
DOWSER_TICKS = [5, 6, 48, 28]
DOWSER_NAMES = ["1m", "1h", "1d", "4w"]


class Dowser(object):
Expand All @@ -21,25 +16,24 @@ class Dowser(object):
"""
history = {}
samples = []

def __init__(self):
#TODO: how to limit it only to server process not the monitor
#TODO: cover multi-process configuration - maybe as separate daemon...
# TODO: how to limit it only to server process not the monitor
# TODO: cover multi-process configuration - maybe as separate daemon...
self.running = False
self.samples = [0] * len(DOWSER_MAXENTRIES)
self.runthread = threading.Thread(target=self.start)
self.runthread.daemon = True
self.runthread.start()


def start(self):
self.running = True
while self.running:
self.tick()
time.sleep(DOWSER_TICKS[0])

def tick(self):
gc.collect()

typecounts = {}

for obj in gc.get_objects():
Expand All @@ -49,30 +43,29 @@ def tick(self):
typecounts[typename] += 1
else:
typecounts[typename] = 1

for typename, count in typecounts.iteritems():
# typename = objtype.__module__ + "." + objtype.__name__
if typename not in self.history:
self.history[typename] = map(lambda x: deque([0] * x), DOWSER_MAXENTRIES)
self.history[typename][0].appendleft(count)
self.samples[0] = self.samples[0] + 1

self.samples[0] += 1
promote = [False] * (len(DOWSER_MAXENTRIES)-1)
#let's calculate what we promote

# let's calculate what we promote
for i in range(len(self.samples)-1):
if self.samples[i] >= DOWSER_TICKS[i]:
promote[i] = True
self.samples[i+1] = self.samples[i+1] + 1
self.samples[i+1] += 1
self.samples[i] = 0

for typename, hist in self.history.iteritems():
history = self.history[typename]
#let's promote max from (set of entries to lower granulity history)
# let's promote max from (set of entries to lower granularity history)
for i in range(len(self.samples)-1):
if promote[i]:
history[i+1].appendleft(max(itertools.islice(history[i],0,DOWSER_TICKS[i])))
#let's limit history to DOWSER_MAXENTRIES
history[i+1].appendleft(max(itertools.islice(history[i], 0, DOWSER_TICKS[i])))
# let's limit history to DOWSER_MAXENTRIES
for i in range(len(self.samples)):
if len(history[i]) > DOWSER_MAXENTRIES[i]:
history[i].pop()
Expand All @@ -81,5 +74,4 @@ def stop(self):
self.running = False



dowser = Dowser()
56 changes: 26 additions & 30 deletions django_dowser/reftree.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,34 @@

log = logging.getLogger(__name__)


class Tree(object):

def __init__(self, obj):
self.seen = {}
self.maxdepth = None
self.obj = obj
self.filename = sys._getframe().f_code.co_filename
self._ignore = {}

def ignore(self, *objects):
for obj in objects:
self._ignore[id(obj)] = None

def ignore_caller(self):
f = sys._getframe() # = this function
cur = f.f_back # = the function that called us (probably 'walk')
self.ignore(cur, cur.f_builtins, cur.f_locals, cur.f_globals)
caller = f.f_back # = the 'real' caller
self.ignore(caller, caller.f_builtins, caller.f_locals, caller.f_globals)

def walk(self, maxresults=100, maxdepth=None):
"""Walk the object tree, ignoring duplicates and circular refs."""
log.debug("step")
self.seen = {}
self.ignore(self, self.__dict__, self.obj, self.seen, self._ignore)

# Ignore the calling frame, its builtins, globals and locals
self.ignore_caller()

self.maxdepth = maxdepth
count = 0
log.debug("will iterate results")
Expand All @@ -42,7 +43,7 @@ def walk(self, maxresults=100, maxdepth=None):
if maxresults and count >= maxresults:
yield 0, 0, "==== Max results reached ===="
raise StopIteration

def print_tree(self, maxresults=100, maxdepth=None):
"""Walk the object tree, pretty-printing each branch."""
self.ignore_caller()
Expand All @@ -57,35 +58,37 @@ def _repr_container(obj):
repr_list = _repr_container
repr_tuple = _repr_container


def repr_str(obj):
return "%s of len %s: %r" % (type(obj).__name__, len(obj), obj)
repr_unicode = repr_str


def repr_frame(obj):
return "frame from %s line %s" % (obj.f_code.co_filename, obj.f_lineno)


def get_repr(obj, limit=250):
typename = getattr(type(obj), "__name__", None)
handler = globals().get("repr_%s" % typename, repr)

try:
result = handler(obj)
except:
result = "unrepresentable object: %r" % sys.exc_info()[1]

if len(result) > limit:
result = result[:limit] + "..."

return result


class ReferentTree(Tree):

def _gen(self, obj, depth=0):
if self.maxdepth and depth >= self.maxdepth:
yield depth, 0, "---- Max depth reached ----"
raise StopIteration

for ref in gc.get_referents(obj):
if id(ref) in self._ignore:
continue
Expand All @@ -95,18 +98,16 @@ def _gen(self, obj, depth=0):
else:
self.seen[id(ref)] = None
yield depth, id(ref), get_repr(ref)

for child in self._gen(ref, depth + 1):
yield child


class ReferrerTree(Tree):

def _gen(self, obj, depth=0):
if self.maxdepth and depth >= self.maxdepth:
yield depth, 0, "---- Max depth reached ----"
raise StopIteration

refs = gc.get_referrers(obj)
refiter = iter(refs)
self.ignore(refs, refiter)
Expand All @@ -115,7 +116,6 @@ def _gen(self, obj, depth=0):
if isinstance(ref, FrameType):
if ref.f_code.co_filename == self.filename:
continue

if id(ref) in self._ignore:
continue
elif id(ref) in self.seen:
Expand All @@ -124,23 +124,20 @@ def _gen(self, obj, depth=0):
else:
self.seen[id(ref)] = None
yield depth, id(ref), get_repr(ref)

for parent in self._gen(ref, depth + 1):
yield parent



class CircularReferents(Tree):

def walk(self, maxresults=100, maxdepth=None):
"""Walk the object tree, showing circular referents."""
self.stops = 0
self.seen = {}
self.ignore(self, self.__dict__, self.seen, self._ignore)

# Ignore the calling frame, its builtins, globals and locals
self.ignore_caller()

self.maxdepth = maxdepth
count = 0
for result in self._gen(self.obj):
Expand All @@ -149,30 +146,30 @@ def walk(self, maxresults=100, maxdepth=None):
if maxresults and count >= maxresults:
yield 0, 0, "==== Max results reached ===="
raise StopIteration

def _gen(self, obj, depth=0, trail=None):
if self.maxdepth and depth >= self.maxdepth:
self.stops += 1
raise StopIteration

if trail is None:
trail = []

for ref in gc.get_referents(obj):
if id(ref) in self._ignore:
continue
elif id(ref) in self.seen:
continue
else:
self.seen[id(ref)] = None

refrepr = get_repr(ref)
if id(ref) == id(self.obj):
yield trail + [refrepr,]
for child in self._gen(ref, depth + 1, trail + [refrepr,]):
yield trail + [refrepr]

for child in self._gen(ref, depth + 1, trail + [refrepr]):
yield child

def print_tree(self, maxresults=100, maxdepth=None):
"""Walk the object tree, pretty-printing each branch."""
self.ignore_caller()
Expand All @@ -190,4 +187,3 @@ def count_objects():
d = [(v, k) for k, v in d.iteritems()]
d.sort()
return d

17 changes: 9 additions & 8 deletions django_dowser/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
try:
from django.conf.urls import *
from django.conf.urls import url
except ImportError:
from django.conf.urls.defaults import *
from django.conf.urls.defaults import url

from . import views

urlpatterns = patterns('django_dowser.views',
url(r'^trace/(?P<typename>[\.\-\w]+)$', 'trace', name='dowser_trace_type'),
url(r'^trace/(?P<typename>[\.\-\w]+)/(?P<objid>\d+)$', 'trace', name='dowser_trace_object'),
url(r'^tree/(?P<typename>[\.\-\w]+)/(?P<objid>\d+)$', 'tree', name='dowser_tree'),
url(r'^$', 'index', name='dowser_index'),
)
urlpatterns = [
url(r'^trace/(?P<typename>[\.\-\w]+)$', views.trace, name='dowser_trace_type'),
url(r'^trace/(?P<typename>[\.\-\w]+)/(?P<objid>\d+)$', views.trace, name='dowser_trace_object'),
url(r'^tree/(?P<typename>[\.\-\w]+)/(?P<objid>\d+)$', views.tree, name='dowser_tree'),
url(r'^$', views.index, name='dowser_index'),
]
Loading

0 comments on commit b78a359

Please sign in to comment.