Skip to content

Commit 669f51e

Browse files
committed
Add support 4 partial granularity tracing
1 parent 08dbcaf commit 669f51e

17 files changed

+985
-46
lines changed

newrelic/api/transaction.py

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,7 @@ def __exit__(self, exc, value, tb):
637637
trace_id=self.trace_id,
638638
loop_time=self._loop_time,
639639
root=root_node,
640+
partial_granularity_sampled=hasattr(self, "partial_granularity_sampled"),
640641
)
641642

642643
# Clear settings as we are all done and don't need it
@@ -1073,23 +1074,52 @@ def _make_sampling_decision(self):
10731074
return
10741075
priority = self._priority
10751076
sampled = self._sampled
1076-
_logger.debug(
1077-
"Full granularity tracing is enabled. Asking if full granularity wants to sample. priority=%s, sampled=%s",
1078-
priority,
1079-
sampled,
1080-
)
1081-
computed_priority, computed_sampled = self._compute_sampled_and_priority(
1082-
priority,
1083-
sampled,
1084-
remote_parent_sampled_path="distributed_tracing.sampler.remote_parent_sampled",
1085-
remote_parent_sampled_setting=self.settings.distributed_tracing.sampler.remote_parent_sampled,
1086-
remote_parent_not_sampled_path="distributed_tracing.sampler.remote_parent_not_sampled",
1087-
remote_parent_not_sampled_setting=self.settings.distributed_tracing.sampler.remote_parent_not_sampled,
1088-
)
1089-
_logger.debug("Full granularity sampling decision was %s with priority=%s.", sampled, priority)
1090-
self._priority = computed_priority
1091-
self._sampled = computed_sampled
1092-
self._sampling_decision_made = True
1077+
# Compute sampling decision for full granularity.
1078+
if self.settings.distributed_tracing.sampler.full_granularity.enabled:
1079+
_logger.debug(
1080+
"Full granularity tracing is enabled. Asking if full granularity wants to sample. priority=%s, sampled=%s",
1081+
priority,
1082+
sampled,
1083+
)
1084+
computed_priority, computed_sampled = self._compute_sampled_and_priority(
1085+
priority,
1086+
sampled,
1087+
remote_parent_sampled_path="distributed_tracing.sampler.full_granularity.remote_parent_sampled",
1088+
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",
1090+
remote_parent_not_sampled_setting=self.settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled,
1091+
)
1092+
_logger.debug("Full granularity sampling decision was %s with priority=%s.", sampled, priority)
1093+
if computed_sampled or not self.settings.distributed_tracing.sampler.partial_granularity.enabled:
1094+
self._priority = computed_priority
1095+
self._sampled = computed_sampled
1096+
self._sampling_decision_made = True
1097+
return
1098+
1099+
# If full granularity is not going to sample, let partial granularity decide.
1100+
if self.settings.distributed_tracing.sampler.partial_granularity.enabled:
1101+
_logger.debug("Partial granularity tracing is enabled. Asking if partial granularity wants to sample.")
1102+
self._priority, self._sampled = self._compute_sampled_and_priority(
1103+
priority,
1104+
sampled,
1105+
remote_parent_sampled_path="distributed_tracing.sampler.partial_granularity.remote_parent_sampled",
1106+
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",
1108+
remote_parent_not_sampled_setting=self.settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled,
1109+
)
1110+
_logger.debug(
1111+
"Partial granularity sampling decision was %s with priority=%s.", self._sampled, self._priority
1112+
)
1113+
self._sampling_decision_made = True
1114+
if self._sampled:
1115+
self.partial_granularity_sampled = True
1116+
return
1117+
1118+
# This is only reachable if both full and partial granularity tracing are off.
1119+
# Set priority=0 and do not sample. This enables DT headers to still be sent
1120+
# even if the trace is never sampled.
1121+
self._priority = 0
1122+
self._sampled = False
10931123

10941124
def _freeze_path(self):
10951125
if self._frozen_path is None:

