Skip to content

[Dynamic Instrumentation] Add Snapshot Exploration Test infrastructure#8572

Open
dudikeleti wants to merge 25 commits into
masterfrom
dudik/snapshot-exploration-test-infra
Open

[Dynamic Instrumentation] Add Snapshot Exploration Test infrastructure#8572
dudikeleti wants to merge 25 commits into
masterfrom
dudik/snapshot-exploration-test-infra

Conversation

@dudikeleti

@dudikeleti dudikeleti commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary of changes

  • Introduce a new internal snapshot exploration test mode for Dynamic Instrumentation that generates a JSON probe file for a third-party exploration test project, runs those probes in the test host, and writes per-process CSV reports for captured snapshots and probe statuses.
  • Add Nuke targets SetUpSnapshotExplorationTests / RunSnapshotExplorationTests to generate SnapshotExplorationTestProbes.json, run the selected debugger exploration test, and analyze the generated reports/logs after the test completes.
  • Add managed runtime plumbing that is activated only inside a vstest host when DD_INTERNAL_SNAPSHOT_EXPLORATION_TEST_ROOT_PATH is present: SnapshotExplorationTestSink, SnapshotExplorationProbeStatusPoller + SnapshotExplorationProbeStatusReporter, SnapshotExplorationConstants, NoOpDebuggerUploader, and a snapshot-exploration init path on DebuggerManager.
  • Extract an ISnapshotSink interface so normal debugger uploads continue to use SnapshotSink, while snapshot exploration can use SnapshotExplorationTestSink to write report rows instead of buffering uploads for an agent.
  • Treat DD_INTERNAL_SNAPSHOT_EXPLORATION_TEST_ROOT_PATH as an internal test-harness environment variable, not a generated ConfigurationKeys.Debugger setting, and exclude it from configuration telemetry registration validation.
  • Add unit tests for SnapshotExplorationTestSink report writing, CSV escaping, duplicate probe handling, and invalid/partial snapshot grading.

Reason for change

  • We need a mechanism to validate Dynamic Instrumentation across many real-world methods without standing up an agent or live RCM flow.
  • The exploration-test harness gives us broad third-party method coverage, while the new sink/reporting path gives a build-side artifact that identifies which generated probes produced valid snapshots and which failed or were skipped.

Implementation details

  • Snapshot exploration is enabled only when DD_INTERNAL_SNAPSHOT_EXPLORATION_TEST_ROOT_PATH is set and the current process is detected as a vstest host. Child processes may inherit the env var, but they keep the normal debugger path.
  • The Nuke setup target reflects over built exploration-test assemblies and writes SnapshotExplorationTestProbes.json with LOG_PROBE definitions, captureSnapshot=true, and a deterministic sampled when expression for some instance methods.
  • Exploration runs are agent-less: tracing, telemetry retries, and agent discovery are disabled through the test environment so report/log analysis focuses on debugger behavior.
  • In snapshot exploration mode, snapshot/log uploaders share the same report-writing sink, diagnostics and symbol upload are replaced with no-op uploaders, and probe status polling writes local CSV status reports.
  • This PR only adds the local/Nuke infrastructure; it does not add or modify Azure Pipelines stages, schedules, or CI gating for snapshot exploration runs.

Test coverage

  • SnapshotExplorationTestSinkTests (7 cases: report content, nested-logger handling, CSV escaping, probe-id dedup, and invalid/partial-snapshot grading).
  • The end-to-end RunSnapshotExplorationTests Nuke target is intentionally not wired into CI in this PR because it is long-running and debugger-specific.

Other details

@pr-commenter

pr-commenter Bot commented May 6, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-06-17 18:03:55

Comparing candidate commit f20c7cf in PR branch dudik/snapshot-exploration-test-infra with baseline commit af21006 in branch master.

📊 Benchmarking dashboard

Found 0 performance improvements and 1 performance regressions! Performance is the same for 70 metrics, 1 unstable metrics, 62 known flaky benchmarks, 64 flaky benchmarks without significant changes.

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:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TracerBenchmark.StartActiveSpan net472

  • 🟥 throughput [-13555.354op/s; -12195.167op/s] or [-6.878%; -6.188%]

Unstable benchmarks

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

scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TracerBenchmark.StartSpan_SetActive netcoreapp3.1

  • unstable execution_time [+9.962ms; +34.059ms] or [+5.565%; +19.025%]

Known flaky benchmarks

These benchmarks are marked as flaky and will not trigger a failure. Modify FLAKY_BENCHMARKS_REGEX to control which benchmarks are marked as flaky.

scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_RecordException_Sampled net472

  • 🟩 throughput [+7326.549op/s; +8309.874op/s] or [+6.223%; +7.059%]

scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild net472

  • 🟥 throughput [-5885.194op/s; -5359.727op/s] or [-6.978%; -6.355%]

scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild netcoreapp3.1

  • 🟥 throughput [-8670.453op/s; -6960.878op/s] or [-8.816%; -7.078%]

scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net472

  • 🟥 execution_time [+299.927ms; +301.482ms] or [+148.834%; +149.606%]
  • 🟥 throughput [-47.865op/s; -44.261op/s] or [-8.612%; -7.963%]

scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net6.0

  • 🟥 execution_time [+380.186ms; +382.492ms] or [+300.370%; +302.192%]
  • 🟩 throughput [+90.189op/s; +94.089op/s] or [+11.891%; +12.405%]

scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces netcoreapp3.1

  • 🟥 execution_time [+397.549ms; +399.609ms] or [+351.815%; +353.639%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody net472

  • 🟥 allocated_mem [+1.308KB; +1.308KB] or [+27.528%; +27.540%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody net6.0

  • 🟥 allocated_mem [+471 bytes; +472 bytes] or [+9.976%; +9.987%]
  • 🟩 execution_time [-16.838ms; -12.671ms] or [-7.864%; -5.918%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody netcoreapp3.1

  • 🟥 allocated_mem [+1.272KB; +1.272KB] or [+27.500%; +27.510%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody net472

  • 🟥 allocated_mem [+1.307KB; +1.307KB] or [+105.743%; +105.758%]
  • 🟥 throughput [-270501.435op/s; -266935.874op/s] or [-27.620%; -27.255%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody net6.0

  • 🟥 allocated_mem [+471 bytes; +472 bytes] or [+38.557%; +38.566%]
  • 🟩 execution_time [-26.514ms; -21.650ms] or [-11.824%; -9.655%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody netcoreapp3.1

  • 🟥 allocated_mem [+1.272KB; +1.272KB] or [+105.288%; +105.304%]
  • 🟥 throughput [-156516.744op/s; -139934.089op/s] or [-22.488%; -20.106%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody net6.0

  • 🟩 throughput [+9460.516op/s; +12494.052op/s] or [+6.020%; +7.950%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody netcoreapp3.1

  • 🟩 throughput [+7409.578op/s; +10157.050op/s] or [+5.903%; +8.091%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody net6.0

  • 🟩 throughput [+420269.797op/s; +455202.756op/s] or [+14.014%; +15.178%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody netcoreapp3.1

  • 🟩 execution_time [-19.325ms; -14.988ms] or [-8.908%; -6.909%]
  • 🟩 throughput [+182479.727op/s; +236190.415op/s] or [+7.243%; +9.375%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeArgs net472

  • 🟥 execution_time [+299.390ms; +300.172ms] or [+149.595%; +149.986%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeArgs net6.0

  • 🟥 execution_time [+296.510ms; +309.491ms] or [+149.531%; +156.077%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeArgs netcoreapp3.1

  • 🟥 execution_time [+298.875ms; +301.259ms] or [+150.550%; +151.751%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs net472

  • 🟥 execution_time [+296.301ms; +298.291ms] or [+145.531%; +146.509%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs net6.0

  • 🟥 execution_time [+295.435ms; +298.704ms] or [+144.427%; +146.025%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs netcoreapp3.1

  • 🟥 execution_time [+301.086ms; +302.824ms] or [+150.482%; +151.351%]

scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmarkWithAttack net6.0

  • 🟥 execution_time [+21.053µs; +44.676µs] or [+6.721%; +14.263%]
  • 🟥 throughput [-417.579op/s; -218.775op/s] or [-13.017%; -6.820%]

scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest net472

  • 🟥 execution_time [+300.045ms; +300.880ms] or [+149.753%; +150.170%]

scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest net6.0

  • 🟥 execution_time [+406.269ms; +413.879ms] or [+441.428%; +449.696%]

scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest netcoreapp3.1

  • 🟥 execution_time [+359.672ms; +369.151ms] or [+273.096%; +280.293%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net472

  • unstable execution_time [+324.279ms; +385.765ms] or [+149.100%; +177.371%]
  • 🟥 throughput [-553.692op/s; -508.156op/s] or [-50.170%; -46.044%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net6.0

  • unstable execution_time [+146.966ms; +293.132ms] or [+62.631%; +124.920%]
  • 🟥 throughput [-669.040op/s; -585.581op/s] or [-44.625%; -39.058%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces netcoreapp3.1

  • 🟥 allocated_mem [+2.305KB; +2.308KB] or [+5.442%; +5.450%]
  • 🟥 execution_time [+343.593ms; +354.260ms] or [+205.509%; +211.889%]
  • 🟥 throughput [-421.099op/s; -378.295op/s] or [-29.321%; -26.340%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice net6.0

  • 🟩 execution_time [-177.144µs; -123.605µs] or [-8.973%; -6.261%]
  • 🟩 throughput [+36.006op/s; +50.175op/s] or [+7.108%; +9.905%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net472

  • 🟥 execution_time [+299.926ms; +302.311ms] or [+151.037%; +152.238%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net6.0

  • 🟥 execution_time [+299.551ms; +301.407ms] or [+150.105%; +151.035%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch netcoreapp3.1

  • 🟥 execution_time [+302.084ms; +305.430ms] or [+151.754%; +153.435%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync net472

  • 🟥 execution_time [+302.976ms; +304.876ms] or [+152.145%; +153.099%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync net6.0

  • 🟥 execution_time [+299.966ms; +302.030ms] or [+148.320%; +149.340%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync netcoreapp3.1

  • 🟥 execution_time [+301.570ms; +305.654ms] or [+152.849%; +154.919%]

scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync net472

  • 🟥 execution_time [+299.233ms; +301.984ms] or [+150.188%; +151.569%]

scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync net6.0

  • 🟥 execution_time [+303.200ms; +306.436ms] or [+151.117%; +152.730%]
  • 🟩 throughput [+58079.928op/s; +65463.169op/s] or [+11.533%; +12.999%]

scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync netcoreapp3.1

  • 🟥 execution_time [+300.639ms; +303.745ms] or [+149.565%; +151.110%]

scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog net6.0

  • 🟩 execution_time [-16.741ms; -13.098ms] or [-7.785%; -6.091%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark net472

  • unstable execution_time [+6.397µs; +50.774µs] or [+1.580%; +12.541%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark net6.0

  • 🟩 allocated_mem [-19.313KB; -19.289KB] or [-7.045%; -7.036%]
  • unstable execution_time [-33.504µs; +25.643µs] or [-6.622%; +5.068%]
  • unstable throughput [-86.963op/s; +122.046op/s] or [-4.340%; +6.090%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark netcoreapp3.1

  • unstable execution_time [-59.130µs; +6.712µs] or [-10.247%; +1.163%]
  • unstable throughput [+2.559op/s; +181.978op/s] or [+0.146%; +10.397%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatBenchmark net472

  • 🟥 allocated_mem [+8.190KB; +8.196KB] or [+16.663%; +16.675%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatBenchmark net6.0

  • unstable execution_time [+6.656µs; +11.443µs] or [+15.733%; +27.047%]
  • 🟥 throughput [-5031.686op/s; -3131.089op/s] or [-21.182%; -13.181%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatBenchmark netcoreapp3.1

  • unstable execution_time [-13.514µs; -5.248µs] or [-20.966%; -8.143%]
  • unstable throughput [+1331.731op/s; +3164.992op/s] or [+8.171%; +19.418%]

scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog net472

  • 🟥 execution_time [+302.808ms; +304.592ms] or [+153.056%; +153.958%]

scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog net6.0

  • 🟥 execution_time [+305.696ms; +309.313ms] or [+155.598%; +157.439%]

scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog netcoreapp3.1

  • 🟥 execution_time [+298.722ms; +301.479ms] or [+149.547%; +150.927%]

scenario:Benchmarks.Trace.RedisBenchmark.SendReceive net6.0

  • 🟩 throughput [+31181.834op/s; +35171.091op/s] or [+5.902%; +6.657%]

scenario:Benchmarks.Trace.SerilogBenchmark.EnrichedLog net472

  • 🟥 execution_time [+297.766ms; +300.247ms] or [+148.409%; +149.646%]

scenario:Benchmarks.Trace.SerilogBenchmark.EnrichedLog net6.0

  • 🟥 execution_time [+300.411ms; +301.677ms] or [+150.852%; +151.488%]

scenario:Benchmarks.Trace.SerilogBenchmark.EnrichedLog netcoreapp3.1

  • 🟥 execution_time [+303.210ms; +305.454ms] or [+153.769%; +154.907%]

scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore net472

  • 🟥 execution_time [+300.788ms; +301.903ms] or [+150.035%; +150.591%]
  • 🟩 throughput [+65990235.134op/s; +66383500.600op/s] or [+48.058%; +48.345%]

scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore net6.0

  • 🟥 execution_time [+419.940ms; +423.327ms] or [+522.271%; +526.483%]

scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore netcoreapp3.1

  • 🟥 execution_time [+298.857ms; +300.056ms] or [+149.063%; +149.661%]
  • 🟩 throughput [+18086497.807op/s; +19078280.573op/s] or [+8.011%; +8.450%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope net6.0

  • 🟩 throughput [+87674.246op/s; +96387.432op/s] or [+8.186%; +8.999%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope netcoreapp3.1

  • 🟩 throughput [+52159.217op/s; +71137.221op/s] or [+6.037%; +8.234%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan netcoreapp3.1

  • 🟩 throughput [+77526.139op/s; +86300.255op/s] or [+7.700%; +8.571%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes net6.0

  • 🟩 throughput [+47158.540op/s; +58879.508op/s] or [+8.563%; +10.691%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes netcoreapp3.1

  • 🟩 throughput [+25844.787op/s; +35543.139op/s] or [+5.785%; +7.956%]

scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin net6.0

  • 🟩 throughput [+72277.955op/s; +91330.500op/s] or [+8.075%; +10.204%]

Known flaky benchmarks without significant changes:

  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_AddEvent_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_AddEvent_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_AddEvent_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_GetContext_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_GetContext_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_GetContext_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetAttributes_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetAttributes_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetAttributes_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetStatus_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetStatus_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetStatus_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_UpdateName_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_UpdateName_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_UpdateName_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_AddEvent_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_AddEvent_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_AddEvent_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_GetContext_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_GetContext_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_GetContext_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_RecordException_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_RecordException_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetAttributes_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetAttributes_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetAttributes_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetStatus_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetStatus_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetStatus_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_UpdateName_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_UpdateName_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_UpdateName_Sampled netcoreapp3.1
  • scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild net6.0
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody net472
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody net472
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmark net472
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmark net6.0
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmark netcoreapp3.1
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmarkWithAttack net472
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmarkWithAttack netcoreapp3.1
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice net472
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice net6.0
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice netcoreapp3.1
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool net472
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool net6.0
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool netcoreapp3.1
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice net472
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice netcoreapp3.1
  • scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog net472
  • scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog netcoreapp3.1
  • scenario:Benchmarks.Trace.RedisBenchmark.SendReceive net472
  • scenario:Benchmarks.Trace.RedisBenchmark.SendReceive netcoreapp3.1
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope net472
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan net472
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan net6.0
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes net472
  • scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin net472
  • scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin netcoreapp3.1

@dudikeleti
dudikeleti force-pushed the dudik/snapshot-exploration-test-infra branch from baaa4d5 to dcf4b8a Compare June 3, 2026 12:20
@datadog-datadog-prod-us1

This comment has been minimized.

@dd-trace-dotnet-ci-bot

dd-trace-dotnet-ci-bot Bot commented Jun 3, 2026

Copy link
Copy Markdown

Execution-Time Benchmarks Report ⏱️

Execution-time results for samples comparing This PR (8572) and master.

✅ No regressions detected - check the details below

Full Metrics Comparison

FakeDbCommand

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration72.49 ± (72.52 - 72.81) ms73.75 ± (73.56 - 74.08) ms+1.7%✅⬆️
.NET Framework 4.8 - Bailout
duration76.78 ± (76.59 - 76.94) ms76.78 ± (76.77 - 77.21) ms+0.0%✅⬆️
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1092.63 ± (1091.14 - 1097.02) ms1087.85 ± (1087.69 - 1093.41) ms-0.4%
.NET Core 3.1 - Baseline
process.internal_duration_ms22.35 ± (22.32 - 22.38) ms22.56 ± (22.50 - 22.61) ms+0.9%✅⬆️
process.time_to_main_ms83.38 ± (83.19 - 83.57) ms85.31 ± (84.99 - 85.62) ms+2.3%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.90 ± (10.90 - 10.90) MB10.91 ± (10.90 - 10.91) MB+0.1%✅⬆️
runtime.dotnet.threads.count12 ± (12 - 12)12 ± (12 - 12)+0.0%
.NET Core 3.1 - Bailout
process.internal_duration_ms22.49 ± (22.44 - 22.55) ms22.31 ± (22.27 - 22.35) ms-0.8%
process.time_to_main_ms86.26 ± (85.97 - 86.56) ms84.96 ± (84.76 - 85.15) ms-1.5%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.94 ± (10.93 - 10.94) MB10.95 ± (10.94 - 10.95) MB+0.1%✅⬆️
runtime.dotnet.threads.count13 ± (13 - 13)13 ± (13 - 13)+0.0%
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms214.13 ± (213.22 - 215.04) ms215.51 ± (214.55 - 216.47) ms+0.6%✅⬆️
process.time_to_main_ms541.30 ± (540.02 - 542.58) ms543.78 ± (542.45 - 545.11) ms+0.5%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed48.67 ± (48.64 - 48.70) MB48.65 ± (48.63 - 48.68) MB-0.0%
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)-0.3%
.NET 6 - Baseline
process.internal_duration_ms21.72 ± (21.65 - 21.79) ms21.63 ± (21.57 - 21.69) ms-0.4%
process.time_to_main_ms75.86 ± (75.55 - 76.16) ms74.36 ± (74.11 - 74.60) ms-2.0%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.64 ± (10.64 - 10.64) MB10.62 ± (10.62 - 10.62) MB-0.2%
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 6 - Bailout
process.internal_duration_ms21.10 ± (21.06 - 21.13) ms21.36 ± (21.31 - 21.40) ms+1.2%✅⬆️
process.time_to_main_ms73.65 ± (73.48 - 73.83) ms74.73 ± (74.51 - 74.95) ms+1.5%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.74 ± (10.74 - 10.74) MB10.73 ± (10.73 - 10.73) MB-0.1%
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms372.53 ± (370.62 - 374.43) ms370.76 ± (368.64 - 372.87) ms-0.5%
process.time_to_main_ms546.35 ± (544.97 - 547.73) ms547.95 ± (546.89 - 549.02) ms+0.3%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed50.12 ± (50.10 - 50.14) MB50.04 ± (50.01 - 50.06) MB-0.2%
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)-0.2%
.NET 8 - Baseline
process.internal_duration_ms19.44 ± (19.41 - 19.47) ms19.48 ± (19.44 - 19.52) ms+0.2%✅⬆️
process.time_to_main_ms72.19 ± (72.00 - 72.38) ms72.29 ± (72.13 - 72.45) ms+0.1%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed7.67 ± (7.67 - 7.68) MB7.67 ± (7.66 - 7.67) MB-0.1%
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 8 - Bailout
process.internal_duration_ms19.67 ± (19.61 - 19.73) ms19.76 ± (19.71 - 19.81) ms+0.5%✅⬆️
process.time_to_main_ms75.51 ± (75.27 - 75.76) ms75.51 ± (75.31 - 75.72) ms-0.0%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed7.72 ± (7.72 - 7.73) MB7.72 ± (7.71 - 7.72) MB-0.1%
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms293.68 ± (291.34 - 296.02) ms293.89 ± (291.74 - 296.04) ms+0.1%✅⬆️
process.time_to_main_ms497.51 ± (496.53 - 498.49) ms497.73 ± (496.79 - 498.67) ms+0.0%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed37.18 ± (37.15 - 37.21) MB37.21 ± (37.18 - 37.23) MB+0.1%✅⬆️
runtime.dotnet.threads.count27 ± (27 - 27)27 ± (27 - 27)+0.5%✅⬆️

HttpMessageHandler

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration201.20 ± (200.53 - 201.63) ms199.90 ± (199.76 - 200.69) ms-0.6%
.NET Framework 4.8 - Bailout
duration205.28 ± (204.62 - 205.65) ms204.55 ± (203.88 - 204.87) ms-0.4%
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1197.10 ± (1194.03 - 1199.98) ms1196.94 ± (1197.54 - 1203.36) ms-0.0%
.NET Core 3.1 - Baseline
process.internal_duration_ms193.30 ± (192.87 - 193.74) ms192.89 ± (192.45 - 193.33) ms-0.2%
process.time_to_main_ms83.77 ± (83.43 - 84.10) ms83.44 ± (83.14 - 83.73) ms-0.4%
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed16.05 ± (16.03 - 16.07) MB16.09 ± (16.07 - 16.11) MB+0.2%✅⬆️
runtime.dotnet.threads.count20 ± (20 - 20)20 ± (20 - 20)+0.1%✅⬆️
.NET Core 3.1 - Bailout
process.internal_duration_ms192.16 ± (191.74 - 192.58) ms193.30 ± (192.84 - 193.75) ms+0.6%✅⬆️
process.time_to_main_ms83.98 ± (83.72 - 84.24) ms85.17 ± (84.87 - 85.48) ms+1.4%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed16.12 ± (16.10 - 16.15) MB16.07 ± (16.04 - 16.11) MB-0.3%
runtime.dotnet.threads.count21 ± (21 - 21)21 ± (20 - 21)-0.1%
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms386.33 ± (384.64 - 388.01) ms386.21 ± (384.78 - 387.64) ms-0.0%
process.time_to_main_ms537.51 ± (536.18 - 538.84) ms540.10 ± (538.84 - 541.35) ms+0.5%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed57.89 ± (57.68 - 58.10) MB57.77 ± (57.56 - 57.98) MB-0.2%
runtime.dotnet.threads.count30 ± (30 - 30)30 ± (30 - 30)-0.2%
.NET 6 - Baseline
process.internal_duration_ms199.87 ± (199.37 - 200.37) ms198.42 ± (197.98 - 198.87) ms-0.7%
process.time_to_main_ms73.34 ± (73.10 - 73.58) ms72.85 ± (72.58 - 73.13) ms-0.7%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed16.39 ± (16.36 - 16.42) MB16.40 ± (16.38 - 16.42) MB+0.1%✅⬆️
runtime.dotnet.threads.count19 ± (19 - 19)19 ± (19 - 19)+0.9%✅⬆️
.NET 6 - Bailout
process.internal_duration_ms198.67 ± (198.30 - 199.04) ms197.01 ± (196.60 - 197.42) ms-0.8%
process.time_to_main_ms74.45 ± (74.18 - 74.73) ms73.72 ± (73.52 - 73.92) ms-1.0%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed16.41 ± (16.39 - 16.44) MB16.49 ± (16.46 - 16.51) MB+0.4%✅⬆️
runtime.dotnet.threads.count20 ± (20 - 20)20 ± (20 - 20)+0.1%✅⬆️
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms585.08 ± (582.64 - 587.52) ms586.73 ± (584.42 - 589.04) ms+0.3%✅⬆️
process.time_to_main_ms547.69 ± (546.45 - 548.94) ms547.16 ± (546.08 - 548.24) ms-0.1%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed61.44 ± (61.35 - 61.53) MB61.56 ± (61.47 - 61.65) MB+0.2%✅⬆️
runtime.dotnet.threads.count31 ± (31 - 31)31 ± (31 - 31)-0.0%
.NET 8 - Baseline
process.internal_duration_ms197.26 ± (196.82 - 197.70) ms195.11 ± (194.62 - 195.60) ms-1.1%
process.time_to_main_ms72.33 ± (72.04 - 72.63) ms71.81 ± (71.58 - 72.05) ms-0.7%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed11.70 ± (11.69 - 11.72) MB11.71 ± (11.69 - 11.73) MB+0.1%✅⬆️
runtime.dotnet.threads.count18 ± (18 - 18)18 ± (18 - 18)-0.0%
.NET 8 - Bailout
process.internal_duration_ms196.99 ± (196.54 - 197.45) ms195.35 ± (194.71 - 195.99) ms-0.8%
process.time_to_main_ms73.79 ± (73.58 - 74.01) ms73.21 ± (72.93 - 73.50) ms-0.8%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed11.75 ± (11.73 - 11.77) MB11.79 ± (11.77 - 11.81) MB+0.3%✅⬆️
runtime.dotnet.threads.count19 ± (19 - 19)19 ± (19 - 19)-0.0%
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms507.27 ± (504.71 - 509.83) ms511.34 ± (508.57 - 514.11) ms+0.8%✅⬆️
process.time_to_main_ms498.16 ± (497.16 - 499.16) ms495.25 ± (494.23 - 496.26) ms-0.6%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed50.88 ± (50.84 - 50.91) MB50.96 ± (50.93 - 51.00) MB+0.2%✅⬆️
runtime.dotnet.threads.count30 ± (29 - 30)30 ± (29 - 30)+0.2%✅⬆️
Comparison explanation

Execution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:

  • Welch test with statistical test for significance of 5%
  • Only results indicating a difference greater than 5% and 5 ms are considered.

Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard.

Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph).

Duration charts
FakeDbCommand (.NET Framework 4.8)
gantt
    title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8572) - mean (74ms)  : 70, 78
    master - mean (73ms)  : 71, 75

    section Bailout
    This PR (8572) - mean (77ms)  : 74, 80
    master - mean (77ms)  : 75, 79

    section CallTarget+Inlining+NGEN
    This PR (8572) - mean (1,091ms)  : 1048, 1133
    master - mean (1,094ms)  : 1050, 1139

Loading
FakeDbCommand (.NET Core 3.1)
gantt
    title Execution time (ms) FakeDbCommand (.NET Core 3.1)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8572) - mean (115ms)  : 109, 121
    master - mean (113ms)  : 109, 116

    section Bailout
    This PR (8572) - mean (114ms)  : 111, 117
    master - mean (116ms)  : 110, 122

    section CallTarget+Inlining+NGEN
    This PR (8572) - mean (795ms)  : 770, 820
    master - mean (793ms)  : 769, 817

Loading
FakeDbCommand (.NET 6)
gantt
    title Execution time (ms) FakeDbCommand (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8572) - mean (103ms)  : 98, 107
    master - mean (104ms)  : 98, 111

    section Bailout
    This PR (8572) - mean (103ms)  : 98, 108
    master - mean (101ms)  : 98, 104

    section CallTarget+Inlining+NGEN
    This PR (8572) - mean (947ms)  : 912, 983
    master - mean (949ms)  : 908, 991

Loading
FakeDbCommand (.NET 8)
gantt
    title Execution time (ms) FakeDbCommand (.NET 8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8572) - mean (99ms)  : 97, 102
    master - mean (99ms)  : 96, 103

    section Bailout
    This PR (8572) - mean (103ms)  : 99, 107
    master - mean (103ms)  : 100, 107

    section CallTarget+Inlining+NGEN
    This PR (8572) - mean (823ms)  : 784, 861
    master - mean (821ms)  : 785, 858

Loading
HttpMessageHandler (.NET Framework 4.8)
gantt
    title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8572) - mean (200ms)  : 195, 206
    master - mean (201ms)  : 193, 209

    section Bailout
    This PR (8572) - mean (204ms)  : 199, 210
    master - mean (205ms)  : 197, 213

    section CallTarget+Inlining+NGEN
    This PR (8572) - mean (1,200ms)  : 1158, 1243
    master - mean (1,197ms)  : 1163, 1231

Loading
HttpMessageHandler (.NET Core 3.1)
gantt
    title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8572) - mean (286ms)  : 277, 296
    master - mean (287ms)  : 281, 293

    section Bailout
    This PR (8572) - mean (288ms)  : 284, 293
    master - mean (285ms)  : 279, 291

    section CallTarget+Inlining+NGEN
    This PR (8572) - mean (967ms)  : 947, 987
    master - mean (964ms)  : 945, 984

Loading
HttpMessageHandler (.NET 6)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8572) - mean (281ms)  : 272, 290
    master - mean (282ms)  : 274, 289

    section Bailout
    This PR (8572) - mean (280ms)  : 275, 285
    master - mean (282ms)  : 275, 289

    section CallTarget+Inlining+NGEN
    This PR (8572) - mean (1,163ms)  : 1115, 1211
    master - mean (1,161ms)  : 1126, 1197

Loading
HttpMessageHandler (.NET 8)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8572) - mean (278ms)  : 267, 288
    master - mean (281ms)  : 273, 289

    section Bailout
    This PR (8572) - mean (279ms)  : 267, 290
    master - mean (282ms)  : 276, 288

    section CallTarget+Inlining+NGEN
    This PR (8572) - mean (1,039ms)  : 992, 1086
    master - mean (1,038ms)  : 1007, 1070

Loading

@dudikeleti
dudikeleti force-pushed the dudik/snapshot-exploration-test-infra branch 2 times, most recently from 10a46c1 to 3f67c3c Compare June 5, 2026 20:46
@dudikeleti
dudikeleti requested a review from Copilot June 5, 2026 20:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new “snapshot exploration test” mode for Dynamic Instrumentation: a build-driven pipeline that installs many method probes against third-party test projects, captures a single snapshot per probe, writes per-process CSV artifacts, and then analyzes managed/native logs + CSVs to grade probe validity.

Changes:

  • Added snapshot-exploration runtime plumbing (test-host detection/constants, CSV snapshot sink, probe-status CSV reporting/polling, and factory wiring to use no-op uploaders in this mode).
  • Updated snapshot/log sink/uploader plumbing (introduced ISnapshotSink, made snapshot payload nullable end-to-end, and adjusted uploader base disposal semantics).
  • Added Nuke targets + analysis implementation for generating probes and validating results (SetUpSnapshotExplorationTests / RunSnapshotExplorationTests) plus a unit test for the new sink.

Reviewed changes

Copilot reviewed 26 out of 30 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tracer/test/Datadog.Trace.Tests/Telemetry/config_norm_rules.json Adds normalization rule for new internal snapshot exploration config key.
tracer/test/Datadog.Trace.Tests/Debugger/SnapshotExplorationTestSinkTests.cs New unit test validating root-level logger extraction behavior.
tracer/test/Datadog.Trace.Tests/Debugger/DynamicInstrumentationTests.cs Updates mocks for nullable snapshot payload signature.
tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Debugger.g.cs Adds generated const for snapshot exploration root path key.
tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Debugger.g.cs Same generated key addition (netcoreapp3.1).
tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Debugger.g.cs Same generated key addition (net6.0).
tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Debugger.g.cs Same generated key addition (net461).
tracer/src/Datadog.Trace/Debugger/Snapshots/DebuggerSnapshotCreator.cs Small snapshot JSON writer robustness comment/guard.
tracer/src/Datadog.Trace/Debugger/SnapshotExplorationConstants.cs New constants + vstest-host detection helper for snapshot exploration mode.
tracer/src/Datadog.Trace/Debugger/Sink/SnapshotUploader.cs Switches to ISnapshotSink, accepts nullable snapshots, and adds sink disposal hook.
tracer/src/Datadog.Trace/Debugger/Sink/SnapshotSink.cs Implements ISnapshotSink, routes to CSV sink in exploration mode, and propagates nullable snapshots.
tracer/src/Datadog.Trace/Debugger/Sink/SnapshotExplorationTestSink.cs New CSV-writing snapshot sink for exploration test reporting.
tracer/src/Datadog.Trace/Debugger/Sink/NoOpDebuggerUploader.cs New no-op uploader used to disable diagnostics in exploration mode.
tracer/src/Datadog.Trace/Debugger/Sink/ISnapshotUploader.cs Updates snapshot payload to nullable.
tracer/src/Datadog.Trace/Debugger/Sink/ISnapshotSink.cs New sink abstraction used by uploaders/sinks.
tracer/src/Datadog.Trace/Debugger/Sink/DebuggerUploaderBase.cs Makes disposal idempotent via an interlocked flag and adds protected virtual dispose hook.
tracer/src/Datadog.Trace/Debugger/ProbeStatuses/SnapshotExplorationProbeStatusReporter.cs New per-process CSV writer for probe status events in exploration mode.
tracer/src/Datadog.Trace/Debugger/ProbeStatuses/SnapshotExplorationProbeStatusPoller.cs New probe status poller that writes to CSV instead of normal diagnostics flow.
tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionsProcessor.cs Removes unused Newtonsoft import.
tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionEvaluator.cs Refactors/relocates CompileAll helper.
tracer/src/Datadog.Trace/Debugger/ExceptionAutoInstrumentation/ExceptionReplay.cs Switches snapshot sink type + nullable snapshot handling.
tracer/src/Datadog.Trace/Debugger/DynamicInstrumentation.cs Exploration-mode rate limit override and nullable snapshot/log plumbing.
tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs Adds snapshot exploration root path setting and derives exploration-mode enablement.
tracer/src/Datadog.Trace/Debugger/DebuggerManager.SnapshotExploration.cs New snapshot exploration init path (test-host only) + native log polling for probe install readiness.
tracer/src/Datadog.Trace/Debugger/DebuggerManager.cs Routes snapshot exploration mode into the specialized init path.
tracer/src/Datadog.Trace/Debugger/DebuggerFactory.cs Wires exploration mode sinks/uploaders/poller and avoids double-opening the CSV writer.
tracer/src/Datadog.Trace/Configuration/supported-configurations.yaml Documents new internal snapshot exploration root path configuration.
tracer/build/_build/BuildVariables.cs Adds exploration-mode env var changes for offline snapshot runs.
tracer/build/_build/Build.SnapshotExplorationTest.cs New Nuke implementation for generating probes, running tests, and analyzing results.
tracer/build/_build/Build.ExplorationTests.cs Adds Nuke targets and shared test runner helper; updates log env vars for long runs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tracer/build/_build/Build.SnapshotExplorationTest.cs
Comment thread tracer/build/_build/Build.SnapshotExplorationTest.cs Outdated
Comment thread tracer/build/_build/Build.SnapshotExplorationTest.cs
Comment thread tracer/src/Datadog.Trace/Debugger/Sink/ISnapshotSink.cs
Comment thread tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs Outdated
@dudikeleti
dudikeleti force-pushed the dudik/snapshot-exploration-test-infra branch from 3f67c3c to c4d370f Compare June 9, 2026 10:54
@dudikeleti
dudikeleti requested a review from Copilot June 9, 2026 11:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 30 changed files in this pull request and generated 2 comments.

Comment thread tracer/build/_build/Build.SnapshotExplorationTest.cs
Comment thread tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 25 changed files in this pull request and generated 6 comments.

Comment thread tracer/build/_build/Build.SnapshotExplorationTest.cs
Comment thread tracer/src/Datadog.Trace/Configuration/supported-configurations.yaml Outdated
dudikeleti added a commit that referenced this pull request Jun 9, 2026
## Summary of changes

- Simplify `ProbeExpressionEvaluator.CompileAll` by dropping the
redundant `== null ? null :` ternaries; each `Compile*` helper already
null-checks its source and returns `null`.
- Use the nullable-aware `StringUtil.IsNullOrEmpty` instead of
`string.IsNullOrEmpty` in the `#nullable enable`
`ProbeExpressionEvaluator`.
- Harden `DebuggerManager.ShouldSkipDiUpdate` with a property-pattern
null check (`debuggerSettings is { DynamicInstrumentationCanBeEnabled:
false }`).
- Remove unused usings in `ProbeExpressionsProcessor` (`Newtonsoft.Json`
and `Debugger.Snapshots`).
- Add a clarifying comment in `DebuggerSnapshotCreator.EndEntry` about
why arg/local capture must not rely on heuristics.
- Fix a logging bug in `ProbeExpressionsProcessor.AddProbeProcessor` so
it no longer logs `"Successfully created probe processor"` after
creation throws.

## Reason for change

- These are low-risk cleanups split out of #8572 to keep that PR focused
on the snapshot-exploration feature.
- The `AddProbeProcessor` change fixes a real (debug-level) logging
inconsistency: a probe whose processor creation throws would log both a
failure and a success line.

## Implementation details

- `CompileAll` simplification is behavior-preserving:
`CompileTemplates`/`CompileCondition`/`CompileMetric`/`CompileDecorations`
each return `null` when their source is null. One benign difference:
when `SpanDecorations` is null, `CompileDecorations` now emits its
existing debug-level "is null" log.
- `StringUtil.IsNullOrEmpty` only adds value under `#nullable enable`
(it carries the `[NotNullWhen(false)]` annotation), so the swap was
limited to that file; other `string.IsNullOrEmpty` call sites in
non-nullable files were intentionally left unchanged.
- The `ShouldSkipDiUpdate` property pattern is equivalent for the normal
(non-null) case and additionally avoids a potential NRE if settings were
ever null.

## Test coverage

- No new tests added; changes are cleanups plus one debug-only logging
fix.

---------

Co-authored-by: Cursor <[email protected]>
@dudikeleti
dudikeleti requested a review from Copilot June 9, 2026 14:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 25 changed files in this pull request and generated 4 comments.

Comment thread tracer/build/_build/Build.SnapshotExplorationTest.cs Outdated
Comment thread tracer/build/_build/Build.SnapshotExplorationTest.cs Outdated
Comment thread tracer/build/_build/Build.SnapshotExplorationTest.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Sink/SnapshotExplorationTestSink.cs Outdated
@dudikeleti
dudikeleti force-pushed the dudik/snapshot-exploration-test-infra branch 2 times, most recently from 5a7085a to d25407f Compare June 9, 2026 16:43
@dudikeleti
dudikeleti requested a review from Copilot June 9, 2026 16:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.

Comment thread tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs
Comment thread tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs
@dudikeleti
dudikeleti marked this pull request as ready for review June 9, 2026 17:11
@dudikeleti
dudikeleti requested a review from a team as a code owner June 9, 2026 17:11
@dudikeleti
dudikeleti removed the request for review from P403n1x87 June 10, 2026 15:40
@dudikeleti
dudikeleti enabled auto-merge (squash) June 11, 2026 15:10
zacharycmontoya pushed a commit that referenced this pull request Jun 16, 2026
## Summary of changes

- Simplify `ProbeExpressionEvaluator.CompileAll` by dropping the
redundant `== null ? null :` ternaries; each `Compile*` helper already
null-checks its source and returns `null`.
- Use the nullable-aware `StringUtil.IsNullOrEmpty` instead of
`string.IsNullOrEmpty` in the `#nullable enable`
`ProbeExpressionEvaluator`.
- Harden `DebuggerManager.ShouldSkipDiUpdate` with a property-pattern
null check (`debuggerSettings is { DynamicInstrumentationCanBeEnabled:
false }`).
- Remove unused usings in `ProbeExpressionsProcessor` (`Newtonsoft.Json`
and `Debugger.Snapshots`).
- Add a clarifying comment in `DebuggerSnapshotCreator.EndEntry` about
why arg/local capture must not rely on heuristics.
- Fix a logging bug in `ProbeExpressionsProcessor.AddProbeProcessor` so
it no longer logs `"Successfully created probe processor"` after
creation throws.

## Reason for change

- These are low-risk cleanups split out of #8572 to keep that PR focused
on the snapshot-exploration feature.
- The `AddProbeProcessor` change fixes a real (debug-level) logging
inconsistency: a probe whose processor creation throws would log both a
failure and a success line.

## Implementation details

- `CompileAll` simplification is behavior-preserving:
`CompileTemplates`/`CompileCondition`/`CompileMetric`/`CompileDecorations`
each return `null` when their source is null. One benign difference:
when `SpanDecorations` is null, `CompileDecorations` now emits its
existing debug-level "is null" log.
- `StringUtil.IsNullOrEmpty` only adds value under `#nullable enable`
(it carries the `[NotNullWhen(false)]` annotation), so the swap was
limited to that file; other `string.IsNullOrEmpty` call sites in
non-nullable files were intentionally left unchanged.
- The `ShouldSkipDiUpdate` property pattern is equivalent for the normal
(non-null) case and additionally avoids a potential NRE if settings were
ever null.

## Test coverage

- No new tests added; changes are cleanups plus one debug-only logging
fix.

---------

Co-authored-by: Cursor <[email protected]>
dudikeleti and others added 25 commits June 17, 2026 19:02
Introduce a self-contained 'snapshot exploration' test mode that, when enabled,
runs Dynamic Instrumentation against a list of probes (CSV) over a third-party
test project, captures snapshots for every method called, and produces a CSV
report that grades each probe as valid/invalid based on parsed managed and
native log output.

The runtime path is gated behind two internal config keys:
* DD_INTERNAL_SNAPSHOT_EXPLORATION_TEST_ENABLED
* DD_INTERNAL_SNAPSHOT_EXPLORATION_TEST_ROOT_PATH

When the flag is off (production), this PR is a no-op aside from a small
number of nullable-string signatures.

Key pieces:
* New Nuke targets SetUpSnapshotExplorationTests / RunSnapshotExplorationTests
  (Build.SnapshotExplorationTest.cs).
* Per-process CSV report writer (SnapshotExplorationTestSink).
* Mocked DiscoveryService and NoOp ProbeStatusPoller / SymbolUploader so the
  test mode never reaches an agent.
* Per-probe rate-limit and snapshot-cap (ExplorationTestProbeTracker) and
  cheap perf metrics around hot paths (ExplorationTestMetrics).
* DebuggerManager.SnapshotExploration.cs routes initialization through a
  test-host-only path that waits for the native side to receive probes.
* DynamicInstrumentation.IsInitialized is replaced with a Task-based latch so
  callers can await initialization (used by the new init path).
* SnapshotSink/SnapshotUploader switched to ISnapshotSink, accept nullable
  snapshots, ProbeStatusPoller factory returns IProbeStatusPoller.
* Native logs in debugger_method_rewriter.cpp and debugger_rejit_preprocessor.cpp
  get NOTE comments documenting the strings parsed by the build-side analyzer.
* New unit test SnapshotExplorationTestSinkTests verifies the sink writes the
  expected CSV row when the snapshot has a nested logger property.

Does not include the unrelated bug fixes that were also on the original
branch; those are in their own PRs.

Co-authored-by: Cursor <[email protected]>
Restore DD_INTERNAL_DEBUGGER_INSTRUMENT_ALL for line-probe runs (native gating
fix), revert the dead nullable-snapshot churn and silent null-drop, add sink
unit tests (CSV escaping, dedup, invalid grading), and drop unrelated drive-by
edits to keep the PR scoped.
Correct instance/static probe detection using extracted symbols, and reuse the quote-aware CSV parser for probe status reports so escaped fields don’t corrupt status analysis.
@dudikeleti
dudikeleti force-pushed the dudik/snapshot-exploration-test-infra branch from 5b5a6c9 to f20c7cf Compare June 17, 2026 17:03

@andrewlock andrewlock left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have a few concerns with the huge extra complexity this seems to add to production code... It feels like the vast majority of this shouldn't be in Datadog.Trace. I would recommend considering if there's any way to get the same behaviour using a test agent that dumps things to file, if that's what you need. Ultimately it's up to you of course.

RE the build changes, I think we can simplify it a little, but mostly just move it out of being a Nuke partial Build class. Also, can we retire the old exploration tests now (as they're flaky and add little value IMO?) Separate PR if so.

Finally, the config needs to be registered in the config registry, we mustn't use #pragma etc to ignore the errors.

.After(Clean, BuildTracerHome)
.Executes(() =>
{
if (ExplorationTestUseCase != global::ExplorationTestUseCase.Debugger)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems weird that you have to specify a ExplorationTestUseCase, but then this just bails out if it's not set to debugger? 🤔 Should we just make these debugger-specific anyway (they seem to be debugger specific already anyway, because of the whole "snapshot" part?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct. I'll fix it.

})
;

Target SetUpSnapshotExplorationTests

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are these exploration tests going to replace our existing exploration tests? I ask, because they currently flake a lot, they're fixed in time to (old) versions of libraries, and I'm not aware of them ever identifying a "real" issue... so should we just replace those old versions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

At this point, they're indeed a bit less necessary (unless we add new IL rewriting capabilities). Early on, they helped uncover issues that we probably wouldn't have caught ourselves until they surfaced at a customer's site.
That said, you're right that we could probably replace them. The downside is figuring out when to run them. For the existing exploration tests it's easy, we should run them whenever there are changes in the CLR profiler files. But those new tests are end-to-end tests. 🤔
In any case, I think we'll need to clean this up. We could perhaps keep only the new tests, with a few different levels of test granularity.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: Instead of making this a partial class on Build, move it into its own sub-folder, and pass in the variables you need...

The problem with the current approach is that all of these "private" methods are available in every single Nuke target, which is confusing when working on those, and breaks encapsulation

Comment on lines +126 to +129
if (settings.IsSnapshotExplorationTestEnabled)
{
return NoOpSymbolUploader.Instance;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: Is it necessary to add all these special cases in? If we can just disable this functionality using env variables externally, that seems preferable to bloating the size of production code and adding complexity?

@dudikeleti dudikeleti Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure I follow. I'll catch you offline to discuss it.

Comment on lines +221 to +227
// Snapshot exploration test runs take a different initialization path (no agent
// discovery, no-op symbols/probe-status pollers, sink writes to CSV).
// Route here so that Instrumentation.cs stays free of debugger test plumbing.
// This intentionally runs and completes synchronously (returning an already-completed
// task): the test host must not start executing probed code until initialization and
// probe installation have finished. This path only runs inside a vstest test host.
if (settings.IsSnapshotExplorationTestEnabled)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems like we're adding a lot of extra complexity here just to configure all this stuff for testing. Is there a good reason why we can't send all these snapshots to the test agent as usual, and just have the test agent dump them instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, this would not be enough.


ProbeFile = config.WithKeys(ConfigurationKeys.Debugger.DynamicInstrumentationProbeFile).AsString() ?? string.Empty;
// This is an internal test harness flag, not a registry-backed tracer configuration.
#pragma warning disable RS0030

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You must not disable these. You must add new settings to the config registry

/// setup tails these files to detect when the native side has accepted
/// the probe configuration.
/// </summary>
public const string NativeLogFilePattern = "dotnet-tracer-native-*.log";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This feels like a really nasty smell... this sort of thing shouldn't be in the Datadog.Trace binary we ship IMO 😟 This should all be external...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok lets talk offline


namespace Datadog.Trace.Tests.Debugger;

[Collection(nameof(EnvironmentVariablesTestCollection))]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a massive smell 🙁 We are trying to remove these. I don't see a reason we can't just read this from configuration like "normal" instead? 🤔

"_DD_ROOT_DOTNET_SESSION_ID",
"DD_INTERNAL_TRACE_NATIVE_ENGINE_PATH",
"DD_INTERNAL_PROFILING_NATIVE_ENGINE_PATH",
"DD_INTERNAL_SNAPSHOT_EXPLORATION_TEST_ROOT_PATH",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can't add more here 😉

Suggested change
"DD_INTERNAL_SNAPSHOT_EXPLORATION_TEST_ROOT_PATH",

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:debugger area:tests unit tests, integration tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants