35
35
import time
36
36
import timeit
37
37
from collections import defaultdict
38
+ from datetime import timedelta
38
39
from types import TracebackType
39
40
from typing import Any , Callable , Dict , Optional , Tuple , Type , Union
40
41
@@ -68,7 +69,7 @@ def __init__(self, obj: "BaseSpan"):
68
69
self .obj = obj
69
70
self ._nesting_level : int = 0
70
71
self ._start : float = 0
71
- self ._duration : float = 0
72
+ self ._duration : timedelta = timedelta ( seconds = 0 )
72
73
self ._lock = threading .Lock ()
73
74
74
75
def start (self , timestamp : float ):
@@ -81,10 +82,10 @@ def stop(self, timestamp: float):
81
82
with self ._lock :
82
83
self ._nesting_level -= 1
83
84
if self ._nesting_level == 0 :
84
- self ._duration += timestamp - self ._start
85
+ self ._duration += timedelta ( seconds = timestamp - self ._start )
85
86
86
87
@property
87
- def duration (self ) -> float :
88
+ def duration (self ) -> timedelta :
88
89
return self ._duration
89
90
90
91
@@ -97,7 +98,7 @@ def __init__(self, labels=None, start=None):
97
98
self .compression_buffer_lock = threading .Lock ()
98
99
self .start_time : float = time_to_perf_counter (start ) if start is not None else _time_func ()
99
100
self .ended_time : Optional [float ] = None
100
- self .duration : Optional [float ] = None
101
+ self .duration : Optional [timedelta ] = None
101
102
if labels :
102
103
self .label (** labels )
103
104
@@ -117,9 +118,9 @@ def child_ended(self, child: SpanType):
117
118
self .compression_buffer .report ()
118
119
self .compression_buffer = child
119
120
120
- def end (self , skip_frames : int = 0 , duration : Optional [float ] = None ):
121
+ def end (self , skip_frames : int = 0 , duration : Optional [timedelta ] = None ):
121
122
self .ended_time = _time_func ()
122
- self .duration = duration if duration is not None else ( self .ended_time - self .start_time )
123
+ self .duration = duration if duration is not None else timedelta ( seconds = self .ended_time - self .start_time )
123
124
if self .compression_buffer :
124
125
self .compression_buffer .report ()
125
126
self .compression_buffer = None
@@ -226,7 +227,7 @@ def __init__(
226
227
self ._breakdown = None
227
228
super (Transaction , self ).__init__ (start = start )
228
229
229
- def end (self , skip_frames : int = 0 , duration : Optional [float ] = None ):
230
+ def end (self , skip_frames : int = 0 , duration : Optional [timedelta ] = None ):
230
231
super ().end (skip_frames , duration )
231
232
if self ._breakdown :
232
233
for (span_type , span_subtype ), timer in self ._span_timers .items ():
@@ -239,15 +240,15 @@ def end(self, skip_frames: int = 0, duration: Optional[float] = None):
239
240
labels ["span.subtype" ] = span_subtype
240
241
val = timer .val
241
242
self ._breakdown .timer ("span.self_time" , reset_on_collect = True , unit = "us" , ** labels ).update (
242
- int ( val [0 ] * 1000000 ) , val [1 ]
243
+ val [0 ], val [1 ]
243
244
)
244
245
if self .is_sampled :
245
246
self ._breakdown .timer (
246
247
"span.self_time" ,
247
248
reset_on_collect = True ,
248
249
unit = "us" ,
249
250
** {"span.type" : "app" , "transaction.name" : self .name , "transaction.type" : self .transaction_type },
250
- ).update (int (( self .duration - self ._child_durations .duration ) * 1000000 ) )
251
+ ).update (( self .duration - self ._child_durations .duration ). total_seconds () * 1_000_000 )
251
252
252
253
def _begin_span (
253
254
self ,
@@ -369,9 +370,9 @@ def to_dict(self) -> dict:
369
370
"trace_id" : self .trace_parent .trace_id ,
370
371
"name" : encoding .keyword_field (self .name or "" ),
371
372
"type" : encoding .keyword_field (self .transaction_type ),
372
- "duration" : self .duration * 1000 , # milliseconds
373
+ "duration" : self .duration . total_seconds () * 1000 ,
373
374
"result" : encoding .keyword_field (str (self .result )),
374
- "timestamp" : int (self .timestamp * 1000000 ), # microseconds
375
+ "timestamp" : int (self .timestamp * 1_000_000 ), # microseconds
375
376
"outcome" : self .outcome ,
376
377
"sampled" : self .is_sampled ,
377
378
"span_count" : {"started" : self ._span_counter , "dropped" : self .dropped_spans },
@@ -381,7 +382,7 @@ def to_dict(self) -> dict:
381
382
{
382
383
"destination_service_resource" : resource ,
383
384
"outcome" : outcome ,
384
- "duration" : {"count" : v ["count" ], "sum" : {"us" : int (v ["duration.sum.us" ] * 1000000 )}},
385
+ "duration" : {"count" : v ["count" ], "sum" : {"us" : int (v ["duration.sum.us" ])}},
385
386
}
386
387
for (resource , outcome ), v in self ._dropped_span_statistics .items ()
387
388
]
@@ -419,7 +420,7 @@ def track_span_duration(self, span_type, span_subtype, self_duration):
419
420
# TODO: once asynchronous spans are supported, we should check if the transaction is already finished
420
421
# TODO: and, if it has, exit without tracking.
421
422
with self ._span_timers_lock :
422
- self ._span_timers [(span_type , span_subtype )].update (self_duration )
423
+ self ._span_timers [(span_type , span_subtype )].update (self_duration . total_seconds () * 1_000_000 )
423
424
424
425
@property
425
426
def is_sampled (self ) -> bool :
@@ -448,7 +449,7 @@ def track_dropped_span(self, span: SpanType):
448
449
resource = span .context ["destination" ]["service" ]["resource" ]
449
450
stats = self ._dropped_span_statistics [(resource , span .outcome )]
450
451
stats ["count" ] += 1
451
- stats ["duration.sum.us" ] += span .duration
452
+ stats ["duration.sum.us" ] += int ( span .duration . total_seconds () * 1_000_000 )
452
453
except KeyError :
453
454
pass
454
455
@@ -551,7 +552,7 @@ def to_dict(self) -> dict:
551
552
"subtype" : encoding .keyword_field (self .subtype ),
552
553
"action" : encoding .keyword_field (self .action ),
553
554
"timestamp" : int (self .timestamp * 1000000 ), # microseconds
554
- "duration" : self .duration * 1000 , # milliseconds
555
+ "duration" : self .duration . total_seconds () * 1000 ,
555
556
"outcome" : self .outcome ,
556
557
}
557
558
if self .transaction .sample_rate is not None :
@@ -586,7 +587,7 @@ def to_dict(self) -> dict:
586
587
if self .composite :
587
588
result ["composite" ] = {
588
589
"compression_strategy" : self .composite ["compression_strategy" ],
589
- "sum" : self .composite ["sum" ] * 1000 ,
590
+ "sum" : self .composite ["sum" ]. total_seconds () * 1000 ,
590
591
"count" : self .composite ["count" ],
591
592
}
592
593
return result
@@ -650,14 +651,14 @@ def end(self, skip_frames: int = 0, duration: Optional[float] = None):
650
651
651
652
p = self .parent if self .parent else self .transaction
652
653
if self .transaction ._breakdown :
653
- p ._child_durations .stop (self .start_time + self .duration )
654
+ p ._child_durations .stop (self .start_time + self .duration . total_seconds () )
654
655
self .transaction .track_span_duration (
655
656
self .type , self .subtype , self .duration - self ._child_durations .duration
656
657
)
657
658
p .child_ended (self )
658
659
659
660
def report (self ) -> None :
660
- if self .discardable and ( self .duration * 1000 ) < self .transaction .config_exit_span_min_duration :
661
+ if self .discardable and self .duration < self .transaction .config_exit_span_min_duration :
661
662
self .transaction .track_dropped_span (self )
662
663
self .transaction .dropped_spans += 1
663
664
else :
@@ -674,7 +675,7 @@ def try_to_compress(self, sibling: SpanType) -> bool:
674
675
self .composite = {"compression_strategy" : compression_strategy , "count" : 1 , "sum" : self .duration }
675
676
self .composite ["count" ] += 1
676
677
self .composite ["sum" ] += sibling .duration
677
- self .duration = sibling .ended_time - self .start_time
678
+ self .duration = timedelta ( seconds = sibling .ended_time - self .start_time )
678
679
self .transaction ._span_counter -= 1
679
680
return True
680
681
@@ -820,11 +821,11 @@ def __init__(self, frames_collector_func, frames_processing_func, queue_func, co
820
821
self ._ignore_patterns = [re .compile (p ) for p in config .transactions_ignore_patterns or []]
821
822
822
823
@property
823
- def span_frames_min_duration (self ):
824
- if self .config .span_frames_min_duration in (- 1 , None ):
824
+ def span_frames_min_duration (self ) -> Optional [ timedelta ] :
825
+ if self .config .span_frames_min_duration in (timedelta ( seconds = - 1 ) , None ):
825
826
return None
826
827
else :
827
- return self .config .span_frames_min_duration / 1000.0
828
+ return self .config .span_frames_min_duration
828
829
829
830
def begin_transaction (self , transaction_type , trace_parent = None , start = None , auto_activate = True ):
830
831
"""
@@ -918,7 +919,7 @@ def __init__(
918
919
span_subtype : Optional [str ] = None ,
919
920
span_action : Optional [str ] = None ,
920
921
start : Optional [int ] = None ,
921
- duration : Optional [float ] = None ,
922
+ duration : Optional [Union [ float , timedelta ] ] = None ,
922
923
sync : Optional [bool ] = None ,
923
924
):
924
925
self .name = name
@@ -937,6 +938,8 @@ def __init__(
937
938
self .leaf = leaf
938
939
self .labels = labels
939
940
self .start = start
941
+ if duration and not isinstance (duration , timedelta ):
942
+ duration = timedelta (seconds = duration )
940
943
self .duration = duration
941
944
self .sync = sync
942
945
0 commit comments