newrelic/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,13 @@ def _process_configuration(section):
407407
_process_setting(section, "distributed_tracing.sampler.adaptive_sampling_target", "getint", None)
408408
_process_setting(section, "distributed_tracing.sampler.remote_parent_sampled", "get", None)
409409
_process_setting(section, "distributed_tracing.sampler.remote_parent_not_sampled", "get", None)
410+
_process_setting(section, "distributed_tracing.sampler.full_granularity.enabled", "getboolean", None)
411+
_process_setting(section, "distributed_tracing.sampler.full_granularity.remote_parent_sampled", "get", None)
412+
_process_setting(section, "distributed_tracing.sampler.full_granularity.remote_parent_not_sampled", "get", None)
413+
_process_setting(section, "distributed_tracing.sampler.partial_granularity.enabled", "getboolean", None)
414+
_process_setting(section, "distributed_tracing.sampler.partial_granularity.type", "get", None)
415+
_process_setting(section, "distributed_tracing.sampler.partial_granularity.remote_parent_sampled", "get", None)
416+
_process_setting(section, "distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled", "get", None)
410417
_process_setting(section, "span_events.enabled", "getboolean", None)
411418
_process_setting(section, "span_events.max_samples_stored", "getint", None)
412419
_process_setting(section, "span_events.attributes.enabled", "getboolean", None)

newrelic/core/attribute.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,23 @@
109109
"zeebe.client.resourceFile",
110110
}
111111

112+
SPAN_ENTITY_RELATIONSHIP_ATTRIBUTES = {
113+
"cloud.account.id",
114+
"cloud.platform",
115+
"cloud.region",
116+
"cloud.resource_id",
117+
"db.instance",
118+
"db.system",
119+
"http.url",
120+
"messaging.destination.name",
121+
"messaging.system",
122+
"peer.hostname",
123+
"server.address",
124+
"server.port",
125+
"span.kind",
126+
}
127+
128+
112129
MAX_NUM_USER_ATTRIBUTES = 128
113130
MAX_ATTRIBUTE_LENGTH = 255
114131
MAX_NUM_ML_USER_ATTRIBUTES = 64

newrelic/core/config.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,14 @@ class DistributedTracingSamplerSettings(Settings):
337337
pass
338338

339339

340+
class DistributedTracingSamplerFullGranularitySettings(Settings):
341+
pass
342+
343+
344+
class DistributedTracingSamplerPartialGranularitySettings(Settings):
345+
pass
346+
347+
340348
class ServerlessModeSettings(Settings):
341349
pass
342350

