Skip to content

ci: add runtime-aware Riot sharding - #19689

Open
mr-lee wants to merge 6 commits into
mainfrom
dd/ci-runtime-aware-riot-sharding-20260813-1915
Open

ci: add runtime-aware Riot sharding#19689
mr-lee wants to merge 6 commits into
mainfrom
dd/ci-runtime-aware-riot-sharding-20260813-1915

Conversation

@mr-lee

@mr-lee mr-lee commented Aug 13, 2026

Copy link
Copy Markdown

Description

Motivation

Riot suites preserve meaningful ownership and authoring boundaries, but count-based round-robin execution cannot account for large runtime differences between Riot environments. The first same-head shadow run reduced total Riot runner time but left the longest job nearly unchanged because some long poles were already a single Riot hash. This change adds fail-closed runtime test-item slicing so CI can break those poles without changing suitespec authoring or silently dropping or duplicating work.

Changes

  • Preserve semantic suites and the active legacy strategy while generating a branch-scoped, non-blocking balanced shadow lane.
  • Separate semantic suite selection, Riot environment planning, runtime execution-unit planning, history modeling, manifest verification, JUnit verification, and runtime inventory verification into reusable CI allocation modules.
  • Refine measured long pytest commands into execution units such as 107d2ec@1/3; pytest collects the authored command and deterministically selects a disjoint node-ID slice at runtime.
  • Require real command-level Test Visibility item evidence before splitting; fallback estimates and unmeasured commands remain atomic.
  • Emit per-slice inventories and require exact union, no overlap, no empty slices, matching collection digests, and JUnit testcase multiset parity.
  • Keep the balanced topology within the legacy 858-job budget, allowing capacity to move from short suites to measured long poles.
  • Embed balanced assignments in compact GitLab parallel jobs while retaining semantic suite names, services, retries, timeouts, and environment configuration.
  • Include repeated Riot setup/activation cost when scoring new slices.
  • Scope duration overrides by semantic suite when Riot environment hashes are shared by different commands.
  • Measure live Riot critical path from the actual overlapping job fanout interval instead of summing semantic-stage maxima.
  • Fail closed on missing queue data, incomplete shard timing, invalid strategy attribution, malformed runtime evidence, or oversized model artifacts.
  • Keep the checked-in model below 100 KB and retain one-switch rollback to legacy allocation.
  • Document model refresh, runtime slicing, parity verification, shadow validation, promotion, and rollback.

Testing

  • Passed repository-wide scripts/lint checks, including formatting, typing, security, suitespec coverage, dependency/configuration validation, and static analysis.
  • Passed Python compilation, allocation contract checks, shell syntax checks, GitLab generator execution, generated-plan verification, and git diff --check.
  • Generated and verified 169 semantic suites, 1,833 Riot-hash occurrences, 1,961 balanced execution units, 858 legacy jobs, and 858 balanced jobs.
  • Verified the generated child YAML is 356,430 bytes and the checked-in runtime model is 59,495 bytes.
  • Added focused regression coverage for runtime slice expansion, fail-closed evidence, anti-affinity, exact inventory union, JUnit multiset recombination, shared-hash suite overrides, job-budget reallocation, queue/timing completeness, and overlapping-DAG makespan.
  • Focused scripts/run-tests --venv 116989a tests/internal/test_ci_allocation.py was attempted, but repository environment setup stopped before collection when the existing libddwaf 2.0.1 download returned 502 through the sandbox proxy.
  • Measured first shadow pipeline 131076783 at commit 3bd9551: longest Riot job 1,057s to 977s (7.6% lower), Riot runner time 218,256s to 195,007s (10.7% lower), queue p90 1.139s to 1.185s, and jobs 867 to 578.
  • That measured result did not satisfy the critical-path promotion gate. The corrected runtime-sliced topology is not yet measured and remains shadow-only; no 10m51s runtime claim is made from its planner score.

Risks

