Skip to content

Commit 1c01dc5

Browse files
committed
Add SamplerProxy
1 parent 68fce59 commit 1c01dc5

File tree

12 files changed

+93
-53
lines changed

12 files changed

+93
-53
lines changed

newrelic/api/application.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,11 @@ def normalize_name(self, name, rule_type="url"):
156156
return self._agent.normalize_name(self._name, name, rule_type)
157157
return name, False
158158

159-
def compute_sampled(self):
159+
def compute_sampled(self, full_granularity, section, *args, **kwargs):
160160
if not self.active or not self.settings.distributed_tracing.enabled:
161161
return False
162162

163-
return self._agent.compute_sampled(self._name)
163+
return self._agent.compute_sampled(self._name, full_granularity, section, *args, **kwargs)
164164

165165

166166
def application_instance(name=None, activate=True):

newrelic/api/transaction.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,7 @@ def _update_agent_attributes(self):
10061006
def user_attributes(self):
10071007
return create_attributes(self._custom_params, DST_ALL, self.attribute_filter)
10081008

1009-
def sampling_algo_compute_sampled_and_priority(self, priority, sampled):
1009+
def sampling_algo_compute_sampled_and_priority(self, priority, sampled, sampler_kwargs):
10101010
# self._priority and self._sampled are set when parsing the W3C tracestate
10111011
# or newrelic DT headers and may be overridden in _make_sampling_decision
10121012
# based on the configuration. The only time they are set in here is when the
@@ -1016,25 +1016,21 @@ def sampling_algo_compute_sampled_and_priority(self, priority, sampled):
10161016
priority = float(f"{random.random():.6f}") # noqa: S311
10171017
if sampled is None:
10181018
_logger.debug("No trusted account id found. Sampling decision will be made by adaptive sampling algorithm.")
1019-
sampled = self._application.compute_sampled()
1019+
sampled = self._application.compute_sampled(**sampler_kwargs)
10201020
if sampled:
10211021
priority += 1
10221022
return priority, sampled
10231023

