@@ -285,7 +285,7 @@ def __init__(self, application, enabled=None, source=None):
285285 self .tracestate = ""
286286 self ._priority = None
287287 self ._sampled = None
288- self ._traceparent_sampled = None
288+ self ._remote_parent_sampled = None
289289
290290 self ._distributed_trace_state = 0
291291
@@ -569,7 +569,7 @@ def __exit__(self, exc, value, tb):
569569 if self ._settings .distributed_tracing .enabled :
570570 # Sampled and priority need to be computed at the end of the
571571 # transaction when distributed tracing or span events are enabled.
572- self ._compute_sampled_and_priority ()
572+ self ._make_sampling_decision ()
573573
574574 self ._cached_path ._name = self .path
575575 agent_attributes = self .agent_attributes
@@ -636,6 +636,7 @@ def __exit__(self, exc, value, tb):
636636 trace_id = self .trace_id ,
637637 loop_time = self ._loop_time ,
638638 root = root_node ,
639+ partial_granularity_sampled = hasattr (self , "partial_granularity_sampled" ),
639640 )
640641
641642 # Clear settings as we are all done and don't need it
@@ -1004,35 +1005,94 @@ def _update_agent_attributes(self):
10041005 def user_attributes (self ):
10051006 return create_attributes (self ._custom_params , DST_ALL , self .attribute_filter )
10061007
1007- def sampling_algo_compute_sampled_and_priority (self ):
1008- if self ._priority is None :
1008+ def sampling_algo_compute_sampled_and_priority (self , priority , sampled ):
1009+ # self._priority and self._sampled are set when parsing the W3C tracestate
1010+ # or newrelic DT headers and may be overridden in _make_sampling_decision
1011+ # based on the configuration. The only time they are set in here is when the
1012+ # sampling decision must be made by the adaptive sampling algorithm.
1013+ if priority is None :
10091014 # Truncate priority field to 6 digits past the decimal.
1010- self ._priority = float (f"{ random .random ():.6f} " ) # noqa: S311
1011- if self ._sampled is None :
1012- self ._sampled = self ._application .compute_sampled ()
1013- if self ._sampled :
1014- self ._priority += 1
1015-
1016- def _compute_sampled_and_priority (self ):
1017- if self ._traceparent_sampled is None :
1015+ priority = float (f"{ random .random ():.6f} " ) # noqa: S311
1016+ if sampled is None :
1017+ _logger .debug ("No trusted account id found. Sampling decision will be made by adaptive sampling algorithm." )
1018+ sampled = self ._application .compute_sampled ()
1019+ if sampled :
1020+ priority += 1
1021+ return priority , sampled
1022+
1023+ def _compute_sampled_and_priority (self , priority , sampled , remote_parent_sampled_path , remote_parent_sampled_setting , remote_parent_not_sampled_path , remote_parent_not_sampled_setting ):
1024+ if self ._remote_parent_sampled is None :
10181025 config = "default" # Use sampling algo.
1019- elif self ._traceparent_sampled :
1020- setting_path = "distributed_tracing.sampler.remote_parent_sampled"
1021- config = self .settings .distributed_tracing .sampler .remote_parent_sampled
1022- else : # self._traceparent_sampled is False.
1023- setting_path = "distributed_tracing.sampler.remote_parent_not_sampled"
1024- config = self .settings .distributed_tracing .sampler .remote_parent_not_sampled
1025-
1026+ _logger .debug ("Sampling decision made based on no remote parent sampling decision present." )
1027+ elif self ._remote_parent_sampled :
1028+ setting_path = remote_parent_sampled_path
1029+ config = remote_parent_sampled_setting
1030+ _logger .debug ("Sampling decision made based on remote_parent_sampled=%s and %s=%s." , self ._remote_parent_sampled , setting_path , config )
1031+ else : # self._remote_parent_sampled is False.
1032+ setting_path = remote_parent_not_sampled_path
1033+ config = remote_parent_not_sampled_setting
1034+ _logger .debug ("Sampling decision made based on remote_parent_sampled=%s and %s=%s." , self ._remote_parent_sampled , setting_path , config )
10261035 if config == "always_on" :
1027- self . _sampled = True
1028- self . _priority = 2.0
1036+ sampled = True
1037+ priority = 2.0
10291038 elif config == "always_off" :
1030- self . _sampled = False
1031- self . _priority = 0
1039+ sampled = False
1040+ priority = 0
10321041 else :
1033- if config != "default" :
1042+ if config not in ( "default" , "adaptive" ) :
10341043 _logger .warning ("%s=%s is not a recognized value. Using 'default' instead." , setting_path , config )
1035- self .sampling_algo_compute_sampled_and_priority ()
1044+
1045+ _logger .debug ("Let adaptive sampler algorithm decide based on sampled=%s and priority=%s." , sampled , priority )
1046+ priority , sampled = self .sampling_algo_compute_sampled_and_priority (priority , sampled )
1047+ return priority , sampled
1048+
1049+ def _make_sampling_decision (self ):
1050+ # The sampling decision is computed each time a DT header is generated for exit spans as it is needed
1051+ # to send the DT headers. Don't recompute the sampling decision multiple times as it is expensive.
1052+ if hasattr (self , "_sampling_decision_made" ):
1053+ return
1054+ priority = self ._priority
1055+ sampled = self ._sampled
1056+ # Compute sampling decision for full granularity.
1057+ if self .settings .distributed_tracing .sampler .full_granularity .enabled :
1058+ _logger .debug ("Full granularity tracing is enabled. Asking if full granularity wants to sample. priority=%s, sampled=%s" , priority , sampled )
1059+ computed_priority , computed_sampled = self ._compute_sampled_and_priority (
1060+ priority ,
1061+ sampled ,
1062+ remote_parent_sampled_path = "distributed_tracing.sampler.full_granularity.remote_parent_sampled" ,
1063+ remote_parent_sampled_setting = self .settings .distributed_tracing .sampler .full_granularity .remote_parent_sampled ,
1064+ remote_parent_not_sampled_path = "distributed_tracing.sampler.full_granularity.remote_parent_not_sampled" ,
1065+ remote_parent_not_sampled_setting = self .settings .distributed_tracing .sampler .full_granularity .remote_parent_not_sampled ,
1066+ )
1067+ _logger .debug ("Full granularity sampling decision was %s with priority=%s." , sampled , priority )
1068+ if computed_sampled or not self .settings .distributed_tracing .sampler .partial_granularity .enabled :
1069+ self ._priority = computed_priority
1070+ self ._sampled = computed_sampled
1071+ self ._sampling_decision_made = True
1072+ return
1073+
1074+ # If full granularity is not going to sample, let partial granularity decide.
1075+ if self .settings .distributed_tracing .sampler .partial_granularity .enabled :
1076+ _logger .debug ("Partial granularity tracing is enabled. Asking if partial granularity wants to sample." )
1077+ self ._priority , self ._sampled = self ._compute_sampled_and_priority (
1078+ priority ,
1079+ sampled ,
1080+ remote_parent_sampled_path = "distributed_tracing.sampler.partial_granularity.remote_parent_sampled" ,
1081+ remote_parent_sampled_setting = self .settings .distributed_tracing .sampler .partial_granularity .remote_parent_sampled ,
1082+ remote_parent_not_sampled_path = "distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled" ,
1083+ remote_parent_not_sampled_setting = self .settings .distributed_tracing .sampler .partial_granularity .remote_parent_not_sampled ,
1084+ )
1085+ _logger .debug ("Partial granularity sampling decision was %s with priority=%s." , self ._sampled , self ._priority )
1086+ self ._sampling_decision_made = True
1087+ if self ._sampled :
1088+ self .partial_granularity_sampled = True
1089+ return
1090+
1091+ # This is only reachable if both full and partial granularity tracing are off.
1092+ # Set priority=0 and do not sample. This enables DT headers to still be sent
1093+ # even if the trace is never sampled.
1094+ self ._priority = 0
1095+ self ._sampled = False
10361096
10371097 def _freeze_path (self ):
10381098 if self ._frozen_path is None :
@@ -1101,7 +1161,7 @@ def _create_distributed_trace_data(self):
11011161 if not (account_id and application_id and trusted_account_key and settings .distributed_tracing .enabled ):
11021162 return
11031163
1104- self ._compute_sampled_and_priority ()
1164+ self ._make_sampling_decision ()
11051165 data = {
11061166 "ty" : "App" ,
11071167 "ac" : account_id ,
@@ -1204,7 +1264,7 @@ def _accept_distributed_trace_payload(self, payload, transport_type="HTTP"):
12041264 if not any (k in data for k in ("id" , "tx" )):
12051265 self ._record_supportability ("Supportability/DistributedTrace/AcceptPayload/ParseException" )
12061266 return False
1207-
1267+ self . _remote_parent_sampled = data . get ( "sa" )
12081268 settings = self ._settings
12091269 account_id = data .get ("ac" )
12101270 trusted_account_key = settings .trusted_account_key or (
@@ -1254,10 +1314,8 @@ def _accept_distributed_trace_data(self, data, transport_type):
12541314
12551315 self ._trace_id = data .get ("tr" )
12561316
1257- priority = data .get ("pr" )
1258- if priority is not None :
1259- self ._priority = priority
1260- self ._sampled = data .get ("sa" )
1317+ self ._priority = data .get ("pr" )
1318+ self ._sampled = data .get ("sa" )
12611319
12621320 if "ti" in data :
12631321 transport_start = data ["ti" ] / 1000.0
@@ -1297,6 +1355,7 @@ def accept_distributed_trace_headers(self, headers, transport_type="HTTP"):
12971355 try :
12981356 traceparent = ensure_str (traceparent ).strip ()
12991357 data = W3CTraceParent .decode (traceparent )
1358+ self ._remote_parent_sampled = data .pop ("sa" , None )
13001359 except :
13011360 data = None
13021361
@@ -1332,7 +1391,6 @@ def accept_distributed_trace_headers(self, headers, transport_type="HTTP"):
13321391 else :
13331392 self ._record_supportability ("Supportability/TraceContext/TraceState/NoNrEntry" )
13341393
1335- self ._traceparent_sampled = data .get ("sa" )
13361394 self ._accept_distributed_trace_data (data , transport_type )
13371395 self ._record_supportability ("Supportability/TraceContext/Accept/Success" )
13381396 return True
0 commit comments