update to lastest 097ca7f146e6fec7fb598382dfecce8762c8c6cc 2017/05/31 (#4969)
This commit is contained in:
parent
0ce2b24bea
commit
5b6e52e816
|
@ -14,5 +14,5 @@ Portions of this are from commonware:
|
|||
|
||||
https://github.com/jsocol/commonware/blob/master/LICENSE
|
||||
|
||||
.. |Build Status| image:: https://travis-ci.org/andymckay/django-statsd.svg?branch=master
|
||||
:target: https://travis-ci.org/andymckay/django-statsd
|
||||
.. |Build Status| image:: https://travis-ci.org/django-statsd/django-statsd.svg?branch=master
|
||||
:target: https://travis-ci.org/django-statsd/django-statsd
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from django_statsd.clients import statsd
|
||||
import time
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import socket
|
||||
|
||||
from django.utils.importlib import import_module
|
||||
try:
|
||||
from importlib import import_module
|
||||
except ImportError:
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
_statsd = None
|
||||
|
@ -22,7 +26,7 @@ def get_client():
|
|||
# host = socket.gethostbyaddr(host)[2][0]
|
||||
port = get('STATSD_PORT', 8125)
|
||||
prefix = get('STATSD_PREFIX', None)
|
||||
return import_module(client).StatsClient(host, port, prefix)
|
||||
return import_module(client).StatsClient(host=host, port=port, prefix=prefix)
|
||||
|
||||
if not _statsd:
|
||||
_statsd = get_client()
|
||||
|
|
|
@ -22,4 +22,5 @@ class StatsClient(StatsClient):
|
|||
|
||||
def gauge(self, stat, value, rate=1, delta=False):
|
||||
"""Set a gauge value."""
|
||||
log.info('Gauge: %s, %s%s, %s' % (stat, '' if not delta else 'diff ', value , rate))
|
||||
log.info('Gauge: %s, %s%s, %s' % (
|
||||
stat, '' if not delta else 'diff ', value, rate))
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
import inspect
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import Http404
|
||||
|
||||
from django_statsd.clients import statsd
|
||||
|
||||
|
||||
class GraphiteMiddleware(object):
|
||||
try:
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
except ImportError:
|
||||
class MiddlewareMixin(object):
|
||||
pass
|
||||
|
||||
|
||||
class GraphiteMiddleware(MiddlewareMixin):
|
||||
|
||||
def process_response(self, request, response):
|
||||
statsd.incr('response.%s' % response.status_code)
|
||||
|
@ -21,7 +29,7 @@ class GraphiteMiddleware(object):
|
|||
statsd.incr('response.auth.500')
|
||||
|
||||
|
||||
class GraphiteRequestTimingMiddleware(object):
|
||||
class GraphiteRequestTimingMiddleware(MiddlewareMixin):
|
||||
"""statsd's timing data per view."""
|
||||
|
||||
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||
|
@ -48,8 +56,9 @@ class GraphiteRequestTimingMiddleware(object):
|
|||
data = dict(module=request._view_module, name=request._view_name,
|
||||
method=request.method)
|
||||
statsd.timing('view.{module}.{name}.{method}'.format(**data), ms)
|
||||
statsd.timing('view.{module}.{method}'.format(**data), ms)
|
||||
statsd.timing('view.{method}'.format(**data), ms)
|
||||
if getattr(settings, 'STATSD_VIEW_TIMER_DETAILS', True):
|
||||
statsd.timing('view.{module}.{method}'.format(**data), ms)
|
||||
statsd.timing('view.{method}'.format(**data), ms)
|
||||
|
||||
|
||||
class TastyPieRequestTimingMiddleware(GraphiteRequestTimingMiddleware):
|
||||
|
|
|
@ -35,6 +35,6 @@ def model_delete(sender, **kwargs):
|
|||
instance._meta.object_name,
|
||||
))
|
||||
|
||||
if getattr(settings, 'STATSD_MODEL_SIGNALS', True):
|
||||
if getattr(settings, 'STATSD_MODEL_SIGNALS', False):
|
||||
post_save.connect(model_save)
|
||||
post_delete.connect(model_delete)
|
||||
|
|
|
@ -58,7 +58,7 @@ def times_summary(stats):
|
|||
for stat in stats:
|
||||
timings[stat[0].split('|')[0]].append(stat[2])
|
||||
|
||||
for stat, v in timings.iteritems():
|
||||
for stat, v in timings.items():
|
||||
if not v:
|
||||
continue
|
||||
v.sort()
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
from django.conf import settings
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
try:
|
||||
from importlib import import_module
|
||||
except ImportError:
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
patches = getattr(settings, 'STATSD_PATCHES', [])
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import django
|
||||
from django.db.backends import util
|
||||
try:
|
||||
from django.db.backends import utils as util
|
||||
except ImportError:
|
||||
from django.db.backends import util
|
||||
|
||||
from django_statsd.patches.utils import wrap, patch_method
|
||||
from django_statsd.clients import statsd
|
||||
|
@ -33,6 +36,7 @@ def patched_execute(orig_execute, self, query, *args, **kwargs):
|
|||
with statsd.timer(key(self.db, 'execute.%s' % _get_query_type(query))):
|
||||
return orig_execute(self, query, *args, **kwargs)
|
||||
|
||||
|
||||
def patched_executemany(orig_executemany, self, query, *args, **kwargs):
|
||||
with statsd.timer(key(self.db, 'executemany.%s' % _get_query_type(query))):
|
||||
return orig_executemany(self, query, *args, **kwargs)
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
try:
|
||||
from django.conf.urls import patterns, url
|
||||
except ImportError: # django < 1.4
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
from django.conf.urls import url
|
||||
|
||||
import django_statsd.views
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url('^record$', 'django_statsd.views.record', name='django_statsd.record'),
|
||||
)
|
||||
urlpatterns = [
|
||||
url('^record$', django_statsd.views.record, name='django_statsd.record'),
|
||||
]
|
||||
|
|
|
@ -135,6 +135,7 @@ clients = {
|
|||
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def record(request):
|
||||
"""
|
||||
This is a Django method you can link to in your URLs that process
|
||||
|
@ -146,10 +147,11 @@ def record(request):
|
|||
you need for imposing security on this method, so that not just anyone
|
||||
can post to it.
|
||||
"""
|
||||
if 'client' not in request.REQUEST:
|
||||
data = request.POST or request.GET
|
||||
if 'client' not in data:
|
||||
return http.HttpResponseBadRequest()
|
||||
|
||||
client = request.REQUEST['client']
|
||||
client = data.get('client')
|
||||
if client not in clients:
|
||||
return http.HttpResponseBadRequest()
|
||||
|
||||
|
|
|
@ -16,6 +16,12 @@ Credits:
|
|||
Changes
|
||||
-------
|
||||
|
||||
0.3.15:
|
||||
|
||||
- push from travis to pypi to keep files clean
|
||||
- allow less statsd in the middleware
|
||||
- fix to a specific statsd version
|
||||
|
||||
0.3.14:
|
||||
|
||||
- pypy testing support
|
||||
|
@ -168,6 +174,14 @@ To get timings for your database or your cache, put in some monkeypatches::
|
|||
'django_statsd.patches.cache',
|
||||
]
|
||||
|
||||
You can change the host that stats are sent to with the `STATSD_HOST` setting::
|
||||
|
||||
STATSD_HOST = 'localhost'
|
||||
|
||||
Similarly, you can use the `STATSD_PORT`setting to customize the port number (which defaults to `8125`)::
|
||||
|
||||
STATSD_PORT = 8125
|
||||
|
||||
Toolbar integration
|
||||
-------------------
|
||||
|
||||
|
@ -250,11 +264,11 @@ First, make sure you can record the timings in your Django site urls. This
|
|||
could be done by pointing straight to the view or including the URL for
|
||||
example::
|
||||
|
||||
from django_statsd.urls import urlpatterns as statsd_patterns
|
||||
from django_statsd.urls import urlpatterns as statsd_patterns
|
||||
|
||||
urlpatterns = patterns('',
|
||||
('^services/timing/', include(statsd_patterns)),
|
||||
)
|
||||
urlpatterns = [
|
||||
url(r'^services/timing/', include(statsd_patterns)),
|
||||
]
|
||||
|
||||
In this case the URL to the record view will be `/services/timing/record`.
|
||||
|
||||
|
@ -326,6 +340,13 @@ everyone not in INTERNAL_IPS::
|
|||
|
||||
STATSD_RECORD_GUARD = internal_only
|
||||
|
||||
STATSD_VIEW_TIMER_DETAILS (optional)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The middleware sends timing pings for the almost the same thing three times
|
||||
when accessing a view: `module.name.method`, `module.method` and `method` by
|
||||
default. Setting this to `False` just does the former.
|
||||
|
||||
Logging errors
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -341,9 +362,20 @@ do this by adding in the handler. For example in your logging configuration::
|
|||
Testing
|
||||
=======
|
||||
|
||||
You can run tests with the following command:
|
||||
You need to install tox_ to run the tests.
|
||||
You can run the full test matrix with:
|
||||
|
||||
DJANGO_SETTINGS_MODULE='django_statsd.test_settings' nosetests
|
||||
tox
|
||||
|
||||
or choose a specific environment - let's say Python 3.4 and Django 1.11 - with:
|
||||
|
||||
tox -e py34-django111
|
||||
|
||||
You can list all the available environments with:
|
||||
|
||||
tox -l
|
||||
|
||||
.. _tox: http://tox.readthedocs.io/en/latest/index.html
|
||||
|
||||
Nose
|
||||
====
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
mock
|
||||
nose
|
||||
unittest2
|
||||
statsd>=2.0.0
|
||||
django<1.7
|
||||
pytest-django
|
||||
statsd==3.2.1
|
||||
|
|
8
setup.py
8
setup.py
|
@ -4,13 +4,13 @@ from setuptools import setup
|
|||
setup(
|
||||
# Because django-statsd was taken, I called this django-statsd-mozilla.
|
||||
name='django-statsd-mozilla',
|
||||
version='0.3.14',
|
||||
version='0.3.16',
|
||||
description='Django interface with statsd',
|
||||
long_description=open('README.rst').read(),
|
||||
author='Andy McKay',
|
||||
author_email='andym@mozilla.com',
|
||||
license='BSD',
|
||||
install_requires=['statsd>=2.0.0'],
|
||||
install_requires=['statsd >= 2.1.2, != 3.2 , <= 4.0'],
|
||||
packages=['django_statsd',
|
||||
'django_statsd/patches',
|
||||
'django_statsd/clients',
|
||||
|
@ -29,6 +29,8 @@ setup(
|
|||
'Intended Audience :: Developers',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Framework :: Django'
|
||||
'Framework :: Django',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3'
|
||||
]
|
||||
)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
def pytest_configure():
|
||||
from django.conf import settings
|
||||
|
||||
settings.configure(
|
||||
DEBUG_PROPAGATE_EXCEPTIONS=True,
|
||||
DATABASES={
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ':memory:'
|
||||
}
|
||||
},
|
||||
SITE_ID=1,
|
||||
SECRET_KEY='not very secret in tests',
|
||||
ROOT_URLCONF='django_statsd.urls',
|
||||
INSTALLED_APPS=(
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.staticfiles',
|
||||
'django_statsd',
|
||||
'tests',
|
||||
),
|
||||
STATSD_CLIENT='django_statsd.clients.null',
|
||||
STATSD_PREFIX=None,
|
||||
METLOG=None,
|
||||
)
|
||||
|
||||
try:
|
||||
import django
|
||||
django.setup()
|
||||
except AttributeError:
|
||||
pass
|
|
@ -0,0 +1,546 @@
|
|||
import json
|
||||
import logging.config
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from django.conf import settings
|
||||
from nose.exc import SkipTest
|
||||
from nose import tools as nose_tools
|
||||
|
||||
try:
|
||||
from django.urls import reverse
|
||||
except ImportError:
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse, HttpResponseForbidden
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.utils.http import urlencode
|
||||
|
||||
import mock
|
||||
from nose.tools import eq_
|
||||
from django_statsd.clients import get_client, statsd
|
||||
from django_statsd.patches import utils
|
||||
from django_statsd.patches.db import (
|
||||
patched_callproc,
|
||||
patched_execute,
|
||||
patched_executemany,
|
||||
)
|
||||
from django_statsd import middleware
|
||||
|
||||
cfg = {
|
||||
'version': 1,
|
||||
'formatters': {},
|
||||
'handlers': {
|
||||
'test_statsd_handler': {
|
||||
'class': 'django_statsd.loggers.errors.StatsdHandler',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'test.logging': {
|
||||
'handlers': ['test_statsd_handler'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@mock.patch.object(middleware.statsd, 'incr')
|
||||
class TestIncr(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.req = RequestFactory().get('/')
|
||||
self.res = HttpResponse()
|
||||
|
||||
def test_graphite_response(self, incr):
|
||||
gmw = middleware.GraphiteMiddleware()
|
||||
gmw.process_response(self.req, self.res)
|
||||
assert incr.called
|
||||
|
||||
def test_graphite_response_authenticated(self, incr):
|
||||
self.req.user = mock.Mock()
|
||||
self.req.user.is_authenticated.return_value = True
|
||||
gmw = middleware.GraphiteMiddleware()
|
||||
gmw.process_response(self.req, self.res)
|
||||
eq_(incr.call_count, 2)
|
||||
|
||||
def test_graphite_exception(self, incr):
|
||||
gmw = middleware.GraphiteMiddleware()
|
||||
gmw.process_exception(self.req, None)
|
||||
assert incr.called
|
||||
|
||||
def test_graphite_exception_authenticated(self, incr):
|
||||
self.req.user = mock.Mock()
|
||||
self.req.user.is_authenticated.return_value = True
|
||||
gmw = middleware.GraphiteMiddleware()
|
||||
gmw.process_exception(self.req, None)
|
||||
eq_(incr.call_count, 2)
|
||||
|
||||
|
||||
@mock.patch.object(middleware.statsd, 'timing')
|
||||
class TestTiming(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.req = RequestFactory().get('/')
|
||||
self.res = HttpResponse()
|
||||
|
||||
def test_request_timing(self, timing):
|
||||
func = lambda x: x
|
||||
gmw = middleware.GraphiteRequestTimingMiddleware()
|
||||
gmw.process_view(self.req, func, tuple(), dict())
|
||||
gmw.process_response(self.req, self.res)
|
||||
eq_(timing.call_count, 3)
|
||||
names = ['view.%s.%s.GET' % (func.__module__, func.__name__),
|
||||
'view.%s.GET' % func.__module__,
|
||||
'view.GET']
|
||||
for expected, (args, kwargs) in zip(names, timing.call_args_list):
|
||||
eq_(expected, args[0])
|
||||
|
||||
def test_request_timing_exception(self, timing):
|
||||
func = lambda x: x
|
||||
gmw = middleware.GraphiteRequestTimingMiddleware()
|
||||
gmw.process_view(self.req, func, tuple(), dict())
|
||||
gmw.process_exception(self.req, self.res)
|
||||
eq_(timing.call_count, 3)
|
||||
names = ['view.%s.%s.GET' % (func.__module__, func.__name__),
|
||||
'view.%s.GET' % func.__module__,
|
||||
'view.GET']
|
||||
for expected, (args, kwargs) in zip(names, timing.call_args_list):
|
||||
eq_(expected, args[0])
|
||||
|
||||
def test_request_timing_tastypie(self, timing):
|
||||
func = lambda x: x
|
||||
gmw = middleware.TastyPieRequestTimingMiddleware()
|
||||
gmw.process_view(self.req, func, tuple(), {
|
||||
'api_name': 'my_api_name',
|
||||
'resource_name': 'my_resource_name'
|
||||
})
|
||||
gmw.process_response(self.req, self.res)
|
||||
eq_(timing.call_count, 3)
|
||||
names = ['view.my_api_name.my_resource_name.GET',
|
||||
'view.my_api_name.GET',
|
||||
'view.GET']
|
||||
for expected, (args, kwargs) in zip(names, timing.call_args_list):
|
||||
eq_(expected, args[0])
|
||||
|
||||
def test_request_timing_tastypie_fallback(self, timing):
|
||||
func = lambda x: x
|
||||
gmw = middleware.TastyPieRequestTimingMiddleware()
|
||||
gmw.process_view(self.req, func, tuple(), dict())
|
||||
gmw.process_response(self.req, self.res)
|
||||
eq_(timing.call_count, 3)
|
||||
names = ['view.%s.%s.GET' % (func.__module__, func.__name__),
|
||||
'view.%s.GET' % func.__module__,
|
||||
'view.GET']
|
||||
for expected, (args, kwargs) in zip(names, timing.call_args_list):
|
||||
eq_(expected, args[0])
|
||||
|
||||
|
||||
class TestClient(unittest.TestCase):
|
||||
|
||||
@mock.patch.object(settings, 'STATSD_CLIENT', 'statsd.client')
|
||||
def test_normal(self):
|
||||
eq_(get_client().__module__, 'statsd.client')
|
||||
|
||||
@mock.patch.object(settings, 'STATSD_CLIENT',
|
||||
'django_statsd.clients.null')
|
||||
def test_null(self):
|
||||
eq_(get_client().__module__, 'django_statsd.clients.null')
|
||||
|
||||
@mock.patch.object(settings, 'STATSD_CLIENT',
|
||||
'django_statsd.clients.toolbar')
|
||||
def test_toolbar(self):
|
||||
eq_(get_client().__module__, 'django_statsd.clients.toolbar')
|
||||
|
||||
@mock.patch.object(settings, 'STATSD_CLIENT',
|
||||
'django_statsd.clients.toolbar')
|
||||
def test_toolbar_send(self):
|
||||
client = get_client()
|
||||
eq_(client.cache, {})
|
||||
client.incr('testing')
|
||||
eq_(client.cache, {'testing|count': [[1, 1]]})
|
||||
|
||||
|
||||
class TestMetlogClient(TestCase):
|
||||
|
||||
def check_metlog(self):
|
||||
try:
|
||||
from metlog.config import client_from_dict_config
|
||||
return client_from_dict_config
|
||||
except ImportError:
|
||||
raise SkipTest("Metlog is not installed")
|
||||
|
||||
@nose_tools.raises(AttributeError)
|
||||
def test_no_metlog(self):
|
||||
with self.settings(STATSD_PREFIX='moz_metlog',
|
||||
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
|
||||
get_client()
|
||||
|
||||
def _create_client(self):
|
||||
client_from_dict_config = self.check_metlog()
|
||||
|
||||
# Need to load within the test in case metlog is not installed
|
||||
from metlog.config import client_from_dict_config
|
||||
|
||||
METLOG_CONF = {
|
||||
'logger': 'django-statsd',
|
||||
'sender': {
|
||||
'class': 'metlog.senders.DebugCaptureSender',
|
||||
},
|
||||
}
|
||||
|
||||
return client_from_dict_config(METLOG_CONF)
|
||||
|
||||
def test_get_client(self):
|
||||
metlog = self._create_client()
|
||||
with self.settings(METLOG=metlog,
|
||||
STATSD_PREFIX='moz_metlog',
|
||||
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
|
||||
client = get_client()
|
||||
eq_(client.__module__, 'django_statsd.clients.moz_metlog')
|
||||
|
||||
def test_metlog_incr(self):
|
||||
metlog = self._create_client()
|
||||
with self.settings(METLOG=metlog,
|
||||
STATSD_PREFIX='moz_metlog',
|
||||
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
|
||||
client = get_client()
|
||||
eq_(len(client.metlog.sender.msgs), 0)
|
||||
client.incr('testing')
|
||||
eq_(len(client.metlog.sender.msgs), 1)
|
||||
|
||||
msg = json.loads(client.metlog.sender.msgs[0])
|
||||
eq_(msg['severity'], 6)
|
||||
eq_(msg['payload'], '1')
|
||||
eq_(msg['fields']['rate'], 1)
|
||||
eq_(msg['fields']['name'], 'moz_metlog.testing')
|
||||
eq_(msg['type'], 'counter')
|
||||
|
||||
def test_metlog_decr(self):
|
||||
metlog = self._create_client()
|
||||
with self.settings(METLOG=metlog,
|
||||
STATSD_PREFIX='moz_metlog',
|
||||
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
|
||||
client = get_client()
|
||||
eq_(len(client.metlog.sender.msgs), 0)
|
||||
client.decr('testing')
|
||||
eq_(len(client.metlog.sender.msgs), 1)
|
||||
|
||||
msg = json.loads(client.metlog.sender.msgs[0])
|
||||
eq_(msg['severity'], 6)
|
||||
eq_(msg['payload'], '-1')
|
||||
eq_(msg['fields']['rate'], 1)
|
||||
eq_(msg['fields']['name'], 'moz_metlog.testing')
|
||||
eq_(msg['type'], 'counter')
|
||||
|
||||
def test_metlog_timing(self):
|
||||
metlog = self._create_client()
|
||||
with self.settings(METLOG=metlog,
|
||||
STATSD_PREFIX='moz_metlog',
|
||||
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
|
||||
client = get_client()
|
||||
eq_(len(client.metlog.sender.msgs), 0)
|
||||
client.timing('testing', 512, rate=2)
|
||||
eq_(len(client.metlog.sender.msgs), 1)
|
||||
|
||||
msg = json.loads(client.metlog.sender.msgs[0])
|
||||
eq_(msg['severity'], 6)
|
||||
eq_(msg['payload'], '512')
|
||||
eq_(msg['fields']['rate'], 2)
|
||||
eq_(msg['fields']['name'], 'moz_metlog.testing')
|
||||
eq_(msg['type'], 'timer')
|
||||
|
||||
@nose_tools.raises(AttributeError)
|
||||
def test_metlog_no_prefixes(self):
|
||||
metlog = self._create_client()
|
||||
|
||||
with self.settings(METLOG=metlog,
|
||||
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
|
||||
client = get_client()
|
||||
client.incr('foo', 2)
|
||||
|
||||
def test_metlog_prefixes(self):
|
||||
metlog = self._create_client()
|
||||
|
||||
with self.settings(METLOG=metlog,
|
||||
STATSD_PREFIX='some_prefix',
|
||||
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
|
||||
client = get_client()
|
||||
eq_(len(client.metlog.sender.msgs), 0)
|
||||
|
||||
client.timing('testing', 512, rate=2)
|
||||
client.incr('foo', 2)
|
||||
client.decr('bar', 5)
|
||||
|
||||
eq_(len(client.metlog.sender.msgs), 3)
|
||||
|
||||
msg = json.loads(client.metlog.sender.msgs[0])
|
||||
eq_(msg['severity'], 6)
|
||||
eq_(msg['payload'], '512')
|
||||
eq_(msg['fields']['rate'], 2)
|
||||
eq_(msg['fields']['name'], 'some_prefix.testing')
|
||||
eq_(msg['type'], 'timer')
|
||||
|
||||
msg = json.loads(client.metlog.sender.msgs[1])
|
||||
eq_(msg['severity'], 6)
|
||||
eq_(msg['payload'], '2')
|
||||
eq_(msg['fields']['rate'], 1)
|
||||
eq_(msg['fields']['name'], 'some_prefix.foo')
|
||||
eq_(msg['type'], 'counter')
|
||||
|
||||
msg = json.loads(client.metlog.sender.msgs[2])
|
||||
eq_(msg['severity'], 6)
|
||||
eq_(msg['payload'], '-5')
|
||||
eq_(msg['fields']['rate'], 1)
|
||||
eq_(msg['fields']['name'], 'some_prefix.bar')
|
||||
eq_(msg['type'], 'counter')
|
||||
|
||||
|
||||
# This is primarily for Zamboni, which loads in the custom middleware
|
||||
# classes, one of which, breaks posts to our url. Let's stop that.
|
||||
@mock.patch.object(settings, 'MIDDLEWARE_CLASSES', [])
|
||||
class TestRecord(TestCase):
|
||||
|
||||
urls = 'django_statsd.urls'
|
||||
|
||||
def setUp(self):
|
||||
super(TestRecord, self).setUp()
|
||||
self.url = reverse('django_statsd.record')
|
||||
settings.STATSD_RECORD_GUARD = None
|
||||
self.good = {'client': 'boomerang', 'nt_nav_st': 1,
|
||||
'nt_domcomp': 3}
|
||||
self.stick = {'client': 'stick',
|
||||
'window.performance.timing.domComplete': 123,
|
||||
'window.performance.timing.domInteractive': 456,
|
||||
'window.performance.timing.domLoading': 789,
|
||||
'window.performance.timing.navigationStart': 0,
|
||||
'window.performance.navigation.redirectCount': 3,
|
||||
'window.performance.navigation.type': 1}
|
||||
|
||||
def test_no_client(self):
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_no_valid_client(self):
|
||||
response = self.client.get(self.url, {'client': 'no'})
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_boomerang_almost(self):
|
||||
response = self.client.get(self.url, {'client': 'boomerang'})
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_boomerang_minimum(self):
|
||||
content = self.client.get(
|
||||
self.url, {
|
||||
'client': 'boomerang',
|
||||
'nt_nav_st': 1,
|
||||
}).content.decode()
|
||||
assert content == 'recorded'
|
||||
|
||||
@mock.patch('django_statsd.views.process_key')
|
||||
def test_boomerang_something(self, process_key):
|
||||
content = self.client.get(self.url, self.good).content.decode()
|
||||
assert content == 'recorded'
|
||||
assert process_key.called
|
||||
|
||||
def test_boomerang_post(self):
|
||||
assert self.client.post(self.url + '?' + urlencode(self.good), self.good).status_code == 405
|
||||
|
||||
def test_good_guard(self):
|
||||
settings.STATSD_RECORD_GUARD = lambda r: None
|
||||
response = self.client.get(self.url, self.good)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_bad_guard(self):
|
||||
settings.STATSD_RECORD_GUARD = lambda r: HttpResponseForbidden()
|
||||
response = self.client.get(self.url, self.good)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_stick_get(self):
|
||||
assert self.client.get(self.url, self.stick).status_code == 405
|
||||
|
||||
@mock.patch('django_statsd.views.process_key')
|
||||
def test_stick(self, process_key):
|
||||
assert self.client.post(self.url, self.stick).status_code == 200
|
||||
assert process_key.called
|
||||
|
||||
def test_stick_start(self):
|
||||
data = self.stick.copy()
|
||||
del data['window.performance.timing.navigationStart']
|
||||
assert self.client.post(self.url, data).status_code == 400
|
||||
|
||||
@mock.patch('django_statsd.views.process_key')
|
||||
def test_stick_missing(self, process_key):
|
||||
data = self.stick.copy()
|
||||
del data['window.performance.timing.domInteractive']
|
||||
assert self.client.post(self.url, data).status_code == 200
|
||||
assert process_key.called
|
||||
|
||||
def test_stick_garbage(self):
|
||||
data = self.stick.copy()
|
||||
data['window.performance.timing.domInteractive'] = '<alert>'
|
||||
assert self.client.post(self.url, data).status_code == 400
|
||||
|
||||
def test_stick_some_garbage(self):
|
||||
data = self.stick.copy()
|
||||
data['window.performance.navigation.redirectCount'] = '<alert>'
|
||||
assert self.client.post(self.url, data).status_code == 400
|
||||
|
||||
def test_stick_more_garbage(self):
|
||||
data = self.stick.copy()
|
||||
data['window.performance.navigation.type'] = '<alert>'
|
||||
assert self.client.post(self.url, data).status_code == 400
|
||||
|
||||
|
||||
@mock.patch.object(middleware.statsd, 'incr')
|
||||
class TestErrorLog(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
logging.config.dictConfig(cfg)
|
||||
self.log = logging.getLogger('test.logging')
|
||||
|
||||
def division_error(self):
|
||||
try:
|
||||
1 / 0
|
||||
except:
|
||||
return sys.exc_info()
|
||||
|
||||
def test_emit(self, incr):
|
||||
self.log.error('blargh!', exc_info=self.division_error())
|
||||
assert incr.call_args[0][0] == 'error.zerodivisionerror'
|
||||
|
||||
def test_not_emit(self, incr):
|
||||
self.log.error('blargh!')
|
||||
assert not incr.called
|
||||
|
||||
|
||||
class TestPatchMethod(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPatchMethod, self).setUp()
|
||||
|
||||
class DummyClass(object):
|
||||
|
||||
def sumargs(self, a, b, c=3, d=4):
|
||||
return a + b + c + d
|
||||
|
||||
def badfn(self, a, b=2):
|
||||
raise ValueError
|
||||
|
||||
self.cls = DummyClass
|
||||
|
||||
def test_late_patching(self):
|
||||
"""
|
||||
Objects created before patching should get patched as well.
|
||||
"""
|
||||
def patch_fn(original_fn, self, *args, **kwargs):
|
||||
return original_fn(self, *args, **kwargs) + 10
|
||||
|
||||
obj = self.cls()
|
||||
self.assertEqual(obj.sumargs(1, 2, 3, 4), 10)
|
||||
utils.patch_method(self.cls, 'sumargs')(patch_fn)
|
||||
self.assertEqual(obj.sumargs(1, 2, 3, 4), 20)
|
||||
|
||||
def test_doesnt_call_original_implicitly(self):
|
||||
"""
|
||||
Original fn must be called explicitly from patched to be
|
||||
executed.
|
||||
"""
|
||||
def patch_fn(original_fn, self, *args, **kwargs):
|
||||
return 10
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
obj = self.cls()
|
||||
obj.badfn(1, 2)
|
||||
|
||||
utils.patch_method(self.cls, 'badfn')(patch_fn)
|
||||
self.assertEqual(obj.badfn(1, 2), 10)
|
||||
|
||||
def test_args_kwargs_are_honored(self):
|
||||
"""
|
||||
Args and kwargs must be honored between calls from the patched to
|
||||
the original version.
|
||||
"""
|
||||
def patch_fn(original_fn, self, *args, **kwargs):
|
||||
return original_fn(self, *args, **kwargs)
|
||||
|
||||
utils.patch_method(self.cls, 'sumargs')(patch_fn)
|
||||
obj = self.cls()
|
||||
self.assertEqual(obj.sumargs(1, 2), 10)
|
||||
self.assertEqual(obj.sumargs(1, 1, d=1), 6)
|
||||
self.assertEqual(obj.sumargs(1, 1, 1, 1), 4)
|
||||
|
||||
def test_patched_fn_can_receive_arbitrary_arguments(self):
|
||||
"""
|
||||
Args and kwargs can be received arbitrarily with no contraints on
|
||||
the patched fn, even if the original_fn had a fixed set of
|
||||
allowed args and kwargs.
|
||||
"""
|
||||
def patch_fn(original_fn, self, *args, **kwargs):
|
||||
return args, kwargs
|
||||
|
||||
utils.patch_method(self.cls, 'badfn')(patch_fn)
|
||||
obj = self.cls()
|
||||
self.assertEqual(obj.badfn(1, d=2), ((1,), {'d': 2}))
|
||||
self.assertEqual(obj.badfn(1, d=2), ((1,), {'d': 2}))
|
||||
self.assertEqual(obj.badfn(1, 2, c=1, d=2), ((1, 2), {'c': 1, 'd': 2}))
|
||||
|
||||
|
||||
class TestCursorWrapperPatching(TestCase):
|
||||
example_queries = {
|
||||
'select': 'select * from something;',
|
||||
'insert': 'insert (1, 2) into something;',
|
||||
'update': 'update something set a=1;',
|
||||
}
|
||||
|
||||
def test_patched_callproc_calls_timer(self):
|
||||
for operation, query in list(self.example_queries.items()):
|
||||
with mock.patch.object(statsd, 'timer') as timer:
|
||||
client = mock.Mock(executable_name='client_executable_name')
|
||||
db = mock.Mock(executable_name='name', alias='alias', client=client)
|
||||
instance = mock.Mock(db=db)
|
||||
|
||||
patched_callproc(lambda *args, **kwargs: None, instance, query)
|
||||
|
||||
self.assertEqual(timer.call_count, 1)
|
||||
self.assertEqual(timer.call_args[0][0], 'db.client_executable_name.alias.callproc.%s' % operation)
|
||||
|
||||
def test_patched_execute_calls_timer(self):
|
||||
for operation, query in list(self.example_queries.items()):
|
||||
with mock.patch.object(statsd, 'timer') as timer:
|
||||
client = mock.Mock(executable_name='client_executable_name')
|
||||
db = mock.Mock(executable_name='name', alias='alias', client=client)
|
||||
instance = mock.Mock(db=db)
|
||||
|
||||
patched_execute(lambda *args, **kwargs: None, instance, query)
|
||||
|
||||
self.assertEqual(timer.call_count, 1)
|
||||
self.assertEqual(timer.call_args[0][0], 'db.client_executable_name.alias.execute.%s' % operation)
|
||||
|
||||
def test_patched_executemany_calls_timer(self):
|
||||
for operation, query in list(self.example_queries.items()):
|
||||
with mock.patch.object(statsd, 'timer') as timer:
|
||||
client = mock.Mock(executable_name='client_executable_name')
|
||||
db = mock.Mock(executable_name='name', alias='alias', client=client)
|
||||
instance = mock.Mock(db=db)
|
||||
|
||||
patched_executemany(lambda *args, **kwargs: None, instance, query)
|
||||
|
||||
self.assertEqual(timer.call_count, 1)
|
||||
self.assertEqual(timer.call_args[0][0], 'db.client_executable_name.alias.executemany.%s' % operation)
|
||||
|
||||
@mock.patch('django_statsd.patches.db.patched_callproc')
|
||||
@mock.patch('django_statsd.patches.db.patched_executemany')
|
||||
@mock.patch('django_statsd.patches.db.patched_execute')
|
||||
@mock.patch('django.db.backends.utils.CursorWrapper')
|
||||
def test_cursorwrapper_patching(self, CursorWrapper, execute, executemany, callproc):
|
||||
from django_statsd.patches.db import patch
|
||||
execute.__name__ = 'execute'
|
||||
executemany.__name__ = 'executemany'
|
||||
callproc.__name__ = 'callproc'
|
||||
execute.return_value = 'execute'
|
||||
executemany.return_value = 'executemany'
|
||||
callproc.return_value = 'callproc'
|
||||
patch()
|
||||
|
||||
self.assertEqual(CursorWrapper.execute(), 'execute')
|
||||
self.assertEqual(CursorWrapper.executemany(), 'executemany')
|
||||
self.assertEqual(CursorWrapper.callproc(), 'callproc')
|
|
@ -0,0 +1,32 @@
|
|||
[pytest]
|
||||
addopts=--tb=short
|
||||
|
||||
[tox]
|
||||
envlist =
|
||||
{py27,py33,py34,py35,pypy,pypy3}-django18,
|
||||
{py27,py34,py35,pypy,pypy3}-django{19,110},
|
||||
{py27,py34,py35,py36,pypy,pypy3}-django111,
|
||||
{py35,py36,pypy,pypy3}-djangomaster
|
||||
|
||||
[travis:env]
|
||||
DJANGO =
|
||||
1.8: django18
|
||||
1.9: django19
|
||||
1.10: django110
|
||||
1.11: django111
|
||||
master: djangomaster
|
||||
|
||||
[testenv]
|
||||
commands = py.test
|
||||
setenv =
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
PYTHONWARNINGS=once
|
||||
|
||||
deps =
|
||||
py27: -roptional.txt
|
||||
django18: Django>=1.8,<1.9
|
||||
django19: Django>=1.9,<1.10
|
||||
django110: Django>=1.10,<1.11
|
||||
django111: Django>=1.11,<2.0
|
||||
djangomaster: https://github.com/django/django/archive/master.tar.gz
|
||||
-rrequirements.txt
|
Loading…
Reference in New Issue