@@ -507,6 +515,8 @@ class EventHarvestConfigHarvestLimitSettings(Settings):
507515
_settings.debug = DebugSettings()
508516
_settings.distributed_tracing = DistributedTracingSettings()
509517
_settings.distributed_tracing.sampler = DistributedTracingSamplerSettings()
518+
_settings.distributed_tracing.sampler.full_granularity = DistributedTracingSamplerFullGranularitySettings()
519+
_settings.distributed_tracing.sampler.partial_granularity = DistributedTracingSamplerPartialGranularitySettings()
510520
_settings.error_collector = ErrorCollectorSettings()
511521
_settings.error_collector.attributes = ErrorCollectorAttributesSettings()
512522
_settings.event_harvest_config = EventHarvestConfigSettings()
@@ -845,6 +855,27 @@ def default_otlp_host(host):
845855
_settings.distributed_tracing.sampler.remote_parent_not_sampled = os.environ.get(
846856
"NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_NOT_SAMPLED", "default"
847857
)
858+
_settings.distributed_tracing.sampler.full_granularity.enabled = _environ_as_bool(
859+
"NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_ENABLED", default=True
860+
)
861+
_settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled = os.environ.get(
862+
"NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_SAMPLED", None
863+
)
864+
_settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled = os.environ.get(
865+
"NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_FULL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED", None
866+
)
867+
_settings.distributed_tracing.sampler.partial_granularity.enabled = _environ_as_bool(
868+
"NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_ENABLED", default=False
869+
)
870+
_settings.distributed_tracing.sampler.partial_granularity.type = os.environ.get(
871+
"NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_TYPE", "essential"
872+
)
873+
_settings.distributed_tracing.sampler.partial_granularity.remote_parent_sampled = os.environ.get(
874+
"NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_SAMPLED", "default"
875+
)
876+
_settings.distributed_tracing.sampler.partial_granularity.remote_parent_not_sampled = os.environ.get(
877+
"NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_PARTIAL_GRANULARITY_REMOTE_PARENT_NOT_SAMPLED", "default"
878+
)
848879
_settings.distributed_tracing.exclude_newrelic_header = False
849880
_settings.span_events.enabled = _environ_as_bool("NEW_RELIC_SPAN_EVENTS_ENABLED", default=True)
850881
_settings.event_harvest_config.harvest_limits.span_event_data = _environ_as_int(
@@ -1371,9 +1402,34 @@ def finalize_application_settings(server_side_config=None, settings=_settings):
13711402

13721403
application_settings.attribute_filter = AttributeFilter(flatten_settings(application_settings))
13731404

1405+
simplify_distributed_tracing_sampler_granularity_settings(application_settings)
1406+
13741407
return application_settings
13751408

13761409

1410+
def simplify_distributed_tracing_sampler_granularity_settings(settings):
1411+
# Full granularity settings may appear under:
1412+
# * `distributed_tracing.sampler`
1413+
# * `distributed_tracing.sampler.full_granularity`
1414+
# The `distributed_tracing.sampler.full_granularity` path takes precedence.
1415+
# To simplify logic in the code that uses these settings, store the values that
1416+
# should be used at the `distributed_tracing.sampler.full_granularity` path.
1417+
if not settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled:
1418+
settings.distributed_tracing.sampler.full_granularity.remote_parent_sampled = (
1419+
settings.distributed_tracing.sampler.remote_parent_sampled
1420+
)
1421+
if not settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled:
1422+
settings.distributed_tracing.sampler.full_granularity.remote_parent_not_sampled = (
1423+
settings.distributed_tracing.sampler.remote_parent_not_sampled
1424+
)
1425+
# Partial granularity tracing is not available in infinite tracing mode.
1426+
if settings.infinite_tracing.enabled and settings.distributed_tracing.sampler.partial_granularity.enabled:
1427+
_logger.warning(
1428+
"Improper configuration. Infinite tracing cannot be enabled at the same time as partial granularity tracing. Setting distributed_tracing.sampler.partial_granularity.enabled=False."
1429+
)
1430+
settings.distributed_tracing.sampler.partial_granularity.enabled = False
1431+
1432+
13771433
def _remove_ignored_configs(server_settings):
13781434
if not server_settings.get("agent_config"):
13791435
return server_settings

newrelic/core/data_collector.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,14 @@ def send_ml_events(self, sampling_info, custom_event_data):
117117

118118
def send_span_events(self, sampling_info, span_event_data):
119119
"""Called to submit sample set for span events."""
120-
120+
# TODO: remove this later after list types are suported.
121+
for span_event in span_event_data:
122+
try:
123+
ids = span_event[1].get("nr.ids")
124+
if ids:
125+
span_event[1]["nr.ids"] = ",".join(ids)
126+
except:
127+
pass
121128
payload = (self.agent_run_id, sampling_info, span_event_data)
122129
return self._protocol.send("span_event_data", payload)
123130

newrelic/core/database_node.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,15 @@ def trace_node(self, stats, root, connections):
279279
start_time=start_time, end_time=end_time, name=name, params=params, children=children, label=None
280280
)
281281

282-
def span_event(self, settings, base_attrs=None, parent_guid=None, attr_class=dict):
282+
def span_event(
283+
self,
284+
settings,
285+
base_attrs=None,
286+
parent_guid=None,
287+
attr_class=dict,
288+
partial_granularity_sampled=False,
289+
ct_exit_spans=None,
290+
):
283291
sql = self.formatted
284292

