feat(otel): scaffold OTel consistent probability sampling (ot.th/ot.rv) conformance tests - #7372
feat(otel): scaffold OTel consistent probability sampling (ot.th/ot.rv) conformance tests#7372genesor wants to merge 17 commits into
Conversation
…v) conformance tests
|
|
🎉 All green!🧪 All tests passed 🔗 Commit SHA: 13f800b | Docs | Datadog PR Page | Give us feedback! |
| }, | ||
| ) | ||
|
|
||
| def test_malformed_th_only_treated_as_absent(self): |
There was a problem hiding this comment.
Maybe we could also add a test with malformed rv only, and what we do in this situation (ensure that we fallback on the same behaviour than ot.th without rv described above)
There was a problem hiding this comment.
Same policy as the malformed th case: we just cleanup, we don't derive anything. No plans to add a dedicated malformed-rv-only test for now.
| assert self.dropped_request.status_code == 200 | ||
| ot = _parse_ot(_outbound_tracestate(self.dropped_request)) | ||
|
|
||
| assert ot.get("rv") == self.DROPPED_RV, "inherited rv was not forwarded when overriding an inherited drop" |
There was a problem hiding this comment.
I wouldn't expect rv to be included, as without a th it has no meaning anyway
| assert ot.get("rv") == self.DROPPED_RV, "inherited rv was not forwarded when overriding an inherited drop" | |
| assert "rv" not in ot, "inherited rv was not forwarded when overriding an inherited drop" |
There was a problem hiding this comment.
rv is intentionally forwarded even without th. Per the RFC: "DD makes a non-probability decision (force-keep, rate limiter) -> no th; forward an inherited rv if present, otherwise emit nothing." rv is randomness metadata a later hop can pair with its own th, so it's not meaningless on its own. Keeping the assertions as-is.
| assert self.rv_only_request.status_code == 200 | ||
| ot = _parse_ot(_outbound_tracestate(self.rv_only_request)) | ||
|
|
||
| assert ot.get("rv") == self.INHERITED_RV, "inherited rv was not forwarded on a force-keep decision" |
There was a problem hiding this comment.
I wouldn't expect rv to be included, as without a th it has no meaning anyway
| assert ot.get("rv") == self.INHERITED_RV, "inherited rv was not forwarded on a force-keep decision" | |
| assert "rv" not in ot, "inherited rv was not forwarded on a force-keep decision" |
There was a problem hiding this comment.
I'd rename the test test_force_keep_overrides_inherited_keep_decision
There was a problem hiding this comment.
rv is intentionally forwarded even without th. Per the RFC: "DD makes a non-probability decision (force-keep, rate limiter) -> no th; forward an inherited rv if present, otherwise emit nothing." rv is randomness metadata a later hop can pair with its own th, so it's not meaningless on its own. Keeping the assertions as-is.
There was a problem hiding this comment.
This test covers inbound rv-only with no th. Without th we have no signal that a probability decision happened upstream, so this isn't a case of overriding an inherited keep decision. The suggested name fits the next test instead (test_force_keep_overrides_inherited_drop_decision, which has a full th/rv pair). Keeping the current name, happy to rename to something clearer if test_force_keep_forwards_inherited_rv still reads ambiguous, but not to the suggested name.
vpellan
left a comment
There was a problem hiding this comment.
I also believe that we should add tests for imprecision workaround while going from 64 to 56 bit (see the annex of the RFC about 64->56 bit imprecision)
|
|
||
| @scenarios.otel_sampling_rate_0_1 | ||
| @features.w3c_headers_injection_and_extraction | ||
| class Test_MalformedOtHandling: |
There was a problem hiding this comment.
For these cases, as we generate new th and rv, should th be derived from the sampling rate, and rv from the trace id last 56 bits?
There was a problem hiding this comment.
No, we do not do anything. We have no way of knowing how the decision was made upstream. We just to cleanup
| assert req.status_code == 200, "a malformed ot.th must not cause the trace to be rejected" | ||
|
|
||
| ot = _parse_ot(_outbound_tracestate(req)) | ||
| assert ot.get("rv") == self.MALFORMED_TH_RV, "the well-formed inbound rv was not forwarded" |
There was a problem hiding this comment.
Again, I wouldn't expect rv to be included, as without a th it has no meaning.
| assert ot.get("rv") == self.MALFORMED_TH_RV, "the well-formed inbound rv was not forwarded" | |
| assert "rv" not in ot, "the well-formed inbound rv was forwarded" |
There was a problem hiding this comment.
rv is intentionally forwarded even without th. Per the RFC: "DD makes a non-probability decision (force-keep, rate limiter) -> no th; forward an inherited rv if present, otherwise emit nothing." rv is randomness metadata a later hop can pair with its own th, so it's not meaningless on its own. Keeping the assertions as-is.
|
There are a bunch of tests in assert len(tracestate_1_arr) == 1
assert tracestate_1_arr[0].startswith("dd=")into assert any(item.startswith("dd=") for item in tracestate_1_arr)The test are:
|
|
Good catch, fixed in 52e0d3a. Updated the 5 assertions across the 3 tests to Part 6 already falls into the "new Datadog span context" path, so the same fix covers it. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efe7f14b08
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Motivation
RFC for cross-tracer support of OTel consistent probability sampling (
ot.th/ot.rv) tracestate members across all 9 dd-trace-* SDKs (APMAPI-2171 distributed tracing, APMAPI-2172 trace export).Changes
Scaffolding only — every new test class is landed disabled for all 9 tracers via manifest entries (
missing_feature). No tracer implements this yet.tests/test_otel_tracestate_sampling.py(new): A1-A6 from APMAPI-2171Test_EmitOtOnProbabilityDecision_Rate0_{01,1,2,5,99}— golden-vector rv/th derivation at 5 fixed rates, crossed with the 23 trace IDs fromtests/fixtures/sampling_rates.csv, verified against the Go Knuth-hash sampler formulaTest_ForwardInboundOtUnchanged— inbound th/rv forwarded unchanged, never re-derivedTest_PreserveDdAndOtherVendors— ot= handling doesn't disturb dd= or unrelated vendor tracestate membersTest_ForceKeepClearsTh— a non-probability (force-keep, via ASM/waf) decision erases th but forwards an inherited rvTest_SampledWithoutOtNotFabricated— an inherited sampling decision with no ot= must not fabricate th/rvTest_MalformedOtHandling— malformed th/rv treated as absent, dd=/vendors preserved, trace never rejectedtests/otel/test_tracing_otlp.py: B1/B2 from APMAPI-2172 —Test_Otlp_Carries_Ot,Test_Otlp_Forwards_Inherited_Ot, asserting on the exported OTLP span'straceStateutils/_context/_scenarios/__init__.py: 4 new fixed-rate scenarios (otel_sampling_rate_0_{01,1,2,99}); rate 0.5 reuses the existingsamplingscenariomanifests/*.yml(all 9 languages):missing_feature (APMAPI-2171)/(APMAPI-2172)entries for every new classWorkflow
Reviewer checklist
tests/ormanifests/is modified ? I have the approval from R&P team (note:utils/_context/_scenarios/__init__.pyis modified to add the 4 new scenarios)