Skip to content

Commit 7650f52

Browse files
committed
Add support 4 CT & dropping inprocess spans
CT = Core Tracing
1 parent 777e025 commit 7650f52

13 files changed

+246
-51
lines changed

newrelic/api/transaction.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,13 @@ def __exit__(self, exc, value, tb):
658658
def sampled(self):
659659
return self._sampled
660660

661+
@property
662+
def ct_sampled(self):
663+
# If DT doesn't sample it CT will.
664+
if not self.sampled and (self._settings.distributed_tracing.drop_inprocess_spans.enabled or self._settings.distributed_tracing.unique_spans.enabled):
665+
return True
666+
return False
667+
661668
@property
662669
def priority(self):
663670
return self._priority
@@ -1082,12 +1089,13 @@ def _create_distributed_trace_data(self):
10821089
return
10831090

10841091
self._compute_sampled_and_priority()
1092+
sampled = self.sampled or self.ct_sampled
10851093
data = {
10861094
"ty": "App",
10871095
"ac": account_id,
10881096
"ap": application_id,
10891097
"tr": self.trace_id,
1090-
"sa": self.sampled,
1098+
"sa": sampled,
10911099
"pr": self.priority,
10921100
"tx": self.guid,
10931101
"ti": int(time.time() * 1000.0),

newrelic/common/streaming_utils.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import collections
1616
import logging
1717
import threading
18+
import sys
1819

1920
try:
2021
from newrelic.core.infinite_tracing_pb2 import AttributeValue, SpanBatch
@@ -25,13 +26,76 @@
2526
_logger = logging.getLogger(__name__)
2627

2728

29+
def get_deep_size(obj, seen=None):
30+
"""Recursively calculates the size of an object including nested lists and dicts."""
31+
if seen is None:
32+
seen = set()
33+
size = -8*3 # Subtract 8 for each of the 3 attribute lists as those don't count.
34+
else:
35+
size = 0
36+
37+
# Avoid recursion for already seen objects (handle circular references)
38+
obj_id = id(obj)
39+
if obj_id in seen:
40+
return 0
41+
seen.add(obj_id)
42+
43+
if isinstance(obj, str):
44+
size += len(obj)
45+
return size
46+
elif isinstance(obj, float) or isinstance(obj, int):
47+
size += 8
48+
return size
49+
elif isinstance(obj, bool):
50+
size += 1
51+
return size
52+
elif isinstance(obj, dict):
53+
size += sum(get_deep_size(k, seen) + get_deep_size(v, seen) for k, v in obj.items())
54+
elif isinstance(obj, (list, tuple, set, frozenset)):
55+
size += 8 + sum(get_deep_size(i, seen) for i in obj)
56+
else:
57+
size += 8
58+
59+
return size
60+
61+
62+
def get_deep_size_protobuf(obj):
63+
"""Recursively calculates the size of an object including nested lists and dicts."""
64+
size = 0
65+
if hasattr(obj, "string_value"):
66+
size += len(obj.string_value)
67+
return size
68+
elif hasattr(obj, "double_value"):
69+
size += 8
70+
return size
71+
elif hasattr(obj, "int_value"):
72+
size += 8
73+
return size
74+
elif hasattr(obj, "bool_value"):
75+
size += 1
76+
return size
77+
78+
if hasattr(obj, "agent_attributes"):
79+
size += sum(len(k) + get_deep_size_protobuf(v) for k, v in obj.agent_attributes.items())
80+
if hasattr(obj, "user_attributes"):
81+
size += sum(len(k) + get_deep_size_protobuf(v) for k, v in obj.user_attributes.items())
82+
if hasattr(obj, "intrinsics"):
83+
size += sum(len(k) + get_deep_size_protobuf(v) for k, v in obj.intrinsics.items())
84+
else:
85+
size += 8
86+
87+
return size
88+
89+
2890
class StreamBuffer:
2991
def __init__(self, maxlen, batching=False):
3092
self._queue = collections.deque(maxlen=maxlen)
3193
self._notify = self.condition()
3294
self._shutdown = False
3395
self._seen = 0
3496
self._dropped = 0
97+
self._bytes = 0
98+
self._ct_processing_time = 0
3599
self._settings = None
36100

37101
self.batching = batching
@@ -51,6 +115,8 @@ def put(self, item):
51115
return
52116

53117
self._seen += 1
118+
_logger.debug(f"{item.intrinsics['name']} [{len(item.intrinsics)}, {len(item.user_attributes)}, {len(item.agent_attributes)}] {get_deep_size_protobuf(item)}")
119+
self._bytes += get_deep_size_protobuf(item)
54120

55121
# NOTE: dropped can be over-counted as the queue approaches
56122
# capacity while data is still being transmitted.
@@ -67,8 +133,10 @@ def stats(self):
67133
with self._notify:
68134
seen, dropped = self._seen, self._dropped
69135
self._seen, self._dropped = 0, 0
136+
_bytes, ct_processing_time = self._bytes, self._ct_processing_time
137+
self._bytes, self._ct_processing_time = 0, 0
70138

71-
return seen, dropped
139+
return seen, dropped, _bytes, ct_processing_time
72140

73141
def __bool__(self):
74142
return bool(self._queue)

newrelic/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@ def _process_configuration(section):
398398
_process_setting(section, "custom_insights_events.max_attribute_value", "getint", None)
399399
_process_setting(section, "ml_insights_events.enabled", "getboolean", None)
400400
_process_setting(section, "distributed_tracing.enabled", "getboolean", None)
401+
_process_setting(section, "distributed_tracing.drop_inprocess_spans.enabled", "getboolean", None)
402+
_process_setting(section, "distributed_tracing.unique_spans.enabled", "getboolean", None)
403+
_process_setting(section, "distributed_tracing.minimize_attributes.enabled", "getboolean", None)
401404
_process_setting(section, "distributed_tracing.exclude_newrelic_header", "getboolean", None)
402405
_process_setting(section, "span_events.enabled", "getboolean", None)
403406
_process_setting(section, "span_events.max_samples_stored", "getint", None)

newrelic/core/application.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,11 @@ def connect_to_data_collector(self, activate_agent):
507507
sampling_target_period = 60.0
508508
else:
509509
sampling_target_period = configuration.sampling_target_period_in_seconds
510-
self.adaptive_sampler = AdaptiveSampler(configuration.sampling_target, sampling_target_period)
510+
sampling_target = configuration.sampling_target
511+
# If span reduction is enabled double the transaction reservoir size.
512+
if configuration.distributed_tracing.drop_inprocess_spans.enabled or configuration.distributed_tracing.unique_spans.enabled:
513+
sampling_target = configuration.sampling_target*2
514+
self.adaptive_sampler = AdaptiveSampler(sampling_target, sampling_target_period)
511515

512516
active_session.connect_span_stream(self._stats_engine.span_stream, self.record_custom_metric)
513517

@@ -1367,11 +1371,14 @@ def harvest(self, shutdown=False, flexible=False):
13671371
span_stream = stats.span_stream
13681372
# Only merge stats as part of default harvest
13691373
if span_stream is not None and not flexible:
1370-
spans_seen, spans_dropped = span_stream.stats()
1374+
spans_seen, spans_dropped, _bytes, ct_processing_time = span_stream.stats()
13711375
spans_sent = spans_seen - spans_dropped
13721376

13731377
internal_count_metric("Supportability/InfiniteTracing/Span/Seen", spans_seen)
13741378
internal_count_metric("Supportability/InfiniteTracing/Span/Sent", spans_sent)
1379+
print(f"spans sent: {spans_sent}")
1380+
internal_count_metric("Supportability/InfiniteTracing/Bytes/Seen", _bytes)
1381+
internal_count_metric("Supportability/CoreTracing/TotalTime", ct_processing_time*1000) # Time in ms.
13751382
else:
13761383
spans = stats.span_events
13771384
if spans:
@@ -1388,6 +1395,9 @@ def harvest(self, shutdown=False, flexible=False):
13881395
spans_sampled = spans.num_samples
13891396
internal_count_metric("Supportability/SpanEvent/TotalEventsSeen", spans_seen)
13901397
internal_count_metric("Supportability/SpanEvent/TotalEventsSent", spans_sampled)
1398+
print(f"spans sent: {spans_sampled}")
1399+
internal_count_metric("Supportability/DistributedTracing/Bytes/Seen", spans.bytes)
1400+
internal_count_metric("Supportability/SpanEvent/TotalCoreTracingTime", spans.ct_processing_time*1000) # Time in ms.
13911401

13921402
stats.reset_span_events()
13931403

newrelic/core/attribute.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,23 @@
102102
"server.address",
103103
}
104104

105+
SPAN_ENTITY_RELATIONSHIP_ATTRIBUTES = {
106+
"cloud.account.id",
107+
"cloud.platform",
108+
"cloud.region",
109+
"cloud.resource_id",
110+
"db.instance",
111+
"db.system",
112+
"http.url",
113+
"messaging.destination.name",
114+
"messaging.system",
115+
"peer.hostname",
116+
"server.address",
117+
"server.port",
118+
"span.kind",
119+
}
120+
121+
105122
MAX_NUM_USER_ATTRIBUTES = 128
106123
MAX_ATTRIBUTE_LENGTH = 255
107124
MAX_NUM_ML_USER_ATTRIBUTES = 64

newrelic/core/config.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,18 @@ class DistributedTracingSettings(Settings):
324324
pass
325325

326326

327+
class DistributedTracingDropInprocessSpansSettings(Settings):
328+
pass
329+
330+
331+
class DistributedTracingUniqueSpansSettings(Settings):
332+
pass
333+
334+
335+
class DistributedTracingMinimizeAttributesSettings(Settings):
336+
pass
337+
338+
327339
class ServerlessModeSettings(Settings):
328340
pass
329341

@@ -493,6 +505,9 @@ class EventHarvestConfigHarvestLimitSettings(Settings):
493505
_settings.datastore_tracer.instance_reporting = DatastoreTracerInstanceReportingSettings()
494506
_settings.debug = DebugSettings()
495507
_settings.distributed_tracing = DistributedTracingSettings()
508+
_settings.distributed_tracing.drop_inprocess_spans = DistributedTracingDropInprocessSpansSettings()
509+
_settings.distributed_tracing.unique_spans = DistributedTracingUniqueSpansSettings()
510+
_settings.distributed_tracing.minimize_attributes = DistributedTracingMinimizeAttributesSettings()
496511
_settings.error_collector = ErrorCollectorSettings()
497512
_settings.error_collector.attributes = ErrorCollectorAttributesSettings()
498513
_settings.event_harvest_config = EventHarvestConfigSettings()
@@ -814,6 +829,9 @@ def default_otlp_host(host):
814829
_settings.ml_insights_events.enabled = False
815830