Runtime slicing could expose collection differences, hidden test ordering assumptions, or setup costs that differ from the fitted model. Splitting also repeats Riot activation work. The implementation limits slices to measured pytest commands, keeps sibling slices in separate jobs, preserves the total job budget, records exact inventories and JUnit identities, and leaves legacy active until live correctness, runner-time, queue, reliability, and critical-path gates pass.

Additional Notes

This is internal CI infrastructure with no customer-facing behavior change. No release note is needed; apply the changelog/no-changelog label.


PR by Bits - View session in Datadog

Comment @DataDog to request changes

Co-authored-by: mr-lee <1302416+mr-lee@users.noreply.github.com>
@datadog-prod-us1-3

datadog-prod-us1-3 Bot commented Aug 13, 2026

Copy link
Copy Markdown

View session in Datadog

Bits Code status: ✅ Done

CI Auto-fix: Disabled | Enable

Comment @DataDog to request changes

@mr-lee
mr-lee requested review from a team as code owners August 13, 2026 19:43
@mr-lee
mr-lee requested a review from juanjux August 13, 2026 19:43
@mr-lee mr-lee added the Bits AI label Aug 13, 2026
@datadog-prod-us1-3

Copy link
Copy Markdown

I can only run on private repositories.

@datadog-prod-us1-3

datadog-prod-us1-3 Bot commented Aug 13, 2026

Copy link
Copy Markdown

Pipelines  Tests

Unblock PR with BitsAI

⚠️ Warnings

🚦 110 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-py | appsec/appsec_threats_fastapi_iast-allocation-shadow 11/16   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-py | appsec/appsec_threats_fastapi_iast-allocation-shadow 3/16   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-py | appsec/appsec_threats_fastapi_no_iast-allocation-shadow 7/16   View in Datadog   GitLab

View all 110 failed jobs.

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

Useful? React with 👍 / 👎

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

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codeowners resolved as

Resolved from the full PR diff against main using the target branch CODEOWNERS file.
CODEOWNERS team requests not listed below are not required by the current file set.

.gitignore                                                              @DataDog/apm-core-python
.gitlab-ci.yml                                                          @DataDog/python-guild @DataDog/apm-core-python
.gitlab/scripts/get-riot-hashes.sh                                      @DataDog/python-guild @DataDog/apm-core-python
.gitlab/scripts/get-riot-pip-cache-key.sh                               @DataDog/python-guild @DataDog/apm-core-python
.gitlab/testrunner.yml                                                  @DataDog/python-guild @DataDog/apm-core-python
.gitlab/tests.yml                                                       @DataDog/python-guild @DataDog/apm-core-python
ci/ci-allocation-policy.json                                            @DataDog/apm-core-python
ci/ci-allocation-runtime-model.json                                     @DataDog/apm-core-python
conftest.py                                                             @DataDog/python-guild
ddtrace/internal/writer/writer.py                                       @DataDog/apm-core-python
ddtrace/llmobs/_integrations/agent_manifest.py                          @DataDog/ml-observability
ddtrace/llmobs/_integrations/pydantic_ai.py                             @DataDog/ml-observability
ddtrace/llmobs/types.py                                                 @DataDog/ml-observability
docs/contributing-testing.rst                                           @DataDog/python-guild
releasenotes/notes/pydantic-ai-agent-manifest-canonical-schema-ddbf5af7f1d63366.yaml  @DataDog/apm-python
riotfile.py                                                             @DataDog/apm-python
scripts/ci_allocation/__init__.py                                       @DataDog/python-guild
scripts/ci_allocation/history.py                                        @DataDog/python-guild
scripts/ci_allocation/jobs.py                                           @DataDog/python-guild
scripts/ci_allocation/junit.py                                          @DataDog/python-guild
scripts/ci_allocation/manifest.py                                       @DataDog/python-guild
scripts/ci_allocation/planner.py                                        @DataDog/python-guild
scripts/ci_allocation/pr_history.py                                     @DataDog/python-guild
scripts/ci_allocation/runtime.py                                        @DataDog/python-guild
scripts/ci_allocation/suites.py                                         @DataDog/python-guild
scripts/ci_allocation_cli.py                                            @DataDog/python-guild
scripts/gen_gitlab_config.py                                            @DataDog/python-guild
tests/appsec/appsec_utils.py                                            @DataDog/asm-python
tests/appsec/contrib_appsec/test_django.py                              @DataDog/asm-python
tests/appsec/iast/fixtures/taint_sinks/path_traversal.py                @DataDog/asm-python
tests/appsec/iast/test_product_inspect_regression.py                    @DataDog/asm-python
tests/appsec/iast/test_telemetry.py                                     @DataDog/asm-python
tests/appsec/suitespec.yml                                              @DataDog/asm-python
tests/contrib/flask/test_appsec_flask_snapshot.py                       @DataDog/asm-python
tests/contrib/pydantic_ai/test_pydantic_ai_llmobs.py                    @DataDog/ml-observability
tests/contrib/pydantic_ai/utils.py                                      @DataDog/ml-observability
tests/internal/test_ci_allocation.py                                    @DataDog/apm-core-python
tests/llmobs/test_integrations_utils.py                                 @DataDog/ml-observability
tests/profiling/test_gunicorn.py                                        @DataDog/profiling-python

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 13, 2026

