Skip to content

Commit 6b54ca3

Browse files
authored
chore: refactor tracer partial flushing logic (#14860)
## Description This simplify the code and removes the edge case where we tag a complete trace chunk with unnecessary partial flush tag We move some of the complexity of removing finished spans from a trace into a method. We add __slots__ to _Trace object to aid in memory usage and size. Small performance win to use `span.duration_is is not None` to check if a span if finished instead of using the `finished` property function. ## Testing <!-- Describe your testing strategy or note what tests are included --> ## Risks <!-- Note any risks associated with this change, or "None" if no risks --> ## Additional Notes <!-- Any other information that would be helpful for reviewers -->
1 parent 24f0351 commit 6b54ca3

14 files changed

+415
-439
lines changed

ddtrace/_trace/processor/__init__.py

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,20 @@ def process_trace(self, trace: List[Span]) -> Optional[List[Span]]:
263263

264264

265265
class _Trace:
266-
def __init__(self, spans=None, num_finished=0):
267-
self.spans = spans if spans is not None else []
268-
self.num_finished = num_finished
266+
__slots__ = ("spans", "num_finished")
267+
268+
def __init__(self, spans: Optional[List[Span]] = None, num_finished: int = 0):
269+
self.spans: List[Span] = spans if spans is not None else []
270+
self.num_finished: int = num_finished
271+
272+
def remove_finished(self) -> List[Span]:
273+
# perf: Avoid Span.finished which is a computed property and has function call overhead
274+
# so check Span.duration_ns manually.
275+
finished = [s for s in self.spans if s.duration_ns is not None]
276+
if finished:
277+
self.spans[:] = [s for s in self.spans if s.duration_ns is None]
278+
self.num_finished = 0
279+
return finished
269280

270281

271282
class SpanAggregator(SpanProcessor):
@@ -350,31 +361,22 @@ def on_span_finish(self, span: Span) -> None:
350361
return
351362

352363
trace = self._traces[span.trace_id]
353-
num_buffered = len(trace.spans)
354364
trace.num_finished += 1
355-
should_partial_flush = self.partial_flush_enabled and trace.num_finished >= self.partial_flush_min_spans
356-
is_trace_complete = trace.num_finished >= len(trace.spans)
357-
if not is_trace_complete and not should_partial_flush:
358-
return
359-
360-
if not is_trace_complete:
361-
finished = [s for s in trace.spans if s.finished]
362-
if not finished:
363-
return
364-
trace.spans[:] = [s for s in trace.spans if not s.finished] # In-place update
365-
trace.num_finished = 0
366-
else:
365+
num_buffered = len(trace.spans)
366+
is_trace_complete = trace.num_finished >= num_buffered
367+
num_finished = trace.num_finished
368+
should_partial_flush = False
369+
if is_trace_complete:
367370
finished = trace.spans
368371
del self._traces[span.trace_id]
369372
# perf: Flush span finish metrics to the telemetry writer after the trace is complete
370373
self._queue_span_count_metrics("spans_finished", "integration_name")
371-
372-
num_finished = len(finished)
373-
if should_partial_flush:
374-
# FIXME(munir): should_partial_flush should return false if all the spans in the trace are finished.
375-
# For example if partial flushing min spans is 10 and the trace has 10 spans, the trace should
376-
# not have a partial flush metric. This trace was processed in its entirety.
377-
finished[0].set_metric("_dd.py.partial_flush", num_finished)
374+
elif self.partial_flush_enabled and num_finished >= self.partial_flush_min_spans:
375+
should_partial_flush = True
376+
finished = trace.remove_finished()
377+
finished[0].set_metric("_dd.py.partial_flush", num_finished)
378+
else:
379+
return
378380

379381
# perf: Process spans outside of the span aggregator lock
380382
spans = finished

tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_disabled.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"type": "test_session_end"
3434
},
3535
"metrics": {
36-
"_dd.py.partial_flush": 1,
3736
"_dd.top_level": 1,
3837
"_dd.tracer_kr": 1.0,
3938
"_sampling_priority_v1": 1,
@@ -174,7 +173,6 @@
174173
"type": "test"
175174
},
176175
"metrics": {
177-
"_dd.py.partial_flush": 1,
178176
"_dd.top_level": 1,
179177
"_dd.tracer_kr": 1.0,
180178
"_sampling_priority_v1": 1,

tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_enabled.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"type": "test_session_end"
3434
},
3535
"metrics": {
36-
"_dd.py.partial_flush": 1,
3736
"_dd.top_level": 1,
3837
"_dd.tracer_kr": 1.0,
3938
"_sampling_priority_v1": 1,
@@ -175,7 +174,6 @@
175174
"type": "test"
176175
},
177176
"metrics": {
178-
"_dd.py.partial_flush": 1,
179177
"_dd.top_level": 1,
180178
"_dd.tracer_kr": 1.0,
181179
"_sampling_priority_v1": 1,

tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"type": "test_session_end"
3434
},
3535
"metrics": {
36-
"_dd.py.partial_flush": 1,
3736
"_dd.top_level": 1,
3837
"_dd.tracer_kr": 1.0,
3938
"_sampling_priority_v1": 1,
@@ -169,7 +168,6 @@
169168
"type": "test"
170169
},
171170
"metrics": {
172-
"_dd.py.partial_flush": 1,
173171
"_dd.top_level": 1,
174172
"_dd.tracer_kr": 1.0,
175173
"_sampling_priority_v1": 1,

tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_will_include_lines_pct.json

Lines changed: 45 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,43 +11,42 @@
1111
"meta": {
1212
"_dd.origin": "ciapp-test",
1313
"_dd.p.dm": "-0",
14-
"_dd.p.tid": "6751748100000000",
14+
"_dd.p.tid": "68ee770a00000000",
1515
"component": "pytest",
1616
"language": "python",
17-
"library_version": "2.18.0.dev124+gc03b9e422",
18-
"os.architecture": "x86_64",
17+
"library_version": "3.17.0.dev37+g6f7c158e6.d20251014",
18+
"os.architecture": "aarch64",
1919
"os.platform": "Linux",
20-
"os.version": "5.15.0-1071-aws",
21-
"runtime-id": "5714e89371d3405f970eb00f53936a67",
20+
"os.version": "6.10.14-linuxkit",
21+
"runtime-id": "cb2348822a0142f7923918d45063d5d0",
2222
"runtime.name": "CPython",
23-
"runtime.version": "3.10.14",
23+
"runtime.version": "3.12.11",
2424
"span.kind": "test",
2525
"test.command": "pytest --ddtrace",
2626
"test.framework": "pytest",
27-
"test.framework_version": "6.2.5",
27+
"test.framework_version": "8.4.2",
2828
"test.module": "",
2929
"test.module_path": "",
3030
"test.name": "test_add_two_number_list",
3131
"test.source.file": "test_tools.py",
3232
"test.status": "pass",
3333
"test.suite": "test_tools.py",
3434
"test.type": "test",
35-
"test_module_id": "18268695399323445148",
36-
"test_session_id": "15721560387983696792",
37-
"test_suite_id": "5983435943999745407",
35+
"test_module_id": "5787288656364304641",
36+
"test_session_id": "5197474445235026729",
37+
"test_suite_id": "8497613065864877119",
3838
"type": "test"
3939
},
4040
"metrics": {
41-
"_dd.py.partial_flush": 1,
4241
"_dd.top_level": 1,
4342
"_dd.tracer_kr": 1.0,
4443
"_sampling_priority_v1": 1,
45-
"process_id": 35337,
44+
"process_id": 4189,
4645
"test.source.end": 9,
4746
"test.source.start": 3
4847
},
49-
"duration": 2404850,
50-
"start": 1733391489340338256
48+
"duration": 1569042,
49+
"start": 1760458506005108126
5150
}],
5251
[
5352
{
@@ -62,35 +61,34 @@
6261
"meta": {
6362
"_dd.origin": "ciapp-test",
6463
"_dd.p.dm": "-0",
65-
"_dd.p.tid": "6751748100000000",
64+
"_dd.p.tid": "68ee770900000000",
6665
"component": "pytest",
6766
"language": "python",
68-
"library_version": "2.18.0.dev124+gc03b9e422",
69-
"os.architecture": "x86_64",
67+
"library_version": "3.17.0.dev37+g6f7c158e6.d20251014",
68+
"os.architecture": "aarch64",
7069
"os.platform": "Linux",
71-
"os.version": "5.15.0-1071-aws",
72-
"runtime-id": "5714e89371d3405f970eb00f53936a67",
70+
"os.version": "6.10.14-linuxkit",
71+
"runtime-id": "cb2348822a0142f7923918d45063d5d0",
7372
"runtime.name": "CPython",
74-
"runtime.version": "3.10.14",
73+
"runtime.version": "3.12.11",
7574
"span.kind": "test",
7675
"test.code_coverage.enabled": "false",
7776
"test.command": "pytest --ddtrace",
7877
"test.framework": "pytest",
79-
"test.framework_version": "6.2.5",
78+
"test.framework_version": "8.4.2",
8079
"test.itr.tests_skipping.enabled": "false",
8180
"test.status": "pass",
82-
"test_session_id": "15721560387983696792",
81+
"test_session_id": "5197474445235026729",
8382
"type": "test_session_end"
8483
},
8584
"metrics": {
86-
"_dd.py.partial_flush": 1,
8785
"_dd.top_level": 1,
8886
"_dd.tracer_kr": 1.0,
8987
"_sampling_priority_v1": 1,
90-
"process_id": 35337
88+
"process_id": 4189
9189
},
92-
"duration": 45677337,
93-
"start": 1733391489299231481
90+
"duration": 30965084,
91+
"start": 1760458505977856084
9492
},
9593
{
9694
"name": "pytest.test_module",
@@ -104,35 +102,35 @@
104102
"meta": {
105103
"_dd.origin": "ciapp-test",
106104
"_dd.p.dm": "-0",
107-
"_dd.p.tid": "6751748100000000",
105+
"_dd.p.tid": "68ee770900000000",
108106
"component": "pytest",
109107
"language": "python",
110-
"library_version": "2.18.0.dev124+gc03b9e422",
111-
"os.architecture": "x86_64",
108+
"library_version": "3.17.0.dev37+g6f7c158e6.d20251014",
109+
"os.architecture": "aarch64",
112110
"os.platform": "Linux",
113-
"os.version": "5.15.0-1071-aws",
111+
"os.version": "6.10.14-linuxkit",
114112
"runtime.name": "CPython",
115-
"runtime.version": "3.10.14",
113+
"runtime.version": "3.12.11",
116114
"span.kind": "test",
117115
"test.code_coverage.enabled": "false",
118116
"test.command": "pytest --ddtrace",
119117
"test.framework": "pytest",
120-
"test.framework_version": "6.2.5",
118+
"test.framework_version": "8.4.2",
121119
"test.itr.tests_skipping.enabled": "false",
122120
"test.module": "",
123121
"test.module_path": "",
124122
"test.status": "pass",
125-
"test_module_id": "18268695399323445148",
126-
"test_session_id": "15721560387983696792",
123+
"test_module_id": "5787288656364304641",
124+
"test_session_id": "5197474445235026729",
127125
"type": "test_module_end"
128126
},
129127
"metrics": {
130128
"_dd.py.partial_flush": 1,
131129
"_dd.tracer_kr": 1.0,
132130
"_sampling_priority_v1": 1
133131
},
134-
"duration": 4050622,
135-
"start": 1733391489340123850
132+
"duration": 2606333,
133+
"start": 1760458506004979543
136134
},
137135
{
138136
"name": "pytest.test_suite",
@@ -146,33 +144,33 @@
146144
"meta": {
147145
"_dd.origin": "ciapp-test",
148146
"_dd.p.dm": "-0",
149-
"_dd.p.tid": "6751748100000000",
147+
"_dd.p.tid": "68ee770900000000",
150148
"component": "pytest",
151149
"language": "python",
152-
"library_version": "2.18.0.dev124+gc03b9e422",
153-
"os.architecture": "x86_64",
150+
"library_version": "3.17.0.dev37+g6f7c158e6.d20251014",
151+
"os.architecture": "aarch64",
154152
"os.platform": "Linux",
155-
"os.version": "5.15.0-1071-aws",
153+
"os.version": "6.10.14-linuxkit",
156154
"runtime.name": "CPython",
157-
"runtime.version": "3.10.14",
155+
"runtime.version": "3.12.11",
158156
"span.kind": "test",
159157
"test.command": "pytest --ddtrace",
160158
"test.framework": "pytest",
161-
"test.framework_version": "6.2.5",
159+
"test.framework_version": "8.4.2",
162160
"test.module": "",
163161
"test.module_path": "",
164162
"test.status": "pass",
165163
"test.suite": "test_tools.py",
166-
"test_module_id": "18268695399323445148",
167-
"test_session_id": "15721560387983696792",
168-
"test_suite_id": "5983435943999745407",
164+
"test_module_id": "5787288656364304641",
165+
"test_session_id": "5197474445235026729",
166+
"test_suite_id": "8497613065864877119",
169167
"type": "test_suite_end"
170168
},
171169
"metrics": {
172170
"_dd.py.partial_flush": 1,
173171
"_dd.tracer_kr": 1.0,
174172
"_sampling_priority_v1": 1
175173
},
176-
"duration": 3652661,
177-
"start": 1733391489340244453
174+
"duration": 2406917,
175+
"start": 1760458506005054584
178176
}]]

0 commit comments

Comments
 (0)