Skip to content

fix: avoid using svc_src: m when service is among active integration defaults#17712

Draft
emmettbutler wants to merge 34 commits intoemmett.butler/unignore-svc-srcfrom
emmett.butler/sqlite3-snapshot-2
Draft

fix: avoid using svc_src: m when service is among active integration defaults#17712
emmettbutler wants to merge 34 commits intoemmett.butler/unignore-svc-srcfrom
emmett.butler/sqlite3-snapshot-2

Conversation

@emmettbutler
Copy link
Copy Markdown
Collaborator

@emmettbutler emmettbutler commented Apr 23, 2026

This change fixes an issue reported via Support in which svc_src is set to m in cases where service matches the _default_service of an active integration config. In such cases, the intended behavior is that it svc_src is equal to service.

@emmettbutler emmettbutler requested review from a team as code owners April 23, 2026 22:23
@emmettbutler emmettbutler marked this pull request as draft April 23, 2026 22:24
@pr-commenter
Copy link
Copy Markdown

pr-commenter Bot commented Apr 23, 2026

Performance SLOs

Comparing candidate emmett.butler/sqlite3-snapshot-2 (aaa0415) with baseline emmett.butler/unignore-svc-src (d025b3e)

📈 Performance Regressions (1 suite)
📈 telemetryaddmetric - 30/30

✅ 1-count-metric-1-times

Time: ✅ 2.324µs (SLO: <20.000µs 📉 -88.4%) vs baseline: 📈 +11.7%

Memory: ✅ 36.569MB (SLO: <38.000MB -3.8%) vs baseline: +5.5%


✅ 1-count-metrics-100-times

Time: ✅ 155.132µs (SLO: <220.000µs 📉 -29.5%) vs baseline: +3.0%

Memory: ✅ 36.746MB (SLO: <38.000MB -3.3%) vs baseline: +6.4%


✅ 1-distribution-metric-1-times

Time: ✅ 2.431µs (SLO: <20.000µs 📉 -87.8%) vs baseline: +0.7%

Memory: ✅ 36.589MB (SLO: <38.000MB -3.7%) vs baseline: +5.9%


✅ 1-distribution-metrics-100-times

Time: ✅ 168.558µs (SLO: <230.000µs 📉 -26.7%) vs baseline: +2.3%

Memory: ✅ 36.608MB (SLO: <38.000MB -3.7%) vs baseline: +5.9%


✅ 1-gauge-metric-1-times

Time: ✅ 2.300µs (SLO: <20.000µs 📉 -88.5%) vs baseline: +1.2%

Memory: ✅ 36.569MB (SLO: <38.000MB -3.8%) vs baseline: +6.1%


✅ 1-gauge-metrics-100-times

Time: ✅ 168.948µs (SLO: <190.000µs 📉 -11.1%) vs baseline: +3.2%

Memory: ✅ 36.746MB (SLO: <38.000MB -3.3%) vs baseline: +6.2%


✅ 1-rate-metric-1-times

Time: ✅ 2.279µs (SLO: <20.000µs 📉 -88.6%) vs baseline: +3.5%

Memory: ✅ 36.667MB (SLO: <38.000MB -3.5%) vs baseline: +6.3%


✅ 1-rate-metrics-100-times

Time: ✅ 169.244µs (SLO: <250.000µs 📉 -32.3%) vs baseline: +3.9%

Memory: ✅ 36.471MB (SLO: <38.000MB -4.0%) vs baseline: +5.5%


✅ 100-count-metrics-100-times

Time: ✅ 15.682ms (SLO: <22.000ms 📉 -28.7%) vs baseline: +4.5%

Memory: ✅ 36.451MB (SLO: <38.000MB -4.1%) vs baseline: +5.5%


✅ 100-distribution-metrics-100-times

Time: ✅ 1.763ms (SLO: <2.550ms 📉 -30.9%) vs baseline: +1.5%

Memory: ✅ 36.569MB (SLO: <38.000MB -3.8%) vs baseline: +5.6%


✅ 100-gauge-metrics-100-times

Time: ✅ 1.731ms (SLO: <1.850ms -6.5%) vs baseline: +3.8%

Memory: ✅ 36.510MB (SLO: <38.000MB -3.9%) vs baseline: +5.8%


✅ 100-rate-metrics-100-times

Time: ✅ 1.745ms (SLO: <2.550ms 📉 -31.5%) vs baseline: +4.2%

Memory: ✅ 36.549MB (SLO: <38.000MB -3.8%) vs baseline: +5.9%


✅ flush-1-metric

Time: ✅ 3.540µs (SLO: <20.000µs 📉 -82.3%) vs baseline: -1.6%

Memory: ✅ 36.510MB (SLO: <38.000MB -3.9%) vs baseline: +4.6%


✅ flush-100-metrics

Time: ✅ 173.881µs (SLO: <250.000µs 📉 -30.4%) vs baseline: -1.2%

Memory: ✅ 36.530MB (SLO: <38.000MB -3.9%) vs baseline: +4.6%


✅ flush-1000-metrics

Time: ✅ 2.187ms (SLO: <2.500ms 📉 -12.5%) vs baseline: +0.1%

Memory: ✅ 37.336MB (SLO: <38.750MB -3.6%) vs baseline: +4.7%

🟡 Near SLO Breach (7 suites)
🟡 djangosimple - 28/28

✅ appsec

Time: ✅ 20.136ms (SLO: <22.300ms -9.7%) vs baseline: +2.3%

Memory: ✅ 71.600MB (SLO: <73.500MB -2.6%) vs baseline: +5.2%


✅ exception-replay-enabled

Time: ✅ 1.386ms (SLO: <1.450ms -4.4%) vs baseline: +0.9%

Memory: ✅ 69.570MB (SLO: <71.500MB -2.7%) vs baseline: +4.8%


✅ iast

Time: ✅ 20.170ms (SLO: <22.250ms -9.3%) vs baseline: +2.2%

Memory: ✅ 71.438MB (SLO: <75.000MB -4.7%) vs baseline: +5.0%


✅ profiler

Time: ✅ 15.166ms (SLO: <16.550ms -8.4%) vs baseline: -0.2%

Memory: ✅ 60.365MB (SLO: <61.000MB 🟡 -1.0%) vs baseline: +4.9%


✅ resource-renaming

Time: ✅ 20.015ms (SLO: <21.750ms -8.0%) vs baseline: +2.3%

Memory: ✅ 71.460MB (SLO: <73.500MB -2.8%) vs baseline: +4.9%


✅ span-code-origin

Time: ✅ 20.362ms (SLO: <28.200ms 📉 -27.8%) vs baseline: +2.2%

Memory: ✅ 71.811MB (SLO: <75.000MB -4.3%) vs baseline: +5.4%


✅ tracer

Time: ✅ 20.167ms (SLO: <21.750ms -7.3%) vs baseline: +2.0%

Memory: ✅ 71.487MB (SLO: <75.000MB -4.7%) vs baseline: +5.1%


✅ tracer-and-profiler

Time: ✅ 21.530ms (SLO: <23.500ms -8.4%) vs baseline: +2.3%

Memory: ✅ 73.511MB (SLO: <75.000MB 🟡 -2.0%) vs baseline: +5.1%


✅ tracer-dont-create-db-spans

Time: ✅ 20.226ms (SLO: <21.500ms -5.9%) vs baseline: +2.1%

Memory: ✅ 71.526MB (SLO: <75.000MB -4.6%) vs baseline: +4.9%


✅ tracer-minimal

Time: ✅ 18.022ms (SLO: <18.500ms -2.6%) vs baseline: +0.5%

Memory: ✅ 71.465MB (SLO: <75.000MB -4.7%) vs baseline: +5.0%


✅ tracer-no-caches

Time: ✅ 18.991ms (SLO: <19.650ms -3.4%) vs baseline: ~same

Memory: ✅ 71.467MB (SLO: <75.000MB -4.7%) vs baseline: +4.9%


✅ tracer-no-databases

Time: ✅ 21.060ms (SLO: <21.100ms 🟡 -0.2%) vs baseline: +1.6%

Memory: ✅ 71.408MB (SLO: <75.000MB -4.8%) vs baseline: +4.9%


✅ tracer-no-middleware

Time: ✅ 19.906ms (SLO: <21.500ms -7.4%) vs baseline: +1.9%

Memory: ✅ 71.529MB (SLO: <75.000MB -4.6%) vs baseline: +5.0%


✅ tracer-no-templates

Time: ✅ 19.952ms (SLO: <22.000ms -9.3%) vs baseline: +2.9%

Memory: ✅ 71.500MB (SLO: <73.500MB -2.7%) vs baseline: +4.9%


🟡 errortrackingflasksqli - 6/6

✅ errortracking-enabled-all

Time: ✅ 2.232ms (SLO: <2.300ms -3.0%) vs baseline: +5.5%

Memory: ✅ 58.275MB (SLO: <60.000MB -2.9%) vs baseline: +4.7%


✅ errortracking-enabled-user

Time: ✅ 2.243ms (SLO: <2.250ms 🟡 -0.3%) vs baseline: +5.9%

Memory: ✅ 58.393MB (SLO: <60.000MB -2.7%) vs baseline: +4.8%


✅ tracer-enabled

Time: ✅ 2.234ms (SLO: <2.300ms -2.9%) vs baseline: +5.7%

Memory: ✅ 58.471MB (SLO: <60.000MB -2.5%) vs baseline: +5.0%


🟡 flasksqli - 6/6

✅ appsec-enabled

Time: ✅ 2.231ms (SLO: <4.200ms 📉 -46.9%) vs baseline: +5.6%

Memory: ✅ 58.570MB (SLO: <66.000MB 📉 -11.3%) vs baseline: +5.0%


✅ iast-enabled

Time: ✅ 2.239ms (SLO: <2.800ms 📉 -20.0%) vs baseline: +5.5%

Memory: ✅ 58.648MB (SLO: <62.500MB -6.2%) vs baseline: +5.0%


✅ tracer-enabled

Time: ✅ 2.228ms (SLO: <2.250ms 🟡 -1.0%) vs baseline: +5.6%

Memory: ✅ 58.629MB (SLO: <60.000MB -2.3%) vs baseline: +4.8%


🟡 otelspan - 22/22

✅ add-event

Time: ✅ 41.540ms (SLO: <47.150ms 📉 -11.9%) vs baseline: +0.3%

Memory: ✅ 41.469MB (SLO: <47.000MB 📉 -11.8%) vs baseline: +4.9%


✅ add-metrics

Time: ✅ 235.230ms (SLO: <344.800ms 📉 -31.8%) vs baseline: ~same

Memory: ✅ 45.526MB (SLO: <47.500MB -4.2%) vs baseline: +5.2%


✅ add-tags

Time: ✅ 264.456ms (SLO: <330.000ms 📉 -19.9%) vs baseline: +0.4%

Memory: ✅ 45.192MB (SLO: <47.500MB -4.9%) vs baseline: +4.3%


✅ get-context

Time: ✅ 81.136ms (SLO: <92.350ms 📉 -12.1%) vs baseline: +0.7%

Memory: ✅ 41.189MB (SLO: <46.500MB 📉 -11.4%) vs baseline: +4.9%


✅ is-recording

Time: ✅ 38.024ms (SLO: <44.500ms 📉 -14.6%) vs baseline: +0.4%

Memory: ✅ 41.013MB (SLO: <47.500MB 📉 -13.7%) vs baseline: +5.5%


✅ record-exception

Time: ✅ 62.689ms (SLO: <67.650ms -7.3%) vs baseline: -0.1%

Memory: ✅ 41.810MB (SLO: <47.000MB 📉 -11.0%) vs baseline: +5.2%


✅ set-status

Time: ✅ 43.659ms (SLO: <50.400ms 📉 -13.4%) vs baseline: +0.8%

Memory: ✅ 40.836MB (SLO: <47.000MB 📉 -13.1%) vs baseline: +4.9%


✅ start

Time: ✅ 38.853ms (SLO: <44.500ms 📉 -12.7%) vs baseline: +4.8%

Memory: ✅ 41.122MB (SLO: <47.000MB 📉 -12.5%) vs baseline: +5.6%


✅ start-finish

Time: ✅ 90.351ms (SLO: <92.000ms 🟡 -1.8%) vs baseline: +0.6%

Memory: ✅ 38.928MB (SLO: <46.500MB 📉 -16.3%) vs baseline: +5.5%


✅ start-finish-telemetry

Time: ✅ 92.156ms (SLO: <93.000ms 🟡 -0.9%) vs baseline: +0.2%

Memory: ✅ 38.791MB (SLO: <46.500MB 📉 -16.6%) vs baseline: +4.9%


✅ update-name

Time: ✅ 39.077ms (SLO: <45.150ms 📉 -13.5%) vs baseline: +1.0%

Memory: ✅ 40.944MB (SLO: <47.000MB 📉 -12.9%) vs baseline: +5.0%


🟡 recursivecomputation - 8/8

✅ deep

Time: ✅ 311.880ms (SLO: <320.950ms -2.8%) vs baseline: +0.1%

Memory: ✅ 37.356MB (SLO: <38.750MB -3.6%) vs baseline: +5.6%


✅ deep-profiled

Time: ✅ 331.777ms (SLO: <359.150ms -7.6%) vs baseline: -1.2%

Memory: ✅ 43.608MB (SLO: <46.000MB -5.2%) vs baseline: +5.7%


✅ medium

Time: ✅ 7.365ms (SLO: <7.450ms 🟡 -1.1%) vs baseline: ~same

Memory: ✅ 36.313MB (SLO: <38.000MB -4.4%) vs baseline: +5.0%


✅ shallow

Time: ✅ 1.041ms (SLO: <1.050ms 🟡 -0.8%) vs baseline: +2.6%

Memory: ✅ 36.176MB (SLO: <38.000MB -4.8%) vs baseline: +5.0%


🟡 span - 26/26

✅ add-event

Time: ✅ 20.547ms (SLO: <22.500ms -8.7%) vs baseline: +1.0%

Memory: ✅ 38.653MB (SLO: <53.000MB 📉 -27.1%) vs baseline: +5.2%


✅ add-metrics

Time: ✅ 90.801ms (SLO: <93.500ms -2.9%) vs baseline: +0.3%

Memory: ✅ 42.861MB (SLO: <53.000MB 📉 -19.1%) vs baseline: +5.1%


✅ add-tags

Time: ✅ 136.959ms (SLO: <155.000ms 📉 -11.6%) vs baseline: +0.5%

Memory: ✅ 42.661MB (SLO: <53.000MB 📉 -19.5%) vs baseline: +5.1%


✅ get-context

Time: ✅ 18.006ms (SLO: <20.500ms 📉 -12.2%) vs baseline: +2.1%

Memory: ✅ 38.122MB (SLO: <53.000MB 📉 -28.1%) vs baseline: +5.5%


✅ is-recording

Time: ✅ 18.049ms (SLO: <20.500ms 📉 -12.0%) vs baseline: +1.5%

Memory: ✅ 38.083MB (SLO: <53.000MB 📉 -28.1%) vs baseline: +5.5%


✅ record-exception

Time: ✅ 41.921ms (SLO: <42.000ms 🟡 -0.2%) vs baseline: +0.3%

Memory: ✅ 38.928MB (SLO: <53.000MB 📉 -26.6%) vs baseline: +4.8%


✅ set-status

Time: ✅ 19.779ms (SLO: <22.000ms 📉 -10.1%) vs baseline: +2.3%

Memory: ✅ 38.299MB (SLO: <53.000MB 📉 -27.7%) vs baseline: +5.8%


✅ start

Time: ✅ 19.002ms (SLO: <20.500ms -7.3%) vs baseline: +7.4%

Memory: ✅ 38.221MB (SLO: <53.000MB 📉 -27.9%) vs baseline: +5.9%


✅ start-finish

Time: ✅ 57.960ms (SLO: <58.500ms 🟡 -0.9%) vs baseline: -0.2%

Memory: ✅ 36.156MB (SLO: <38.000MB -4.9%) vs baseline: +4.6%


✅ start-finish-telemetry

Time: ✅ 59.312ms (SLO: <60.000ms 🟡 -1.1%) vs baseline: -0.3%

Memory: ✅ 36.176MB (SLO: <38.000MB -4.8%) vs baseline: +4.9%


✅ start-finish-traceid128

Time: ✅ 60.219ms (SLO: <62.000ms -2.9%) vs baseline: -0.7%

Memory: ✅ 36.333MB (SLO: <38.000MB -4.4%) vs baseline: +5.3%


✅ start-traceid128

Time: ✅ 17.943ms (SLO: <22.500ms 📉 -20.3%) vs baseline: +1.4%

Memory: ✅ 38.103MB (SLO: <53.000MB 📉 -28.1%) vs baseline: +5.4%


✅ update-name

Time: ✅ 18.520ms (SLO: <22.000ms 📉 -15.8%) vs baseline: +1.5%

Memory: ✅ 38.151MB (SLO: <53.000MB 📉 -28.0%) vs baseline: +4.7%


🟡 tracer - 6/6

✅ large

Time: ✅ 32.948ms (SLO: <33.950ms -3.0%) vs baseline: +1.2%

Memory: ✅ 37.336MB (SLO: <39.250MB -4.9%) vs baseline: +4.5%


✅ medium

Time: ✅ 3.185ms (SLO: <3.500ms -9.0%) vs baseline: +1.3%

Memory: ✅ 36.117MB (SLO: <38.750MB -6.8%) vs baseline: +4.5%


✅ small

Time: ✅ 386.540µs (SLO: <390.000µs 🟡 -0.9%) vs baseline: +4.8%

Memory: ✅ 36.215MB (SLO: <38.750MB -6.5%) vs baseline: +5.0%

⚠️ Unstable Tests (1 suite)
⚠️ coreapiscenario - 10/10 (1 unstable)

⚠️ context_with_data_listeners

Time: ⚠️ 13.583µs (SLO: <20.000µs 📉 -32.1%) vs baseline: -0.5%

Memory: ✅ 36.137MB (SLO: <38.000MB -4.9%) vs baseline: +4.6%


✅ context_with_data_no_listeners

Time: ✅ 3.603µs (SLO: <10.000µs 📉 -64.0%) vs baseline: +0.3%

Memory: ✅ 36.176MB (SLO: <38.000MB -4.8%) vs baseline: +4.6%


✅ get_item_exists

Time: ✅ 0.581µs (SLO: <10.000µs 📉 -94.2%) vs baseline: +0.8%

Memory: ✅ 36.294MB (SLO: <38.000MB -4.5%) vs baseline: +4.9%


✅ get_item_missing

Time: ✅ 0.643µs (SLO: <10.000µs 📉 -93.6%) vs baseline: +0.4%

Memory: ✅ 36.038MB (SLO: <38.000MB -5.2%) vs baseline: +4.2%


✅ set_item

Time: ✅ 24.406µs (SLO: <30.000µs 📉 -18.6%) vs baseline: +0.4%

Memory: ✅ 36.137MB (SLO: <38.000MB -4.9%) vs baseline: +4.9%

✅ All Tests Passing (10 suites)
errortrackingdjangosimple - 6/6

✅ errortracking-enabled-all

Time: ✅ 16.366ms (SLO: <19.850ms 📉 -17.6%) vs baseline: +0.7%

Memory: ✅ 71.388MB (SLO: <75.000MB -4.8%) vs baseline: +5.6%


✅ errortracking-enabled-user

Time: ✅ 16.380ms (SLO: <19.400ms 📉 -15.6%) vs baseline: +0.7%

Memory: ✅ 71.329MB (SLO: <75.000MB -4.9%) vs baseline: +5.3%


✅ tracer-enabled

Time: ✅ 17.510ms (SLO: <19.450ms -10.0%) vs baseline: +0.5%

Memory: ✅ 71.369MB (SLO: <75.000MB -4.8%) vs baseline: +5.4%


flasksimple - 16/16

✅ appsec-get

Time: ✅ 3.512ms (SLO: <4.750ms 📉 -26.1%) vs baseline: +3.6%

Memory: ✅ 58.519MB (SLO: <66.500MB 📉 -12.0%) vs baseline: +4.9%


✅ appsec-post

Time: ✅ 3.004ms (SLO: <6.750ms 📉 -55.5%) vs baseline: +3.6%

Memory: ✅ 58.677MB (SLO: <66.500MB 📉 -11.8%) vs baseline: +5.2%


✅ appsec-telemetry

Time: ✅ 3.545ms (SLO: <4.750ms 📉 -25.4%) vs baseline: +5.1%

Memory: ✅ 58.667MB (SLO: <66.500MB 📉 -11.8%) vs baseline: +5.0%


✅ debugger

Time: ✅ 1.877ms (SLO: <2.000ms -6.2%) vs baseline: ~same

Memory: ✅ 49.389MB (SLO: <51.500MB -4.1%) vs baseline: +5.2%


✅ iast-get

Time: ✅ 1.868ms (SLO: <2.000ms -6.6%) vs baseline: +0.2%

Memory: ✅ 45.987MB (SLO: <49.000MB -6.1%) vs baseline: +5.1%


✅ profiler

Time: ✅ 1.918ms (SLO: <2.100ms -8.7%) vs baseline: ~same

Memory: ✅ 52.042MB (SLO: <53.500MB -2.7%) vs baseline: +5.0%


✅ resource-renaming

Time: ✅ 3.478ms (SLO: <3.650ms -4.7%) vs baseline: +3.1%