Copy link
Copy Markdown

Dependency direction analysis

📈 Existing violations got worse

3 pre-existing violation(s) increased in severity (e.g. their target became more depended-on, or got pulled into an import cycle), though the edge itself isn't new:

ddtrace.contrib.internal.claude_agent_sdk._streaming -×-> ddtrace.llmobs.types  (contrib -> product:llmobs, score=34, +1 vs base)
ddtrace.contrib.internal.vllm.extractors -×-> ddtrace.llmobs.types  (contrib -> product:llmobs, score=34, +1 vs base)
ddtrace.contrib.internal.openai._realtime -×-> ddtrace.llmobs.types  (contrib -> product:llmobs, score=34, +1 vs base)

⚠️ Existing dependency direction violations

There are 254 dependency direction violations that already exist on the base branch and have not been changed by this PR.

Show existing violations (showing 5 of 254 highest severity)
ddtrace.internal.tracemethods -×-> ddtrace.trace  (internal-core -> product:tracing, score=134)
ddtrace.internal.ci_visibility.recorder -×-> ddtrace.trace  (product:ci_visibility -> product:tracing, score=132)
ddtrace.appsec._contrib.flask -×-> ddtrace.trace  (product:appsec -> product:tracing, score=132)
ddtrace.debugging._signal.model -×-> ddtrace.trace  (product:debugging -> product:tracing, score=132)
ddtrace.llmobs._integrations.google_adk -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=132)

To see all violations, download the layers-base.json and layers-pr.json artifacts from this CI job and run:

uv run --script scripts/import-analysis/layers.py compare layers-base.json layers-pr.json

✅ Dependency direction violations removed

1 violation(s) have been removed by this PR.

ddtrace.internal.writer.writer -×-> ddtrace._trace.span  (internal-core -> product:tracing, score=60)

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 13, 2026

Copy link
Copy Markdown

Circular import analysis

⚠️ Existing circular imports

There are 5 circular imports that already exist on the base branch and have not been changed by this PR.

ddtrace.contrib.internal.django.patch -> ddtrace.contrib.internal.django.response -> ddtrace.contrib.internal.django.patch
ddtrace.contrib.internal.pytorch._distributed -> ddtrace.contrib.internal.pytorch._rank_root -> ddtrace.contrib.internal.pytorch._distributed
ddtrace.errortracking._handled_exceptions.bytecode_injector -> ddtrace.errortracking._handled_exceptions.callbacks -> ddtrace.errortracking._handled_exceptions.collector -> ddtrace.errortracking._handled_exceptions.bytecode_reporting -> ddtrace.errortracking._handled_exceptions.bytecode_injector
ddtrace.llmobs -> ddtrace.llmobs._evaluators -> ddtrace.llmobs._evaluators.format -> ddtrace.llmobs._experiment -> ddtrace.llmobs
ddtrace.appsec._asm_request_context -> ddtrace.appsec._iast._iast_request_context_base -> ddtrace.appsec._iast._iast_env -> ddtrace.appsec._iast.reporter -> ddtrace.appsec._exploit_prevention.stack_traces -> ddtrace.appsec._asm_request_context

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dbfae8ba36

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .gitlab/scripts/get-riot-hashes.sh Outdated
Comment on lines +10 to +11
riot list --hash-only "${SUITE_NAME}" | sort | \
./scripts/ci_allocation_cli.py select \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore executable permission on the Riot hash helper

This commit changes this script from mode 100755 to 100644, but .gitlab/tests.yml still executes it directly, and get-riot-pip-cache-key.sh does the same while generating the pipeline. On Unix runners both paths now fail with Permission denied, preventing test configuration generation and Riot jobs from running; retain the executable bit.

Useful? React with 👍 / 👎.

lines.append(f" - riot -v run -s --pass-env wait -- {' '.join(wait_for)}")

env = self.env
env = dict(self.env or {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore executable permission on the GitLab config generator

This commit changes scripts/gen_gitlab_config.py from mode 100755 to 100644, while the tests-gen job in .gitlab-ci.yml invokes it directly as scripts/gen_gitlab_config.py --verbose. On Unix runners that job terminates immediately with Permission denied, so the generated child pipeline and all tests are unavailable; preserve mode 100755.

Useful? React with 👍 / 👎.

Comment thread scripts/ci_allocation/jobs.py Outdated
Comment on lines +39 to +41
nanoseconds = attributes.get(nanoseconds_key, 0)
if isinstance(nanoseconds, (int, float)) and not isinstance(nanoseconds, bool):
return float(nanoseconds) / 1_000_000_000

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject job exports that omit queue timing

When a Datadog job event contains neither queue_seconds nor ci.queue_time, this default turns the missing measurement into a valid 0.0. build-live-report then emits apparently complete zero-valued queue evidence, so check-ratchet can approve a candidate even though the required queue regression signal was absent; missing queue timing should fail ingestion or be represented as missing evidence.

Useful? React with 👍 / 👎.

Comment on lines +529 to +532
for strategy in ("legacy", "balanced"):
selected_jobs = [job for job in jobs if job.pipeline_id == pipeline_id and job.strategy == strategy]
if not selected_jobs:
raise AllocationError(f"live shadow CI job timings are missing for {pipeline_id} {strategy}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require every expected shard in live job timing exports

For a partial or paginated CI job export, this only checks that at least one job exists for each pipeline and strategy. If a slow shard is missing while other shard events remain, the report still computes makespan and runner consumption from the incomplete set and can pass the promotion ratchet; validate the observed shard indices and totals for every suite and strategy before accepting CI-job timing evidence.

Useful? React with 👍 / 👎.

Comment thread scripts/ci_allocation/jobs.py Outdated
if not isinstance(stage_name, str) or not stage_name:
raise AllocationError("Datadog CI job event is missing its stage name")
suite, shard_index, shard_total = suite_from_job_name(job_name, stage_name)
strategy = "balanced" if "-allocation-shadow" in job_name else "legacy"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Read the strategy instead of inferring it from shadow names

When the policy promotes balanced to the active strategy, the required jobs retain their normal names and only shadow jobs carry -allocation-shadow, so this records every active balanced job as legacy. Subsequent job-history ingestion and model provenance therefore mix balanced topology timings into the legacy population; derive the strategy from the emitted allocation tag or otherwise encode it for active jobs.

Useful? React with 👍 / 👎.

Comment on lines +53 to +57
execution = {
key: value
for key, value in properties.items()
if key.startswith("riot.") and key not in {"riot.hash", "riot.ci.allocation_strategy"}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Refuse to claim metadata parity without metadata evidence

When xdist omits testsuite properties—the case the new filename fallback explicitly anticipates—both strategies produce an empty execution dictionary here. The dictionaries compare equal, so verify-junit reports execution_metadata_parity: true even though the filenames only prove the strategy and Riot hash, not Python version or other Riot execution metadata; require the expected metadata to be present or validate it against another authoritative artifact before asserting parity.

Useful? React with 👍 / 👎.

Co-authored-by: mr-lee <1302416+mr-lee@users.noreply.github.com>
@pr-commenter

pr-commenter Bot commented Aug 13, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-08-14 22:08:52

Comparing candidate commit 40503d7 in PR branch dd/ci-runtime-aware-riot-sharding-20260813-1915 with baseline commit 41afee5 in branch main.

📊 Benchmarking dashboard

Found 0 performance improvements and 9 performance regressions! Performance is the same for 613 metrics, 10 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:httppropagationinject-ids_only

  • 🟥 execution_time [+2.414µs; +2.590µs] or [+11.426%; +12.259%]

scenario:iast_aspects-re_expand_aspect

  • 🟥 execution_time [+260.773µs; +305.545µs] or [+7.410%; +8.682%]

scenario:iastaspects-lower_aspect

  • 🟥 execution_time [+56.092µs; +60.480µs] or [+24.405%; +26.315%]

scenario:iastaspects-translate_aspect

  • 🟥 execution_time [+60.072µs; +66.217µs] or [+14.442%; +15.919%]

scenario:iastaspectsospath-ospathbasename_aspect

  • 🟥 execution_time [+138.109µs; +147.022µs] or [+36.011%; +38.335%]

scenario:iastaspectssplit-rsplit_aspect

  • 🟥 execution_time [+10.351µs; +15.655µs] or [+7.358%; +11.129%]

scenario:span-start

  • 🟥 execution_time [+1.164ms; +1.309ms] or [+7.643%; +8.599%]

scenario:telemetryaddmetric-1-count-metric-1-times

  • 🟥 execution_time [+453.480ns; +487.306ns] or [+17.254%; +18.541%]

scenario:tracer-small

  • 🟥 execution_time [+27.467µs; +30.405µs] or [+8.030%; +8.889%]

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:coreapiscenario-context_with_data_listeners

  • unstable execution_time [-888.684ns; +598.018ns] or [-8.043%; +5.412%]

scenario:coreapiscenario-core_dispatch_1_listener

  • unstable execution_time [-33.251ns; +33.978ns] or [-5.437%; +5.556%]

scenario:coreapiscenario-core_dispatch_50_listeners

  • unstable execution_time [-1760.173ns; +1541.491ns] or [-10.289%; +9.011%]

scenario:coreapiscenario-core_dispatch_exception_listeners

  • unstable execution_time [-1322.391ns; +1158.016ns] or [-10.172%; +8.907%]

scenario:coreapiscenario-core_dispatch_listeners

  • unstable execution_time [-340.182ns; +311.856ns] or [-9.233%; +8.464%]

scenario:coreapiscenario-core_dispatch_no_args_listeners

  • unstable execution_time [-266.934ns; +244.259ns] or [-9.098%; +8.325%]

scenario:coreapiscenario-core_dispatch_with_results_1_listener

  • unstable execution_time [-96.334ns; +51.527ns] or [-8.153%; +4.361%]

scenario:coreapiscenario-core_dispatch_with_results_50_listeners

  • unstable execution_time [-3814.766ns; +4202.648ns] or [-9.395%; +10.351%]

scenario:coreapiscenario-core_dispatch_with_results_listeners

  • unstable execution_time [-892.009ns; +648.225ns] or [-10.844%; +7.880%]

scenario:packagesupdateimporteddependencies-import_many_stdlib_cached

  • unstable execution_time [-61.481µs; +59.382µs] or [-9.646%; +9.316%]

Co-authored-by: mr-lee <1302416+mr-lee@users.noreply.github.com>
@mr-lee mr-lee added the changelog/no-changelog A changelog entry is not required for this PR. label Aug 14, 2026
datadog-datadog-prod-us1 Bot and others added 2 commits August 14, 2026 18:22
Co-authored-by: mr-lee <1302416+mr-lee@users.noreply.github.com>
Co-authored-by: mr-lee <1302416+mr-lee@users.noreply.github.com>
Co-authored-by: mr-lee <1302416+mr-lee@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bits AI changelog/no-changelog A changelog entry is not required for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant