Skip to content

Commit 922db2f

Browse files
authored
Add usage metrics for Daphne and Hypercorn. (#665)
* Add usage metrics for Daphne and Hypercorn. * [Mega-Linter] Apply linters fixes Co-authored-by: umaannamalai <[email protected]>
1 parent 968f3dc commit 922db2f

File tree

7 files changed

+50
-17
lines changed

7 files changed

+50
-17
lines changed

newrelic/api/asgi_application.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ async def send(self, event):
254254
return await self._send(event)
255255

256256

257-
def ASGIApplicationWrapper(wrapped, application=None, name=None, group=None, framework=None):
257+
def ASGIApplicationWrapper(wrapped, application=None, name=None, group=None, framework=None, dispatcher=None):
258258
def nr_asgi_wrapper(wrapped, instance, args, kwargs):
259259
double_callable = asgiref_compatibility.is_double_callable(wrapped)
260260
if double_callable:
@@ -271,9 +271,7 @@ async def nr_async_asgi(receive, send):
271271
# Check to see if any transaction is present, even an inactive
272272
# one which has been marked to be ignored or which has been
273273
# stopped already.
274-
275274
transaction = current_transaction(active_only=False)
276-
277275
if transaction:
278276
# If there is any active transaction we will return without
279277
# applying a new ASGI application wrapper context. In the
@@ -290,6 +288,9 @@ async def nr_async_asgi(receive, send):
290288
if framework:
291289
transaction.add_framework_info(name=framework[0], version=framework[1])
292290

291+
if dispatcher:
292+
transaction.add_dispatcher_info(name=dispatcher[0], version=dispatcher[1])
293+
293294
# Also override the web transaction name to be the name of
294295
# the wrapped callable if not explicitly named, and we want
295296
# the default name to be that of the ASGI component for the
@@ -323,6 +324,9 @@ async def nr_async_asgi(receive, send):
323324
if framework:
324325
transaction.add_framework_info(name=framework[0], version=framework[1])
325326

327+
if dispatcher:
328+
transaction.add_dispatcher_info(name=dispatcher[0], version=dispatcher[1])
329+
326330
# Override the initial web transaction name to be the supplied
327331
# name, or the name of the wrapped callable if wanting to use
328332
# the callable as the default. This will override the use of a

newrelic/api/transaction.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ def __init__(self, application, enabled=None, source=None):
187187

188188
self._frameworks = set()
189189
self._message_brokers = set()
190+
self._dispatchers = set()
190191

191192
self._frozen_path = None
192193

@@ -550,6 +551,10 @@ def __exit__(self, exc, value, tb):
550551
for message_broker, version in self._message_brokers:
551552
self.record_custom_metric("Python/MessageBroker/%s/%s" % (message_broker, version), 1)
552553

554+
if self._dispatchers:
555+
for dispatcher, version in self._dispatchers:
556+
self.record_custom_metric("Python/Dispatcher/%s/%s" % (dispatcher, version), 1)
557+
553558
if self._settings.distributed_tracing.enabled:
554559
# Sampled and priority need to be computed at the end of the
555560
# transaction when distributed tracing or span events are enabled.
@@ -1701,6 +1706,10 @@ def add_messagebroker_info(self, name, version=None):
17011706
if name:
17021707
self._message_brokers.add((name, version))
17031708

1709+
def add_dispatcher_info(self, name, version=None):
1710+
if name:
1711+
self._dispatchers.add((name, version))
1712+
17041713
def dump(self, file):
17051714
"""Dumps details about the transaction to the file object."""
17061715

newrelic/api/wsgi_application.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
import time
1919

2020
from newrelic.api.application import application_instance
21-
from newrelic.api.transaction import current_transaction
22-
from newrelic.api.time_trace import notice_error
23-
from newrelic.api.web_transaction import WSGIWebTransaction
2421
from newrelic.api.function_trace import FunctionTrace, FunctionTraceWrapper
2522
from newrelic.api.html_insertion import insert_html_snippet, verify_body_exists
2623
from newrelic.api.time_trace import notice_error
@@ -80,12 +77,12 @@ def close(self):
8077
self.response_trace = None
8178

8279
try:
83-
with FunctionTrace(name='Finalize', group='Python/WSGI'):
80+
with FunctionTrace(name="Finalize", group="Python/WSGI"):
8481

8582
if isinstance(self.generator, _WSGIApplicationMiddleware):
8683
self.generator.close()
8784

88-
elif hasattr(self.generator, 'close'):
85+
elif hasattr(self.generator, "close"):
8986
FunctionTraceWrapper(self.generator.close)()
9087

9188
except: # Catch all
@@ -437,7 +434,7 @@ def close(self):
437434
# Call close() on the iterable as required by the
438435
# WSGI specification.
439436

440-
if hasattr(self.iterable, 'close'):
437+
if hasattr(self.iterable, "close"):
441438
FunctionTraceWrapper(self.iterable.close)()
442439

443440
def __iter__(self):
@@ -510,7 +507,7 @@ def __iter__(self):
510507
yield data
511508

512509

513-
def WSGIApplicationWrapper(wrapped, application=None, name=None, group=None, framework=None):
510+
def WSGIApplicationWrapper(wrapped, application=None, name=None, group=None, framework=None, dispatcher=None):
514511

515512
# Python 2 does not allow rebinding nonlocal variables, so to fix this
516513
# framework must be stored in list so it can be edited by closure.
@@ -556,6 +553,9 @@ def _nr_wsgi_application_wrapper_(wrapped, instance, args, kwargs):
556553
if framework:
557554
transaction.add_framework_info(name=framework[0], version=framework[1])
558555

556+
if dispatcher:
557+
transaction.add_dispatcher_info(name=dispatcher[0], version=dispatcher[1])
558+
559559
# Also override the web transaction name to be the name of
560560
# the wrapped callable if not explicitly named, and we want
561561
# the default name to be that of the WSGI component for the
@@ -618,6 +618,9 @@ def _args(environ, start_response, *args, **kwargs):
618618
if framework:
619619
transaction.add_framework_info(name=framework[0], version=framework[1])
620620

621+
if dispatcher:
622+
transaction.add_dispatcher_info(name=dispatcher[0], version=dispatcher[1])
623+
621624
# Override the initial web transaction name to be the supplied
622625
# name, or the name of the wrapped callable if wanting to use
623626
# the callable as the default. This will override the use of a
@@ -672,7 +675,7 @@ def write(data):
672675
if "wsgi.input" in environ:
673676
environ["wsgi.input"] = _WSGIInputWrapper(transaction, environ["wsgi.input"])
674677

675-
with FunctionTrace(name='Application', group='Python/WSGI'):
678+
with FunctionTrace(name="Application", group="Python/WSGI"):
676679
with FunctionTrace(name=callable_name(wrapped), source=wrapped):
677680
if settings and settings.browser_monitoring.enabled and not transaction.autorum_disabled:
678681
result = _WSGIApplicationMiddleware(wrapped, environ, _start_response, transaction)

newrelic/hooks/adapter_daphne.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
from newrelic.api.asgi_application import ASGIApplicationWrapper
16+
from newrelic.common.package_version_utils import get_package_version
1617

1718

1819
@property
@@ -22,9 +23,10 @@ def application(self):
2223

2324
@application.setter
2425
def application(self, value):
26+
dispatcher_details = ("Daphne", get_package_version("daphne"))
2527
# Wrap app only once
2628
if value and not getattr(value, "_nr_wrapped", False):
27-
value = ASGIApplicationWrapper(value)
29+
value = ASGIApplicationWrapper(value, dispatcher=dispatcher_details)
2830
value._nr_wrapped = True
2931
self._nr_application = value
3032

newrelic/hooks/adapter_hypercorn.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from newrelic.api.asgi_application import ASGIApplicationWrapper
1616
from newrelic.api.wsgi_application import WSGIApplicationWrapper
1717
from newrelic.common.object_wrapper import wrap_function_wrapper
18+
from newrelic.common.package_version_utils import get_package_version
1819

1920

2021
def bind_worker_serve(app, *args, **kwargs):
@@ -24,6 +25,7 @@ def bind_worker_serve(app, *args, **kwargs):
2425
async def wrap_worker_serve(wrapped, instance, args, kwargs):
2526
import hypercorn
2627

28+
dispatcher_details = ("Hypercorn", get_package_version("hypercorn"))
2729
wrapper_module = getattr(hypercorn, "app_wrappers", None)
2830
asgi_wrapper_class = getattr(wrapper_module, "ASGIWrapper", None)
2931
wsgi_wrapper_class = getattr(wrapper_module, "WSGIWrapper", None)
@@ -32,13 +34,14 @@ async def wrap_worker_serve(wrapped, instance, args, kwargs):
3234

3335
# Hypercorn 0.14.1 introduced wrappers for ASGI and WSGI apps that need to be above our instrumentation.
3436
if asgi_wrapper_class is not None and isinstance(app, asgi_wrapper_class):
35-
app.app = ASGIApplicationWrapper(app.app)
37+
app.app = ASGIApplicationWrapper(app.app, dispatcher=dispatcher_details)
3638
elif wsgi_wrapper_class is not None and isinstance(app, wsgi_wrapper_class):
37-
app.app = WSGIApplicationWrapper(app.app)
39+
app.app = WSGIApplicationWrapper(app.app, dispatcher=dispatcher_details)
3840
else:
39-
app = ASGIApplicationWrapper(app)
41+
app = ASGIApplicationWrapper(app, dispatcher=dispatcher_details)
4042

4143
app._nr_wrapped = True
44+
4245
return await wrapped(app, *args, **kwargs)
4346

4447

tests/adapter_daphne/test_daphne.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,12 @@ async def fake_app(*args, **kwargs):
112112

113113
@override_application_settings({"transaction_name.naming_scheme": "framework"})
114114
def test_daphne_200(port, app):
115-
@validate_transaction_metrics(callable_name(app))
115+
@validate_transaction_metrics(
116+
callable_name(app),
117+
custom_metrics=[
118+
("Python/Dispatcher/Daphne/%s" % daphne.__version__, 1),
119+
],
120+
)
116121
@raise_background_exceptions()
117122
@wait_for_background_threads()
118123
def response():

tests/adapter_hypercorn/test_hypercorn.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,14 @@ def wait_for_port(port, retries=10):
128128

129129
@override_application_settings({"transaction_name.naming_scheme": "framework"})
130130
def test_hypercorn_200(port, app):
131-
@validate_transaction_metrics(callable_name(app))
131+
hypercorn_version = pkg_resources.get_distribution("hypercorn").version
132+
133+
@validate_transaction_metrics(
134+
callable_name(app),
135+
custom_metrics=[
136+
("Python/Dispatcher/Hypercorn/%s" % hypercorn_version, 1),
137+
],
138+
)
132139
@raise_background_exceptions()
133140
@wait_for_background_threads()
134141
def response():

0 commit comments

Comments
 (0)