1313# limitations under the License.
1414
1515import asyncio
16+ from time import time_ns
1617
1718import redis
1819import redis .asyncio
@@ -326,6 +327,30 @@ def test_basics(self):
326327 )
327328 self .assertEqual (span .attributes .get ("db.redis.args_length" ), 2 )
328329
330+ def test_execute_command_traced_full_time (self ):
331+ """Command should be traced for coroutine execution time, not creation time."""
332+ coro_created_time = None
333+ finish_time = None
334+
335+ async def pipeline_simple ():
336+ nonlocal coro_created_time
337+ nonlocal finish_time
338+
339+ # delay coroutine creation from coroutine execution
340+ coro = self .redis_client .get ("foo" )
341+ coro_created_time = time_ns ()
342+ await coro
343+ finish_time = time_ns ()
344+
345+ # create coroutine
346+ async_call (pipeline_simple ())
347+
348+ spans = self .memory_exporter .get_finished_spans ()
349+ self .assertEqual (len (spans ), 1 )
350+ span = spans [0 ]
351+ self .assertTrue (span .start_time > coro_created_time )
352+ self .assertTrue (span .end_time < finish_time )
353+
329354 def test_pipeline_traced (self ):
330355 async def pipeline_simple ():
331356 async with self .redis_client .pipeline (
@@ -348,6 +373,36 @@ async def pipeline_simple():
348373 )
349374 self .assertEqual (span .attributes .get ("db.redis.pipeline_length" ), 3 )
350375
376+ def test_pipeline_traced_full_time (self ):
377+ """Command should be traced for coroutine execution time, not creation time."""
378+ coro_created_time = None
379+ finish_time = None
380+
381+ async def pipeline_simple ():
382+ async with self .redis_client .pipeline (
383+ transaction = False
384+ ) as pipeline :
385+ nonlocal coro_created_time
386+ nonlocal finish_time
387+ pipeline .set ("blah" , 32 )
388+ pipeline .rpush ("foo" , "éé" )
389+ pipeline .hgetall ("xxx" )
390+
391+ # delay coroutine creation from coroutine execution
392+ coro = pipeline .execute ()
393+ coro_created_time = time_ns ()
394+ await coro
395+ finish_time = time_ns ()
396+
397+ # create coroutine
398+ async_call (pipeline_simple ())
399+
400+ spans = self .memory_exporter .get_finished_spans ()
401+ self .assertEqual (len (spans ), 1 )
402+ span = spans [0 ]
403+ self .assertTrue (span .start_time > coro_created_time )
404+ self .assertTrue (span .end_time < finish_time )
405+
351406 def test_pipeline_immediate (self ):
352407 async def pipeline_immediate ():
353408 async with self .redis_client .pipeline () as pipeline :
@@ -367,6 +422,34 @@ async def pipeline_immediate():
367422 span .attributes .get (SpanAttributes .DB_STATEMENT ), "SET b 2"
368423 )
369424
425+ def test_pipeline_immediate_traced_full_time (self ):
426+ """Command should be traced for coroutine execution time, not creation time."""
427+ coro_created_time = None
428+ finish_time = None
429+
430+ async def pipeline_simple ():
431+ async with self .redis_client .pipeline (
432+ transaction = False
433+ ) as pipeline :
434+ nonlocal coro_created_time
435+ nonlocal finish_time
436+ pipeline .set ("a" , 1 )
437+
438+ # delay coroutine creation from coroutine execution
439+ coro = pipeline .immediate_execute_command ("SET" , "b" , 2 )
440+ coro_created_time = time_ns ()
441+ await coro
442+ finish_time = time_ns ()
443+
444+ # create coroutine
445+ async_call (pipeline_simple ())
446+
447+ spans = self .memory_exporter .get_finished_spans ()
448+ self .assertEqual (len (spans ), 1 )
449+ span = spans [0 ]
450+ self .assertTrue (span .start_time > coro_created_time )
451+ self .assertTrue (span .end_time < finish_time )
452+
370453 def test_parent (self ):
371454 """Ensure OpenTelemetry works with redis."""
372455 ot_tracer = trace .get_tracer ("redis_svc" )
@@ -416,6 +499,30 @@ def test_basics(self):
416499 )
417500 self .assertEqual (span .attributes .get ("db.redis.args_length" ), 2 )
418501
502+ def test_execute_command_traced_full_time (self ):
503+ """Command should be traced for coroutine execution time, not creation time."""
504+ coro_created_time = None
505+ finish_time = None
506+
507+ async def pipeline_simple ():
508+ nonlocal coro_created_time
509+ nonlocal finish_time
510+
511+ # delay coroutine creation from coroutine execution
512+ coro = self .redis_client .get ("foo" )
513+ coro_created_time = time_ns ()
514+ await coro
515+ finish_time = time_ns ()
516+
517+ # create coroutine
518+ async_call (pipeline_simple ())
519+
520+ spans = self .memory_exporter .get_finished_spans ()
521+ self .assertEqual (len (spans ), 1 )
522+ span = spans [0 ]
523+ self .assertTrue (span .start_time > coro_created_time )
524+ self .assertTrue (span .end_time < finish_time )
525+
419526 def test_pipeline_traced (self ):
420527 async def pipeline_simple ():
421528 async with self .redis_client .pipeline (
@@ -438,6 +545,36 @@ async def pipeline_simple():
438545 )
439546 self .assertEqual (span .attributes .get ("db.redis.pipeline_length" ), 3 )
440547
548+ def test_pipeline_traced_full_time (self ):
549+ """Command should be traced for coroutine execution time, not creation time."""
550+ coro_created_time = None
551+ finish_time = None
552+
553+ async def pipeline_simple ():
554+ async with self .redis_client .pipeline (
555+ transaction = False
556+ ) as pipeline :
557+ nonlocal coro_created_time
558+ nonlocal finish_time
559+ pipeline .set ("blah" , 32 )
560+ pipeline .rpush ("foo" , "éé" )
561+ pipeline .hgetall ("xxx" )
562+
563+ # delay coroutine creation from coroutine execution
564+ coro = pipeline .execute ()
565+ coro_created_time = time_ns ()
566+ await coro
567+ finish_time = time_ns ()
568+
569+ # create coroutine
570+ async_call (pipeline_simple ())
571+
572+ spans = self .memory_exporter .get_finished_spans ()
573+ self .assertEqual (len (spans ), 1 )
574+ span = spans [0 ]
575+ self .assertTrue (span .start_time > coro_created_time )
576+ self .assertTrue (span .end_time < finish_time )
577+
441578 def test_parent (self ):
442579 """Ensure OpenTelemetry works with redis."""
443580 ot_tracer = trace .get_tracer ("redis_svc" )
0 commit comments