Skip to content

[OpenTelemetry] Avoid hash collisions for wide metric tag values - #7979

Merged
Kielek merged 8 commits into
open-telemetry:mainfrom
Kielek:codex/fix-ant-2026-wnpgq6ju
Oct 19, 2026
Merged

Kielek merged 8 commits into
open-telemetry:mainfrom
Kielek:codex/fix-ant-2026-wnpgq6ju

Conversation

@Kielek

@Kielek Kielek commented Oct 19, 2026

Copy link
Copy Markdown
Member

This fix addresses a ANT-2026-WNPGQ6JU finding from Anthropic's OSS Scanner.
Project enrollment: anthropics/oss-scanner#117.

Changes

Distinct wide metric tag values can share their built-in 32-bit hash. Their
metric lookups then scan one bucket up to the cardinality limit. Hash the full
equality-relevant bits of long, ulong, double, decimal, Guid,
DateTime, DateTimeOffset, and TimeSpan before the seeded 32-bit fold.
Normalize equivalent floating-point, decimal, and timestamp representations.
Both ordinary and alternate span lookup use the corrected hash on every target.
No public or experimental API changes; API tracking updates are not applicable.

Regressions show 2000 missing-key comparisons before the fix and at most eight
afterward for all eight types, then verify all existing entries. Additional
coverage checks equal representations, word cancellation, span consistency,
and every Guid byte. The final full SDK Release suite passes on net10.0,
net9.0, net8.0, and net472: 6792 passed, 17 existing skips, 0 failures.
The complete Release solution builds with zero warnings/errors. Explicit
netstandard2.0 and netstandard2.1 asset probes pass on CoreCLR. Non-Windows
runtimes were not tested. Formatting verification exits zero with workspace
reference-loading warnings and unsupported F# formatting. The unmodified
source sanity check passes in a tracked-source snapshot using CI line endings;
the native post-build run encounters generated Windows CRLF files.

Benchmark comparison

Both comparisons use the repository's unmodified benchmark.ps1, default job,
MemoryDiagnoser, and identical benchmark source at each paired ref. Values are
boxed during setup. Each executed SDK assembly's ProductVersion contains its
requested commit. Results below are verbatim BenchmarkDotNet GitHub reports;
ratios are within each report, not baseline-versus-fix ratios.

.NET 10 compares benchmark-only baseline d94ac21
with 6f5e47a. The final follow-up changes
only legacy Guid hashing, so the measured modern long path is unchanged.
Colliding-long overflow falls from 26335.70 to 53.97 ns, with no measured
allocation in these four cases. Ordinary-long and string controls are noisy
on this hybrid CPU; no small control improvement or regression is inferred.

.NET 10 original SDK


BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9457)
13th Gen Intel Core i9-13950HX 2.20GHz, 1 CPU, 32 logical and 24 physical cores
.NET SDK 10.0.402
  [Host]    : .NET 10.0.13 (10.0.13, 10.0.1326.50504), X64 RyuJIT x86-64-v3
  .NET 10.0 : .NET 10.0.13 (10.0.13, 10.0.1326.50504), X64 RyuJIT x86-64-v3

Job=.NET 10.0  Runtime=.NET 10.0  Toolchain=net10.0  

Method Mean Error StdDev Median Ratio RatioSD Allocated Alloc Ratio
OrdinaryLongAtLimit 87.99 ns 1.790 ns 4.288 ns 86.61 ns 1.00 0.07 - NA
CollidingLongAtLimit 26,335.70 ns 423.663 ns 396.294 ns 26,216.12 ns 299.96 14.57 - NA
StringAtLimit 97.66 ns 1.855 ns 3.705 ns 96.69 ns 1.11 0.07 - NA
Int32AtLimit 47.73 ns 0.716 ns 0.670 ns 47.69 ns 0.54 0.03 - NA

.NET 10 fix


BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9457)
13th Gen Intel Core i9-13950HX 2.20GHz, 1 CPU, 32 logical and 24 physical cores
.NET SDK 10.0.402
  [Host]    : .NET 10.0.13 (10.0.13, 10.0.1326.50504), X64 RyuJIT x86-64-v3
  .NET 10.0 : .NET 10.0.13 (10.0.13, 10.0.1326.50504), X64 RyuJIT x86-64-v3