Memory: ✅ 58.713MB (SLO: <60.000MB -2.1%) vs baseline: +5.2%


✅ tracer

Time: ✅ 3.491ms (SLO: <3.650ms -4.4%) vs baseline: +3.4%

Memory: ✅ 58.649MB (SLO: <60.000MB -2.3%) vs baseline: +5.1%


forktime - 4/4

✅ baseline

Time: ✅ 1.937ms (SLO: <3.000ms 📉 -35.4%) vs baseline: +4.5%

Memory: ✅ 29.295MB (SLO: <33.000MB 📉 -11.2%) vs baseline: +4.8%


✅ configured

Time: ✅ 9.346ms (SLO: <17.000ms 📉 -45.0%) vs baseline: +0.1%

Memory: ✅ 58.564MB (SLO: <60.000MB -2.4%) vs baseline: +4.8%


httppropagationextract - 60/60

✅ all_styles_all_headers

Time: ✅ 77.781µs (SLO: <100.000µs 📉 -22.2%) vs baseline: +0.1%

Memory: ✅ 36.255MB (SLO: <38.000MB -4.6%) vs baseline: +5.1%


✅ b3_headers

Time: ✅ 12.959µs (SLO: <20.000µs 📉 -35.2%) vs baseline: -0.2%

Memory: ✅ 36.707MB (SLO: <38.000MB -3.4%) vs baseline: +6.2%


✅ b3_single_headers

Time: ✅ 11.967µs (SLO: <20.000µs 📉 -40.2%) vs baseline: -0.6%

Memory: ✅ 36.667MB (SLO: <38.000MB -3.5%) vs baseline: +6.3%


✅ datadog_tracecontext_tracestate_not_propagated_on_trace_id_no_match

Time: ✅ 60.621µs (SLO: <80.000µs 📉 -24.2%) vs baseline: +0.3%

Memory: ✅ 36.235MB (SLO: <38.000MB -4.6%) vs baseline: +4.9%


✅ datadog_tracecontext_tracestate_propagated_on_trace_id_match

Time: ✅ 64.350µs (SLO: <80.000µs 📉 -19.6%) vs baseline: +0.8%

Memory: ✅ 36.490MB (SLO: <38.000MB -4.0%) vs baseline: +5.4%


✅ empty_headers

Time: ✅ 1.302µs (SLO: <10.000µs 📉 -87.0%) vs baseline: -0.7%

Memory: ✅ 36.372MB (SLO: <38.000MB -4.3%) vs baseline: +5.3%


✅ full_t_id_datadog_headers

Time: ✅ 21.840µs (SLO: <30.000µs 📉 -27.2%) vs baseline: +0.7%

Memory: ✅ 36.608MB (SLO: <38.000MB -3.7%) vs baseline: +5.8%


✅ invalid_priority_header

Time: ✅ 5.966µs (SLO: <10.000µs 📉 -40.3%) vs baseline: +0.2%

Memory: ✅ 36.549MB (SLO: <38.000MB -3.8%) vs baseline: +5.8%


✅ invalid_span_id_header

Time: ✅ 5.920µs (SLO: <10.000µs 📉 -40.8%) vs baseline: -0.3%

Memory: ✅ 36.589MB (SLO: <38.000MB -3.7%) vs baseline: +5.7%


✅ invalid_tags_header

Time: ✅ 5.939µs (SLO: <10.000µs 📉 -40.6%) vs baseline: +0.6%

Memory: ✅ 36.805MB (SLO: <38.000MB -3.1%) vs baseline: +6.7%


✅ invalid_trace_id_header

Time: ✅ 5.961µs (SLO: <10.000µs 📉 -40.4%) vs baseline: +0.6%

Memory: ✅ 36.392MB (SLO: <38.000MB -4.2%) vs baseline: +5.3%


✅ large_header_no_matches

Time: ✅ 26.946µs (SLO: <30.000µs 📉 -10.2%) vs baseline: -0.5%

Memory: ✅ 36.392MB (SLO: <38.000MB -4.2%) vs baseline: +5.3%


✅ large_valid_headers_all

Time: ✅ 28.118µs (SLO: <40.000µs 📉 -29.7%) vs baseline: -0.4%

Memory: ✅ 36.805MB (SLO: <38.000MB -3.1%) vs baseline: +6.4%


✅ medium_header_no_matches

Time: ✅ 9.302µs (SLO: <20.000µs 📉 -53.5%) vs baseline: +1.0%

Memory: ✅ 36.490MB (SLO: <38.000MB -4.0%) vs baseline: +5.4%


✅ medium_valid_headers_all

Time: ✅ 10.663µs (SLO: <20.000µs 📉 -46.7%) vs baseline: +0.3%

Memory: ✅ 36.766MB (SLO: <38.000MB -3.2%) vs baseline: +6.4%


✅ none_propagation_style

Time: ✅ 1.389µs (SLO: <10.000µs 📉 -86.1%) vs baseline: -0.9%

Memory: ✅ 36.451MB (SLO: <38.000MB -4.1%) vs baseline: +5.5%


✅ tracecontext_headers

Time: ✅ 32.904µs (SLO: <40.000µs 📉 -17.7%) vs baseline: ~same

Memory: ✅ 36.412MB (SLO: <38.000MB -4.2%) vs baseline: +5.4%


✅ valid_headers_all

Time: ✅ 5.901µs (SLO: <10.000µs 📉 -41.0%) vs baseline: -0.2%

Memory: ✅ 36.628MB (SLO: <38.000MB -3.6%) vs baseline: +6.0%


✅ valid_headers_basic

Time: ✅ 5.504µs (SLO: <10.000µs 📉 -45.0%) vs baseline: +0.2%

Memory: ✅ 36.471MB (SLO: <38.000MB -4.0%) vs baseline: +5.4%


✅ wsgi_empty_headers

Time: ✅ 1.301µs (SLO: <10.000µs 📉 -87.0%) vs baseline: -0.5%

Memory: ✅ 36.392MB (SLO: <38.000MB -4.2%) vs baseline: +5.1%


✅ wsgi_invalid_priority_header

Time: ✅ 5.932µs (SLO: <10.000µs 📉 -40.7%) vs baseline: -0.4%

Memory: ✅ 36.766MB (SLO: <38.000MB -3.2%) vs baseline: +6.2%


✅ wsgi_invalid_span_id_header

Time: ✅ 1.303µs (SLO: <10.000µs 📉 -87.0%) vs baseline: +0.7%

Memory: ✅ 36.353MB (SLO: <38.000MB -4.3%) vs baseline: +5.1%


✅ wsgi_invalid_tags_header

Time: ✅ 5.980µs (SLO: <10.000µs 📉 -40.2%) vs baseline: +0.6%

Memory: ✅ 36.825MB (SLO: <38.000MB -3.1%) vs baseline: +6.3%


✅ wsgi_invalid_trace_id_header

Time: ✅ 5.979µs (SLO: <10.000µs 📉 -40.2%) vs baseline: ~same

Memory: ✅ 36.451MB (SLO: <38.000MB -4.1%) vs baseline: +5.5%


✅ wsgi_large_header_no_matches

Time: ✅ 28.210µs (SLO: <40.000µs 📉 -29.5%) vs baseline: +0.4%

Memory: ✅ 36.746MB (SLO: <38.000MB -3.3%) vs baseline: +6.2%


✅ wsgi_large_valid_headers_all

Time: ✅ 29.308µs (SLO: <40.000µs 📉 -26.7%) vs baseline: +0.3%

Memory: ✅ 36.726MB (SLO: <38.000MB -3.4%) vs baseline: +6.3%


✅ wsgi_medium_header_no_matches

Time: ✅ 9.605µs (SLO: <20.000µs 📉 -52.0%) vs baseline: +0.5%

Memory: ✅ 36.766MB (SLO: <38.000MB -3.2%) vs baseline: +6.1%


✅ wsgi_medium_valid_headers_all

Time: ✅ 11.035µs (SLO: <20.000µs 📉 -44.8%) vs baseline: +0.8%

Memory: ✅ 36.628MB (SLO: <38.000MB -3.6%) vs baseline: +5.9%


✅ wsgi_valid_headers_all

Time: ✅ 5.965µs (SLO: <10.000µs 📉 -40.3%) vs baseline: -0.3%

Memory: ✅ 36.667MB (SLO: <38.000MB -3.5%) vs baseline: +5.8%


✅ wsgi_valid_headers_basic

Time: ✅ 5.512µs (SLO: <10.000µs 📉 -44.9%) vs baseline: +0.3%

Memory: ✅ 36.608MB (SLO: <38.000MB -3.7%) vs baseline: +5.6%


httppropagationinject - 16/16

✅ ids_only

Time: ✅ 21.018µs (SLO: <30.000µs 📉 -29.9%) vs baseline: +3.2%

Memory: ✅ 36.333MB (SLO: <38.000MB -4.4%) vs baseline: +5.1%


✅ with_all

Time: ✅ 27.286µs (SLO: <40.000µs 📉 -31.8%) vs baseline: +0.7%

Memory: ✅ 36.372MB (SLO: <38.000MB -4.3%) vs baseline: +4.9%


✅ with_dd_origin

Time: ✅ 24.084µs (SLO: <30.000µs 📉 -19.7%) vs baseline: -0.1%

Memory: ✅ 36.392MB (SLO: <38.000MB -4.2%) vs baseline: +5.3%


✅ with_priority_and_origin

Time: ✅ 23.404µs (SLO: <40.000µs 📉 -41.5%) vs baseline: -0.5%

Memory: ✅ 36.490MB (SLO: <38.000MB -4.0%) vs baseline: +5.6%


✅ with_sampling_priority

Time: ✅ 20.199µs (SLO: <30.000µs 📉 -32.7%) vs baseline: -1.0%

Memory: ✅ 36.471MB (SLO: <38.000MB -4.0%) vs baseline: +5.5%


✅ with_tags

Time: ✅ 25.129µs (SLO: <40.000µs 📉 -37.2%) vs baseline: -0.5%

Memory: ✅ 36.648MB (SLO: <38.000MB -3.6%) vs baseline: +6.1%


✅ with_tags_invalid

Time: ✅ 26.401µs (SLO: <40.000µs 📉 -34.0%) vs baseline: -0.8%

Memory: ✅ 36.196MB (SLO: <38.000MB -4.7%) vs baseline: +4.6%


✅ with_tags_max_size

Time: ✅ 25.596µs (SLO: <40.000µs 📉 -36.0%) vs baseline: -0.9%

Memory: ✅ 36.353MB (SLO: <38.000MB -4.3%) vs baseline: +5.1%


otelsdkspan - 24/24

✅ add-event

Time: ✅ 40.849ms (SLO: <42.000ms -2.7%) vs baseline: +0.5%

Memory: ✅ 39.105MB (SLO: <40.750MB -4.0%) vs baseline: +4.9%


✅ add-link

Time: ✅ 36.519ms (SLO: <38.550ms -5.3%) vs baseline: +0.4%

Memory: ✅ 39.046MB (SLO: <40.750MB -4.2%) vs baseline: +5.1%


✅ add-metrics

Time: ✅ 220.675ms (SLO: <232.000ms -4.9%) vs baseline: +1.1%

Memory: ✅ 39.086MB (SLO: <40.750MB -4.1%) vs baseline: +5.0%


✅ add-tags

Time: ✅ 211.778ms (SLO: <221.600ms -4.4%) vs baseline: +0.8%

Memory: ✅ 39.007MB (SLO: <40.750MB -4.3%) vs baseline: +4.9%


✅ get-context

Time: ✅ 29.190ms (SLO: <31.300ms -6.7%) vs baseline: +0.7%

Memory: ✅ 39.046MB (SLO: <40.750MB -4.2%) vs baseline: +4.8%


✅ is-recording

Time: ✅ 29.230ms (SLO: <31.000ms -5.7%) vs baseline: -0.4%

Memory: ✅ 39.145MB (SLO: <40.750MB -3.9%) vs baseline: +5.0%


✅ record-exception

Time: ✅ 63.387ms (SLO: <65.850ms -3.7%) vs baseline: -0.4%

Memory: ✅ 39.027MB (SLO: <40.750MB -4.2%) vs baseline: +4.8%


✅ set-status

Time: ✅ 31.728ms (SLO: <34.150ms -7.1%) vs baseline: -0.8%

Memory: ✅ 39.086MB (SLO: <40.750MB -4.1%) vs baseline: +5.0%


✅ start

Time: ✅ 29.519ms (SLO: <30.150ms -2.1%) vs baseline: +1.8%

Memory: ✅ 39.007MB (SLO: <40.750MB -4.3%) vs baseline: +4.7%


✅ start-finish

Time: ✅ 33.931ms (SLO: <35.350ms -4.0%) vs baseline: -0.3%

Memory: ✅ 39.145MB (SLO: <40.750MB -3.9%) vs baseline: +5.0%


✅ start-finish-telemetry

Time: ✅ 33.843ms (SLO: <35.450ms -4.5%) vs baseline: -0.3%

Memory: ✅ 39.046MB (SLO: <40.750MB -4.2%) vs baseline: +4.9%


✅ update-name

Time: ✅ 31.010ms (SLO: <33.400ms -7.2%) vs baseline: -0.2%

Memory: ✅ 39.046MB (SLO: <40.750MB -4.2%) vs baseline: +4.9%


ratelimiter - 12/12

✅ defaults

Time: ✅ 2.347µs (SLO: <10.000µs 📉 -76.5%) vs baseline: -0.2%

Memory: ✅ 36.549MB (SLO: <38.000MB -3.8%) vs baseline: +5.0%


✅ high_rate_limit

Time: ✅ 2.413µs (SLO: <10.000µs 📉 -75.9%) vs baseline: +0.7%

Memory: ✅ 36.451MB (SLO: <38.000MB -4.1%) vs baseline: +4.9%


✅ long_window

Time: ✅ 2.368µs (SLO: <10.000µs 📉 -76.3%) vs baseline: +0.8%

Memory: ✅ 36.510MB (SLO: <38.000MB -3.9%) vs baseline: +5.1%


✅ low_rate_limit

Time: ✅ 2.362µs (SLO: <10.000µs 📉 -76.4%) vs baseline: ~same

Memory: ✅ 36.530MB (SLO: <38.000MB -3.9%) vs baseline: +5.0%


✅ no_rate_limit

Time: ✅ 0.833µs (SLO: <10.000µs 📉 -91.7%) vs baseline: +1.0%

Memory: ✅ 36.589MB (SLO: <38.000MB -3.7%) vs baseline: +5.2%


✅ short_window

Time: ✅ 2.494µs (SLO: <10.000µs 📉 -75.1%) vs baseline: +0.6%

Memory: ✅ 36.608MB (SLO: <38.000MB -3.7%) vs baseline: +5.4%


samplingrules - 8/8

✅ average_match

Time: ✅ 145.411µs (SLO: <200.000µs 📉 -27.3%) vs baseline: -1.7%

Memory: ✅ 36.255MB (SLO: <38.000MB -4.6%) vs baseline: +5.1%


✅ high_match

Time: ✅ 168.334µs (SLO: <200.000µs 📉 -15.8%) vs baseline: +0.3%

Memory: ✅ 36.235MB (SLO: <38.000MB -4.6%) vs baseline: +5.4%


✅ low_match

Time: ✅ 122.263µs (SLO: <130.000µs -6.0%) vs baseline: -0.6%

Memory: ✅ 619.379MB (SLO: <780.000MB 📉 -20.6%) vs baseline: +4.9%


✅ very_low_match

Time: ✅ 2.686ms (SLO: <4.000ms 📉 -32.8%) vs baseline: +0.2%

Memory: ✅ 73.647MB (SLO: <85.000MB 📉 -13.4%) vs baseline: +5.1%


sethttpmeta - 32/32

✅ all-disabled

Time: ✅ 10.599µs (SLO: <20.000µs 📉 -47.0%) vs baseline: -0.2%

Memory: ✅ 37.257MB (SLO: <38.750MB -3.9%) vs baseline: +5.0%


✅ all-enabled

Time: ✅ 40.023µs (SLO: <50.000µs 📉 -20.0%) vs baseline: +1.6%

Memory: ✅ 37.218MB (SLO: <38.750MB -4.0%) vs baseline: +5.3%


✅ collectipvariant_exists

Time: ✅ 40.291µs (SLO: <50.000µs 📉 -19.4%) vs baseline: ~same

Memory: ✅ 37.198MB (SLO: <38.750MB -4.0%) vs baseline: +5.1%


✅ no-collectipvariant

Time: ✅ 39.527µs (SLO: <50.000µs 📉 -20.9%) vs baseline: -0.1%

Memory: ✅ 37.159MB (SLO: <38.750MB -4.1%) vs baseline: +5.0%


✅ no-useragentvariant

Time: ✅ 38.236µs (SLO: <50.000µs 📉 -23.5%) vs baseline: -0.1%

Memory: ✅ 37.159MB (SLO: <38.750MB -4.1%) vs baseline: +5.0%


✅ obfuscation-no-query

Time: ✅ 39.994µs (SLO: <50.000µs 📉 -20.0%) vs baseline: -1.1%

Memory: ✅ 37.198MB (SLO: <38.750MB -4.0%) vs baseline: +5.0%


✅ obfuscation-regular-case-explicit-query

Time: ✅ 75.613µs (SLO: <90.000µs 📉 -16.0%) vs baseline: ~same

Memory: ✅ 37.493MB (SLO: <38.750MB -3.2%) vs baseline: +4.9%


✅ obfuscation-regular-case-implicit-query

Time: ✅ 76.349µs (SLO: <90.000µs 📉 -15.2%) vs baseline: -0.4%

Memory: ✅ 37.415MB (SLO: <38.750MB -3.4%) vs baseline: +4.7%


✅ obfuscation-send-querystring-disabled

Time: ✅ 154.461µs (SLO: <170.000µs -9.1%) vs baseline: ~same

Memory: ✅ 37.493MB (SLO: <38.750MB -3.2%) vs baseline: +5.0%


✅ obfuscation-worst-case-explicit-query

Time: ✅ 149.360µs (SLO: <160.000µs -6.7%) vs baseline: +0.3%

Memory: ✅ 37.395MB (SLO: <38.750MB -3.5%) vs baseline: +4.7%


✅ obfuscation-worst-case-implicit-query

Time: ✅ 154.868µs (SLO: <170.000µs -8.9%) vs baseline: -0.2%

Memory: ✅ 37.473MB (SLO: <38.750MB -3.3%) vs baseline: +4.9%


✅ useragentvariant_exists_1

Time: ✅ 39.082µs (SLO: <50.000µs 📉 -21.8%) vs baseline: +0.8%

Memory: ✅ 37.198MB (SLO: <38.750MB -4.0%) vs baseline: +5.1%


✅ useragentvariant_exists_2

Time: ✅ 39.947µs (SLO: <50.000µs 📉 -20.1%) vs baseline: -0.2%

Memory: ✅ 37.002MB (SLO: <38.750MB -4.5%) vs baseline: +4.7%


✅ useragentvariant_exists_3

Time: ✅ 39.230µs (SLO: <50.000µs 📉 -21.5%) vs baseline: -0.2%

Memory: ✅ 37.159MB (SLO: <38.750MB -4.1%) vs baseline: +5.4%


✅ useragentvariant_not_exists_1

Time: ✅ 38.894µs (SLO: <50.000µs 📉 -22.2%) vs baseline: +0.3%

Memory: ✅ 37.218MB (SLO: <38.750MB -4.0%) vs baseline: +5.5%


✅ useragentvariant_not_exists_2

Time: ✅ 38.833µs (SLO: <50.000µs 📉 -22.3%) vs baseline: -0.2%

Memory: ✅ 37.277MB (SLO: <38.750MB -3.8%) vs baseline: +5.4%


telemetrydependencies - 4/4

✅ first-50-deps-sca-off

Time: ✅ 2.221ms (SLO: <3.500ms 📉 -36.5%) vs baseline: +0.8%

Memory: ✅ 40.639MB (SLO: <46.000MB 📉 -11.7%) vs baseline: +5.3%


✅ first-50-deps-sca-on

Time: ✅ 2.265ms (SLO: <3.750ms 📉 -39.6%) vs baseline: +0.5%

Memory: ✅ 40.639MB (SLO: <46.000MB 📉 -11.7%) vs baseline: +5.0%

ℹ️ Scenarios Missing SLO Configuration (26 scenarios)

The following scenarios exist in candidate data but have no SLO thresholds configured:

  • coreapiscenario-core_dispatch_listeners
  • coreapiscenario-core_dispatch_no_listeners
  • coreapiscenario-core_dispatch_with_results_listeners
  • coreapiscenario-core_dispatch_with_results_no_listeners
  • djangosimple-baseline
  • errortrackingdjangosimple-baseline
  • errortrackingflasksqli-baseline
  • flasksimple-baseline
  • flasksqli-baseline
  • sethttpmeta-obfuscation-disabled
  • startup-baseline
  • startup-baseline_django
  • startup-baseline_flask
  • startup-ddtrace_run
  • startup-ddtrace_run_appsec
  • startup-ddtrace_run_profiling
  • startup-ddtrace_run_runtime_metrics
  • startup-ddtrace_run_send_span
  • startup-ddtrace_run_telemetry_disabled
  • startup-ddtrace_run_telemetry_enabled
  • startup-import_ddtrace
  • startup-import_ddtrace_auto
  • startup-import_ddtrace_auto_django
  • startup-import_ddtrace_auto_flask
  • startup-import_ddtrace_django
  • startup-import_ddtrace_flask

