diff --git a/newrelic/api/opentelemetry.py b/newrelic/api/opentelemetry.py index d1b59a619..56df7dedd 100644 --- a/newrelic/api/opentelemetry.py +++ b/newrelic/api/opentelemetry.py @@ -142,7 +142,7 @@ def __init__( def _sampled(self): # Uses NR to determine if the trace is sampled - # + # # transaction.sampled can be `None`, `True`, `False`. # If `None`, this has not been computed by NR which # can also mean the following: @@ -272,15 +272,14 @@ def start_span( *args, **kwargs, ): - nr_trace_type = FunctionTrace transaction = current_transaction() self.nr_application = application_instance() self.attributes = attributes or {} - + if not self.nr_application.settings.otel_bridge.enabled: return otel_api_trace.INVALID_SPAN - + # Retrieve parent span parent_span_context = otel_api_trace.get_current_span(context).get_span_context() @@ -436,4 +435,4 @@ def get_tracer( *args, **kwargs, ): - return Tracer(resource=self._resource, instrumentation_library=instrumenting_module_name, *args, **kwargs) \ No newline at end of file + return Tracer(*args, resource=self._resource, instrumentation_library=instrumenting_module_name, **kwargs) diff --git a/newrelic/api/transaction.py b/newrelic/api/transaction.py index 6eb94cbcf..3fcb0908e 100644 --- a/newrelic/api/transaction.py +++ b/newrelic/api/transaction.py @@ -1067,6 +1067,17 @@ def _compute_sampled_and_priority( elif config == "always_off": sampled = False priority = 0 + elif config == "trace_id_ratio_based": + _logger.debug("Let trace id ratio based sampler algorithm decide based on trace_id = %s.", self._trace_id) + priority, sampled = self.sampling_algo_compute_sampled_and_priority( + priority, + sampled, + { + "full_granularity": full_granularity, + "section": section, + "trace_id": int(self._trace_id.lower().zfill(32), 16), + }, + ) else: if config not in ("default", "adaptive"): _logger.warning("%s=%s is not a recognized value. Using 'default' instead.", setting_path, config) diff --git a/newrelic/config.py b/newrelic/config.py index cba1f3938..ef78b1185 100644 --- a/newrelic/config.py +++ b/newrelic/config.py @@ -95,7 +95,17 @@ def _map_aws_account_id(s): # modules to look up customised settings defined in the loaded # configuration file. -_config_object = configparser.RawConfigParser() + +def ratio(value): + try: + val = float(value) + if 0 < val <= 1: + return val + except ValueError: + pass + + +_config_object = configparser.RawConfigParser(converters={"ratio": ratio}) # Cache of the parsed global settings found in the configuration # file. We cache these so can dump them out to the log file once @@ -108,7 +118,7 @@ def _map_aws_account_id(s): def _reset_config_parser(): global _config_object global _cache_object - _config_object = configparser.RawConfigParser() + _config_object = configparser.RawConfigParser(converters={"ratio": ratio}) _cache_object = [] @@ -378,6 +388,8 @@ def _process_dt_hidden_setting(section, option, getter): target = _settings fields = option.split(".", 1) + if value == "trace_id_ratio_based": + raise configparser.NoOptionError("trace_id_ratio_sampler option can only be set by configuring the ratio") while True: if len(fields) == 1: value = value or "default" @@ -538,6 +550,9 @@ def _process_configuration(section): _process_dt_sampler_setting( section, "distributed_tracing.sampler.full_granularity.root.adaptive.sampling_target", "getint" ) + _process_dt_sampler_setting( + section, "distributed_tracing.sampler.full_granularity.root.trace_id_ratio_based.ratio", "getratio" + ) _process_dt_setting( section, "distributed_tracing.sampler.full_granularity.remote_parent_sampled", @@ -547,6 +562,11 @@ def _process_configuration(section): _process_dt_sampler_setting( section, "distributed_tracing.sampler.full_granularity.remote_parent_sampled.adaptive.sampling_target", "getint" ) + _process_dt_sampler_setting( + section, + "distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based.ratio", + "getratio", + ) _process_dt_setting( section, "distributed_tracing.sampler.full_granularity.remote_parent_not_sampled", @@ -558,6 +578,11 @@ def _process_configuration(section): "distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.adaptive.sampling_target", "getint", ) + _process_dt_sampler_setting( + section, + "distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio", + "getratio", + ) _process_setting(section, "distributed_tracing.sampler.full_granularity.enabled", "getboolean", None) _process_setting(section, "distributed_tracing.sampler.partial_granularity.enabled", "getboolean", None) _process_setting(section, "distributed_tracing.sampler.partial_granularity.type", "get", None) @@ -565,12 +590,20 @@ def _process_configuration(section): _process_dt_sampler_setting( section, "distributed_tracing.sampler.partial_granularity.root.adaptive.sampling_target", "getint" ) + _process_dt_sampler_setting( + section, "distributed_tracing.sampler.partial_granularity.root.trace_id_ratio_based.ratio", "getratio" + ) _process_dt_hidden_setting(section, "distributed_tracing.sampler.partial_granularity.remote_parent_sampled", "get") _process_dt_sampler_setting( section, "distributed_tracing.sampler.partial_granularity.remote_parent_sampled.adaptive.sampling_target", "getint", ) + _process_dt_sampler_setting( + section, + "distributed_tracing.sampler.partial_granularity.remote_parent_sampled.trace_id_ratio_based.ratio", + "getratio", + ) _process_dt_hidden_setting( section, "distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled", "get" ) @@ -579,6 +612,11 @@ def _process_configuration(section): "distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.adaptive.sampling_target", "getint", ) + _process_dt_sampler_setting( + section, + "distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio", + "getratio", + ) _process_setting(section, "span_events.enabled", "getboolean", None) _process_setting(section, "span_events.max_samples_stored", "getint", None) _process_setting(section, "span_events.attributes.enabled", "getboolean", None) diff --git a/newrelic/core/config.py b/newrelic/core/config.py index 1923424af..a05d8c85c 100644 --- a/newrelic/core/config.py +++ b/newrelic/core/config.py @@ -351,6 +351,10 @@ class DistributedTracingSamplerFullGranularityRootAdaptiveSettings: pass +class DistributedTracingSamplerFullGranularityRootTraceIdRatioBasedSettings: + pass + + class DistributedTracingSamplerFullGranularityRemoteParentSampledSettings: pass @@ -359,6 +363,10 @@ class DistributedTracingSamplerFullGranularityRemoteParentSampledAdaptiveSetting pass +class DistributedTracingSamplerFullGranularityRemoteParentSampledTraceIdRatioBasedSettings: + pass + + class DistributedTracingSamplerFullGranularityRemoteParentNotSampledSettings: pass @@ -367,6 +375,10 @@ class DistributedTracingSamplerFullGranularityRemoteParentNotSampledAdaptiveSett pass +class DistributedTracingSamplerFullGranularityRemoteParentNotSampledTraceIdRatioBasedSettings: + pass + + class DistributedTracingSamplerPartialGranularitySettings(Settings): _root = "default" _remote_parent_sampled = "default" @@ -381,6 +393,10 @@ class DistributedTracingSamplerPartialGranularityRootAdaptiveSettings: pass +class DistributedTracingSamplerPartialGranularityRootTraceIdRatioBasedSettings: + pass + + class DistributedTracingSamplerPartialGranularityRemoteParentSampledSettings: pass @@ -389,6 +405,10 @@ class DistributedTracingSamplerPartialGranularityRemoteParentSampledAdaptiveSett pass +class DistributedTracingSamplerPartialGranularityRemoteParentSampledTraceIdRatioBasedSettings: + pass + + class DistributedTracingSamplerPartialGranularityRemoteParentNotSampledSettings: pass @@ -397,6 +417,10 @@ class DistributedTracingSamplerPartialGranularityRemoteParentNotSampledAdaptiveS pass +class DistributedTracingSamplerPartialGranularityRemoteParentNotSampledTraceIdRatioBasedSettings: + pass + + class ServerlessModeSettings(Settings): pass @@ -576,18 +600,27 @@ class OtelBridgeSettings(Settings): _settings.distributed_tracing.sampler.full_granularity.root.adaptive = ( DistributedTracingSamplerFullGranularityRootAdaptiveSettings() ) +_settings.distributed_tracing.sampler.full_granularity.root.trace_id_ratio_based = ( + DistributedTracingSamplerFullGranularityRootTraceIdRatioBasedSettings() +) _settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled = ( DistributedTracingSamplerFullGranularityRemoteParentSampledSettings() ) _settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.adaptive = ( DistributedTracingSamplerFullGranularityRemoteParentSampledAdaptiveSettings() ) +_settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based = ( + DistributedTracingSamplerFullGranularityRemoteParentSampledTraceIdRatioBasedSettings() +) _settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled = ( DistributedTracingSamplerFullGranularityRemoteParentNotSampledSettings() ) _settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.adaptive = ( DistributedTracingSamplerFullGranularityRemoteParentNotSampledAdaptiveSettings() ) +_settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.trace_id_ratio_based = ( + DistributedTracingSamplerFullGranularityRemoteParentNotSampledTraceIdRatioBasedSettings() +) _settings.distributed_tracing.sampler.partial_granularity = DistributedTracingSamplerPartialGranularitySettings() _settings.distributed_tracing.sampler.partial_granularity.root = ( DistributedTracingSamplerPartialGranularityRootSettings() @@ -595,18 +628,30 @@ class OtelBridgeSettings(Settings): _settings.distributed_tracing.sampler.partial_granularity.root.adaptive = ( DistributedTracingSamplerPartialGranularityRootAdaptiveSettings() ) +_settings.distributed_tracing.sampler.partial_granularity.root.trace_id_ratio_based = ( + DistributedTracingSamplerPartialGranularityRootTraceIdRatioBasedSettings() +) _settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled = ( DistributedTracingSamplerPartialGranularityRemoteParentSampledSettings() ) _settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.adaptive = ( DistributedTracingSamplerPartialGranularityRemoteParentSampledAdaptiveSettings() ) +_settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.trace_id_ratio_based = ( + DistributedTracingSamplerPartialGranularityRemoteParentSampledTraceIdRatioBasedSettings() +) +_settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.trace_id_ratio_based = ( + DistributedTracingSamplerPartialGranularityRemoteParentSampledAdaptiveSettings() +) _settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled = ( DistributedTracingSamplerPartialGranularityRemoteParentNotSampledSettings() ) _settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.adaptive = ( DistributedTracingSamplerPartialGranularityRemoteParentNotSampledAdaptiveSettings() ) +_settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.trace_id_ratio_based = ( + DistributedTracingSamplerPartialGranularityRemoteParentNotSampledTraceIdRatioBasedSettings() +) _settings.error_collector = ErrorCollectorSettings() _settings.error_collector.attributes = ErrorCollectorAttributesSettings() _settings.event_harvest_config = EventHarvestConfigSettings() @@ -649,6 +694,14 @@ class OtelBridgeSettings(Settings): _settings.audit_log_file = os.environ.get("NEW_RELIC_AUDIT_LOG", None) +def _environ_as_sampler(name, default): + val = os.environ.get(name, default) + # The trace_id_ratio_based value can only be set by setting the ratio + if val == "trace_id_ratio_based": + return default + return val + + def _environ_as_int(name, default=0): val = os.environ.get(name, default) try: @@ -663,11 +716,27 @@ def _environ_as_float(name, default=0.0): val = os.environ.get(name, default) try: + if default is None and val is None: + return None return float(val) except ValueError: return default +def _environ_as_ratio(name, default=0.0): + val = os.environ.get(name, default) + + try: + if default is None and val is None: + return None + f_val = float(val) + if 0 < f_val <= 1: + return f_val + except ValueError: + return default + return default + + def _environ_as_bool(name, default=False): flag = os.environ.get(name, default) if default is None or default: @@ -952,15 +1021,36 @@ def default_otlp_host(host): "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ENABLED", default=True ) _settings.distributed_tracing.sampler.full_granularity._root = ( - "adaptive" - if os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT_ADAPTIVE_SAMPLING_TARGET", None) - else None -) or os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT", "default") + ( + "trace_id_ratio_based" + if _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT_TRACE_ID_RATIO_BASED_RATIO", None + ) + else None + ) + or ( + "adaptive" + if os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT_ADAPTIVE_SAMPLING_TARGET", None) + else None + ) + or _environ_as_sampler("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT", "default") +) _settings.distributed_tracing.sampler.full_granularity.root.adaptive.sampling_target = _environ_as_int( "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT_ADAPTIVE_SAMPLING_TARGET", None ) +_settings.distributed_tracing.sampler.full_granularity.root.trace_id_ratio_based.ratio = _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT_TRACE_ID_RATIO_BASED_RATIO", None +) _settings.distributed_tracing.sampler.full_granularity._remote_parent_sampled = ( ( + "trace_id_ratio_based" + if _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO", + None, + ) + else None + ) + or ( "adaptive" if os.environ.get( "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_SAMPLED_ADAPTIVE_SAMPLING_TARGET", @@ -968,14 +1058,27 @@ def default_otlp_host(host): ) else None ) - or os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_SAMPLED", None) + or _environ_as_sampler("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_SAMPLED", None) or os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_SAMPLED", "default") ) _settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.adaptive.sampling_target = _environ_as_int( "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_SAMPLED_ADAPTIVE_SAMPLING_TARGET", None ) +_settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based.ratio = ( + _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO", None + ) +) _settings.distributed_tracing.sampler.full_granularity._remote_parent_not_sampled = ( ( + "trace_id_ratio_based" + if _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO", + None, + ) + else None + ) + or ( "adaptive" if os.environ.get( "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_ADAPTIVE_SAMPLING_TARGET", @@ -983,7 +1086,7 @@ def default_otlp_host(host): ) else None ) - or os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED", None) + or _environ_as_sampler("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED", None) or os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_NOT_SAMPLED", "default") ) _settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.adaptive.sampling_target = ( @@ -992,6 +1095,12 @@ def default_otlp_host(host): None, ) ) +_settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio = ( + _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO", + None, + ) +) _settings.distributed_tracing.sampler.partial_granularity.enabled = _environ_as_bool( "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ENABLED", default=False ) @@ -999,39 +1108,89 @@ def default_otlp_host(host): "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_TYPE", "essential" ) _settings.distributed_tracing.sampler.partial_granularity._root = ( - "adaptive" - if os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT_ADAPTIVE_SAMPLING_TARGET", None) - else None -) or os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT", "default") + ( + "trace_id_ratio_based" + if _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT_TRACE_ID_RATIO_BASED_RATIO", None + ) + else None + ) + or ( + "adaptive" + if os.environ.get( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT_ADAPTIVE_SAMPLING_TARGET", None + ) + else None + ) + or _environ_as_sampler("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT", "default") +) _settings.distributed_tracing.sampler.partial_granularity.root.adaptive.sampling_target = _environ_as_int( "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT_ADAPTIVE_SAMPLING_TARGET", None ) +_settings.distributed_tracing.sampler.partial_granularity.root.trace_id_ratio_based.ratio = _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT_TRACE_ID_RATIO_BASED_RATIO", None +) _settings.distributed_tracing.sampler.partial_granularity._remote_parent_sampled = ( - "adaptive" - if os.environ.get( - "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED_ADAPTIVE_SAMPLING_TARGET", None + ( + "trace_id_ratio_based" + if _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO", + None, + ) + else None + ) + or ( + "adaptive" + if os.environ.get( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED_ADAPTIVE_SAMPLING_TARGET", + None, + ) + else None ) - else None -) or os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED", "default") + or _environ_as_sampler("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED", "default") +) _settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.adaptive.sampling_target = ( _environ_as_int( "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED_ADAPTIVE_SAMPLING_TARGET", None ) ) -_settings.distributed_tracing.sampler.partial_granularity._remote_parent_not_sampled = ( - "adaptive" - if os.environ.get( - "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_ADAPTIVE_SAMPLING_TARGET", +_settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.trace_id_ratio_based.ratio = ( + _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO", None, ) - else None -) or os.environ.get("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED", "default") +) +_settings.distributed_tracing.sampler.partial_granularity._remote_parent_not_sampled = ( + ( + "trace_id_ratio_based" + if _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO", + None, + ) + else None + ) + or ( + "adaptive" + if os.environ.get( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_ADAPTIVE_SAMPLING_TARGET", + None, + ) + else None + ) + or _environ_as_sampler( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED", "default" + ) +) _settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.adaptive.sampling_target = ( _environ_as_int( "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_ADAPTIVE_SAMPLING_TARGET", None, ) ) +_settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio = _environ_as_ratio( + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO", + None, +) _settings.distributed_tracing.exclude_newrelic_header = False _settings.span_events.enabled = _environ_as_bool("NEW_RELIC_SPAN_EVENTS_ENABLED", default=True) _settings.event_harvest_config.harvest_limits.span_event_data = _environ_as_int( diff --git a/newrelic/core/samplers/sampler_proxy.py b/newrelic/core/samplers/sampler_proxy.py index 0f9ee64df..b334c929a 100644 --- a/newrelic/core/samplers/sampler_proxy.py +++ b/newrelic/core/samplers/sampler_proxy.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from newrelic.core.samplers.adaptive_sampler import AdaptiveSampler +from newrelic.core.samplers.trace_id_ratio_based_sampler import TraceIdRatioBasedSampler class SamplerProxy: @@ -22,37 +23,115 @@ def __init__(self, settings): sampling_target_period = settings.sampling_target_period_in_seconds adaptive_sampler = AdaptiveSampler(settings.sampling_target, sampling_target_period) self._samplers = {"global": adaptive_sampler} - # Add adaptive sampler instances for each config section if configured. - self.add_adaptive_sampler( - (True, 0), - settings.distributed_tracing.sampler.full_granularity.root.adaptive.sampling_target, - sampling_target_period, - ) - self.add_adaptive_sampler( - (True, 1), - settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.adaptive.sampling_target, - sampling_target_period, - ) - self.add_adaptive_sampler( - (True, 2), - settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.adaptive.sampling_target, - sampling_target_period, - ) - self.add_adaptive_sampler( - (False, 0), - settings.distributed_tracing.sampler.partial_granularity.root.adaptive.sampling_target, - sampling_target_period, - ) - self.add_adaptive_sampler( - (False, 1), - settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.adaptive.sampling_target, - sampling_target_period, - ) - self.add_adaptive_sampler( - (False, 2), - settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.adaptive.sampling_target, - sampling_target_period, - ) + + full_gran_root_ratio = None + full_gran_parent_sampled_ratio = None + full_gran_parent_not_sampled_ratio = None + # Add sampler instances for each config section if configured. + if settings.distributed_tracing.sampler.full_granularity.enabled: + # If the ratio is not defined fallback to adaptive sampler. + if ( + settings.distributed_tracing.sampler.full_granularity._root == "trace_id_ratio_based" + and settings.distributed_tracing.sampler.full_granularity.root.trace_id_ratio_based.ratio + ): + full_gran_root_ratio = ( + settings.distributed_tracing.sampler.full_granularity.root.trace_id_ratio_based.ratio + ) + self.add_trace_id_ratio_based_sampler((True, 0), full_gran_root_ratio) + else: + self.add_adaptive_sampler( + (True, 0), + settings.distributed_tracing.sampler.full_granularity.root.adaptive.sampling_target, + sampling_target_period, + ) + # If the ratio is not defined fallback to adaptive sampler. + if ( + settings.distributed_tracing.sampler.full_granularity._remote_parent_sampled == "trace_id_ratio_based" + and settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based.ratio + ): + full_gran_parent_sampled_ratio = settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based.ratio + self.add_trace_id_ratio_based_sampler((True, 1), full_gran_parent_sampled_ratio) + else: + self.add_adaptive_sampler( + (True, 1), + settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.adaptive.sampling_target, + sampling_target_period, + ) + # If the ratio is not defined fallback to adaptive sampler. + if ( + settings.distributed_tracing.sampler.full_granularity._remote_parent_not_sampled + == "trace_id_ratio_based" + and settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio + ): + full_gran_parent_not_sampled_ratio = settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio + self.add_trace_id_ratio_based_sampler((True, 2), full_gran_parent_not_sampled_ratio) + else: + self.add_adaptive_sampler( + (True, 2), + settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.adaptive.sampling_target, + sampling_target_period, + ) + if settings.distributed_tracing.sampler.partial_granularity.enabled: + # If the ratio is not defined fallback to adaptive sampler. + if ( + settings.distributed_tracing.sampler.partial_granularity._root == "trace_id_ratio_based" + and settings.distributed_tracing.sampler.partial_granularity.root.trace_id_ratio_based.ratio + ): + # If both full and partial are set to use the trace id ratio based sampler, + # set partial granularity ratio = full ratio + partial ratio. + ratio = settings.distributed_tracing.sampler.partial_granularity.root.trace_id_ratio_based.ratio + if full_gran_root_ratio: + ratio = min(ratio + full_gran_root_ratio, 1) + self.add_trace_id_ratio_based_sampler((False, 0), ratio) + else: + self.add_adaptive_sampler( + (False, 0), + settings.distributed_tracing.sampler.partial_granularity.root.adaptive.sampling_target, + sampling_target_period, + ) + # If the ratio is not defined fallback to adaptive sampler. + if ( + settings.distributed_tracing.sampler.partial_granularity._remote_parent_sampled + == "trace_id_ratio_based" + and settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.trace_id_ratio_based.ratio + ): + # If both full and partial are set to use the trace id ratio based sampler, + # set partial granularity ratio = full ratio + partial ratio. + ratio = settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.trace_id_ratio_based.ratio + if full_gran_parent_sampled_ratio: + ratio = min(ratio + full_gran_parent_sampled_ratio, 1) + self.add_trace_id_ratio_based_sampler((False, 1), ratio) + else: + self.add_adaptive_sampler( + (False, 1), + settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.adaptive.sampling_target, + sampling_target_period, + ) + # If the ratio is not defined fallback to adaptive sampler. + if ( + settings.distributed_tracing.sampler.partial_granularity._remote_parent_not_sampled + == "trace_id_ratio_based" + and settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio + ): + # If both full and partial are set to use the trace id ratio based sampler, + # set partial granularity ratio = full ratio + partial ratio. + ratio = settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio + if full_gran_parent_not_sampled_ratio: + ratio = min(ratio + full_gran_parent_not_sampled_ratio, 1) + self.add_trace_id_ratio_based_sampler((False, 2), ratio) + else: + self.add_adaptive_sampler( + (False, 2), + settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.adaptive.sampling_target, + sampling_target_period, + ) + + def add_trace_id_ratio_based_sampler(self, key, ratio): + """ + Add a trace id ratio based sampler instance to self._samplers. + """ + ratio_sampler = TraceIdRatioBasedSampler(ratio) + self._samplers[key] = ratio_sampler def add_adaptive_sampler(self, key, sampling_target, sampling_target_period): """ diff --git a/newrelic/core/samplers/trace_id_ratio_based_sampler.py b/newrelic/core/samplers/trace_id_ratio_based_sampler.py new file mode 100644 index 000000000..457347cb1 --- /dev/null +++ b/newrelic/core/samplers/trace_id_ratio_based_sampler.py @@ -0,0 +1,33 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# For compatibility with 64 bit trace IDs, the sampler checks the 64 +# low-order bits of the trace ID to decide whether to sample a given trace. +TRACE_ID_LIMIT = (1 << 64) - 1 + + +class TraceIdRatioBasedSampler: + """ + This replicates behavior of TraceIdRatioBased sampler in + https://github.com/open-telemetry/opentelemetry-python/blob/main/opentelemetry-sdk/src/opentelemetry/sdk/trace/sampling.py. + """ + + def __init__(self, ratio): + self.ratio = ratio + self.bound = round(ratio * (TRACE_ID_LIMIT + 1)) + + def compute_sampled(self, trace_id): + if trace_id & TRACE_ID_LIMIT < self.bound: + return True + return False diff --git a/newrelic/hooks/hybridagent_opentelemetry.py b/newrelic/hooks/hybridagent_opentelemetry.py index 4c5538d09..41248657f 100644 --- a/newrelic/hooks/hybridagent_opentelemetry.py +++ b/newrelic/hooks/hybridagent_opentelemetry.py @@ -62,7 +62,7 @@ def wrap_get_tracer_provider(wrapped, instance, args, kwargs): if _TRACER_PROVIDER is None: from newrelic.api.opentelemetry import TracerProvider - + hybrid_agent_tracer_provider = TracerProvider("hybrid_agent_tracer_provider") _TRACER_PROVIDER = hybrid_agent_tracer_provider return _TRACER_PROVIDER diff --git a/tests/agent_features/test_distributed_tracing.py b/tests/agent_features/test_distributed_tracing.py index da70418e9..a29a5d3b7 100644 --- a/tests/agent_features/test_distributed_tracing.py +++ b/tests/agent_features/test_distributed_tracing.py @@ -1324,3 +1324,73 @@ def _test(): ) _test() + + +@pytest.mark.parametrize( + "dt_settings,dt_headers,expected_sampling_instance_called,expected_ratio", + ( + ( # Ratio for partial granularity does not exceed 1. + { + "distributed_tracing.sampler.full_granularity.enabled": True, + "distributed_tracing.sampler.partial_granularity.enabled": True, + "distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based.ratio": 0.5, + "distributed_tracing.sampler.full_granularity._remote_parent_sampled": "trace_id_ratio_based", + "distributed_tracing.sampler.partial_granularity.remote_parent_sampled.trace_id_ratio_based.ratio": 0.7, + "distributed_tracing.sampler.partial_granularity._remote_parent_sampled": "trace_id_ratio_based", + }, + {"traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"}, + (False, 1), + 1, + ), + ( # Partial granularity ratio = full ratio + partial ratio. + { + "distributed_tracing.sampler.full_granularity.enabled": True, + "distributed_tracing.sampler.partial_granularity.enabled": True, + "distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based.ratio": 0.5, + "distributed_tracing.sampler.full_granularity._remote_parent_sampled": "trace_id_ratio_based", + "distributed_tracing.sampler.partial_granularity.remote_parent_sampled.trace_id_ratio_based.ratio": 0.5, + "distributed_tracing.sampler.partial_granularity._remote_parent_sampled": "trace_id_ratio_based", + }, + {"traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"}, + (False, 1), + 1, + ), + ( # Trace ID ratio sampler is called for full granularity. + { + "distributed_tracing.sampler.full_granularity.enabled": True, + "distributed_tracing.sampler.partial_granularity.enabled": False, + "distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based.ratio": 1, + "distributed_tracing.sampler.full_granularity._remote_parent_sampled": "trace_id_ratio_based", + }, + {"traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"}, + (True, 1), + 1, + ), + ), +) +def test_distributed_trace_uses_sampling_instance( + dt_settings, dt_headers, expected_sampling_instance_called, expected_ratio +): + test_settings = _override_settings.copy() + test_settings.update(dt_settings) + function_called_decorator = validate_function_called( + "newrelic.core.samplers.trace_id_ratio_based_sampler", "TraceIdRatioBasedSampler.compute_sampled" + ) + + @function_called_decorator + @override_application_settings(test_settings) + @background_task(name="test_distributed_trace_attributes") + def _test(): + txn = current_transaction() + application = txn._application._agent._applications.get(txn.settings.app_name) + # Re-initialize sampler proxy after overriding settings. + application.sampler.__init__(txn.settings) + + accept_distributed_trace_headers(dt_headers) + # Explicitly call this so we can assert sampling decision during the transaction + # as opposed to after it ends and we lose the application context. + txn._make_sampling_decision() + + assert application.sampler._samplers[expected_sampling_instance_called].ratio == expected_ratio + + _test() diff --git a/tests/agent_unittests/test_distributed_tracing_settings.py b/tests/agent_unittests/test_distributed_tracing_settings.py index ab3bd25f4..150658659 100644 --- a/tests/agent_unittests/test_distributed_tracing_settings.py +++ b/tests/agent_unittests/test_distributed_tracing_settings.py @@ -44,7 +44,17 @@ distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.adaptive.sampling_target = 20 """ -INI_FILE_FULL_GRAN_MULTIPLE_SAMPLERS = b""" +INI_FILE_FULL_GRAN_CONFLICTS_RATIO = b""" +[newrelic] +distributed_tracing.sampler.root = always_on +distributed_tracing.sampler.remote_parent_sampled = always_on +distributed_tracing.sampler.remote_parent_not_sampled = always_off +distributed_tracing.sampler.full_granularity.root.trace_id_ratio_based.ratio = .5 +distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based.ratio = .1 +distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio = .2 +""" + +INI_FILE_FULL_GRAN_MULTIPLE_SAMPLERS_INVALID_RATIO = b""" [newrelic] distributed_tracing.sampler.full_granularity.root.adaptive.sampling_target = 5 distributed_tracing.sampler.full_granularity.remote_parent_sampled.adaptive.sampling_target = 10 @@ -54,6 +64,30 @@ distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.trace_id_ratio_based.sampling_target = 20 """ +INI_FILE_FULL_GRAN_NO_RATIO = b""" +[newrelic] +distributed_tracing.sampler.full_granularity.root = trace_id_ratio_based +distributed_tracing.sampler.full_granularity.remote_parent_sampled = trace_id_ratio_based +distributed_tracing.sampler.full_granularity.remote_parent_not_sampled = trace_id_ratio_based +""" + +INI_FILE_FULL_GRAN_MULTIPLE_VALID_SAMPLERS = b""" +[newrelic] +distributed_tracing.sampler.full_granularity.root.adaptive.sampling_target = 5 +distributed_tracing.sampler.full_granularity.remote_parent_sampled.adaptive.sampling_target = 10 +distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.adaptive.sampling_target = 20 +distributed_tracing.sampler.full_granularity.root.trace_id_ratio_based.ratio = .5 +distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based.ratio = .1 +distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio = .2 +""" + +INI_FILE_PARTIAL_GRAN_NO_RATIO = b""" +[newrelic] +distributed_tracing.sampler.partial_granularity.root = trace_id_ratio_based +distributed_tracing.sampler.partial_granularity.remote_parent_sampled = trace_id_ratio_based +distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled = trace_id_ratio_based +""" + INI_FILE_PARTIAL_GRAN_CONFLICTS_ADAPTIVE = b""" [newrelic] distributed_tracing.sampler.partial_granularity.remote_parent_sampled = always_on @@ -63,6 +97,15 @@ distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.adaptive.sampling_target = 20 """ +INI_FILE_PARTIAL_GRAN_CONFLICTS_RATIO = b""" +[newrelic] +distributed_tracing.sampler.partial_granularity.remote_parent_sampled = always_on +distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled = always_off +distributed_tracing.sampler.partial_granularity.root.trace_id_ratio_based.ratio = .5 +distributed_tracing.sampler.partial_granularity.remote_parent_sampled.trace_id_ratio_based.ratio = .1 +distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio = .2 +""" + INI_FILE_PARTIAL_GRAN_MULTIPLE_SAMPLERS = b""" [newrelic] distributed_tracing.sampler.partial_granularity.root.adaptive.sampling_target = 5 @@ -99,6 +142,11 @@ def test_distributed_trace_setings(ini, env, expected_format, global_settings): {}, ("adaptive", "adaptive", "adaptive", 5, 10, 20), ), + ( # More specific sampler path overrides less specific path in ini file. + INI_FILE_FULL_GRAN_CONFLICTS_RATIO, + {}, + ("trace_id_ratio_based", "trace_id_ratio_based", "trace_id_ratio_based", 0.5, 0.1, 0.2), + ), ( # ini file configuration takes precedence over env vars. INI_FILE_FULL_GRAN_CONFLICTS_ADAPTIVE, { @@ -144,11 +192,47 @@ def test_distributed_trace_setings(ini, env, expected_format, global_settings): }, ("adaptive", "adaptive", "adaptive", 20, 20, 20), ), - ( # Ignores other unknown samplers. - INI_FILE_FULL_GRAN_MULTIPLE_SAMPLERS, + ( # Ratio takes precendence over adaptive in env vars. + INI_FILE_EMPTY, + { + "NEW_RELIC_ENABLED": "true", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT": "always_on", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_SAMPLED": "always_on", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_NOT_SAMPLED": "always_off", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT_ADAPTIVE_SAMPLING_TARGET": "20", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_SAMPLED_ADAPTIVE_SAMPLING_TARGET": "20", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_ADAPTIVE_SAMPLING_TARGET": "20", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT_TRACE_ID_RATIO_BASED_RATIO": ".5", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO": ".1", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO": ".2", + }, + ("trace_id_ratio_based", "trace_id_ratio_based", "trace_id_ratio_based", 0.5, 0.1, 0.2), + ), + ( # Falls back on adaptive when invalid ratio. + INI_FILE_EMPTY, + { + "NEW_RELIC_ENABLED": "true", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ROOT_TRACE_ID_RATIO_BASED_RATIO": "5", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO": "10", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO": "0", + }, + ("default", "default", "default", None, None, None), + ), + ( # Ignores ratio sampler when invalid ratio path is provided. + INI_FILE_FULL_GRAN_MULTIPLE_SAMPLERS_INVALID_RATIO, {}, ("adaptive", "adaptive", "adaptive", 5, 10, 20), ), + ( # Ignores ratio sampler when ratio is not defined. + INI_FILE_FULL_GRAN_NO_RATIO, + {}, + ("default", "default", "default", None, None, None), + ), + ( # Ratio takes precedence over adaptive. + INI_FILE_FULL_GRAN_MULTIPLE_VALID_SAMPLERS, + {}, + ("trace_id_ratio_based", "trace_id_ratio_based", "trace_id_ratio_based", 0.5, 0.1, 0.2), + ), ), ) def test_full_granularity_precedence(ini, env, global_settings, expected): @@ -159,15 +243,30 @@ def test_full_granularity_precedence(ini, env, global_settings, expected): assert app_settings.distributed_tracing.sampler.full_granularity._root == expected[0] assert app_settings.distributed_tracing.sampler.full_granularity._remote_parent_sampled == expected[1] assert app_settings.distributed_tracing.sampler.full_granularity._remote_parent_not_sampled == expected[2] - assert app_settings.distributed_tracing.sampler.full_granularity.root.adaptive.sampling_target == expected[3] - assert ( - app_settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.adaptive.sampling_target - == expected[4] - ) - assert ( - app_settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.adaptive.sampling_target - == expected[5] - ) + if expected[0] == "trace_id_ratio_based": + assert app_settings.distributed_tracing.sampler.full_granularity.root.trace_id_ratio_based.ratio == expected[3] + else: + assert app_settings.distributed_tracing.sampler.full_granularity.root.adaptive.sampling_target == expected[3] + if expected[1] == "trace_id_ratio_based": + assert ( + app_settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.trace_id_ratio_based.ratio + == expected[4] + ) + else: + assert ( + app_settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled.adaptive.sampling_target + == expected[4] + ) + if expected[2] == "trace_id_ratio_based": + assert ( + app_settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio + == expected[5] + ) + else: + assert ( + app_settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled.adaptive.sampling_target + == expected[5] + ) @pytest.mark.parametrize( @@ -183,6 +282,11 @@ def test_full_granularity_precedence(ini, env, global_settings, expected): {}, ("adaptive", "adaptive", "adaptive", 5, 10, 20), ), + ( # More specific sampler path overrides less specific path in ini file. + INI_FILE_PARTIAL_GRAN_CONFLICTS_RATIO, + {}, + ("trace_id_ratio_based", "trace_id_ratio_based", "trace_id_ratio_based", 0.5, 0.1, 0.2), + ), ( # ini config takes precedence over env vars. INI_FILE_PARTIAL_GRAN_CONFLICTS_ADAPTIVE, { @@ -204,6 +308,17 @@ def test_full_granularity_precedence(ini, env, global_settings, expected): }, ("always_on", "always_on", "always_off", None, None, None), ), + ( # Ignores ratio if ratio is not defined. + INI_FILE_EMPTY, + { + "NEW_RELIC_ENABLED": "true", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT": "trace_id_ratio_based", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED": "trace_id_ratio_based", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED": "trace_id_ratio_based", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO": ".1", + }, + ("default", "default", "trace_id_ratio_based", None, None, 0.1), + ), ( # More specific sampler path overrides less specific path in env vars. INI_FILE_EMPTY, { @@ -217,11 +332,42 @@ def test_full_granularity_precedence(ini, env, global_settings, expected): }, ("adaptive", "adaptive", "adaptive", 5, 10, 20), ), + ( # Ratio takes precedence over adaptive in env vars. + INI_FILE_EMPTY, + { + "NEW_RELIC_ENABLED": "true", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT": "always_on", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED": "always_on", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED": "always_off", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT_ADAPTIVE_SAMPLING_TARGET": "5", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED_ADAPTIVE_SAMPLING_TARGET": "10", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_ADAPTIVE_SAMPLING_TARGET": "20", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT_TRACE_ID_RATIO_BASED_RATIO": ".5", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO": ".1", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO": ".2", + }, + ("trace_id_ratio_based", "trace_id_ratio_based", "trace_id_ratio_based", 0.5, 0.1, 0.2), + ), ( # Ignores other unknown samplers. INI_FILE_PARTIAL_GRAN_MULTIPLE_SAMPLERS, {}, ("adaptive", "adaptive", "adaptive", 5, 10, 20), ), + ( # Ignores ratio sampler when ratio is not defined. + INI_FILE_PARTIAL_GRAN_NO_RATIO, + {}, + ("default", "default", "default", None, None, None), + ), + ( # Falls back on adaptive when invalid ratio. + INI_FILE_EMPTY, + { + "NEW_RELIC_ENABLED": "true", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ROOT_TRACE_ID_RATIO_BASED_RATIO": "5", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO": "10", + "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO": "0", + }, + ("default", "default", "default", None, None, None), + ), ), ) def test_partial_granularity_precedence(ini, env, global_settings, expected): @@ -232,12 +378,30 @@ def test_partial_granularity_precedence(ini, env, global_settings, expected): assert app_settings.distributed_tracing.sampler.partial_granularity._root == expected[0] assert app_settings.distributed_tracing.sampler.partial_granularity._remote_parent_sampled == expected[1] assert app_settings.distributed_tracing.sampler.partial_granularity._remote_parent_not_sampled == expected[2] - assert app_settings.distributed_tracing.sampler.partial_granularity.root.adaptive.sampling_target == expected[3] - assert ( - app_settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.adaptive.sampling_target - == expected[4] - ) - assert ( - app_settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.adaptive.sampling_target - == expected[5] - ) + if expected[0] == "trace_id_ratio_based": + assert ( + app_settings.distributed_tracing.sampler.partial_granularity.root.trace_id_ratio_based.ratio == expected[3] + ) + else: + assert app_settings.distributed_tracing.sampler.partial_granularity.root.adaptive.sampling_target == expected[3] + + if expected[1] == "trace_id_ratio_based": + assert ( + app_settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.trace_id_ratio_based.ratio + == expected[4] + ) + else: + assert ( + app_settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled.adaptive.sampling_target + == expected[4] + ) + if expected[2] == "trace_id_ratio_based": + assert ( + app_settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.trace_id_ratio_based.ratio + == expected[5] + ) + else: + assert ( + app_settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled.adaptive.sampling_target + == expected[5] + ) diff --git a/tests/hybridagent_opentelemetry/conftest.py b/tests/hybridagent_opentelemetry/conftest.py index fc5afe53b..a6d457926 100644 --- a/tests/hybridagent_opentelemetry/conftest.py +++ b/tests/hybridagent_opentelemetry/conftest.py @@ -32,9 +32,10 @@ app_name="Python Agent Test (Hybrid Agent)", default_settings=_default_settings ) + @pytest.fixture(scope="session") def tracer(): trace_provider = TracerProvider() trace.set_tracer_provider(trace_provider) - return trace.get_tracer(__name__) \ No newline at end of file + return trace.get_tracer(__name__) diff --git a/tests/hybridagent_opentelemetry/test_settings.py b/tests/hybridagent_opentelemetry/test_settings.py index 6b50b020f..1ccd109ab 100644 --- a/tests/hybridagent_opentelemetry/test_settings.py +++ b/tests/hybridagent_opentelemetry/test_settings.py @@ -13,59 +13,40 @@ # limitations under the License. import pytest +from testing_support.fixtures import override_application_settings +from testing_support.validators.validate_span_events import validate_span_events from newrelic.api.background_task import background_task from newrelic.api.time_trace import current_trace from newrelic.api.transaction import current_transaction -from testing_support.validators.validate_span_events import validate_span_events -from testing_support.fixtures import override_application_settings - -@pytest.mark.parametrize( - "enabled", - [True, False] -) +@pytest.mark.parametrize("enabled", [True, False]) def test_distributed_tracing_enabled(tracer, enabled): @override_application_settings({"otel_bridge.enabled": enabled}) - @validate_span_events( - count=1, - exact_intrinsics={ - "name": "Function/Foo" - } - ) - @validate_span_events( - count=1 if enabled else 0, - exact_intrinsics={ - "name": "Function/Bar" - } - ) - @validate_span_events( - count=1 if enabled else 0, - exact_intrinsics={ - "name": "Function/Baz" - } - ) + @validate_span_events(count=1, exact_intrinsics={"name": "Function/Foo"}) + @validate_span_events(count=1 if enabled else 0, exact_intrinsics={"name": "Function/Bar"}) + @validate_span_events(count=1 if enabled else 0, exact_intrinsics={"name": "Function/Baz"}) @background_task(name="Foo") def _test(): with tracer.start_as_current_span(name="Bar") as bar_span: bar_span_id = bar_span.get_span_context().span_id bar_trace_id = bar_span.get_span_context().trace_id - + with tracer.start_as_current_span(name="Baz") as baz_span: nr_trace = current_trace() nr_transaction = current_transaction() baz_span_id = baz_span.get_span_context().span_id baz_trace_id = baz_span.get_span_context().trace_id - + assert bar_trace_id == baz_trace_id - + if enabled: assert bar_trace_id == baz_trace_id == int(nr_transaction.trace_id, 16) assert baz_span_id == int(nr_trace.guid, 16) assert bar_span_id == int(nr_trace.parent.guid, 16) assert nr_trace is not nr_trace.root - + else: assert bar_trace_id == baz_trace_id == 0 assert baz_span_id == bar_span_id == 0 diff --git a/tests/hybridagent_opentelemetry/test_traces_attributes.py b/tests/hybridagent_opentelemetry/test_traces_attributes.py index b50a99e1b..b9f370593 100644 --- a/tests/hybridagent_opentelemetry/test_traces_attributes.py +++ b/tests/hybridagent_opentelemetry/test_traces_attributes.py @@ -13,14 +13,13 @@ # limitations under the License. from opentelemetry import trace +from testing_support.validators.validate_span_events import validate_span_events from newrelic.api.background_task import background_task from newrelic.api.function_trace import function_trace from newrelic.api.time_trace import add_custom_span_attribute from newrelic.api.transaction import add_custom_attribute -from testing_support.validators.validate_span_events import validate_span_events - def test_trace_with_span_attributes(tracer): @validate_span_events(