Job=.NET 10.0  Runtime=.NET 10.0  Toolchain=net10.0  

Method Mean Error StdDev Ratio RatioSD Allocated Alloc Ratio
OrdinaryLongAtLimit 96.91 ns 6.402 ns 18.575 ns 1.05 0.33 - NA
CollidingLongAtLimit 53.97 ns 1.043 ns 2.130 ns 0.58 0.15 - NA
StringAtLimit 48.96 ns 0.903 ns 1.075 ns 0.53 0.13 - NA
Int32AtLimit 52.03 ns 0.801 ns 0.626 ns 0.56 0.14 - NA

The legacy comparison uses final fix 37f09f0
and benchmark-only baseline a5dad5ba78022761f06135d0210f80dabedddf84, net472
on .NET Framework 4.8.1, affinity mask 1. Guid has no measured allocation.
Decimal retains the portable GetBits array API on non-NET targets
(net462/netstandard2.0/netstandard2.1), adding 40 B per record here.
Modern NET decimal extraction uses a 16-byte stack buffer. Ordinary-value CPU
costs are visible alongside collision improvements in the tables. The Guid
managed explicit-layout extractor follows the existing MetricPointValueStorage
pattern; no decimal private layout or unsafe code is used. No arithmetic
alternative was proposed or measured. Maintainers should weigh these explicit
compatibility costs; no allocation-free claim is made for legacy decimal.

Framework original SDK


BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9457)
13th Gen Intel Core i9-13950HX 2.20GHz, 1 CPU, 32 logical and 24 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9345.0), X64 RyuJIT VectorSize=256
  .NET Framework 4.7.2 : .NET Framework 4.8.1 (4.8.9345.0), X64 RyuJIT VectorSize=256

Job=.NET Framework 4.7.2  Affinity=00000000000000000000000000000001  Runtime=.NET Framework 4.7.2  
Toolchain=net472  

Method ValueType Colliding Mean Error StdDev Ratio RatioSD Allocated Alloc Ratio
ExistingTagSet Decimal False 150.1 ns 2.50 ns 2.22 ns 1.00 0.02 - NA
RecordAtLimit Decimal False 166.6 ns 3.26 ns 4.87 ns 1.11 0.04 - NA
ExistingTagSet Decimal True 61,939.4 ns 1,201.94 ns 1,124.29 ns 1.00 0.02 - NA
RecordAtLimit Decimal True 122,914.9 ns 1,663.25 ns 1,555.81 ns 1.99 0.04 - NA
ExistingTagSet Guid False 138.6 ns 1.40 ns 1.17 ns 1.00 0.01 - NA
RecordAtLimit Guid False 139.9 ns 1.95 ns 1.83 ns 1.01 0.02 - NA
ExistingTagSet Guid True 50,732.6 ns 1,010.32 ns 1,081.03 ns 1.00 0.03 - NA
RecordAtLimit Guid True 101,621.9 ns 1,502.02 ns 1,404.99 ns 2.00 0.05 - NA

Framework fix


BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9457)
13th Gen Intel Core i9-13950HX 2.20GHz, 1 CPU, 32 logical and 24 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9345.0), X64 RyuJIT VectorSize=256
  .NET Framework 4.7.2 : .NET Framework 4.8.1 (4.8.9345.0), X64 RyuJIT VectorSize=256

Job=.NET Framework 4.7.2  Affinity=00000000000000000000000000000001  Runtime=.NET Framework 4.7.2  
Toolchain=net472  

Method ValueType Colliding Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
ExistingTagSet Decimal False 167.0 ns 3.33 ns 5.91 ns 1.00 0.05 0.0062 40 B 1.00
RecordAtLimit Decimal False 217.1 ns 2.70 ns 2.25 ns 1.30 0.05 0.0062 40 B 1.00
ExistingTagSet Decimal True 166.0 ns 1.71 ns 1.60 ns 1.00 0.01 0.0062 40 B 1.00
RecordAtLimit Decimal True 179.2 ns 1.66 ns 1.55 ns 1.08 0.01 0.0062 40 B 1.00
ExistingTagSet Guid False 153.1 ns 2.19 ns 1.94 ns 1.00 0.02 - - NA
RecordAtLimit Guid False 176.7 ns 3.51 ns 2.93 ns 1.15 0.02 - - NA
ExistingTagSet Guid True 161.9 ns 2.33 ns 2.18 ns 1.00 0.02 - - NA
RecordAtLimit Guid True 155.2 ns 1.94 ns 1.81 ns 0.96 0.02 - - NA

