@@ -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,87 @@ 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 :
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 
10361089
10371090    def  _freeze_path (self ):
10381091        if  self ._frozen_path  is  None :
@@ -1101,7 +1154,7 @@ def _create_distributed_trace_data(self):
11011154        if  not  (account_id  and  application_id  and  trusted_account_key  and  settings .distributed_tracing .enabled ):
11021155            return 
11031156
1104-         self ._compute_sampled_and_priority ()
1157+         self ._make_sampling_decision ()
11051158        data  =  {
11061159            "ty" : "App" ,
11071160            "ac" : account_id ,
@@ -1184,6 +1237,7 @@ def _accept_distributed_trace_payload(self, payload, transport_type="HTTP"):
11841237            return  False 
11851238
11861239        try :
1240+             self ._remote_parent_sampled  =  payload .get ("sa" )
11871241            version  =  payload .get ("v" )
11881242            major_version  =  version  and  int (version [0 ])
11891243
@@ -1254,10 +1308,8 @@ def _accept_distributed_trace_data(self, data, transport_type):
12541308
12551309        self ._trace_id  =  data .get ("tr" )
12561310
1257-         priority  =  data .get ("pr" )
1258-         if  priority  is  not   None :
1259-             self ._priority  =  priority 
1260-             self ._sampled  =  data .get ("sa" )
1311+         self ._priority  =  data .get ("pr" )
1312+         self ._sampled  =  data .get ("sa" )
12611313
12621314        if  "ti"  in  data :
12631315            transport_start  =  data ["ti" ] /  1000.0 
@@ -1297,6 +1349,7 @@ def accept_distributed_trace_headers(self, headers, transport_type="HTTP"):
12971349            try :
12981350                traceparent  =  ensure_str (traceparent ).strip ()
12991351                data  =  W3CTraceParent .decode (traceparent )
1352+                 self ._remote_parent_sampled  =  data .get ("sa" )
13001353            except :
13011354                data  =  None 
13021355
@@ -1332,7 +1385,6 @@ def accept_distributed_trace_headers(self, headers, transport_type="HTTP"):
13321385                    else :
13331386                        self ._record_supportability ("Supportability/TraceContext/TraceState/NoNrEntry" )
13341387
1335-             self ._traceparent_sampled  =  data .get ("sa" )
13361388            self ._accept_distributed_trace_data (data , transport_type )
13371389            self ._record_supportability ("Supportability/TraceContext/Accept/Success" )
13381390            return  True 
0 commit comments