@cit-pr-commenter-54b7da
Copy link
Copy Markdown

cit-pr-commenter-54b7da Bot commented Apr 23, 2026

Codeowners resolved as

releasenotes/notes/svc-src-multi-defaults-8fc529fc93537b0a.yaml         @DataDog/apm-python

@datadog-official
Copy link
Copy Markdown
Contributor

datadog-official Bot commented Apr 23, 2026

Tests

🎉 All green!

❄️ No new flaky tests detected
🧪 All tests passed

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: aaa0415 | Docs | Datadog PR Page | Give us feedback!

@emmettbutler emmettbutler added the changelog/no-changelog A changelog entry is not required for this PR. label Apr 24, 2026
dubloom and others added 19 commits April 24, 2026 07:05
# Description

Remove Pin from valkey. 

Note that I removed a traced method that was just used by Pin.

# Risks

Tests were not modified so the risks should be low.

Co-authored-by: louis.tricot <[email protected]>
#17675)

## Description

This fixes a surprising race condition happening under AnyIO asynchronous testing where we could sometimes end up constructing a `GatheringFuture` without an active event loop. The assumption was that `gather` would always be called with an event loop existing, but that can apparently be not the case at exit with certain additional  `asyncio`-related packages. 

Co-authored-by: thomas.kowalski <[email protected]>
## Description

Fixes #17557.

This PR moves  `ConfigType` import from `ddtrace.llmobs._experiment.py` in the `_writer.py` to `ExperimentConfigType`, and hides it behind a `TYPE_CHECK` flag to avoid any potential circular imports at app runtime.

## Risks

Minimal. The change only affects the form of two type annotations (object → forward-reference string). `TypedDict` accepts string annotations, and `ConfigType` is not referenced at runtime in `_writer.py`.

## Additional Notes

Claude session: `a3853e6b-9ff8-4941-8644-e0728502235d`
Resume: `claude --resume a3853e6b-9ff8-4941-8644-e0728502235d`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: yun.kim <[email protected]>
…17338)

## Description

Adds `injectible_lines` (list of contiguous line ranges extractable from a code object) and `has_injectible_lines` (boolean convenience flag) to the `Scope` dataclass. Includes correctness tests for the range-building helper.

Refs: [DEBUG-5381](https://datadoghq.atlassian.net/browse/DEBUG-5381)

[DEBUG-5381]: https://datadoghq.atlassian.net/browse/DEBUG-5381?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

Co-authored-by: gabriele.tornetta <[email protected]>
… URL discrepancies (#17668)

## Description

Adds telemetry metrics to track when git commit SHA or repository URL values differ across providers (user-supplied `DD_GIT_*` env vars, CI provider, local git client). This helps understand how common these discrepancies are in customer setups.

Two new count metrics:
- `git.commit_sha_match` — emitted every time git info is built, with tag `matched: "true"|"false"`
- `git.commit_sha_discrepancy` — emitted for each mismatched pair, with tags `expected_provider`, `discrepant_provider`, and `type` (`commit_discrepancy` or `repository_discrepancy`)

All 6 provider-pair × field combinations are checked explicitly, matching the cross-language reference implementation (Ruby: DataDog/datadog-ci-rb#360).

## Testing

Unit tests covering:
- All 6 check combinations (3 provider pairs × 2 fields)
- Edge cases: empty strings, missing values, no telemetry instance
- Telemetry method serialization (`test_telemetry.py`)

## Risks

Low — telemetry-only change, no impact on tag values or test execution behavior.

Co-authored-by: federico.mon <[email protected]>
## Description

Targeted refactor of `ddtrace/internal/telemetry/dependency_tracker.py` and the writer's heartbeat path, focused on concurrency safety and removing redundant work in `collect_report`. No behavior change on the wire.

**Concurrency fix (heartbeat):**
`writer._report_heartbeat` previously called `tracker.get_all_dependencies()` and then serialized each `DependencyEntry` outside the tracker lock. The returned list was a snapshot, but entries themselves remained live — concurrent SCA hooks (`attach_metadata` / `register_cve`) mutate `DependencyEntry.metadata` and `ReachabilityMetadata.value["reached"]`. In particular, `json.dumps(self.value)` iterates the `reached` list, and a concurrent `append` is not safe. Now the heartbeat calls a new `DependencyTracker.snapshot_for_heartbeat()` that builds telemetry dicts while holding `self._lock`.

**collect_report cleanup:**
- Removed the thin `_update_imported` wrapper (single caller, pure pass-through).
- Normalized new-dep names exactly once; the resulting set is reused both for sent-marking and for skipping just-reported deps in the re-report scan. The previous code normalized each new dep twice and also called `_normalize_dep_name(entry.name)` for every tracked entry on every flush.
- Split `collect_report` into `_mark_sent(keys)` and `_collect_rereports(skip_keys)` helpers for clarity (SRP). Public API unchanged.
- Dropped unused `get_all_dependencies()`.

**Split out:** the `data.get_host_info` `@cached()` migration is tracked separately in #17669 to keep this PR focused on the tracker.

**Context:** Findings from a pragmatic code review (L5) of the telemetry dependency tracker on an earlier branch. These are internal-only changes; nothing externally observable shifts. The three standalone helpers (`update_imported_dependencies`, `attach_reachability_metadata`, `register_cve_metadata`) are preserved untouched — they are called from benchmarks, tests, and monkey-patched by `tests/appsec/architectures/mini.py`. The deferred `tracer_config` imports are also preserved: `settings/_config.py` imports telemetry at module top, so a top-level import in the tracker would create a circular import.

## Performance

Isolated A/B microbenchmark of `collect_report()`'s body — byte-for-byte reproduction of `main` vs. this branch, same mocked distribution lookups on both sides, 33 interleaved samples per case. Times are the best of the run; medians track the same direction.

| Scenario (SCA enabled unless noted) | Before (main) | After (this PR) | Δ |
|---|---:|---:|---:|
| 2 000 tracked deps, 50 new imports | 2 695 µs | 1 981 µs | **+26.5 % faster** |
| 5 000 tracked deps, 50 new imports | 6 567 µs | 4 752 µs | **+27.6 % faster** |
| 10 000 tracked deps, 50 new imports | 13 075 µs | 9 539 µs | **+27.0 % faster** |
| **Idle tick**, 5 000 deps, 0 new | 1 880 µs | 148 µs | **+92.1 % faster** |
| **Idle tick**, 10 000 deps, 0 new | 3 790 µs | 298 µs | **+92.1 % faster** |
| SCA **disabled**, 2 000 deps, 50 new | 1 767 µs | 1 783 µs | ~flat (−0.9 %) |

**Where the speedup comes from:** the previous re-report scan called `_normalize_dep_name(entry.name)` (a regex substitution) on every tracked entry, every tick. The refactor iterates `imported.items()` and uses the already-normalized dict key directly, eliminating N regex calls per heartbeat. The idle-path win is large because with no new modules the only work done is that re-report scan.

The SCA-disabled fast path was not touched and is statistically flat, as expected.

## Testing

- `hatch run lint:fmt -- ...` — clean
- `pytest tests/telemetry/test_dependency.py` — **69 passed**, including a new `TestSnapshotForHeartbeat` class:
  - Empty-tracker case returns `[]`
  - All entries serialized correctly; `metadata=None` entries omit the `"metadata"` key
  - Already-sent metadata is still included in the heartbeat payload
  - **Concurrency test**: a writer thread repeatedly calls `attach_metadata` while the main thread builds 200 snapshots. Without the lock, the writer races `json.dumps` iteration over `reached`; with the lock, the test is clean.
- `pytest tests/telemetry/test_data.py` — 15 passed, 2 failures that are pre-existing and unrelated (subprocess-marked tests that assume docker paths in `conftest.py`; verified identical failure on clean `origin/main`).
- `pytest tests/telemetry/test_writer.py` — same pre-existing subprocess-path failures on both clean `main` and this branch; no new regressions.

## Risks

Low — internal refactor with no wire-format or public-API changes.

- `snapshot_for_heartbeat()` holds the tracker lock during a list comprehension that calls `to_telemetry_dict(include_all_metadata=True)` per entry, which includes a `json.dumps` per `ReachabilityMetadata`. On an app with 10k+ tracked deps this is marginally longer than the previous lock-then-serialize-outside pattern, but in exchange we get correctness. Heartbeats run at most once per extended-interval (default 24h), so this is not hot.
- `collect_report` helper split is a pure refactor; `_mark_sent` and `_collect_rereports` remain private and require the caller to hold `self._lock`.

## Additional Notes

- Added `changelog/no-changelog` — the SCA-related telemetry (reachability metadata on `DependencyEntry`) is unreleased, and the rest of this PR is internal refactor.


Co-authored-by: alberto.vara <[email protected]>
…ctives (#17677)

## Summary

The APM_TRACING remote-config handler in `ddtrace/llmobs/_product.py` previously wrote `llmobs.enabled = None` into `_rc_value` on every poll that did not carry an `llmobs` section, then branched on `enabled_config.value()`. When LLMObs had been enabled programmatically via `LLMObs.enable()`, `_llmobs_enabled` had no `_env_value` or `_code_value` set, so `value()` fell through to the default (`False`), which fired the `elif not enabled_config.value() and LLMObs.enabled:` branch and silently called `LLMObs.disable()` on every RC poll.

Services that enabled LLMObs via `DD_LLMOBS_ENABLED=true` were not affected — `_env_value` shielded `value()` from the `_rc_value = None` fallback. This is why the regression was hard to reproduce in simple tests and why the original test `test_rc_missing_llmobs_does_not_disable_enabled_llmobs` passes against the broken handler.

### Root cause

`LLMObs.enable()` flipped `cls.enabled = True` but never wrote `ddtrace.config._llmobs_enabled`, so `_ConfigItem.value()` contradicted the effective state. The RC handler trusted `value()` and disabled LLMObs whenever the config appeared to say "off" — which it always did for programmatic enables.

### Fix

Align `ddtrace.config._llmobs_enabled` with the effective state of LLMObs at every call site. `LLMObs.enable()` / `LLMObs.disable()` now mirror `cls.enabled` into `config._llmobs_enabled` via `Config.__setattr__` (→ `set_value(bool, "code")`), gated on the existing `_auto` parameter:

- **User-initiated calls** (`_auto=False`): write `_code_value` so `value()` reflects the user's explicit choice.
- **Auto-start / RC-initiated calls** (`_auto=True`): skip the write. The caller (the RC handler, or the env-var auto-start path) has already set the appropriate higher-precedence source (`remote_config` or `env_var`); stamping `"code"` on top would mask it when `_rc_value` later clears.

`disable()` gains the same `_auto` parameter and the RC handler passes `_auto=True` when it disables.

With the invariant correct at every call site, the RC handler reverts to the simple `value()`-based form — the pre-regression shape, plus `lib_config.get("llmobs") or {}` as a defense against `{"llmobs": null}` payloads, plus `_auto=True` on the `LLMObs.disable()` call. `set_value` is called unconditionally, so stale `_rc_value` clears naturally when a directive disappears from the merged `lib_config`.

## Test plan

- [x] `test_enable_disable_keeps_global_config_llmobs_enabled_in_sync` (in `test_llmobs_service.py`) pins the invariant: `ddtrace.config._llmobs_enabled` reflects effective state after `LLMObs.enable()` and after `LLMObs.disable()`.
- [x] `test_rc_missing_llmobs_does_not_disable_programmatically_enabled_llmobs` — the scenario that reproduces the 4.8.0rc bug. Simulates the post-`enable()` invariant (both `cls.enabled=True` and `config._llmobs_enabled=True`) and verifies that `{}`, `{"llmobs": {}}`, and `{"llmobs": {"ml_app_name": "x"}}` payloads do not call `LLMObs.disable()`.
- [x] `test_rc_directive_removal_clears_rc_override` — a first payload sets `_rc_value` on both `_llmobs_enabled` and `_llmobs_ml_app`; a subsequent payload without an `llmobs` section clears both back to `None`.
- [x] `test_rc_missing_llmobs_is_noop` now also covers `{"llmobs": null}` (pre-fix would have crashed on `None.get()`).
- [x] Existing `test_rc_missing_llmobs_does_not_disable_enabled_llmobs` / `test_rc_enables_llmobs_and_sets_ml_app` / `test_rc_disables_llmobs` still exercise the env-var and happy paths.
- [ ] CI green on the full llmobs test suite.

## Manual verification

Reproduced and verified against a real staging Datadog Agent + staging RC service.

### Reproduction conditions

1. LLMObs enabled programmatically via `LLMObs.enable(ml_app=...)` (NOT via `DD_LLMOBS_ENABLED` env var).
2. `DD_ENV=staging` + `DD_SERVICE=<svc>` so the staging RC rule targets the process.
3. That RC rule ships a `lib_config` with no `llmobs.*` key (in staging it carries only `exception_replay_enabled: True`).
4. Tracer started via `ddtrace-run` so remote config is active and hits the local agent.

### Observed on unpatched `ddtrace==4.8.0rc4`

```
Disabling LLMObs via Remote Config: {}
```

…flipping LLMObs off for the remainder of the process (1/12 iterations shipped spans).

### Observed with this PR's fix applied

- `Disabling LLMObs via Remote Config` — **0 occurrences**
- `LLMObs.enabled=True` across **12/12** iterations
- `Dispatching 1 payloads to product APM_TRACING` still fires; the handler correctly no-ops on the empty `llmobs` section
- LLMObs spans flushed successfully to the staging intake throughout the run

Co-authored-by: ZStriker19 <[email protected]>
Co-authored-by: zach.groves <[email protected]>
…tags (#16531)

## Description

The v3 pytest plugin (`ddtrace.testing.internal.pytest`) does not call `_patch_all()` by default, so the Selenium integration was never applied — browser tests were missing `test.is_browser`, `test.browser.*` tags.

This PR explicitly patches Selenium in `pytest_sessionstart` when the ddtrace trace filter is active (which is the default for the v3 plugin), ensuring browser test visibility tags are set even without `--ddtrace-patch-all`.

## Changes

- **`ddtrace/testing/internal/pytest/plugin.py`**: Patch Selenium in `pytest_sessionstart` when `enable_ddtrace_trace_filter` is true but `enable_all_ddtrace_integrations` is false.
- **`ddtrace/contrib/internal/selenium/patch.py`**: Added docstring explaining how the integration works with both v2 and v3 plugins.
- **`tests/contrib/selenium/test_selenium_chrome.py`**: Added `test_selenium_v3_plugin_tags` to verify browser tags are set with the v3 plugin. Existing snapshot tests are pinned to the old plugin (`DD_PYTEST_USE_NEW_PLUGIN=false`) since they rely on agent traces.

## Testing

- New test `test_selenium_v3_plugin_tags` uses `pytester` with a mocked `WebDriver` to verify `test.is_browser`, `test.browser.name`, and `test.browser.version` tags are correctly set.

## Risks

None — the Selenium patch is wrapped in a try/except and only applies in the v3 plugin path.

Co-authored-by: federico.mon <[email protected]>
## Summary

Split out of #17667. Replace the `_host_info` module-level global + `None`-check in `ddtrace/internal/telemetry/data.py` with `@callonce`, the idiomatic no-arg memoization decorator in this codebase (already used by `ddtrace/internal/packages.py` and `ddtrace/internal/debug.py`).

- Removes the unsynchronized `None`-check race. `@callonce` also has no explicit lock, but `get_host_info` is deterministic and idempotent, so a benign double-compute on first-call contention produces identical results with no torn state.
- `get_host_info()` is still the public entry point and still returns the same dict shape.
- Originally this PR used `@cached()` (i.e. `functools.lru_cache`), but `@cached()` requires at least one hashable argument and forced a dummy `_: tuple = ()` parameter plus a wrapper function. `@callonce` is purpose-built for zero-arg functions and can be applied directly to `get_host_info`, eliminating the indirection.

## Performance

Microbenchmark on the warm path (A/B reproduction of the two implementations, 10 000 calls per sample, 11 samples):

| Per-call (warm) | Before (global None-check) | After (`@callonce`) | Δ |
|---|---:|---:|---:|
| Best | ~20 ns | ~30-60 ns | negligible |

`get_host_info()` is called at most once per telemetry heartbeat (~60 s), so any small constant-factor difference is not observable in practice. The motivation is code clarity and consistency with the rest of the codebase, not speed.

## Test plan

- [x] `tests/telemetry/test_data.py::test_get_host_info` passes on Python 3.14 via `scripts/run-tests`.
- [x] Existing `tests/telemetry/` suite passes (no behavior change at the function boundary — same dict contents, same call signature).
- [x] No release note needed — internal refactor, `changelog/no-changelog` applied.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: alberto.vara <[email protected]>
## Summary
- Adds `.omc/` to `.gitignore` so oh-my-claudecode local state (e.g. `project-memory.json`) is not tracked.

## Test plan
- [x] `git check-ignore` confirms `.omc/` and its contents are ignored.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: alberto.vara <[email protected]>
[MLOB-6633]

## Description

Extracted from #17235 to narrow review scope. This PR changes the default `model_name` and `model_provider` for LLM and embedding spans from `""` / `"custom"` to the shared `UNKNOWN_MODEL_NAME` / `UNKNOWN_MODEL_PROVIDER` constants (both `"unknown"`) consistently across every entry point:

- Auto-instrumentation fallback in `_build_span_meta` (`ddtrace/llmobs/_llmobs.py`)
- Manual instrumentation APIs `LLMObs.llm()` and `LLMObs.embedding()`
- Decorator helper `_get_llmobs_span_options` used by `@llm` and `@embedding`

Previously these sites independently defaulted to `""` or `"custom"`; unifying them prevents the inconsistency where the same "no provider specified" signal would surface as `"custom"` through the manual APIs but `"unknown"` through the auto-instrumentation path.

## Testing

- `tests/llmobs/test_llmobs_service.py`: renamed `test_default_model_provider_set_to_custom` → `..._unknown` (same for embedding), updated `test_llm_span_no_model_sets_default` / `test_embedding_span_no_model_sets_default` / `test_ml_app_override` to assert the new default via the `UNKNOWN_MODEL_NAME` / `UNKNOWN_MODEL_PROVIDER` constants.
- `tests/llmobs/test_llmobs.py`: renamed `test_model_provider_defaults_to_custom` → `..._unknown`, updated to use the constant.
- `tests/llmobs/test_llmobs_decorators.py`: updated 8 assertion sites to use the constants.
- `tests/llmobs/_utils.py`: helper uses the constants.
- `tests/contrib/claude_agent_sdk/test_claude_agent_sdk_llmobs.py`: assertion fix for auto-instrumented default.

## Risks

**Customer-visible behavior change.** Any monitor or dashboard filtering `model_provider:custom` against spans that relied on the default — whether from auto-instrumentation, manual `LLMObs.llm()` / `LLMObs.embedding()` calls, or the `@llm` / `@embedding` decorators — will stop matching once those spans start emitting `"unknown"`. The release note calls this out; users filtering explicitly by a non-default provider value are unaffected.

Public API docstrings on `LLMObs.llm()` and `LLMObs.embedding()` are updated to document the new default, but the function signatures are unchanged (no caller code needs to change).

## Additional Notes

Part of a 3-PR split of #17235. Order:
1. **This PR** — default model=unknown (S)
2. #17684 — trace/parent IDs as strings (M)
3. #17685 — initialize tags and span name at span start (L, stacked on #17684)

Claude session: `da6fb254-912e-4d04-aec2-ed09cd1980ac`
Resume: `claude --resume da6fb254-912e-4d04-aec2-ed09cd1980ac`

[MLOB-6633]: https://datadoghq.atlassian.net/browse/MLOB-6633

Co-authored-by: yun.kim <[email protected]>
…llocator (#17664)

## Description

This PR fixes a segmentation fault in the memory allocation profiler that occurs when a hook call races with `memalloc` start/stop operations. The issue arises from concurrent access to the saved allocator struct, which could be partially written while being read, resulting in`NULL` function pointers being dereferenced.  The key indicator in that case is that `#1 0x0000000000000000` frame -- we are trying to execute a null function pointer.

````
Error UnixSignal: Process terminated with SEGV_MAPERR (SIGSEGV)
#0   0x00007ff3c303a8d4  
#1   0x0000000000000000 memalloc_alloc (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_memalloc.cpp:68)
#2   0x00007ff39dcb3b20 memalloc_alloc (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_memalloc.cpp:68)
#3   0x00007ff39dcb3b20 memalloc_malloc(void*, unsigned long) (/go/src/github.com/DataDog/apm-reliability/dd-trace-py/ddtrace/profiling/collector/_memalloc.cpp:80)
#4   0x00007ff3c3087e1b PyUnicode_New 
#5   0x00007ff3c30889f4  
#6   0x00007ff3c3170c84  
#7   0x00007ff3c316b931  
#8   0x00007ff3c31aaac8  
#9   0x00007ff3c31033ac  
#10  0x00007ff3c310e2a6 PyObject_CallMethodObjArgs 
#11  0x00007ff3c310e46d  
#12  0x00007ff3c31a96c2  
#13  0x00007ff3c3102fd7 PyObject_Vectorcall 
#14  0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#15  0x00007ff3c323c094  
#16  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#17  0x00007ff3c323c094  
#18  0x00007ff3c30e997d PyObject_CallOneArg 
#19  0x00007ff3c306a480 _PyObject_GenericGetAttrWithDict 
#20  0x00007ff3c30c620d PyObject_GetAttr 
#21  0x00007ff3c32309e7 _PyEval_EvalFrameDefault 
#22  0x00007ff3c323c094  
#23  0x00007ff3c312880e  
#24  0x00007ff3c30e917c _PyObject_MakeTpCall 
#25  0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#26  0x00007ff3c323c094  
#27  0x00007ff3c312880e  
#28  0x00007ff3c30e917c _PyObject_MakeTpCall 
#29  0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#30  0x00007ff3c323c094  
#31  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#32  0x00007ff3c323c094  
#33  0x00007ff3c317d0fd  
#34  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#35  0x00007ff3c323c094  
#36  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#37  0x00007ff3c323c094  
#38  0x00007ff3c317d1b5  
#39  0x00007ff3c3102fd7 PyObject_Vectorcall 
#40  0x00007ff3c3232f4a _PyEval_EvalFrameDefault 
#41  0x00007ff3c3240da5  
#42  0x00007ff3c324112d  
#43  0x00007ff3c3233be1 _PyEval_EvalFrameDefault 
#44  0x00007ff3c323c094  
#45  0x00007ff3c317d1b5  
#46  0x00007ff3c3102fd7 PyObject_Vectorcall 
#47  0x00007ff3c3232f4a _PyEval_EvalFrameDefault 
#48  0x00007ff3c323c094  
#49  0x00007ff3c31033ac  
#50  0x00007ff3c310358d PyObject_CallFunctionObjArgs 
#51  0x00007ff3bf7eb91d WraptBoundFunctionWrapper_call (/project/src/wrapt/_wrappers.c:3750)
#52  0x00007ff3c3104055 _PyObject_Call 
#53  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#54  0x00007ff3c323c094  
#55  0x00007ff3c317d23c  
#56  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#57  0x00007ff3c323c094  
#58  0x00007ff3c310416f _PyObject_Call 
#59  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#60  0x00007ff3c3240da5  
#61  0x00007ff3c324112d  
#62  0x00007ff3c3233be1 _PyEval_EvalFrameDefault 
#63  0x00007ff3c323c094  
#64  0x00007ff3c317d1b5  
#65  0x00007ff3c3102fd7 PyObject_Vectorcall 
#66  0x00007ff3c3232f4a _PyEval_EvalFrameDefault 
#67  0x00007ff3c323c094  
#68  0x00007ff3c317d1b5  
#69  0x00007ff3c310416f _PyObject_Call 
#70  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#71  0x00007ff3c323c094  
#72  0x00007ff3c317d1b5  
#73  0x00007ff3c310416f _PyObject_Call 
#74  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#75  0x00007ff3c323c094  
#76  0x00007ff3c31033ac  
#77  0x00007ff3c310358d PyObject_CallFunctionObjArgs 
#78  0x00007ff3bf7eb91d WraptBoundFunctionWrapper_call (/project/src/wrapt/_wrappers.c:3750)
#79  0x00007ff3c30e917c _PyObject_MakeTpCall 
#80  0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#81  0x00007ff3c323c094  
#82  0x00007ff3c317d518  
#83  0x00007ff3c3155963  
#84  0x00007ff3c315393d  
#85  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#86  0x00007ff3c323c094  
#87  0x00007ff3c317d0fd  
#88  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#89  0x00007ff3c323c094  
#90  0x00007ff3c317d0fd  
#91  0x00007ff3c317d518  
#92  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#93  0x00007ff3c323c094  
#94  0x00007ff3c30e9371 _PyObject_FastCallDictTstate 
#95  0x00007ff3c30e958d _PyObject_Call_Prepend 
#96  0x00007ff3c3109150  
#97  0x00007ff3c3104055 _PyObject_Call 
#98  0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#99  0x00007ff3c323c094  
#100 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#101 0x00007ff3c30e958d _PyObject_Call_Prepend 
#102 0x00007ff3c3109150  
#103 0x00007ff3c30e917c _PyObject_MakeTpCall 
#104 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#105 0x00007ff3c323c094  
#106 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#107 0x00007ff3c30e958d _PyObject_Call_Prepend 
#108 0x00007ff3c3109150  
#109 0x00007ff3c30e917c _PyObject_MakeTpCall 
#110 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#111 0x00007ff3c323c094  
#112 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#113 0x00007ff3c30e958d _PyObject_Call_Prepend 
#114 0x00007ff3c3109150  
#115 0x00007ff3c30e917c _PyObject_MakeTpCall 
#116 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#117 0x00007ff3c323c094  
#118 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#119 0x00007ff3c30e958d _PyObject_Call_Prepend 
#120 0x00007ff3c3109150  
#121 0x00007ff3c30e917c _PyObject_MakeTpCall 
#122 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#123 0x00007ff3c323c094  
#124 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#125 0x00007ff3c30e958d _PyObject_Call_Prepend 
#126 0x00007ff3c3109150  
#127 0x00007ff3c30e917c _PyObject_MakeTpCall 
#128 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#129 0x00007ff3c323c094  
#130 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#131 0x00007ff3c30e958d _PyObject_Call_Prepend 
#132 0x00007ff3c3109150  
#133 0x00007ff3c30e917c _PyObject_MakeTpCall 
#134 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#135 0x00007ff3c323c094  
#136 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#137 0x00007ff3c30e958d _PyObject_Call_Prepend 
#138 0x00007ff3c3109150  
#139 0x00007ff3c30e917c _PyObject_MakeTpCall 
#140 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#141 0x00007ff3c323c094  
#142 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#143 0x00007ff3c30e958d _PyObject_Call_Prepend 
#144 0x00007ff3c3109150  
#145 0x00007ff3c30e917c _PyObject_MakeTpCall 
#146 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#147 0x00007ff3c323c094  
#148 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#149 0x00007ff3c30e958d _PyObject_Call_Prepend 
#150 0x00007ff3c3109150  
#151 0x00007ff3c30e917c _PyObject_MakeTpCall 
#152 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#153 0x00007ff3c323c094  
#154 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#155 0x00007ff3c30e958d _PyObject_Call_Prepend 
#156 0x00007ff3c3109150  
#157 0x00007ff3c30e917c _PyObject_MakeTpCall 
#158 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#159 0x00007ff3c323c094  
#160 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#161 0x00007ff3c30e958d _PyObject_Call_Prepend 
#162 0x00007ff3c3109150  
#163 0x00007ff3c30e917c _PyObject_MakeTpCall 
#164 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#165 0x00007ff3c323c094  
#166 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#167 0x00007ff3c30e958d _PyObject_Call_Prepend 
#168 0x00007ff3c3109150  
#169 0x00007ff3c30e917c _PyObject_MakeTpCall 
#170 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#171 0x00007ff3c323c094  
#172 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#173 0x00007ff3c30e958d _PyObject_Call_Prepend 
#174 0x00007ff3c3109150  
#175 0x00007ff3c30e917c _PyObject_MakeTpCall 
#176 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#177 0x00007ff3c323c094  
#178 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#179 0x00007ff3c30e958d _PyObject_Call_Prepend 
#180 0x00007ff3c3109150  
#181 0x00007ff3c30e917c _PyObject_MakeTpCall 
#182 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#183 0x00007ff3c323c094  
#184 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#185 0x00007ff3c30e958d _PyObject_Call_Prepend 
#186 0x00007ff3c3109150  
#187 0x00007ff3c30e917c _PyObject_MakeTpCall 
#188 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#189 0x00007ff3c323c094  
#190 0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#191 0x00007ff3c323c094  
#192 0x00007ff3c30e92f1 _PyObject_FastCallDictTstate 
#193 0x00007ff3c30e958d _PyObject_Call_Prepend 
#194 0x00007ff3c3109150  
#195 0x00007ff3c30e917c _PyObject_MakeTpCall 
#196 0x00007ff3c32335a2 _PyEval_EvalFrameDefault 
#197 0x00007ff3c323c094  
#198 0x00007ff3c317d0fd  
#199 0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#200 0x00007ff3c323c094  
#201 0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#202 0x00007ff3c323c094  
#203 0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#204 0x00007ff3c323c094  
#205 0x00007ff3c3233dd3 _PyEval_EvalFrameDefault 
#206 0x00007ff3c323c094  
#207 0x00007ff3c317d23c  
#208 0x00007ff3c31a7ec5  
#209 0x00007ff3c301ac77  
#210 0x00007ff3c357c573  
````

The fix implements two key changes.

1. **Hook functions (`memalloc_alloc`, `memalloc_realloc`)**: Snapshot the allocator struct locally before use and guard indirect function calls with `NULL` checks. This prevents crashes if a partially-written struct is observed during a start/stop race.

2. **Start/stop operations (`memalloc_start`, `memalloc_stop`)**: Use local variables and single assignments when publishing the allocator struct to `global_memalloc_ctx.pymem_allocator_obj`. This ensures concurrent hook calls observe either the old or new struct, never a partially-written intermediate state.

The real root cause is that `PyMem_GetAllocator` is not documented as atomic, and the struct could be read field-by-field while being written to concurrently.  By using local copies and single assignments, we ensure atomicity at the C level and prevent observation of inconsistent state.

Co-authored-by: thomas.kowalski <[email protected]>
## Description

When the RC agent deletes a config entry it sends a payload with `content=None`. Previously, `APMTracingCallback` tracked active configs incrementally across poll cycles, but deletion payloads were silently skipped — the config was never removed from the internal map and stale settings persisted indefinitely.

**Fix:** rebuild `_config_map` from scratch on every invocation. Because deletion payloads (`content=None`) are skipped during the rebuild, deleted configs are simply absent from the new map. The next `dispatch` reflects the updated (or empty) configuration automatically, with no extra bookkeeping.

## Testing

Three new regression tests in `tests/internal/remoteconfig/test_remoteconfig.py`, all `@pytest.mark.subprocess`, passing locally on Python 3.14:

- `test_apm_tracing_same_config_id_delete_is_not_skipped` — verifies via the live `dispatch → tracer_rc` chain that a deletion payload in the same batch as a content payload wins; asserts `ddtrace.config._tracing_enabled` is not overwritten by stale content.
- `test_apm_tracing_rc_handlers_dispatched_from_products` — verifies the full `on() → APMTracingCallback → dispatch()` round-trip reaches downstream product handlers.
- `test_apm_tracing_start_collects_product_apm_capabilities` — verifies `start()` aggregates `APMCapabilities` from all registered products into the RC registration.

## Risks

Low. The behavioral change is limited to deletion payloads, which previously caused a silent no-op. The rebuild approach assumes the agent sends a full config set on every poll (the same assumption made by the previous cleanup loop).

## Additional Notes

None.

Co-authored-by: munir.abdinur <[email protected]>
…#17703)

## Description

It can be nice to know some extra information about the artifacts that were built. Especially on generic S3 paths like `/main/` which are not tied directly to a commit sha or pipeline id.

This PR introduces two changes:

1. Adds metadata about pipeline id, commit sha, timestamp, pacakge version, etc to `index.html` files generated. Both as text and html metadata.
2. Adds a `metadata.txt` file which contains the same information as `key=value` pairs

## 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 -->


Co-authored-by: brett.langdon <[email protected]>
## Description

This PR fixes the following crash.

```
Error UnixSignal: Process terminated with SEGV_MAPERR (SIGSEGV)
#0   0x00005a87adf31055 dictkeys_get_index (/usr/src/python/Objects/dictobject.c:344:13)
#1   0x00005a87adf31055 unicodekeys_lookup_generic (/usr/src/python/Objects/dictobject.c:876:14)
#2   0x00005a87adf31055 _Py_dict_lookup (/usr/src/python/Objects/dictobject.c:1056:18)
#3   0x00005a87adf31055 PyDict_GetItemWithError (/usr/src/python/Objects/dictobject.c:1789:10)
#4   0x00007b2e6fc1a8e0 __pyx_pw_7ddtrace_8internal_9telemetry_18metrics_namespaces_15MetricNamespace_5add_metric
#5   0x00005a87ae00c955 _PyObject_VectorcallTstate (/usr/src/python/./Include/internal/pycore_call.h:92:11)
#6   0x00005a87ae00c955 PyObject_Vectorcall (/usr/src/python/Objects/call.c:299:12)
#7   0x00005a87ae05ee1d _PyEval_EvalFrameDefault (/usr/src/python/Python/ceval.c:4760:23)
#8   0x00005a87ae05e060 _PyEval_EvalFrame (/usr/src/python/./Include/internal/pycore_ceval.h:73:16)
#9   0x00005a87ae05e060 _PyEval_Vector (/usr/src/python/Python/ceval.c:6425:24)
#10  0x00005a87ae00d587 PyObject_Call
#11  0x00005a87ae061fd8 do_call_core (/usr/src/python/Python/ceval.c:7343:12)
#12  0x00005a87ae061fd8 _PyEval_EvalFrameDefault (/usr/src/python/Python/ceval.c:5367:22)
#13  0x00005a87ae05e060 _PyEval_EvalFrame (/usr/src/python/./Include/internal/pycore_ceval.h:73:16)
#14  0x00005a87ae05e060 _PyEval_Vector (/usr/src/python/Python/ceval.c:6425:24)
#15  0x00005a87ae00e0ba _PyObject_VectorcallTstate (/usr/src/python/./Include/internal/pycore_call.h:92:11)
#16  0x00005a87ae00e0ba method_vectorcall (/usr/src/python/Objects/classobject.c:59:18)
#17  0x00005a87ae00c955 _PyObject_VectorcallTstate (/usr/src/python/./Include/internal/pycore_call.h:92:11)
#18  0x00005a87ae00c955 PyObject_Vectorcall (/usr/src/python/Objects/call.c:299:12)
#19  0x00005a87ae05ee1d _PyEval_EvalFrameDefault (/usr/src/python/Python/ceval.c:4760:23)
#20  0x00005a87ae05e060 _PyEval_EvalFrame (/usr/src/python/./Include/internal/pycore_ceval.h:73:16)
#21  0x00005a87ae05e060 _PyEval_Vector (/usr/src/python/Python/ceval.c:6425:24)
#22  0x00005a87ae00d02f _PyFunction_Vectorcall (/usr/src/python/Objects/call.c:393:16)
#23  0x00005a87ae00d02f _PyObject_FastCallDictTstate (/usr/src/python/Objects/call.c:141:15)
#24  0x00005a87ae00d02f _PyObject_Call_Prepend (/usr/src/python/Objects/call.c:482:24)
#25  0x00005a87ae0bafd9 slot_tp_call (/usr/src/python/Objects/typeobject.c:7623:15)
#26  0x00005a87ae00c4d9 _PyObject_MakeTpCall (/usr/src/python/Objects/call.c:214:18)
#27  0x00005a87ae05ee1d _PyEval_EvalFrameDefault (/usr/src/python/Python/ceval.c:4760:23)
#28  0x00005a87ae05e060 _PyEval_EvalFrame (/usr/src/python/./Include/internal/pycore_ceval.h:73:16)
#29  0x00005a87ae05e060 _PyEval_Vector (/usr/src/python/Python/ceval.c:6425:24)
#30  0x00005a87ae00d02f _PyFunction_Vectorcall (/usr/src/python/Objects/call.c:393:16)
#31  0x00005a87ae00d02f _PyObject_FastCallDictTstate (/usr/src/python/Objects/call.c:141:15)
#32  0x00005a87ae00d02f _PyObject_Call_Prepend (/usr/src/python/Objects/call.c:482:24)
#33  0x00005a87ae041f7b slot_tp_init (/usr/src/python/Objects/typeobject.c:7854:15)
#34  0x00005a87ae040731 type_call (/usr/src/python/Objects/typeobject.c:1103:19)
#35  0x00005a87ae00c4d9 _PyObject_MakeTpCall (/usr/src/python/Objects/call.c:214:18)
#36  0x00005a87ae05ee1d _PyEval_EvalFrameDefault (/usr/src/python/Python/ceval.c:4760:23)
#37  0x00005a87ae05e060 _PyEval_EvalFrame (/usr/src/python/./Include/internal/pycore_ceval.h:73:16)
#38  0x00005a87ae05e060 _PyEval_Vector (/usr/src/python/Python/ceval.c:6425:24)
#39  0x00005a87ae00e134 _PyObject_VectorcallTstate (/usr/src/python/./Include/internal/pycore_call.h:92:11)
#40  0x00005a87ae00e134 method_vectorcall (/usr/src/python/Objects/classobject.c:89:18)
#41  0x00005a87ae00d587 PyObject_Call
#42  0x00005a87ae061fd8 do_call_core (/usr/src/python/Python/ceval.c:7343:12)
#43  0x00005a87ae061fd8 _PyEval_EvalFrameDefault (/usr/src/python/Python/ceval.c:5367:22)
#44  0x00005a87ae05e060 _PyEval_EvalFrame (/usr/src/python/./Include/internal/pycore_ceval.h:73:16)
#45  0x00005a87ae05e060 _PyEval_Vector (/usr/src/python/Python/ceval.c:6425:24)
#46  0x00005a87ae00d587 PyObject_Call
#47  0x00005a87ae061fd8 do_call_core (/usr/src/python/Python/ceval.c:7343:12)
#48  0x00005a87ae061fd8 _PyEval_EvalFrameDefault (/usr/src/python/Python/ceval.c:5367:22)
#49  0x00005a87ae05e060 _PyEval_EvalFrame (/usr/src/python/./Include/internal/pycore_ceval.h:73:16)
#50  0x00005a87ae05e060 _PyEval_Vector (/usr/src/python/Python/ceval.c:6425:24)
#51  0x00005a87ae00e04b _PyObject_VectorcallTstate (/usr/src/python/./Include/internal/pycore_call.h:92:11)
#52  0x00005a87ae00e04b method_vectorcall (/usr/src/python/Objects/classobject.c:67:20)
#53  0x00005a87ae00d587 PyObject_Call
#54  0x00005a87ae10e9b3 thread_run (/usr/src/python/./Modules/_threadmodule.c:1124:21)
#55  0x00005a87ae0f1098 pythread_wrapper (/usr/src/python/Python/thread_pthread.h:241:5)
#56  0x00007b2e71c49609 start_thread
#57  0x00007b2e71a14353 clone
```

Co-authored-by: brettlangdon <[email protected]>
## Description

Allowing failure to keep release pipelines green

## 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 -->


Co-authored-by: gyuheon.oh <[email protected]>
emmettbutler and others added 5 commits April 24, 2026 07:05
)

## Description

`PeriodicThread_join` (native, in `ddtrace/internal/_threads.cpp`) guarded its
argument parsing with `args != NULL && kwargs != NULL`. CPython passes
`kwargs == NULL` whenever the caller uses only positional arguments, so
`t.join(0.1)` skipped parsing and `timeout` stayed `Py_None`, falling through
to `self->_stopped->wait()` — an unbounded wait.

Both `PeriodicService.join` (`ddtrace/internal/periodic.py:64`) and
`Timer.join` (`ddtrace/internal/periodic.py:151`) forward to the underlying
worker positionally, so every caller that did `service.join(timeout=...)` was
silently waiting for the thread to exit on its own instead of honoring the
requested timeout. The telemetry writer shutdown path (`self.join(timeout=2)`)
is one user-visible example.

The fix drops the spurious `kwargs != NULL` check: `PyArg_ParseTupleAndKeywords`
already accepts `kwargs == NULL`.

This bug has been in the file since `PeriodicThread_join` was first
introduced; it was found by a randomized lifecycle stress test targeting the
native `_threads.cpp` that I'm prototyping separately.

## Testing

New regression test in `tests/internal/test_periodic.py`:
`test_periodic_join_positional_timeout_is_honored` — asserts both
`t.join(0.1)` and `t.join(timeout=0.1)` return within 1s against a long-interval
thread. Before the fix, the positional form hangs forever; after the fix both
forms return in ~0.1s.

Quick standalone verification on a Linux workspace:

Before:
```
kwarg join(timeout=0.1):  elapsed=0.100s
WATCHDOG: positional join hung past 2s — killing
```

After:
```
positional join(0.1):  elapsed=0.100s
keyword   join(t=0.1): elapsed=0.100s
```

## Risks

Very low. Behavior change is scoped to `PeriodicThread.join(X)` called
positionally — previously an unbounded wait, now bounded by `X`. Callers that
were relying on the (undocumented) infinite-wait-when-passing-a-number
behavior would be affected, but that matches no documented contract and all
existing positional callsites in the repo already want the timeout honored
(they are `service.join(timeout=...)` wrappers forwarding positionally).

No API surface change. Release note included.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: erwan.viollet <[email protected]>
@emmettbutler emmettbutler force-pushed the emmett.butler/sqlite3-snapshot-2 branch from 62fca60 to b58ef83 Compare April 24, 2026 14:06
@emmettbutler emmettbutler changed the base branch from main to emmett.butler/unignore-svc-src April 24, 2026 14:06
@emmettbutler emmettbutler changed the title turn sqlite test into a snapshot one fix: avoid using svc_src: m when service is among active integration defaults Apr 24, 2026
@emmettbutler emmettbutler removed the changelog/no-changelog A changelog entry is not required for this PR. label Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.