Skip to content

Commit 5623745

Browse files
committed
Prioritize full over partial granularity
* priority +=2 when full granularity and sampled=true * priority +=1 when partial granularity and sampled=true * always_on sampler for full granularity sets priority=3 instead of 2
1 parent 3bc035f commit 5623745

File tree

3 files changed

+117
-14
lines changed

3 files changed

+117
-14
lines changed

newrelic/api/transaction.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,12 @@ def __exit__(self, exc, value, tb):
571571
# Sampled and priority need to be computed at the end of the
572572
# transaction when distributed tracing or span events are enabled.
573573
self._make_sampling_decision()
574+
else:
575+
# If dt is disabled, set sampled=False and priority random number between 0 and 1.
576+
# The priority of the transaction is used for other data like transaction
577+
# events even when span events are not sent.
578+
self._priority = float(f"{random.random():.6f}") # noqa: S311
579+
self._sampled = False
574580

575581
self._cached_path._name = self.path
576582
agent_attributes = self.agent_attributes
@@ -1017,8 +1023,9 @@ def sampling_algo_compute_sampled_and_priority(self, priority, sampled, sampler_
10171023
if sampled is None:
10181024
_logger.debug("No trusted account id found. Sampling decision will be made by adaptive sampling algorithm.")
10191025
sampled = self._application.compute_sampled(**sampler_kwargs)
1026+
# Increment the priority + 2 for full and + 1 for partial granularity.
10201027
if sampled:
1021-
priority += 1
1028+
priority += 1 + int(sampler_kwargs.get("full_granularity"))
10221029
return priority, sampled
10231030

10241031
def _compute_sampled_and_priority(
@@ -1063,7 +1070,8 @@ def _compute_sampled_and_priority(
10631070
)
10641071
if config == "always_on":
10651072
sampled = True
1066-
priority = 2.0
1073+
# priority=3 for full granularity and priority=2 for partial granularity.
1074+
priority = 2.0 + int(full_granularity)
10671075
elif config == "always_off":
10681076
sampled = False
10691077
priority = 0
@@ -1139,9 +1147,9 @@ def _make_sampling_decision(self):
11391147
return
11401148

11411149
# This is only reachable if both full and partial granularity tracing are off.
1142-
# Set priority=0 and do not sample. This enables DT headers to still be sent
1143-
# even if the trace is never sampled.
1144-
self._priority = 0
1150+
# Set priority to random number between 0 and 1 and do not sample. This enables
1151+
# DT headers to still be sent even if the trace is never sampled.
1152+
self._priority = float(f"{random.random():.6f}") # noqa: S311
11451153
self._sampled = False
11461154

11471155
def _freeze_path(self):

tests/agent_features/test_distributed_tracing.py

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import copy
1717
import json
1818
import time
19+
import random
1920

2021
import pytest
2122
import webtest
@@ -25,6 +26,7 @@
2526
from testing_support.validators.validate_function_not_called import validate_function_not_called
2627
from testing_support.validators.validate_transaction_event_attributes import validate_transaction_event_attributes
2728
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics
29+
from testing_support.validators.validate_transaction_object_attributes import validate_transaction_object_attributes
2830

2931
from newrelic.api.application import application_instance
3032
from newrelic.api.function_trace import function_trace
@@ -542,17 +544,17 @@ def _test_inbound_dt_payload_acceptance():
542544
"newrelic_header,traceparent_sampled,newrelic_sampled,root_setting,remote_parent_sampled_setting,remote_parent_not_sampled_setting,expected_sampled,expected_priority,expected_adaptive_sampling_algo_called",
543545
(
544546
(False, None, None, "default", "default", "default", None, None, True), # Uses adaptive sampling algo.
545-
(False, None, None, "always_on", "default", "default", True, 2, False), # Always sampled.
547+
(False, None, None, "always_on", "default", "default", True, 3, False), # Always sampled.
546548
(False, None, None, "always_off", "default", "default", False, 0, False), # Never sampled.
547549
(True, True, None, "default", "default", "default", None, None, True), # Uses adaptive sampling algo.
548-
(True, True, None, "default", "always_on", "default", True, 2, False), # Always sampled.
550+
(True, True, None, "default", "always_on", "default", True, 3, False), # Always sampled.
549551
(True, True, None, "default", "always_off", "default", False, 0, False), # Never sampled.
550552
(True, False, None, "default", "default", "default", None, None, True), # Uses adaptive sampling algo.
551553
(True, False, None, "default", "always_on", "default", None, None, True), # Uses adaptive sampling alog.
552554
(True, False, None, "default", "always_off", "default", None, None, True), # Uses adaptive sampling algo.
553555
(True, True, None, "default", "default", "always_on", None, None, True), # Uses adaptive sampling algo.
554556
(True, True, None, "default", "default", "always_off", None, None, True), # Uses adaptive sampling algo.
555-
(True, False, None, "default", "default", "always_on", True, 2, False), # Always sampled.
557+
(True, False, None, "default", "default", "always_on", True, 3, False), # Always sampled.
556558
(True, False, None, "default", "default", "always_off", False, 0, False), # Never sampled.
557559
(
558560
True,
@@ -587,9 +589,9 @@ def _test_inbound_dt_payload_acceptance():
587589
1.23456,
588590
False,
589591
), # Uses sampling decision in W3C TraceState header.
590-
(True, True, False, "default", "always_on", "default", True, 2, False), # Always sampled.
592+
(True, True, False, "default", "always_on", "default", True, 3, False), # Always sampled.
591593
(True, True, True, "default", "always_off", "default", False, 0, False), # Never sampled.
592-
(True, False, False, "default", "default", "always_on", True, 2, False), # Always sampled.
594+
(True, False, False, "default", "default", "always_on", True, 3, False), # Always sampled.
593595
(True, False, True, "default", "default", "always_off", False, 0, False), # Never sampled.
594596
(
595597
True,
@@ -602,7 +604,7 @@ def _test_inbound_dt_payload_acceptance():
602604
0.1234,
603605
False,
604606
), # Uses sampling and priority from newrelic header.
605-
(True, None, True, "default", "always_on", "default", True, 2, False), # Always sampled.
607+
(True, None, True, "default", "always_on", "default", True, 3, False), # Always sampled.
606608
(True, None, True, "default", "always_off", "default", False, 0, False), # Never sampled.
607609
(
608610
True,
@@ -637,7 +639,7 @@ def _test_inbound_dt_payload_acceptance():
637639
0.1234,
638640
False,
639641
), # Uses sampling and priority from newrelic header.
640-
(True, None, False, "default", "default", "always_on", True, 2, False), # Always sampled.
642+
(True, None, False, "default", "default", "always_on", True, 3, False), # Always sampled.
641643
(True, None, False, "default", "default", "always_off", False, 0, False), # Never sampled.
642644
(True, None, None, "default", "default", "default", None, None, True), # Uses adaptive sampling algo.
643645
),
@@ -878,8 +880,7 @@ def _test():
878880
"full_granularity_enabled,full_granularity_remote_parent_sampled_setting,partial_granularity_enabled,partial_granularity_remote_parent_sampled_setting,expected_sampled,expected_priority,expected_adaptive_sampling_algo_called",
879881
(
880882
(True, "always_off", True, "adaptive", None, None, True), # Uses adaptive sampling algo.
881-
(True, "always_on", True, "adaptive", True, 2, False), # Uses adaptive sampling algo.
882-
(False, "always_on", False, "adaptive", False, 0, False), # Uses adaptive sampling algo.
883+
(True, "always_on", True, "adaptive", True, 3, False), # Always samples.
883884
),
884885
)
885886
def test_distributed_trace_remote_parent_sampling_decision_between_full_and_partial_granularity(
@@ -1394,3 +1395,59 @@ def _test():
13941395
assert application.sampler._samplers[expected_sampling_instance_called].ratio == expected_ratio
13951396

13961397
_test()
1398+
1399+
1400+
@pytest.mark.parametrize(
1401+
"dt_settings,expected_priority,expected_sampled",
1402+
(
1403+
( # When dt is enabled but full and partial are disabled.
1404+
{
1405+
"distributed_tracing.sampler.full_granularity.enabled": False,
1406+
"distributed_tracing.sampler.partial_granularity.enabled": False,
1407+
},
1408+
0.123, # random
1409+
False,
1410+
),
1411+
( # When dt is disabled.
1412+
{
1413+
"distributed_tracing.enabled": False,
1414+
},
1415+
0.123, # random
1416+
False,
1417+
),
1418+
( # Verify when full granularity sampled +2 is added to the priority.
1419+
{
1420+
"distributed_tracing.sampler.full_granularity.root.trace_id_ratio_based.ratio": 1,
1421+
},
1422+
2.123, # random + 2
1423+
True,
1424+
),
1425+
( # Verify when partial granularity sampled +1 is added to the priority.
1426+
{
1427+
"distributed_tracing.sampler.full_granularity.enabled": False,
1428+
"distributed_tracing.sampler.partial_granularity.enabled": True,
1429+
"distributed_tracing.sampler.partial_granularity.root.trace_id_ratio_based.ratio": 1,
1430+
},
1431+
1.123, # random + 1
1432+
True,
1433+
),
1434+
),
1435+
)
1436+
def test_distributed_trace_enabled_settings_set_correct_sampled_priority(
1437+
dt_settings,
1438+
expected_priority,
1439+
expected_sampled,
1440+
monkeypatch,
1441+
):
1442+
monkeypatch.setattr(random, 'random', lambda *args, **kwargs: 0.123)
1443+
1444+
test_settings = _override_settings.copy()
1445+
test_settings.update(dt_settings)
1446+
1447+
@override_application_settings(test_settings)
1448+
@validate_transaction_object_attributes({"sampled": expected_sampled, "priority": expected_priority})
1449+
@background_task(name="test_distributed_trace_attributes")
1450+
def _test():
1451+
pass
1452+
1453+
_test()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2010 New Relic, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from newrelic.common.object_wrapper import transient_function_wrapper
16+
17+
18+
def validate_transaction_object_attributes(attributes):
19+
# This validates attributes on the transaction object that is passed to
20+
# `StatsEngine.record_transaction`.
21+
#
22+
# Args:
23+
#
24+
# attributes: a dictionary of expected attributes (key) and values (value)
25+
26+
@transient_function_wrapper("newrelic.core.stats_engine", "StatsEngine.record_transaction")
27+
def _validate_attributes(wrapped, instance, args, kwargs):
28+
def _bind_params(transaction, *args, **kwargs):
29+
return transaction
30+
31+
transaction = _bind_params(*args, **kwargs)
32+
33+
for attr, value in attributes.items():
34+
assert getattr(transaction, attr) == value
35+
36+
return wrapped(*args, **kwargs)
37+
38+
return _validate_attributes

0 commit comments

Comments
 (0)