285293
if sql:
@@ -288,4 +296,11 @@ def span_event(self, settings, base_attrs=None, parent_guid=None, attr_class=dic
288296

289297
self.agent_attributes["db.statement"] = sql
290298

291-
return super().span_event(settings, base_attrs=base_attrs, parent_guid=parent_guid, attr_class=attr_class)
299+
return super().span_event(
300+
settings,
301+
base_attrs=base_attrs,
302+
parent_guid=parent_guid,
303+
attr_class=attr_class,
304+
partial_granularity_sampled=partial_granularity_sampled,
305+
ct_exit_spans=ct_exit_spans,
306+
)

newrelic/core/external_node.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,15 @@ def trace_node(self, stats, root, connections):
169169
start_time=start_time, end_time=end_time, name=name, params=params, children=children, label=None
170170
)
171171

172-
def span_event(self, settings, base_attrs=None, parent_guid=None, attr_class=dict):
172+
def span_event(
173+
self,
174+
settings,
175+
base_attrs=None,
176+
parent_guid=None,
177+
attr_class=dict,
178+
partial_granularity_sampled=False,
179+
ct_exit_spans=None,
180+
):
173181
self.agent_attributes["http.url"] = self.http_url
174182

175183
i_attrs = (base_attrs and base_attrs.copy()) or attr_class()
@@ -180,4 +188,11 @@ def span_event(self, settings, base_attrs=None, parent_guid=None, attr_class=dic
180188
if self.method:
181189
_, i_attrs["http.method"] = attribute.process_user_attribute("http.method", self.method)
182190

183-
return super().span_event(settings, base_attrs=i_attrs, parent_guid=parent_guid, attr_class=attr_class)
191+
return super().span_event(
192+
settings,
193+
base_attrs=i_attrs,
194+
parent_guid=parent_guid,
195+
attr_class=attr_class,
196+
partial_granularity_sampled=partial_granularity_sampled,
197+
ct_exit_spans=ct_exit_spans,
198+
)

newrelic/core/function_node.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,23 @@ def trace_node(self, stats, root, connections):
114114
start_time=start_time, end_time=end_time, name=name, params=params, children=children, label=self.label
115115
)
116116

117-
def span_event(self, settings, base_attrs=None, parent_guid=None, attr_class=dict):
117+
def span_event(
118+
self,
119+
settings,
120+
base_attrs=None,
121+
parent_guid=None,
122+
attr_class=dict,
123+
partial_granularity_sampled=False,
124+
ct_exit_spans=None,
125+
):
118126
i_attrs = (base_attrs and base_attrs.copy()) or attr_class()
119127
i_attrs["name"] = f"{self.group}/{self.name}"
120128

121-
return super().span_event(settings, base_attrs=i_attrs, parent_guid=parent_guid, attr_class=attr_class)
129+
return super().span_event(
130+
settings,
131+
base_attrs=i_attrs,
132+
parent_guid=parent_guid,
133+
attr_class=attr_class,
134+
partial_granularity_sampled=partial_granularity_sampled,
135+
ct_exit_spans=ct_exit_spans,
136+
)

newrelic/core/loop_node.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,23 @@ def trace_node(self, stats, root, connections):
7979
start_time=start_time, end_time=end_time, name=name, params=params, children=children, label=None
8080
)
8181

82-
def span_event(self, settings, base_attrs=None, parent_guid=None, attr_class=dict):
82+
def span_event(
83+
self,
84+
settings,
85+
base_attrs=None,
86+
parent_guid=None,
87+
attr_class=dict,
88+
partial_granularity_sampled=False,
89+
ct_exit_spans=None,
90+
):
8391
i_attrs = (base_attrs and base_attrs.copy()) or attr_class()
8492
i_attrs["name"] = f"EventLoop/Wait/{self.name}"
8593

86-
return super().span_event(settings, base_attrs=i_attrs, parent_guid=parent_guid, attr_class=attr_class)
94+
return super().span_event(
95+
settings,
96+
base_attrs=i_attrs,
97+
parent_guid=parent_guid,
98+
attr_class=attr_class,
99+
partial_granularity_sampled=partial_granularity_sampled,
100+
ct_exit_spans=ct_exit_spans,
101+
)

0 commit comments

Comments
 (0)