816831
_settings.distributed_tracing.enabled = _environ_as_bool("NEW_RELIC_DISTRIBUTED_TRACING_ENABLED", default=True)
832+
_settings.distributed_tracing.drop_inprocess_spans.enabled = _environ_as_bool("NEW_RELIC_DISTRIBUTED_TRACING_DROP_INPROCESS_SPANS_ENABLED", default=True)
833+
_settings.distributed_tracing.unique_spans.enabled = _environ_as_bool("NEW_RELIC_DISTRIBUTED_TRACING_UNIQUE_SPANS_ENABLED", default=False)
834+
_settings.distributed_tracing.minimize_attributes.enabled = _environ_as_bool("NEW_RELIC_DISTRIBUTED_TRACING_MINIMIZE_ATTRIBUTES_ENABLED", default=True)
817835
_settings.distributed_tracing.exclude_newrelic_header = False
818836
_settings.span_events.enabled = _environ_as_bool("NEW_RELIC_SPAN_EVENTS_ENABLED", default=True)
819837
_settings.span_events.attributes.enabled = True

newrelic/core/external_node.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,16 +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, *args, **kwargs):
172+
def span_event(self, settings, base_attrs=None, parent_guid=None, attr_class=dict, *args, **kwargs):
173173
self.agent_attributes["http.url"] = self.http_url
174-
attrs = super().span_event(*args, **kwargs)
175-
i_attrs = attrs[0]
176174

175+
i_attrs = base_attrs and base_attrs.copy() or attr_class()
177176
i_attrs["category"] = "http"
178177
i_attrs["span.kind"] = "client"
179178
_, i_attrs["component"] = attribute.process_user_attribute("component", self.library)
180179

181180
if self.method:
182181
_, i_attrs["http.method"] = attribute.process_user_attribute("http.method", self.method)
183182

184-
return attrs
183+
return super().span_event(settings, base_attrs=i_attrs, parent_guid=parent_guid, attr_class=attr_class, *args, **kwargs)

newrelic/core/function_node.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,8 @@ 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, *args, **kwargs):
118-
attrs = super().span_event(*args, **kwargs)
119-
i_attrs = attrs[0]
120-
117+
def span_event(self, settings, base_attrs=None, parent_guid=None, attr_class=dict, *args, **kwargs):
118+
i_attrs = base_attrs and base_attrs.copy() or attr_class()
121119
i_attrs["name"] = f"{self.group}/{self.name}"
122120

123-
return attrs
121+
return super().span_event(settings, base_attrs=i_attrs, parent_guid=parent_guid, attr_class=attr_class, *args, **kwargs)

newrelic/core/loop_node.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,8 @@ 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, *args, **kwargs):
83-
attrs = super().span_event(*args, **kwargs)
84-
i_attrs = attrs[0]
85-
82+
def span_event(self, settings, base_attrs=None, parent_guid=None, attr_class=dict, *args, **kwargs):
83+
i_attrs = base_attrs and base_attrs.copy() or attr_class()
8684
i_attrs["name"] = f"EventLoop/Wait/{self.name}"
8785

88-
return attrs
86+
return super().span_event(settings, base_attrs=i_attrs, parent_guid=parent_guid, attr_class=attr_class, *args, **kwargs)

0 commit comments

Comments
 (0)