Skip to content

Commit 4a37128

Browse files
hmstepaneklrafeei
authored andcommitted
Reduce number of spans in django framework (#779)
* Do not wrap useless middlewares * Fixup: use frozenset
1 parent 777e025 commit 4a37128

File tree

4 files changed

+64
-95
lines changed

4 files changed

+64
-95
lines changed

newrelic/hooks/framework_django.py

Lines changed: 41 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@
3939
from newrelic.config import extra_settings
4040
from newrelic.core.config import global_settings
4141

42+
# These middleware are not useful to wrap as they don't do anything particularly
43+
# interesting that could cause performance issues.
44+
MIDDLEWARE_DENY_WRAP = frozenset(
45+
{
46+
"django.middleware.csrf:CsrfViewMiddleware",
47+
"django.middleware.clickjacking:XFrameOptionsMiddleware",
48+
"django.contrib.messages.middleware:MessageMiddleware",
49+
"django.middleware.csrf:CsrfViewMiddleware",
50+
"django.middleware.common:CommonMiddleware",
51+
"django.middleware.security:SecurityMiddleware",
52+
}
53+
)
54+
4255
_logger = logging.getLogger(__name__)
4356

4457
_boolean_states = {
@@ -144,6 +157,7 @@ def should_add_browser_timing(response, transaction):
144157
# Response middleware for automatically inserting RUM header into HTML response returned by application
145158

146159

160+
147161
def browser_timing_insertion(response, transaction):
148162
# No point continuing if header is empty. This can occur if RUM is not enabled within the UI. We don't want to
149163
# generate the header just yet as we want to do that as late as possible so that application server time in header
@@ -176,6 +190,7 @@ def browser_timing_insertion(response, transaction):
176190
# will be automatically inserted into set of tag libraries when performing step to instrument the middleware.
177191

178192

193+
179194
def newrelic_browser_timing_header():
180195
from django.utils.safestring import mark_safe
181196

@@ -238,77 +253,12 @@ def wrapper(wrapped, instance, args, kwargs):
238253
return FunctionWrapper(wrapped, wrapper)
239254

240255
for wrapped in middleware:
241-
yield wrapper(wrapped)
242-
243-
244-
# Because this is not being used in any version of Django that is
245-
# within New Relic's support window, no tests will be added
246-
# for this. However, value exists to keeping backwards compatible
247-
# functionality, so instead of removing this instrumentation, this
248-
# will be excluded from the coverage analysis.
249-
def wrap_view_middleware(middleware): # pragma: no cover
250-
# This is no longer being used. The changes to strip the
251-
# wrapper from the view handler when passed into the function
252-
# urlresolvers.reverse() solves most of the problems. To back
253-
# that up, the object wrapper now proxies various special
254-
# methods so that comparisons like '==' will work. The object
255-
# wrapper can even be used as a standin for the wrapped object
256-
# when used as a key in a dictionary and will correctly match
257-
# the original wrapped object.
258-
259-
# Wrapper to be applied to view middleware. Records the time
260-
# spent in the middleware as separate function node and also
261-
# attempts to name the web transaction after the name of the
262-
# middleware with success being determined by the priority.
263-
# This wrapper is special in that it must strip the wrapper
264-
# from the view handler when being passed to the view
265-
# middleware to avoid issues where middleware wants to do
266-
# comparisons between the passed middleware and some other
267-
# value. It is believed that the view handler should never
268-
# actually be called from the view middleware so not an
269-
# issue that no longer wrapped at this point.
270-
271-
def wrapper(wrapped):
272-
# The middleware if a class method would already be
273-
# bound at this point, so is safe to determine the name
274-
# when it is being wrapped rather than on each
275-
# invocation.
276-
277-
name = callable_name(wrapped)
278-
279-
def wrapper(wrapped, instance, args, kwargs):
280-
transaction = current_transaction()
281-
282-
def _wrapped(request, view_func, view_args, view_kwargs):
283-
# This strips the view handler wrapper before call.
284-
285-
if hasattr(view_func, "_nr_last_object"):
286-
view_func = view_func._nr_last_object
287-
288-
return wrapped(request, view_func, view_args, view_kwargs)
289-
290-
if transaction is None:
291-
return _wrapped(*args, **kwargs)
292-
293-
before = (transaction.name, transaction.group)
256+
do_not_wrap = is_denied_middleware(callable_name(wrapped))
294257

295-
with FunctionTrace(name=name, source=wrapped):
296-
try:
297-
return _wrapped(*args, **kwargs)
298-
299-
finally:
300-
# We want to name the transaction after this
301-
# middleware but only if the transaction wasn't
302-
# named from within the middleware itself explicitly.
303-
304-
after = (transaction.name, transaction.group)
305-
if before == after:
306-
transaction.set_transaction_name(name, priority=2)
307-
308-
return FunctionWrapper(wrapped, wrapper)
309-
310-
for wrapped in middleware:
311-
yield wrapper(wrapped)
258+
if do_not_wrap:
259+
yield wrapped
260+
else:
261+
yield wrapper(wrapped)
312262

313263

314264
def wrap_trailing_middleware(middleware):
@@ -323,7 +273,13 @@ def wrap_trailing_middleware(middleware):
323273
# invocation.
324274

325275
for wrapped in middleware:
326-
yield FunctionTraceWrapper(wrapped, name=callable_name(wrapped))
276+
name = callable_name(wrapped)
277+
do_not_wrap = is_denied_middleware(name)
278+
279+
if do_not_wrap:
280+
yield wrapped
281+
else:
282+
yield FunctionTraceWrapper(wrapped, name=name)
327283

328284

329285
def insert_and_wrap_middleware(handler, *args, **kwargs):
@@ -1149,17 +1105,28 @@ def _wrapper(wrapped, instance, args, kwargs):
11491105
return _wrapper(middleware)
11501106

11511107

1108+
def is_denied_middleware(callable_name):
1109+
for middleware in MIDDLEWARE_DENY_WRAP:
1110+
if middleware in callable_name:
1111+
return True
1112+
return False
1113+
1114+
11521115
def _nr_wrapper_convert_exception_to_response_(wrapped, instance, args, kwargs):
11531116
def _bind_params(original_middleware, *args, **kwargs):
11541117
return original_middleware
11551118

11561119
original_middleware = _bind_params(*args, **kwargs)
11571120
converted_middleware = wrapped(*args, **kwargs)
11581121
name = callable_name(original_middleware)
1122+
do_not_wrap = is_denied_middleware(name)
11591123

1160-
if is_coroutine_function(converted_middleware) or is_asyncio_coroutine(converted_middleware):
1161-
return _nr_wrap_converted_middleware_async_(converted_middleware, name)
1162-
return _nr_wrap_converted_middleware_(converted_middleware, name)
1124+
if do_not_wrap:
1125+
return converted_middleware
1126+
else:
1127+
if is_coroutine_function(converted_middleware) or is_asyncio_coroutine(converted_middleware):
1128+
return _nr_wrap_converted_middleware_async_(converted_middleware, name)
1129+
return _nr_wrap_converted_middleware_(converted_middleware, name)
11631130

11641131

11651132
def instrument_django_core_handlers_exception(module):

tests/component_djangorestframework/test_application.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,16 @@ def target_application():
5858
("Python/WSGI/Application", 1),
5959
("Python/WSGI/Response", 1),
6060
("Python/WSGI/Finalize", 1),
61-
(f"Function/django.middleware.common:CommonMiddleware{process_request_method}", 1),
61+
(f"Function/django.middleware.common:CommonMiddleware{process_request_method}", None),
6262
(f"Function/django.contrib.sessions.middleware:SessionMiddleware{process_request_method}", 1),
6363
(f"Function/django.contrib.auth.middleware:AuthenticationMiddleware{process_request_method}", 1),
64-
(f"Function/django.contrib.messages.middleware:MessageMiddleware{process_request_method}", 1),
64+
(f"Function/django.contrib.messages.middleware:MessageMiddleware{process_request_method}", None),
6565
(f"Function/{url_module_path}:{url_resolver_cls}.resolve", 1),
66-
(f"Function/django.middleware.csrf:CsrfViewMiddleware{process_view_method}", 1),
67-
(f"Function/django.contrib.messages.middleware:MessageMiddleware{process_response_method}", 1),
68-
(f"Function/django.middleware.csrf:CsrfViewMiddleware{process_response_method}", 1),
66+
(f"Function/django.middleware.csrf:CsrfViewMiddleware{process_view_method}", None),
67+
(f"Function/django.contrib.messages.middleware:MessageMiddleware{process_response_method}", None),
68+
(f"Function/django.middleware.csrf:CsrfViewMiddleware{process_response_method}", None),
6969
(f"Function/django.contrib.sessions.middleware:SessionMiddleware{process_response_method}", 1),
70-
(f"Function/django.middleware.common:CommonMiddleware{process_response_method}", 1),
70+
(f"Function/django.middleware.common:CommonMiddleware{process_response_method}", None),
7171
]
7272

7373
_test_application_index_scoped_metrics = list(_scoped_metrics)

tests/framework_django/test_application.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,27 @@ def target_application():
4040
# MIDDLEWARE defined in the version-specific Django settings.py file.
4141

4242
_test_django_pre_1_10_middleware_scoped_metrics = [
43-
(("Function/django.middleware.common:CommonMiddleware.process_request"), 1),
43+
(("Function/django.middleware.common:CommonMiddleware.process_request"), None),
4444
(("Function/django.contrib.sessions.middleware:SessionMiddleware.process_request"), 1),
4545
(("Function/django.contrib.auth.middleware:AuthenticationMiddleware.process_request"), 1),
46-
(("Function/django.contrib.messages.middleware:MessageMiddleware.process_request"), 1),
47-
(("Function/django.middleware.csrf:CsrfViewMiddleware.process_view"), 1),
48-
(("Function/django.contrib.messages.middleware:MessageMiddleware.process_response"), 1),
49-
(("Function/django.middleware.csrf:CsrfViewMiddleware.process_response"), 1),
46+
(("Function/django.contrib.messages.middleware:MessageMiddleware.process_request"), None),
47+
(("Function/django.middleware.csrf:CsrfViewMiddleware.process_view"), None),
48+
(("Function/django.contrib.messages.middleware:MessageMiddleware.process_response"), None),
49+
(("Function/django.middleware.csrf:CsrfViewMiddleware.process_response"), None),
5050
(("Function/django.contrib.sessions.middleware:SessionMiddleware.process_response"), 1),
51-
(("Function/django.middleware.common:CommonMiddleware.process_response"), 1),
51+
(("Function/django.middleware.common:CommonMiddleware.process_response"), None),
5252
(("Function/django.middleware.gzip:GZipMiddleware.process_response"), 1),
5353
(("Function/newrelic.hooks.framework_django:browser_timing_insertion"), 1),
5454
]
5555

5656
_test_django_post_1_10_middleware_scoped_metrics = [
57-
("Function/django.middleware.security:SecurityMiddleware", 1),
57+
("Function/django.middleware.security:SecurityMiddleware", None),
5858
("Function/django.contrib.sessions.middleware:SessionMiddleware", 1),
59-
("Function/django.middleware.common:CommonMiddleware", 1),
60-
("Function/django.middleware.csrf:CsrfViewMiddleware", 1),
59+
("Function/django.middleware.common:CommonMiddleware", None),
60+
("Function/django.middleware.csrf:CsrfViewMiddleware", None),
6161
("Function/django.contrib.auth.middleware:AuthenticationMiddleware", 1),
62-
("Function/django.contrib.messages.middleware:MessageMiddleware", 1),
63-
("Function/django.middleware.clickjacking:XFrameOptionsMiddleware", 1),
62+
("Function/django.contrib.messages.middleware:MessageMiddleware", None),
63+
("Function/django.middleware.clickjacking:XFrameOptionsMiddleware", None),
6464
("Function/django.middleware.gzip:GZipMiddleware", 1),
6565
]
6666

tests/framework_django/test_asgi_application.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
import django
1818
import pytest
19-
from testing_support.asgi_testing import AsgiTest
2019
from testing_support.fixtures import (
2120
override_application_settings,
2221
override_generic_settings,
@@ -34,12 +33,15 @@
3433
if DJANGO_VERSION[0] < 3:
3534
pytest.skip("support for asgi added in django 3", allow_module_level=True)
3635

36+
# Import this here so it is not run if Django is less than 3.0.
37+
from testing_support.asgi_testing import AsgiTest # noqa: E402
38+
3739
scoped_metrics = [
3840
("Function/django.contrib.sessions.middleware:SessionMiddleware", 1),
39-
("Function/django.middleware.common:CommonMiddleware", 1),
40-
("Function/django.middleware.csrf:CsrfViewMiddleware", 1),
41+
("Function/django.middleware.common:CommonMiddleware", None),
42+
("Function/django.middleware.csrf:CsrfViewMiddleware", None),
4143
("Function/django.contrib.auth.middleware:AuthenticationMiddleware", 1),
42-
("Function/django.contrib.messages.middleware:MessageMiddleware", 1),
44+
("Function/django.contrib.messages.middleware:MessageMiddleware", None),
4345
("Function/django.middleware.gzip:GZipMiddleware", 1),
4446
("Function/middleware:ExceptionTo410Middleware", 1),
4547
("Function/django.urls.resolvers:URLResolver.resolve", "present"),

0 commit comments

Comments
 (0)