1414
1515import re
1616
17- from newrelic .api .datastore_trace import DatastoreTrace
17+ from newrelic .api .datastore_trace import DatastoreTrace , DatastoreTraceWrapper , wrap_datastore_trace
1818from newrelic .api .time_trace import current_trace
1919from 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" ,
136137 "client_no_evict" ,
137138 "client_pause" ,
138139 "client_reply" ,
140+ "client_setinfo" ,
139141 "client_setname" ,
140142 "client_tracking" ,
141143 "client_trackinginfo" ,
162164 "cluster_reset" ,
163165 "cluster_save_config" ,
164166 "cluster_set_config_epoch" ,
165- "client_setinfo" ,
166167 "cluster_setslot" ,
167168 "cluster_slaves" ,
168169 "cluster_slots" ,
248249 "hmset_dict" ,
249250 "hmset" ,
250251 "hrandfield" ,
251- "hscan_inter " ,
252+ "hscan_iter " ,
252253 "hscan" ,
253254 "hset" ,
254255 "hsetnx" ,
399400 "syndump" ,
400401 "synupdate" ,
401402 "tagvals" ,
402- "tfcall" ,
403403 "tfcall_async" ,
404+ "tfcall" ,
404405 "tfunction_delete" ,
405406 "tfunction_list" ,
406407 "tfunction_load" ,
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
500508def _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-
670666def instrument_redis_commands_core (module ):
671667 _instrument_redis_commands_module (module , "CoreCommands" )
672668
0 commit comments