Merge requirement checklist

  • CONTRIBUTING guidelines followed (license requirements, successful build/tests, etc.)
  • Tests added/updated
  • CHANGELOG.md file(s) updated for non-trivial changes
  • Changes in public API reviewed (if applicable)
  • AI usage disclosure:
    • No AI used
    • AI-assisted
    • Bulk AI-generated
  • I have the experience and knowledge necessary to understand, review, and validate all content in this PR1

Note

This PR description was generated with OpenAI Codex.

Footnotes

  1. Yes, I can answer maintainer questions about the content of this PR without using AI. ↩

Compare overflow lookups for colliding long values with ordinary long,
string, and integer values using BenchmarkDotNet.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-WNPGQ6JU.
Hash the full equality-relevant bits of built-in wide metric tag values
before folding to a dictionary hash. Preserve equal-value hashes across
floating-point, decimal, date, and time representations on all targets.
Add regression coverage for collision scans and equality compatibility.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-WNPGQ6JU.
Use the existing managed explicit-layout pattern to read all Guid bits
without a temporary array on legacy targets. Verify every Guid byte and
measure existing-series and overflow costs for Guid and decimal tags.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-WNPGQ6JU.
@github-actions github-actions Bot added pkg:OpenTelemetry Issues related to OpenTelemetry NuGet package perf Performance related labels Oct 19, 2026
Document the corrected wide metric tag hash behavior under Unreleased and
link the entry to PR open-telemetry#7979.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-WNPGQ6JU.
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Pull request dashboard status

Merged · refreshed 2026-10-19 17:53 UTC

Status above doesn't look right?
  • Anything look wrong? Report it with what you expected; it helps us improve the dashboard.

@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.20%. Comparing base (044ac1d) to head (3820d8f).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #7979      +/-   ##
==========================================
+ Coverage   90.19%   90.20%   +0.01%     
==========================================
  Files         366      366              
  Lines       21440    21496      +56     
  Branches     3943     3949       +6     
==========================================
+ Hits        19337    19390      +53     
- Misses       1525     1528       +3     
  Partials      578      578              
Flag Coverage Δ
unittests-Project-Experimental 90.24% <100.00%> (+0.01%) ⬆️
unittests-Project-Stable 90.24% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/OpenTelemetry/Metrics/Tags.cs 79.54% <100.00%> (+9.54%) ⬆️

... and 2 files with indirect coverage changes

@Kielek
Kielek marked this pull request as ready for review October 19, 2026 11:13
@Kielek
Kielek requested a review from a team as a code owner October 19, 2026 11:13
Comment thread test/Benchmarks/Metrics/LegacyMetricTagHashBenchmarks.cs Outdated
Comment thread test/Benchmarks/Metrics/MetricTagHashBenchmarks.cs Outdated
Comment thread test/OpenTelemetry.Tests/Metrics/TagsTests.cs Outdated
@martincostello martincostello added this to the v1.20.1 milestone Oct 19, 2026
Kielek and others added 3 commits October 19, 2026 13:27
Keep the measured before/after evidence and permanent regression tests,
while removing the two point-in-time benchmark harnesses from the PR.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-WNPGQ6JU.
Move equal wide-value representations into a static TheoryData method and
run each group through MemberData, preserving all hash and equality checks.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-WNPGQ6JU.
@Kielek
Kielek added this pull request to the merge queue Oct 19, 2026
Merged via the queue into open-telemetry:main with commit 4540417 Oct 19, 2026
80 checks passed
@Kielek
Kielek deleted the codex/fix-ant-2026-wnpgq6ju branch October 19, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

perf Performance related pkg:OpenTelemetry Issues related to OpenTelemetry NuGet package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants