From b0bbc1403988ccec93d0f97ed41bc13bc91270c4 Mon Sep 17 00:00:00 2001 From: Grant Toeppen Date: Thu, 17 Apr 2014 16:04:25 -0700 Subject: [PATCH 1/3] version check on django to see how we wrap the CursorDebugWrapper --- django_statsd/patches/db.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/django_statsd/patches/db.py b/django_statsd/patches/db.py index e5bea2b..4806d4c 100644 --- a/django_statsd/patches/db.py +++ b/django_statsd/patches/db.py @@ -9,11 +9,6 @@ def key(db, attr): def __getattr__(self, attr): - """ - The CursorWrapper is a pretty small wrapper around the cursor. - If you are NOT in debug mode, this is the wrapper that's used. - Sadly if it's in debug mode, we get a different wrapper. - """ if django.VERSION < (1, 6) and self.db.is_managed(): # In Django 1.6 you can't put a connection in managed mode self.db.set_dirty() @@ -39,7 +34,16 @@ def wrap_class(base): def patch(): + """ + The CursorWrapper is a pretty small wrapper around the cursor. + If you are NOT in debug mode, this is the wrapper that's used. + Sadly if it's in debug mode, we get a different wrapper for version earlier than 1.6. + """ + # So that it will work when DEBUG = True. - util.CursorDebugWrapper = wrap_class(util.CursorDebugWrapper) + if django.VERSION < (1, 6): + util.CursorDebugWrapper = wrap_class(util.CursorDebugWrapper) + else: + util.CursorDebugWrapper.__getattr__ = __getattr__ # So that it will work when DEBUG = False. util.CursorWrapper.__getattr__ = __getattr__ From 257561cf43440bd8a28f8766212e6115f4549071 Mon Sep 17 00:00:00 2001 From: jawn_b Date: Sat, 26 Apr 2014 16:43:24 -0400 Subject: [PATCH 2/3] Fix issue where db timings were not being patch properly for django 1.6 and DEBUG=False --- django_statsd/patches/db.py | 44 +++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/django_statsd/patches/db.py b/django_statsd/patches/db.py index 4806d4c..82307ef 100644 --- a/django_statsd/patches/db.py +++ b/django_statsd/patches/db.py @@ -1,14 +1,18 @@ import django from django.db.backends import util - +from django_statsd.clients import statsd from django_statsd.patches.utils import wrap def key(db, attr): return 'db.%s.%s.%s' % (db.client.executable_name, db.alias, attr) - def __getattr__(self, attr): + """ + The CursorWrapper is a pretty small wrapper around the cursor. + If you are NOT in debug mode, this is the wrapper that's used. + Sadly if it's in debug mode, we get a different wrapper. + """ if django.VERSION < (1, 6) and self.db.is_managed(): # In Django 1.6 you can't put a connection in managed mode self.db.set_dirty() @@ -20,19 +24,27 @@ def __getattr__(self, attr): return getattr(self.cursor, attr) -def wrap_class(base): +def wrap_class(base, super_class=None): + """Returns a sub-class of the argument 'base' + The sub class has its execute and executemany methods will record query timings. + + If the execute and executemany methods you're overriding makes super() + calls, the argument super_class can be provided to not break things. + """ + class Wrapper(base): - def execute(self, *args, **kw): - return wrap(super(Wrapper, self).execute, - key(self.db, 'execute'))(*args, **kw) - def executemany(self, *args, **kw): - return wrap(super(Wrapper, self).executemany, - key(self.db, 'executemany'))(*args, **kw) + def execute(self, *args, **kwargs): + with statsd.timer(key(self.db, 'execute')): + return super(super_class, self).execute(*args, **kwargs) + def executemany(self, *args, **kwargs): + with statsd.timer(key(self.db, 'executemany')): + return super(super_class, self).execute(*args, **kwargs) + + super_class = super_class or Wrapper return Wrapper - def patch(): """ The CursorWrapper is a pretty small wrapper around the cursor. @@ -40,10 +52,10 @@ def patch(): Sadly if it's in debug mode, we get a different wrapper for version earlier than 1.6. """ - # So that it will work when DEBUG = True. - if django.VERSION < (1, 6): - util.CursorDebugWrapper = wrap_class(util.CursorDebugWrapper) + if django.VERSION > (1, 6): + util.CursorDebugWrapper = wrap_class(util.CursorDebugWrapper, + super_class=util.CursorDebugWrapper) + util.CursorWrapper = wrap_class(util.CursorWrapper) else: - util.CursorDebugWrapper.__getattr__ = __getattr__ - # So that it will work when DEBUG = False. - util.CursorWrapper.__getattr__ = __getattr__ + util.CursorDebugWrapper = wrap_class(util.CursorDebugWrapper) + util.CursorWrapper.__getattr__ = __getattr__ From 7329f99cea34482955cc13a106276c8620fc25ac Mon Sep 17 00:00:00 2001 From: jawn_b Date: Sun, 27 Apr 2014 11:11:14 -0400 Subject: [PATCH 3/3] Instead of making super() calls just monkey patch things directly. --- django_statsd/patches/db.py | 57 +++++++++++++++------------------- django_statsd/patches/utils.py | 15 ++++++++- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/django_statsd/patches/db.py b/django_statsd/patches/db.py index 82307ef..fd6c8e1 100644 --- a/django_statsd/patches/db.py +++ b/django_statsd/patches/db.py @@ -1,50 +1,27 @@ import django from django.db.backends import util +from django_statsd.patches.utils import wrap, patch_method from django_statsd.clients import statsd -from django_statsd.patches.utils import wrap def key(db, attr): return 'db.%s.%s.%s' % (db.client.executable_name, db.alias, attr) -def __getattr__(self, attr): +def pre_django_1_6_cursorwrapper_getattr(self, attr): """ The CursorWrapper is a pretty small wrapper around the cursor. If you are NOT in debug mode, this is the wrapper that's used. Sadly if it's in debug mode, we get a different wrapper. """ - if django.VERSION < (1, 6) and self.db.is_managed(): - # In Django 1.6 you can't put a connection in managed mode + if self.db.is_managed(): self.db.set_dirty() if attr in self.__dict__: return self.__dict__[attr] else: - if attr in ['execute', 'executemany']: + if attr in ['execute', 'executemany', 'callproc']: return wrap(getattr(self.cursor, attr), key(self.db, attr)) return getattr(self.cursor, attr) - -def wrap_class(base, super_class=None): - """Returns a sub-class of the argument 'base' - The sub class has its execute and executemany methods will record query timings. - - If the execute and executemany methods you're overriding makes super() - calls, the argument super_class can be provided to not break things. - """ - - class Wrapper(base): - - def execute(self, *args, **kwargs): - with statsd.timer(key(self.db, 'execute')): - return super(super_class, self).execute(*args, **kwargs) - - def executemany(self, *args, **kwargs): - with statsd.timer(key(self.db, 'executemany')): - return super(super_class, self).execute(*args, **kwargs) - - super_class = super_class or Wrapper - return Wrapper - def patch(): """ The CursorWrapper is a pretty small wrapper around the cursor. @@ -52,10 +29,26 @@ def patch(): Sadly if it's in debug mode, we get a different wrapper for version earlier than 1.6. """ + def execute(orig_execute, self, *args, **kwargs): + with statsd.timer(key(self.db, 'execute')): + return orig_execute(self, *args, **kwargs) + + def executemany(orig_executemany, self, *args, **kwargs): + with statsd.timer(key(self.db, 'executemany')): + return orig_executemany(self, *args, **kwargs) + + def callproc(orig_callproc, self, *args, **kwargs): + with statsd.timer(key(self.db, 'callproc')): + return orig_callproc(self, *args, **kwargs) + if django.VERSION > (1, 6): - util.CursorDebugWrapper = wrap_class(util.CursorDebugWrapper, - super_class=util.CursorDebugWrapper) - util.CursorWrapper = wrap_class(util.CursorWrapper) + # In 1.6+ util.CursorDebugWrapper just makes calls to CursorWrapper + # As such, we only need to instrument CursorWrapper. + # Instrumenting both will result in duplicated metrics + patch_method(util.CursorWrapper, 'execute')(execute) + patch_method(util.CursorWrapper, 'executemany')(executemany) + patch_method(util.CursorWrapper, 'callproc')(callproc) else: - util.CursorDebugWrapper = wrap_class(util.CursorDebugWrapper) - util.CursorWrapper.__getattr__ = __getattr__ + util.CursorWrapper.__getattr__ = pre_django_1_6_cursorwrapper_getattr + patch_method(util.CursorDebugWrapper, 'execute')(execute) + patch_method(util.CursorDebugWrapper, 'executemany')(executemany) diff --git a/django_statsd/patches/utils.py b/django_statsd/patches/utils.py index 2eff28c..74f2344 100644 --- a/django_statsd/patches/utils.py +++ b/django_statsd/patches/utils.py @@ -1,6 +1,19 @@ from django_statsd.clients import statsd -from functools import partial +from functools import partial, wraps +def patch_method(target, name, external_decorator=None): + + def decorator(patch_function): + original_function = getattr(target, name) + + @wraps(patch_function) + def wrapper(*args, **kw): + return patch_function(original_function, *args, **kw) + + setattr(target, name, wrapper) + return wrapper + + return decorator def wrapped(method, key, *args, **kw): with statsd.timer(key):