Skip to content

Commit 5996de6

Browse files
TimPansinolrafeeimergify[bot]
authored
Fix Redis Generator Methods (#947)
* Fix scan_iter for redis * Replace generator methods * Update instance info instrumentation * Remove mistake from uninstrumented methods * Add skip condition to asyncio generator tests * Add skip condition to asyncio generator tests --------- Co-authored-by: Lalleh Rafeei <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent cc3e285 commit 5996de6

File tree

4 files changed

+294
-40
lines changed

4 files changed

+294
-40
lines changed

newrelic/hooks/datastore_redis.py

+35-39
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414

1515
import re
1616

17-
from newrelic.api.datastore_trace import DatastoreTrace
17+
from newrelic.api.datastore_trace import DatastoreTrace, DatastoreTraceWrapper, wrap_datastore_trace
1818
from newrelic.api.time_trace import current_trace
1919
from newrelic.api.transaction import current_transaction
20-
from newrelic.common.object_wrapper import function_wrapper, wrap_function_wrapper
20+
from newrelic.common.object_wrapper import wrap_function_wrapper
21+
from newrelic.common.async_wrapper import coroutine_wrapper, async_generator_wrapper, generator_wrapper
2122

2223
_redis_client_sync_methods = {
2324
"acl_dryrun",
@@ -136,6 +137,7 @@
136137
"client_no_evict",
137138
"client_pause",
138139
"client_reply",
140+
"client_setinfo",
139141
"client_setname",
140142
"client_tracking",
141143
"client_trackinginfo",
@@ -162,7 +164,6 @@
162164
"cluster_reset",
163165
"cluster_save_config",
164166
"cluster_set_config_epoch",
165-
"client_setinfo",
166167
"cluster_setslot",
167168
"cluster_slaves",
168169
"cluster_slots",
@@ -248,7 +249,7 @@
248249
"hmset_dict",
249250
"hmset",
250251
"hrandfield",
251-
"hscan_inter",
252+
"hscan_iter",
252253
"hscan",
253254
"hset",
254255
"hsetnx",
@@ -399,8 +400,8 @@
399400
"syndump",
400401
"synupdate",
401402
"tagvals",
402-
"tfcall",
403403
"tfcall_async",
404+
"tfcall",
404405
"tfunction_delete",
405406
"tfunction_list",
406407
"tfunction_load",
@@ -473,6 +474,13 @@
473474
"zunionstore",
474475
}
475476

477+
_redis_client_gen_methods = {
478+
"scan_iter",
479+
"hscan_iter",
480+
"sscan_iter",
481+
"zscan_iter",
482+
}
483+
476484
_redis_client_methods = _redis_client_sync_methods.union(_redis_client_async_methods)
477485

478486
_redis_multipart_commands = set(["client", "cluster", "command", "config", "debug", "sentinel", "slowlog", "script"])
@@ -498,50 +506,31 @@ def _instance_info(kwargs):
498506

499507

500508
def _wrap_Redis_method_wrapper_(module, instance_class_name, operation):
501-
def _nr_wrapper_Redis_method_(wrapped, instance, args, kwargs):
502-
transaction = current_transaction()
503-
504-
if transaction is None:
505-
return wrapped(*args, **kwargs)
506-
507-
dt = DatastoreTrace(product="Redis", target=None, operation=operation, source=wrapped)
508-
509-
transaction._nr_datastore_instance_info = (None, None, None)
510-
511-
with dt:
512-
result = wrapped(*args, **kwargs)
513-
514-
host, port_path_or_id, db = transaction._nr_datastore_instance_info
515-
dt.host = host
516-
dt.port_path_or_id = port_path_or_id
517-
dt.database_name = db
518-
519-
return result
520-
521509
name = "%s.%s" % (instance_class_name, operation)
522-
wrap_function_wrapper(module, name, _nr_wrapper_Redis_method_)
510+
if operation in _redis_client_gen_methods:
511+
async_wrapper = generator_wrapper
512+
else:
513+
async_wrapper = None
523514

515+
wrap_datastore_trace(module, name, product="Redis", target=None, operation=operation, async_wrapper=async_wrapper)
524516

525-
def _wrap_asyncio_Redis_method_wrapper(module, instance_class_name, operation):
526-
@function_wrapper
527-
async def _nr_wrapper_asyncio_Redis_async_method_(wrapped, instance, args, kwargs):
528-
transaction = current_transaction()
529-
if transaction is None:
530-
return await wrapped(*args, **kwargs)
531-
532-
with DatastoreTrace(product="Redis", target=None, operation=operation):
533-
return await wrapped(*args, **kwargs)
534517

518+
def _wrap_asyncio_Redis_method_wrapper(module, instance_class_name, operation):
535519
def _nr_wrapper_asyncio_Redis_method_(wrapped, instance, args, kwargs):
536520
from redis.asyncio.client import Pipeline
537521

538522
if isinstance(instance, Pipeline):
539523
return wrapped(*args, **kwargs)
540524

541-
# Method should be run when awaited, therefore we wrap in an async wrapper.
542-
return _nr_wrapper_asyncio_Redis_async_method_(wrapped)(*args, **kwargs)
525+
# Method should be run when awaited or iterated, therefore we wrap in an async wrapper.
526+
return DatastoreTraceWrapper(wrapped, product="Redis", target=None, operation=operation, async_wrapper=async_wrapper)(*args, **kwargs)
543527

544528
name = "%s.%s" % (instance_class_name, operation)
529+
if operation in _redis_client_gen_methods:
530+
async_wrapper = async_generator_wrapper
531+
else:
532+
async_wrapper = coroutine_wrapper
533+
545534
wrap_function_wrapper(module, name, _nr_wrapper_asyncio_Redis_method_)
546535

547536

@@ -614,7 +603,15 @@ def _nr_Connection_send_command_wrapper_(wrapped, instance, args, kwargs):
614603
except:
615604
pass
616605

617-
transaction._nr_datastore_instance_info = (host, port_path_or_id, db)
606+
# Find DatastoreTrace no matter how many other traces are inbetween
607+
trace = current_trace()
608+
while trace is not None and not isinstance(trace, DatastoreTrace):
609+
trace = getattr(trace, "parent", None)
610+
611+
if trace is not None:
612+
trace.host = host
613+
trace.port_path_or_id = port_path_or_id
614+
trace.database_name = db
618615

619616
# Older Redis clients would when sending multi part commands pass
620617
# them in as separate arguments to send_command(). Need to therefore
@@ -666,7 +663,6 @@ def instrument_asyncio_redis_client(module):
666663
if hasattr(class_, operation):
667664
_wrap_asyncio_Redis_method_wrapper(module, "Redis", operation)
668665

669-
670666
def instrument_redis_commands_core(module):
671667
_instrument_redis_commands_module(module, "CoreCommands")
672668

tests/datastore_redis/conftest.py

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import pytest
1616

1717
from testing_support.fixtures import collector_agent_registration_fixture, collector_available_fixture # noqa: F401; pylint: disable=W0611
18+
from testing_support.fixture.event_loop import event_loop as loop # noqa: F401; pylint: disable=W0611
1819

1920

2021
_default_settings = {

0 commit comments

Comments
 (0)