10241024
def _compute_sampled_and_priority(
1025-
self,
1026-
priority,
1027-
sampled,
1028-
remote_parent_sampled_path,
1029-
remote_parent_sampled_setting,
1030-
remote_parent_not_sampled_path,
1031-
remote_parent_not_sampled_setting,
1025+
self, priority, sampled, full_granularity, remote_parent_sampled_setting, remote_parent_not_sampled_setting
10321026
):
10331027
if self._remote_parent_sampled is None:
1028+
section = 0
10341029
config = "default" # Use sampling algo.
10351030
_logger.debug("Sampling decision made based on no remote parent sampling decision present.")
10361031
elif self._remote_parent_sampled:
1037-
setting_path = remote_parent_sampled_path
1032+
section = 1
1033+
setting_path = f"distributed_tracing.sampler.{'full_granularity' if full_granularity else 'partial_granularity'}.remote_parent_sampled"
10381034
config = remote_parent_sampled_setting
10391035
_logger.debug(
10401036
"Sampling decision made based on remote_parent_sampled=%s and %s=%s.",
@@ -1043,7 +1039,8 @@ def _compute_sampled_and_priority(
10431039
config,
10441040
)
10451041
else: # self._remote_parent_sampled is False.
1046-
setting_path = remote_parent_not_sampled_path
1042+
section = 2
1043+
setting_path = f"distributed_tracing.sampler.{'full_granularity' if full_granularity else 'partial_granularity'}.remote_parent_not_sampled"
10471044
config = remote_parent_not_sampled_setting
10481045
_logger.debug(
10491046
"Sampling decision made based on remote_parent_sampled=%s and %s=%s.",
@@ -1064,7 +1061,9 @@ def _compute_sampled_and_priority(
10641061
_logger.debug(
10651062
"Let adaptive sampler algorithm decide based on sampled=%s and priority=%s.", sampled, priority
10661063
)
1067-
priority, sampled = self.sampling_algo_compute_sampled_and_priority(priority, sampled)
1064+
priority, sampled = self.sampling_algo_compute_sampled_and_priority(
1065+
priority, sampled, {"full_granularity": full_granularity, "section": section}
1066+
)
10681067
return priority, sampled
10691068

10701069
def _make_sampling_decision(self):
@@ -1084,9 +1083,8 @@ def _make_sampling_decision(self):
10841083
computed_priority, computed_sampled = self._compute_sampled_and_priority(
10851084
priority,
10861085
sampled,
1087-
remote_parent_sampled_path="distributed_tracing.sampler.full_granularity.remote_parent_sampled",
1086+
full_granularity=True,
10881087
remote_parent_sampled_setting=self.settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled,
1089-
remote_parent_not_sampled_path="distributed_tracing.sampler.full_granularity.remote_parent_not_sampled",
10901088
remote_parent_not_sampled_setting=self.settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled,
10911089
)
10921090
_logger.debug("Full granularity sampling decision was %s with priority=%s.", sampled, priority)
@@ -1102,9 +1100,8 @@ def _make_sampling_decision(self):
11021100
self._priority, self._sampled = self._compute_sampled_and_priority(
11031101
priority,
11041102
sampled,
1105-
remote_parent_sampled_path="distributed_tracing.sampler.partial_granularity.remote_parent_sampled",
1103+
full_granularity=False,
11061104
remote_parent_sampled_setting=self.settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled,
1107-
remote_parent_not_sampled_path="distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled",
11081105
remote_parent_not_sampled_setting=self.settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled,
11091106
)
11101107
_logger.debug(

newrelic/core/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,9 +581,9 @@ def normalize_name(self, app_name, name, rule_type="url"):
581581

582582
return application.normalize_name(name, rule_type)
583583

584-
def compute_sampled(self, app_name):
584+
def compute_sampled(self, app_name, full_granularity, section, *args, **kwargs):
585585
application = self._applications.get(app_name, None)
586-
return application.compute_sampled()
586+
return application.compute_sampled(full_granularity, section, *args, **kwargs)
587587

588588
def _harvest_shutdown_is_set(self):
589589
try:

newrelic/core/application.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from functools import partial
2424

2525
from newrelic.common.object_names import callable_name
26-
from newrelic.core.adaptive_sampler import AdaptiveSampler
2726
from newrelic.core.agent_control_health import (
2827
HealthStatus,
2928
agent_control_health_instance,
@@ -37,6 +36,7 @@
3736
from newrelic.core.internal_metrics import InternalTrace, InternalTraceContext, internal_count_metric, internal_metric
3837
from newrelic.core.profile_sessions import profile_session_manager
3938
from newrelic.core.rules_engine import RulesEngine, SegmentCollapseEngine
39+
from newrelic.core.samplers.sampler_proxy import SamplerProxy
4040
from newrelic.core.stats_engine import CustomMetrics, StatsEngine
4141
from newrelic.network.exceptions import (
4242
DiscardDataForRequest,
@@ -78,7 +78,7 @@ def __init__(self, app_name, linked_applications=None):
7878
self._transaction_count = 0
7979
self._last_transaction = 0.0
8080

81-
self.adaptive_sampler = None
81+
self.sampler = None
8282

8383
self._global_events_account = 0
8484

@@ -156,11 +156,11 @@ def configuration(self):
156156
def active(self):
157157
return self.configuration is not None
158158

159-
def compute_sampled(self):
160-
if self.adaptive_sampler is None:
159+
def compute_sampled(self, full_granularity, section, *args, **kwargs):
160+
if self.sampler is None:
161161
return False
162162

163-
return self.adaptive_sampler.compute_sampled()
163+
return self.sampler.compute_sampled(full_granularity, section, *args, **kwargs)
164164

165165
def dump(self, file):
166166
"""Dumps details about the application to the file object."""
@@ -501,12 +501,7 @@ def connect_to_data_collector(self, activate_agent):
501501

502502
with self._stats_lock:
503503
self._stats_engine.reset_stats(configuration, reset_stream=True)
504-
505-
if configuration.serverless_mode.enabled:
506-
sampling_target_period = 60.0
507-
else:
508-
sampling_target_period = configuration.sampling_target_period_in_seconds
509-
self.adaptive_sampler = AdaptiveSampler(configuration.sampling_target, sampling_target_period)
504+
self.sampler = SamplerProxy(configuration)
510505

511506
active_session.connect_span_stream(self._stats_engine.span_stream, self.record_custom_metric)
512507

newrelic/core/samplers/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
File renamed without changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
from newrelic.core.samplers.adaptive_sampler import AdaptiveSampler
15+
16+
17+
class SamplerProxy:
18+
def __init__(self, settings):
19+
if settings.serverless_mode.enabled:
20+
sampling_target_period = 60.0
21+
else:
22+
sampling_target_period = settings.sampling_target_period_in_seconds
23+
adaptive_sampler = AdaptiveSampler(settings.sampling_target, sampling_target_period)
24+
self._samplers = [adaptive_sampler]
25+
26+
def get_sampler(self, full_granularity, section):
27+
return self._samplers[0]
28+
29+
def compute_sampled(self, full_granularity, section, *args, **kwargs):
30+
"""
31+
full_granularity: True is full granularity, False is partial granularity
32+
section: 0-root, 1-remote_parent_sampled, 2-remote_parent_not_sampled
33+
"""
34+
return self.get_sampler(full_granularity, section).compute_sampled(*args, **kwargs)

tests/agent_features/test_distributed_tracing.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -593,11 +593,11 @@ def test_distributed_trace_remote_parent_sampling_decision_full_granularity(
593593
)
594594
if expected_adaptive_sampling_algo_called:
595595
function_called_decorator = validate_function_called(
596-
"newrelic.core.adaptive_sampler", "AdaptiveSampler.compute_sampled"
596+
"newrelic.core.samplers.adaptive_sampler", "AdaptiveSampler.compute_sampled"
597597
)
598598
else:
599599
function_called_decorator = validate_function_not_called(
600-
"newrelic.core.adaptive_sampler", "AdaptiveSampler.compute_sampled"
600+
"newrelic.core.samplers.adaptive_sampler", "AdaptiveSampler.compute_sampled"
601601
)
602602

603603
@function_called_decorator
@@ -684,11 +684,11 @@ def test_distributed_trace_remote_parent_sampling_decision_partial_granularity(
684684
)
685685
if expected_adaptive_sampling_algo_called:
686686
function_called_decorator = validate_function_called(
687-
"newrelic.core.adaptive_sampler", "AdaptiveSampler.compute_sampled"
687+
"newrelic.core.samplers.adaptive_sampler", "AdaptiveSampler.compute_sampled"
688688
)
689689
else:
690690
function_called_decorator = validate_function_not_called(
691-
"newrelic.core.adaptive_sampler", "AdaptiveSampler.compute_sampled"
691+
"newrelic.core.samplers.adaptive_sampler", "AdaptiveSampler.compute_sampled"
692692
)
693693

694694
@function_called_decorator
@@ -752,11 +752,11 @@ def test_distributed_trace_remote_parent_sampling_decision_between_full_and_part
752752
)
753753
if expected_adaptive_sampling_algo_called:
754754
function_called_decorator = validate_function_called(
755-
"newrelic.core.adaptive_sampler", "AdaptiveSampler.compute_sampled"
755+
"newrelic.core.samplers.adaptive_sampler", "AdaptiveSampler.compute_sampled"
756756
)
757757
else:
758758
function_called_decorator = validate_function_not_called(
759-
"newrelic.core.adaptive_sampler", "AdaptiveSampler.compute_sampled"
759+
"newrelic.core.samplers.adaptive_sampler", "AdaptiveSampler.compute_sampled"
760760
)
761761

762762
@function_called_decorator

tests/agent_unittests/test_harvest_loop.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -542,30 +542,30 @@ def test_adaptive_sampling(transaction_node, monkeypatch):
542542
app = Application("Python Agent Test (Harvest Loop)")
543543

544544
# Should always return false for sampling prior to connect
545-
assert app.compute_sampled() is False
545+
assert app.compute_sampled(True, 0) is False
546546

547547
app.connect_to_data_collector(None)
548548

549549
# First harvest, first N should be sampled
550550
for _ in range(settings.sampling_target):
551-
assert app.compute_sampled() is True
551+
assert app.compute_sampled(True, 0) is True
552552

553-
assert app.compute_sampled() is False
553+
assert app.compute_sampled(True, 0) is False
554554

555555
# fix random.randrange to return 0
556556
monkeypatch.setattr(random, "randrange", lambda *args, **kwargs: 0)
557557

558558
# Multiple resets should behave the same
559559
for _ in range(2):
560560
# Set the last_reset to longer than the period so a reset will occur.
561-
app.adaptive_sampler.last_reset = time.time() - app.adaptive_sampler.period
561+
app.sampler.get_sampler(True, 0).last_reset = time.time() - app.sampler.get_sampler(True, 0).period
562562

563563
# Subsequent harvests should allow sampling of 2X the target
564564
for _ in range(2 * settings.sampling_target):
565-
assert app.compute_sampled() is True
565+
assert app.compute_sampled(True, 0) is True
566566

567567
# No further samples should be saved
568-
assert app.compute_sampled() is False
568+
assert app.compute_sampled(True, 0) is False
569569

570570

571571
@override_generic_settings(
@@ -709,20 +709,20 @@ def test_serverless_mode_adaptive_sampling(time_to_next_reset, computed_count, c
709709
app = Application("Python Agent Test (Harvest Loop)")
710710

711711
app.connect_to_data_collector(None)
712-
app.adaptive_sampler.computed_count = 123
713-
app.adaptive_sampler.last_reset = time.time() - 60 + time_to_next_reset
712+
app.sampler.get_sampler(True, 0).computed_count = 123
713+
app.sampler.get_sampler(True, 0).last_reset = time.time() - 60 + time_to_next_reset
714714

715-
assert app.compute_sampled() is True
716-
assert app.adaptive_sampler.computed_count == computed_count
717-
assert app.adaptive_sampler.computed_count_last == computed_count_last
715+
assert app.compute_sampled(True, 0) is True
716+
assert app.sampler.get_sampler(True, 0).computed_count == computed_count
717+
assert app.sampler.get_sampler(True, 0).computed_count_last == computed_count_last
718718

719719

720-
@validate_function_not_called("newrelic.core.adaptive_sampler", "AdaptiveSampler._reset")
720+
@validate_function_not_called("newrelic.core.samplers.adaptive_sampler", "AdaptiveSampler._reset")
721721
@override_generic_settings(settings, {"developer_mode": True})
722722
def test_compute_sampled_no_reset():
723723
app = Application("Python Agent Test (Harvest Loop)")
724724
app.connect_to_data_collector(None)
725-
assert app.compute_sampled() is True
725+
assert app.compute_sampled(True, 0) is True
726726

727727

728728
def test_analytic_event_sampling_info():

tests/cross_agent/test_distributed_tracing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def load_tests():
6565

6666

6767
def override_compute_sampled(override):
68-
@transient_function_wrapper("newrelic.core.adaptive_sampler", "AdaptiveSampler.compute_sampled")
68+
@transient_function_wrapper("newrelic.core.samplers.adaptive_sampler", "AdaptiveSampler.compute_sampled")
6969
def _override_compute_sampled(wrapped, instance, args, kwargs):
7070
if override:
7171
return True

0 commit comments

Comments
 (0)