Skip to content

Reduce allocations in HttpRequestUtils.GetUrl()#8203

Merged
andrewlock merged 8 commits intomasterfrom
andrew/http-request-utils
Feb 18, 2026
Merged

Reduce allocations in HttpRequestUtils.GetUrl()#8203
andrewlock merged 8 commits intomasterfrom
andrew/http-request-utils

Conversation

@andrewlock
Copy link
Member

@andrewlock andrewlock commented Feb 13, 2026

Summary of changes

Reduces the allocations that occur in HttpRequestUtils.GetUrl(), particularly on .NET 6+

Reason for change

While profiling a simple aspnetcore + httprequest app (our aspnetcore benchmark), this showed up as a relatively high source of string allocations (6%) so wanted to try to reduce that. It was explicitly the version that takes a Uri that showed up, so optimized that somewhat.

Implementation details

In newer runtimes (.NET 6+), we can use GetComponents() to avoid allocating each of the individual pieces of url before formatting. We can also take advantage of the fact this whole path is only valid with absolute Uris, so we know we have a Host, and various other minor things.

On older runtimes, GetComponents() horrendously allocates, hence the split implementation 😅

Otherwise, tried lots of things running various benchmarks 😅 Uri is a tricky beast to deal with, because it does a lot of internal caching too. In general though, we'll be dealing with a new Uri instance every time we call this method, so that's what I focused on. Benchmarked both approaches though, just to be sure, and we come out ahead in both cases.

Original benchmarks

Method Runtime Mean Error Allocated
GetUrl_Original .NET 10.0 254.13 ns 5.058 ns 456 B
GetUrl_Updated .NET 10.0 215.42 ns 4.340 ns 288 B
GetUrl_Original .NET 6.0 384.78 ns 4.289 ns 440 B
GetUrl_Updated .NET 6.0 357.27 ns 7.153 ns 280 B
GetUrl_Original .NET Core 2.1 556.36 ns 9.929 ns 544 B
GetUrl_Updated .NET Core 2.1 495.81 ns 7.232 ns 472 B
GetUrl_Original .NET Core 3.1 546.39 ns 7.438 ns 528 B
GetUrl_Updated .NET Core 3.1 506.67 ns 8.758 ns 464 B
GetUrl_Original .NET Framework 4.8 727.18 ns 10.738 ns 786 B
GetUrl_Updated .NET Framework 4.8 783.27 ns 43.809 ns 706 B
GetUrl_Cached_Original .NET 10.0 46.87 ns 1.404 ns 88 B
GetUrl_Cached_Updated .NET 10.0 45.29 ns 0.871 ns 88 B
GetUrl_Cached_Original .NET 6.0 63.98 ns 1.202 ns 88 B
GetUrl_Cached_Updated .NET 6.0 61.60 ns 1.099 ns 88 B
GetUrl_Cached_Original .NET Core 2.1 80.93 ns 1.578 ns 168 B
GetUrl_Cached_Updated .NET Core 2.1 69.74 ns 1.411 ns 160 B
GetUrl_Cached_Original .NET Core 3.1 79.15 ns 1.258 ns 160 B
GetUrl_Cached_Updated .NET Core 3.1 67.23 ns 1.314 ns 152 B
GetUrl_Cached_Original .NET Framework 4.8 84.30 ns 1.350 ns 241 B
GetUrl_Cached_Updated .NET Framework 4.8 66.33 ns 0.709 ns 225 B

EDIT: Shortly after submitting this PR, I discovered DangerousDisablePathAndQueryCanonicalization and tl;dr; that's a nightmare 😅 It's only in .NET 6+, but they don't expose whether it was set publicly, so had to do some gnarly stuff with duck types. I'm not proud, but the benchmarks looks worth it to me:

Method Runtime Mean Error Allocated
GetUrl_Original .NET 10.0 253.9958 ns 3.6006 ns 456 B
GetUrl_Updated .NET 10.0 210.29 ns 2.170 ns 288 B
GetUrl_Original .NET 6.0 383.5104 ns 2.6358 ns 440 B
GetUrl_Updated .NET 6.0 352.9394 ns 2.9057 ns 280 B
GetUrl_Original .NET Core 2.1 541.4805 ns 10.6203 ns 544 B
GetUrl_Updated .NET Core 2.1 490.8919 ns 5.8440 ns 472 B
GetUrl_Original .NET Core 3.1 548.5983 ns 10.8097 ns 528 B
GetUrl_Updated .NET Core 3.1 505.9982 ns 8.7285 ns 464 B
GetUrl_Original .NET Framework 4.8 745.0813 ns 14.5547 ns 786 B
GetUrl_Updated .NET Framework 4.8 686.8337 ns 2.9292 ns 706 B
GetUrl_Dangerous_Original .NET 10.0 208.3430 ns 4.0426 ns 488 B
GetUrl_Dangerous_Updated .NET 10.0 168.41 ns 2.801 ns 342 B
GetUrl_Dangerous_Original .NET 6.0 319.3693 ns 2.4285 ns 472 B
GetUrl_Dangerous_Updated .NET 6.0 296.5951 ns 2.0317 ns 384 B
GetUrl_Redaction_Original .NET 10.0 278.4834 ns 3.6679 ns 488 B
GetUrl_Redaction_Updated .NET 10.0 241.85 ns 2.278 ns 288 B
GetUrl_Redaction_Original .NET 6.0 420.9738 ns 4.4795 ns 472 B
GetUrl_Redaction_Updated .NET 6.0 389.7784 ns 3.2476 ns 280 B
GetUrl_Redaction_Original .NET Core 2.1 591.9719 ns 6.4948 ns 568 B
GetUrl_Redaction_Updated .NET Core 2.1 552.1795 ns 7.9159 ns 472 B
GetUrl_Redaction_Original .NET Core 3.1 608.4211 ns 7.3971 ns 560 B
GetUrl_Redaction_Updated .NET Core 3.1 559.9308 ns 6.6185 ns 464 B
GetUrl_Redaction_Original .NET Framework 4.8 818.2093 ns 10.0129 ns 834 B
GetUrl_Redaction_Updated .NET Framework 4.8 779.5907 ns 7.6899 ns 730 B
GetUrl_Cached_Original .NET 10.0 45.2706 ns 0.5235 ns 88 B
GetUrl_Cached_Updated .NET 10.0 40.9379 ns 0.7165 ns 88 B
GetUrl_Cached_Original .NET 6.0 59.6816 ns 0.4011 ns 88 B
GetUrl_Cached_Updated .NET 6.0 71.1780 ns 1.2479 ns 88 B
GetUrl_Cached_Original .NET Core 2.1 77.7640 ns 1.0165 ns 168 B
GetUrl_Cached_Updated .NET Core 2.1 63.4134 ns 1.0179 ns 160 B
GetUrl_Cached_Original .NET Core 3.1 72.5815 ns 0.9315 ns 160 B
GetUrl_Cached_Updated .NET Core 3.1 61.6694 ns 0.6063 ns 152 B
GetUrl_Cached_Original .NET Framework 4.8 86.4770 ns 0.7504 ns 241 B
GetUrl_Cached_Updated .NET Framework 4.8 72.9856 ns 1.1003 ns 225 B
Benchmarking code

 [MemoryDiagnoser, GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), CategoriesColumn]
    public class HttpRequestUtilBenchmarks
    {
        private Uri _uri;
        private string _result;

        [GlobalSetup]
        public void GlobalSetup()
        {
            _uri = new Uri("https://api.example.com/v1/search?q=test&limit=10");
        }

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("GetUrl", "Cached")]
        public int GetUrl_Cached_Original()
        {
            _result = OriginalHttpRequestUtils.GetUrl(_uri);
            return _result.Length;
        }

        [Benchmark]
        [BenchmarkCategory("GetUrl", "Cached")]
        public int GetUrl_Cached_Updated()
        {
            _result = HttpRequestUtils.GetUrl(_uri);
            return _result.Length;
        }

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("GetUrl", "new")]
        public int GetUrl_Original()
        {
            var uri = new Uri("https://api.example.com/v1/search?q=test&limit=10");
            _result = OriginalHttpRequestUtils.GetUrl(uri);
            return _result.Length;
        }

        [Benchmark]
        [BenchmarkCategory("GetUrl", "new")]
        public int GetUrl_Updated()
        {
            var uri = new Uri("https://api.example.com/v1/search?q=test&limit=10");
            _result = HttpRequestUtils.GetUrl(uri);
            return _result.Length;
        }

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("GetUrl", "redaction")]
        public int GetUrl_Redaction_Original()
        {
            var uri = new Uri("http://localhost/api?id=123&api_key=secret&sort=asc");
            _result = OriginalHttpRequestUtils.GetUrl(uri);
            return _result.Length;
        }

        [Benchmark]
        [BenchmarkCategory("GetUrl", "redaction")]
        public int GetUrl_Redaction_Updated()
        {
            var uri = new Uri("http://localhost/api?id=123&api_key=secret&sort=asc");
            _result = HttpRequestUtils.GetUrl(uri);
            return _result.Length;
        }

#if NET6_0_OR_GREATER
        [Benchmark(Baseline = true)]
        [BenchmarkCategory("GetUrl", "new", "dangerous")]
        public int GetUrl_Dangerous_Original()
        {
            var uri = new Uri("http://localhost/api?id=123&api_key=secret&sort=asc", new UriCreationOptions(){ DangerousDisablePathAndQueryCanonicalization = true});
            _result = OriginalHttpRequestUtils.GetUrl(uri);
            return _result.Length;
        }

        [Benchmark]
        [BenchmarkCategory("GetUrl", "new", "dangerous")]
        public int GetUrl_Dangerous_Updated()
        {
            var uri = new Uri("http://localhost/api?id=123&api_key=secret&sort=asc", new UriCreationOptions(){ DangerousDisablePathAndQueryCanonicalization = true});
            _result = HttpRequestUtils.GetUrl(uri);
            return _result.Length;
        }
#endif

Test coverage

Before making the change, 🤖 Claude created some characterization tests to avoid any regressions in behaviour.

Other details

There's also HttpRequestExtensions.GetUrlForSpan() which has similar behaviour and allocations today, that I will likely investigate similarly

Spotted while working on https://datadoghq.atlassian.net/browse/LANGPLAT-842

@andrewlock andrewlock requested a review from a team as a code owner February 13, 2026 18:17
@andrewlock andrewlock added area:tracer The core tracer library (Datadog.Trace, does not include OpenTracing, native code, or integrations) type:performance Performance, speed, latency, resource usage (CPU, memory) labels Feb 13, 2026
@dd-trace-dotnet-ci-bot
Copy link

dd-trace-dotnet-ci-bot bot commented Feb 13, 2026

Execution-Time Benchmarks Report ⏱️

Execution-time results for samples comparing This PR (8203) 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
duration73.03 ± (73.01 - 73.30) ms73.63 ± (73.70 - 74.06) ms+0.8%✅⬆️
.NET Framework 4.8 - Bailout
duration77.33 ± (77.34 - 77.74) ms77.71 ± (77.58 - 77.85) ms+0.5%✅⬆️
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1061.55 ± (1062.59 - 1069.58) ms1062.41 ± (1063.62 - 1068.98) ms+0.1%✅⬆️
.NET Core 3.1 - Baseline
process.internal_duration_ms22.21 ± (22.17 - 22.25) ms22.50 ± (22.47 - 22.54) ms+1.3%✅⬆️
process.time_to_main_ms83.54 ± (83.36 - 83.72) ms84.81 ± (84.64 - 84.98) ms+1.5%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.91 ± (10.91 - 10.91) MB10.95 ± (10.94 - 10.95) MB+0.3%✅⬆️
runtime.dotnet.threads.count12 ± (12 - 12)12 ± (12 - 12)+0.0%
.NET Core 3.1 - Bailout
process.internal_duration_ms22.17 ± (22.13 - 22.22) ms22.51 ± (22.46 - 22.55) ms+1.5%✅⬆️
process.time_to_main_ms85.15 ± (84.96 - 85.34) ms86.86 ± (86.66 - 87.06) ms+2.0%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.95 ± (10.94 - 10.95) MB10.96 ± (10.96 - 10.97) MB+0.2%✅⬆️
runtime.dotnet.threads.count13 ± (13 - 13)13 ± (13 - 13)+0.0%
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms240.13 ± (236.21 - 244.04) ms239.26 ± (235.08 - 243.44) ms-0.4%
process.time_to_main_ms482.18 ± (481.56 - 482.81) ms487.75 ± (487.08 - 488.42) ms+1.2%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed47.53 ± (47.51 - 47.55) MB47.65 ± (47.63 - 47.67) MB+0.2%✅⬆️
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)+0.0%
.NET 6 - Baseline
process.internal_duration_ms21.33 ± (21.28 - 21.38) ms21.51 ± (21.46 - 21.55) ms+0.8%✅⬆️
process.time_to_main_ms73.88 ± (73.71 - 74.05) ms74.83 ± (74.67 - 74.98) ms+1.3%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.63 ± (10.63 - 10.64) MB10.65 ± (10.65 - 10.65) MB+0.2%✅⬆️
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 6 - Bailout
process.internal_duration_ms21.13 ± (21.09 - 21.17) ms21.40 ± (21.34 - 21.46) ms+1.3%✅⬆️
process.time_to_main_ms74.72 ± (74.57 - 74.86) ms75.63 ± (75.50 - 75.77) ms+1.2%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.72 ± (10.71 - 10.73) MB10.75 ± (10.75 - 10.76) MB+0.3%✅⬆️
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms248.46 ± (245.06 - 251.86) ms251.46 ± (247.99 - 254.93) ms+1.2%✅⬆️
process.time_to_main_ms462.36 ± (461.75 - 462.97) ms469.81 ± (469.11 - 470.51) ms+1.6%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed48.35 ± (48.33 - 48.38) MB48.31 ± (48.29 - 48.33) MB-0.1%
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)+0.0%✅⬆️
.NET 8 - Baseline
process.internal_duration_ms19.37 ± (19.34 - 19.41) ms19.68 ± (19.64 - 19.71) ms+1.6%✅⬆️
process.time_to_main_ms72.33 ± (72.17 - 72.48) ms73.93 ± (73.76 - 74.09) ms+2.2%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed7.66 ± (7.66 - 7.67) MB7.67 ± (7.67 - 7.67) MB+0.1%✅⬆️
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 8 - Bailout
process.internal_duration_ms19.45 ± (19.41 - 19.50) ms19.74 ± (19.70 - 19.78) ms+1.5%✅⬆️
process.time_to_main_ms73.79 ± (73.64 - 73.94) ms75.06 ± (74.91 - 75.21) ms+1.7%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed7.71 ± (7.70 - 7.71) MB7.73 ± (7.72 - 7.74) MB+0.3%✅⬆️
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms186.46 ± (185.71 - 187.22) ms186.41 ± (185.65 - 187.17) ms-0.0%
process.time_to_main_ms446.83 ± (446.04 - 447.61) ms445.88 ± (445.17 - 446.59) ms-0.2%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed35.99 ± (35.96 - 36.01) MB35.97 ± (35.94 - 36.00) MB-0.0%
runtime.dotnet.threads.count27 ± (27 - 27)27 ± (27 - 27)+0.0%✅⬆️

HttpMessageHandler

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration192.88 ± (193.20 - 194.06) ms191.91 ± (191.96 - 192.75) ms-0.5%
.NET Framework 4.8 - Bailout
duration196.16 ± (196.04 - 196.61) ms195.65 ± (195.36 - 196.02) ms-0.3%
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1133.09 ± (1134.95 - 1142.63) ms1135.17 ± (1140.24 - 1149.36) ms+0.2%✅⬆️
.NET Core 3.1 - Baseline
process.internal_duration_ms187.03 ± (186.69 - 187.37) ms186.82 ± (186.51 - 187.13) ms-0.1%
process.time_to_main_ms81.26 ± (81.05 - 81.47) ms80.75 ± (80.53 - 80.97) ms-0.6%
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed16.19 ± (16.16 - 16.22) MB16.16 ± (16.13 - 16.19) MB-0.2%
runtime.dotnet.threads.count20 ± (20 - 20)20 ± (20 - 20)+0.5%✅⬆️
.NET Core 3.1 - Bailout
process.internal_duration_ms185.82 ± (185.53 - 186.12) ms187.04 ± (186.72 - 187.35) ms+0.7%✅⬆️
process.time_to_main_ms82.50 ± (82.30 - 82.69) ms82.45 ± (82.27 - 82.62) ms-0.1%
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed16.24 ± (16.19 - 16.28) MB16.28 ± (16.26 - 16.31) MB+0.3%✅⬆️
runtime.dotnet.threads.count21 ± (20 - 21)21 ± (21 - 21)+0.0%✅⬆️
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms429.29 ± (426.13 - 432.45) ms428.96 ± (426.03 - 431.89) ms-0.1%
process.time_to_main_ms471.29 ± (470.77 - 471.81) ms469.44 ± (468.94 - 469.93) ms-0.4%
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed58.09 ± (57.98 - 58.21) MB58.14 ± (58.01 - 58.27) MB+0.1%✅⬆️
runtime.dotnet.threads.count29 ± (29 - 29)29 ± (29 - 29)-0.1%
.NET 6 - Baseline
process.internal_duration_ms191.26 ± (190.88 - 191.64) ms189.96 ± (189.68 - 190.25) ms-0.7%
process.time_to_main_ms70.35 ± (70.18 - 70.53) ms70.03 ± (69.84 - 70.22) ms-0.5%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed16.14 ± (16.01 - 16.27) MB16.04 ± (15.88 - 16.20) MB-0.7%
runtime.dotnet.threads.count18 ± (18 - 19)18 ± (18 - 18)-0.6%
.NET 6 - Bailout
process.internal_duration_ms190.22 ± (189.94 - 190.51) ms189.52 ± (189.30 - 189.74) ms-0.4%
process.time_to_main_ms71.04 ± (70.93 - 71.15) ms70.82 ± (70.72 - 70.93) ms-0.3%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed15.99 ± (15.84 - 16.14) MB15.97 ± (15.81 - 16.13) MB-0.1%
runtime.dotnet.threads.count20 ± (19 - 20)19 ± (19 - 19)-2.9%
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms444.13 ± (441.83 - 446.43) ms447.74 ± (445.49 - 449.99) ms+0.8%✅⬆️
process.time_to_main_ms446.60 ± (446.13 - 447.07) ms445.47 ± (445.05 - 445.89) ms-0.3%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed58.18 ± (58.07 - 58.29) MB58.11 ± (58.01 - 58.22) MB-0.1%
runtime.dotnet.threads.count29 ± (29 - 29)29 ± (29 - 30)+0.2%✅⬆️
.NET 8 - Baseline
process.internal_duration_ms189.12 ± (188.75 - 189.50) ms188.67 ± (188.36 - 188.99) ms-0.2%
process.time_to_main_ms70.06 ± (69.80 - 70.33) ms69.85 ± (69.64 - 70.06) ms-0.3%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed11.75 ± (11.72 - 11.78) MB11.77 ± (11.74 - 11.80) MB+0.2%✅⬆️
runtime.dotnet.threads.count18 ± (18 - 18)18 ± (18 - 18)-0.2%
.NET 8 - Bailout
process.internal_duration_ms188.45 ± (188.05 - 188.84) ms188.55 ± (188.28 - 188.81) ms+0.1%✅⬆️
process.time_to_main_ms70.71 ± (70.56 - 70.86) ms70.70 ± (70.60 - 70.80) ms-0.0%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed11.79 ± (11.76 - 11.82) MB11.81 ± (11.78 - 11.84) MB+0.2%✅⬆️
runtime.dotnet.threads.count19 ± (19 - 19)19 ± (19 - 19)+0.1%✅⬆️
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms364.40 ± (363.06 - 365.73) ms365.11 ± (363.72 - 366.50) ms+0.2%✅⬆️
process.time_to_main_ms432.20 ± (431.53 - 432.87) ms431.77 ± (431.17 - 432.36) ms-0.1%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed47.69 ± (47.66 - 47.73) MB47.78 ± (47.75 - 47.81) MB+0.2%✅⬆️
runtime.dotnet.threads.count29 ± (29 - 29)29 ± (29 - 29)-0.1%
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 (8203) - mean (74ms)  : 71, 77
    master - mean (73ms)  : 71, 75

    section Bailout
    This PR (8203) - mean (78ms)  : 76, 79
    master - mean (78ms)  : 75, 80

    section CallTarget+Inlining+NGEN
    This PR (8203) - mean (1,066ms)  : 1028, 1105
    master - mean (1,066ms)  : 1014, 1118

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 (8203) - mean (114ms)  : 110, 118
    master - mean (113ms)  : 110, 116

    section Bailout
    This PR (8203) - mean (116ms)  : 114, 119
    master - mean (114ms)  : 112, 117

    section CallTarget+Inlining+NGEN
    This PR (8203) - mean (765ms)  : 691, 838
    master - mean (751ms)  : 690, 811

Loading
FakeDbCommand (.NET 6)
gantt
    title Execution time (ms) FakeDbCommand (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8203) - mean (103ms)  : 100, 106
    master - mean (102ms)  : 99, 104

    section Bailout
    This PR (8203) - mean (104ms)  : 102, 106
    master - mean (102ms)  : 100, 104

    section CallTarget+Inlining+NGEN
    This PR (8203) - mean (755ms)  : 693, 817
    master - mean (743ms)  : 680, 806

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

    section Bailout
    This PR (8203) - mean (103ms)  : 101, 104
    master - mean (101ms)  : 99, 103

    section CallTarget+Inlining+NGEN
    This PR (8203) - mean (672ms)  : 649, 695
    master - mean (661ms)  : 641, 681

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 (8203) - mean (192ms)  : 188, 196
    master - mean (194ms)  : 188, 199

    section Bailout
    This PR (8203) - mean (196ms)  : 192, 199
    master - mean (196ms)  : 193, 199

    section CallTarget+Inlining+NGEN
    This PR (8203) - mean (1,145ms)  : 1075, 1215
    master - mean (1,139ms)  : 1083, 1194

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 (8203) - mean (276ms)  : 271, 281
    master - mean (277ms)  : 271, 283

    section Bailout
    This PR (8203) - mean (278ms)  : 273, 282
    master - mean (276ms)  : 272, 280

    section CallTarget+Inlining+NGEN
    This PR (8203) - mean (930ms)  : 883, 976
    master - mean (930ms)  : 870, 990

Loading
HttpMessageHandler (.NET 6)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8203) - mean (268ms)  : 263, 273
    master - mean (270ms)  : 264, 276

    section Bailout
    This PR (8203) - mean (268ms)  : 265, 271
    master - mean (269ms)  : 265, 274

    section CallTarget+Inlining+NGEN
    This PR (8203) - mean (922ms)  : 885, 959
    master - mean (920ms)  : 887, 953

Loading
HttpMessageHandler (.NET 8)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8203) - mean (268ms)  : 264, 273
    master - mean (269ms)  : 261, 277

    section Bailout
    This PR (8203) - mean (269ms)  : 264, 273
    master - mean (269ms)  : 263, 275

    section CallTarget+Inlining+NGEN
    This PR (8203) - mean (828ms)  : 810, 847
    master - mean (827ms)  : 811, 844

Loading

@andrewlock andrewlock force-pushed the andrew/http-request-utils branch from 2aca1e1 to cd373af Compare February 16, 2026 15:06
@pr-commenter
Copy link

pr-commenter bot commented Feb 16, 2026

Benchmarks

Benchmark execution time: 2026-02-18 10:39:14

Comparing candidate commit 3ec7ffa in PR branch andrew/http-request-utils with baseline commit 421a5fb in branch master.

Found 2 performance improvements and 10 performance regressions! Performance is the same for 169 metrics, 11 unstable metrics.

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

  • 🟩 execution_time [-21.735ms; -15.376ms] or [-10.041%; -7.103%]

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

  • 🟥 execution_time [+12.609ms; +18.712ms] or [+6.389%; +9.482%]

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

  • 🟩 execution_time [-103.619ms; -101.660ms] or [-51.980%; -50.997%]

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

  • 🟥 execution_time [+17.976ms; +24.759ms] or [+16.475%; +22.692%]

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

  • 🟥 throughput [-267.780op/s; -124.182op/s] or [-17.828%; -8.268%]

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

  • 🟥 execution_time [+54.491µs; +60.016µs] or [+5.290%; +5.826%]
  • 🟥 throughput [-53.511op/s; -48.692op/s] or [-5.512%; -5.016%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool netcoreapp3.1

  • 🟥 execution_time [+145.300µs; +157.727µs] or [+7.674%; +8.330%]
  • 🟥 throughput [-40.648op/s; -37.584op/s] or [-7.697%; -7.117%]

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

  • 🟥 execution_time [+10.956ms; +16.632ms] or [+5.620%; +8.531%]

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

  • 🟥 throughput [-16696820.252op/s; -15333374.124op/s] or [-6.919%; -6.354%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope net472

  • 🟥 throughput [-48447.271op/s; -45536.907op/s] or [-5.389%; -5.065%]

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request optimizes HttpRequestUtils.GetUrl() to reduce memory allocations, particularly for .NET 6+. The optimization introduces conditional compilation to use Uri.GetComponents() on newer runtimes, and includes duck typing to detect the DangerousDisablePathAndQueryCanonicalization flag to avoid incompatibilities.

Changes:

  • Added optimized path for .NET 6+ using Uri.GetComponents() to reduce allocations
  • Introduced duck typing to detect DangerousDisablePathAndQueryCanonicalization flag in Uri
  • Added comprehensive characterization tests covering various URL formats and edge cases

Reviewed changes

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

File Description
tracer/src/Datadog.Trace/Util/Http/HttpRequestUtils.cs Implements performance optimization with conditional compilation for .NET 6+ using GetComponents(), adds duck typing to detect dangerous Uri flags, splits logic into helper methods
tracer/test/Datadog.Trace.Tests/Util/Http/HttpRequestUtilsTests.cs Adds comprehensive test coverage with 183 test cases covering URL formatting, query strings, fragments, encoding, sensitive data redaction, and dangerous Uri creation

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

// With dangerous create. the fragment isn't removed
[InlineData("http://localhost/path#section", false, true, "http://localhost/path#section")]
[InlineData("http://localhost/path#section", true, true, "http://localhost/path#section")]
[InlineData("http://localhost/path?key=value#section", false, true, "http://localhost/path")]
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The test case on line 50 expects "http://localhost/path" when useDangerousCreate is true and useQueryManager is false, but according to the test on line 48 (without query string), the fragment should be preserved when useDangerousCreate is true. This test case expects the query string AND the fragment to be removed even though dangerous create is enabled. This seems inconsistent with the behavior described in other test cases. Please verify this is the intended behavior and not a typo in the expected value.

Suggested change
[InlineData("http://localhost/path?key=value#section", false, true, "http://localhost/path")]
[InlineData("http://localhost/path?key=value#section", false, true, "http://localhost/path#section")]

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

I know, it's weird. It's because the section ends up in the querystring in this case 🤷 tl;dr; this is effectively a bug in our code, but it's such an edge case, I don't think it needs prioritizing tbh

andrewlock added a commit that referenced this pull request Feb 18, 2026
## Summary of changes

A few minor improvements to the standard `AspNetCoreDiagnosticObserver`

## Reason for change

Looking into obvious perf improvements for ASP.NET Core, but only a few
minor things stood out (apart from related PRs like #8199 and #8203).

## Implementation details

- Reduce size of MVC tags object by not deriving from `WebTags` (we
never set those tags anyway)
- Delay creating spanlinks collection if we don't need it
- HttpRoute always matches AspNetCoreRoute, so can make it readonly

## Test coverage

Covered by existing tests, benchmarks show (tiny) allocation gains

## Other details


Relates to https://datadoghq.atlassian.net/browse/LANGPLAT-842

Related PRs:
- #8167
- #8170
- #8180
- #8196
- #8199
- #8203
andrewlock added a commit that referenced this pull request Feb 18, 2026
## Summary of changes

Reduce allocations for `UriHelpers.CleanUri()` that's called as part of
HTTP requests (among others)

## Reason for change

The allocations showed up in some profiling so decided to dig in. And Oh
Boy, this stuff is _terrible_ on < .NET 6 😅 And it's still terrible
after this PR, just less terrible 😅

## Implementation details

Two main improvements:
- If we need to use a `StringBuilder` to format the `Uri` _anyway_
(because we're stripping identifiers), then use one for the whole
`string`, instead of partially building the string, and then doing
another allocation.
- Use `GetComponents()`, specifying all the components we need, instead
of accessing each of the `Uri` properties ad-hoc. It turns out these
properties are actually very expensive, because they trigger a bunch of
analysis. Doing it one-shot doesn't avoid that, but it still gives some
improvements.


## Test coverage

I wrote a "characterisation" unit test, seeing as we didn't have any, to
describe the current behaviour, to ensure I didn't change anything.
Benchmarking was done (as shown below), comparing the original
implementation with the new implementation, for three different
scenarios:

- `CleanUri`: 
```csharp
_uri = new Uri("http://datadoghq.com/some-path");
UriHelpers.CleanUri(_uri, removeScheme: true, tryRemoveIds: true);
```

- `CleanUri_WithIds`:
```csharp
_uriWithIds= new Uri("http://datadoghq.com/some-path/123");
UriHelpers.CleanUri(_uriWithIds, removeScheme: true, tryRemoveIds: true);
```

- `CleanUri_IgnoreIds`:
```csharp
_uriWithIds= new Uri("http://datadoghq.com/some-path/123");
UriHelpers.CleanUri(_uriWithIds, removeScheme: true, tryRemoveIds: false);
```

The benchmark results heavily depend on the `Uri` provided, so it's only
the relative changes that are interesting here, comparing original to
updated.

> Also note that these took a long time to run, I was on a call for some
of it, so the execution times may be a bit wonky, but allocations was
what I was focusing on 😅

<details><summary>Benchmarking code</summary>
<p>

```csharp
[MemoryDiagnoser, GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), CategoriesColumn]
public class UriHelperBenchmarks
{
    private Uri _uri;
    private Uri _uriWithIds;
    private string _result;

    [GlobalSetup]
    public void GlobalSetup()
    {
        _uri = new Uri("http://datadoghq.com/some-path");
        _uriWithIds = new Uri("http://datadoghq.com/some-path/123");
    }

    [GlobalCleanup]
    public void GlobalCleanup()
    {
    }

    [Benchmark(Baseline = true)]
    [BenchmarkCategory("CleanUri")]
    public int CleanUri_Original()
    {
        _result = OriginalUriHelpers.CleanUri(_uri, removeScheme: true, tryRemoveIds: true);
        return _result.Length;
    }

    [Benchmark(Baseline = true)]
    [BenchmarkCategory("CleanUri_WithIds")]
    public int CleanUri_WithIds_Original()
    {
        _result = OriginalUriHelpers.CleanUri(_uriWithIds, removeScheme: true, tryRemoveIds: true);
        return _result.Length;
    }

    [Benchmark(Baseline = true)]
    [BenchmarkCategory("CleanUri_IgnoreIds")]
    public int CleanUri_IgnoreIds_Original()
    {
        _result = OriginalUriHelpers.CleanUri(_uriWithIds, removeScheme: true, tryRemoveIds: false);
        return _result.Length;
    }

    [Benchmark]
    [BenchmarkCategory("CleanUri")]
    public int CleanUri_Updated()
    {
        _result = UriHelpers.CleanUri(_uri, removeScheme: true, tryRemoveIds: true);
        return _result.Length;
    }

    [Benchmark]
    [BenchmarkCategory("CleanUri_WithIds")]
    public int CleanUri_WithIds_Updated()
    {
        _result = UriHelpers.CleanUri(_uriWithIds, removeScheme: true, tryRemoveIds: true);
        return _result.Length;
    }

    [Benchmark]
    [BenchmarkCategory("CleanUri_IgnoreIds")]
    public int CleanUri_IgnoreIds_Updated()
    {
        _result = UriHelpers.CleanUri(_uriWithIds, removeScheme: true, tryRemoveIds: false);
        return _result.Length;
    }
}
```

</p>
</details> 


<details><summary>Original benchmarks</summary>
<p>



| Method | Runtime | Mean | Allocated |
| --------------------------- | ------------------ | --------: |
--------: |
| CleanUri_Original | .NET 10.0 | 122.02 ns | 168 B |
| CleanUri_Updated | .NET 10.0 | 92.97 ns | 120 B |
| CleanUri_Original | .NET 6.0 | 162.29 ns | 168 B |
| CleanUri_Updated | .NET 6.0 | 159.69 ns | 120 B |
| CleanUri_Original | .NET Core 2.1 | 263.90 ns | 800 B |
| CleanUri_Updated | .NET Core 2.1 | 237.06 ns | 752 B |
| CleanUri_Original | .NET Core 3.1 | 281.37 ns | 792 B |
| CleanUri_Updated | .NET Core 3.1 | 263.45 ns | 744 B |
| CleanUri_Original | .NET Framework 4.8 | 263.97 ns | 802 B |
| CleanUri_Updated | .NET Framework 4.8 | 216.58 ns | 754 B |
| | | | |
| CleanUri_IgnoreIds_Original | .NET 10.0 | 53.41 ns | 128 B |
| CleanUri_IgnoreIds_Updated | .NET 10.0 | 46.89 ns | 80 B |
| CleanUri_IgnoreIds_Original | .NET 6.0 | 70.11 ns | 128 B |
| CleanUri_IgnoreIds_Updated | .NET 6.0 | 61.18 ns | 80 B |
| CleanUri_IgnoreIds_Original | .NET Core 2.1 | 148.38 ns | 856 B |
| CleanUri_IgnoreIds_Updated | .NET Core 2.1 | 151.01 ns | 800 B |
| CleanUri_IgnoreIds_Original | .NET Core 3.1 | 170.16 ns | 848 B |
| CleanUri_IgnoreIds_Updated | .NET Core 3.1 | 173.91 ns | 800 B |
| CleanUri_IgnoreIds_Original | .NET Framework 4.8 | 159.43 ns | 859 B |
| CleanUri_IgnoreIds_Updated | .NET Framework 4.8 | 145.18 ns | 802 B |
| | | | |
| CleanUri_WithIds_Original | .NET 10.0 | 112.47 ns | 168 B |
| CleanUri_WithIds_Updated | .NET 10.0 | 89.27 ns | 120 B |
| CleanUri_WithIds_Original | .NET 6.0 | 147.53 ns | 168 B |
| CleanUri_WithIds_Updated | .NET 6.0 | 138.39 ns | 120 B |
| CleanUri_WithIds_Original | .NET Core 2.1 | 232.67 ns | 912 B |
| CleanUri_WithIds_Updated | .NET Core 2.1 | 249.10 ns | 856 B |
| CleanUri_WithIds_Original | .NET Core 3.1 | 273.64 ns | 888 B |
| CleanUri_WithIds_Updated | .NET Core 3.1 | 252.48 ns | 840 B |
| CleanUri_WithIds_Original | .NET Framework 4.8 | 246.37 ns | 915 B |
| CleanUri_WithIds_Updated | .NET Framework 4.8 | 248.15 ns | 859 B |



| Method | Runtime | Mean | Allocated |
| --------------------------- | ------------------ | -------: |
--------: |
| CleanUri_Original | .NET 10.0 | 240.0 ns | 472 B |
| CleanUri_Updated | .NET 10.0 | 223.9 ns | 424 B |
| CleanUri_Original | .NET 6.0 | 354.1 ns | 456 B |
| CleanUri_Updated | .NET 6.0 | 351.7 ns | 408 B |
| CleanUri_Original | .NET Core 2.1 | 515.3 ns | 1112 B |
| CleanUri_Updated | .NET Core 2.1 | 498.0 ns | 1064 B |
| CleanUri_Original | .NET Core 3.1 | 547.7 ns | 1096 B |
| CleanUri_Updated | .NET Core 3.1 | 536.6 ns | 1048 B |
| CleanUri_Original | .NET Framework 4.8 | 647.9 ns | 1244 B |
| CleanUri_Updated | .NET Framework 4.8 | 628.0 ns | 1196 B |
| | | | |
| CleanUri_IgnoreIds_Original | .NET 10.0 | 210.6 ns | 440 B |
| CleanUri_IgnoreIds_Updated | .NET 10.0 | 179.5 ns | 272 B |
| CleanUri_IgnoreIds_Original | .NET 6.0 | 317.4 ns | 424 B |
| CleanUri_IgnoreIds_Updated | .NET 6.0 | 293.5 ns | 264 B |
| CleanUri_IgnoreIds_Original | .NET Core 2.1 | 493.0 ns | 1176 B |
| CleanUri_IgnoreIds_Updated | .NET Core 2.1 | 439.8 ns | 1000 B |
| CleanUri_IgnoreIds_Original | .NET Core 3.1 | 505.4 ns | 1160 B |
| CleanUri_IgnoreIds_Updated | .NET Core 3.1 | 463.7 ns | 992 B |
| CleanUri_IgnoreIds_Original | .NET Framework 4.8 | 602.3 ns | 1316 B |
| CleanUri_IgnoreIds_Updated | .NET Framework 4.8 | 559.4 ns | 1139 B |
| | | | |
| CleanUri_WithIds_Original | .NET 10.0 | 247.0 ns | 480 B |
| CleanUri_WithIds_Updated | .NET 10.0 | 252.5 ns | 432 B |
| CleanUri_WithIds_Original | .NET 6.0 | 392.9 ns | 464 B |
| CleanUri_WithIds_Updated | .NET 6.0 | 393.4 ns | 416 B |
| CleanUri_WithIds_Original | .NET Core 2.1 | 598.1 ns | 1232 B |
| CleanUri_WithIds_Updated | .NET Core 2.1 | 579.0 ns | 1176 B |
| CleanUri_WithIds_Original | .NET Core 3.1 | 610.8 ns | 1200 B |
| CleanUri_WithIds_Updated | .NET Core 3.1 | 593.2 ns | 1152 B |
| CleanUri_WithIds_Original | .NET Framework 4.8 | 684.0 ns | 1372 B |
| CleanUri_WithIds_Updated | .NET Framework 4.8 | 688.1 ns | 1316 B |


</p>
</details> 

EDIT: I realised that the allocations here aren't quite right, because
this is calling the method on the _same_ `Uri` instance in each
iteration. As `Uri` uses heavy caching, that distorts the allocations a
_lot_. A more accurate test is to use a new `Uri` for each iteration, as
the following benchmarks do. The allocation is more because we're both
allocating the `Uri` _and_ doing the stripping, and the benchmark
doesn't make use of caching, but thankfully we're still better in all
cases.

EDIT 2: Shortly after submitting this PR, I discovered
DangerousDisablePathAndQueryCanonicalization and tl;dr; that's a
nightmare 😅 It's only in .NET 6+, but they don't expose whether it was
set publicly, so had to do some gnarly stuff with duck types. I'm not
proud, but the benchmarks still seem worth pursuing it to me. This is
the same issue that plagues #8203


| Method | Runtime | Mean | Error | Allocated |
| ------------------------------------- | ------------------ |
----------: | ---------: | --------: |
| CleanUri_Original | .NET 10.0 | 241.7598 ns | 3.7925 ns | 472 B |
| CleanUri_Updated | .NET 10.0 | 232.1299 ns | 4.5930 ns | 424 B |
| CleanUri_Original | .NET 6.0 | 360.8269 ns | 6.3565 ns | 456 B |
| CleanUri_Updated | .NET 6.0 | 346.9848 ns | 4.7634 ns | 408 B |
| CleanUri_Original | .NET Core 2.1 | 519.9048 ns | 6.2900 ns | 1112 B |
| CleanUri_Updated | .NET Core 2.1 | 520.4468 ns | 7.2347 ns | 1064 B |
| CleanUri_Original | .NET Core 3.1 | 556.0087 ns | 5.3537 ns | 1096 B |
| CleanUri_Updated | .NET Core 3.1 | 543.6773 ns | 3.7395 ns | 1048 B |
| CleanUri_Original | .NET Framework 4.8 | 632.6677 ns | 5.9946 ns |
1244 B |
| CleanUri_Updated | .NET Framework 4.8 | 632.7827 ns | 12.1315 ns |
1196 B |
| | | | | |
| CleanUri_IgnoreIds_Original | .NET 10.0 | 200.0625 ns | 2.1611 ns |
440 B |
| CleanUri_IgnoreIds_Updated | .NET 10.0 | 172.3196 ns | 3.3675 ns | 272
B |
| CleanUri_IgnoreIds_Original | .NET 6.0 | 337.9443 ns | 6.7787 ns | 424
B |
| CleanUri_IgnoreIds_Updated | .NET 6.0 | 313.6942 ns | 6.1152 ns | 264
B |
| CleanUri_IgnoreIds_Original | .NET Core 2.1 | 493.7106 ns | 9.8265 ns
| 1176 B |
| CleanUri_IgnoreIds_Updated | .NET Core 2.1 | 461.0721 ns | 9.0235 ns |
1000 B |
| CleanUri_IgnoreIds_Original | .NET Core 3.1 | 523.9340 ns | 7.3235 ns
| 1160 B |
| CleanUri_IgnoreIds_Updated | .NET Core 3.1 | 473.7414 ns | 6.9061 ns |
992 B |
| CleanUri_IgnoreIds_Original | .NET Framework 4.8 | 631.2814 ns |
9.9056 ns | 1316 B |
| CleanUri_IgnoreIds_Updated | .NET Framework 4.8 | 605.1636 ns |
10.9916 ns | 1139 B |
| | | | | |
| CleanUri_WithIds_Original | .NET 10.0 | 268.1831 ns | 4.7108 ns | 480
B |
| CleanUri_WithIds_Updated | .NET 10.0 | 268.6544 ns | 8.1581 ns | 432 B
|
| CleanUri_WithIds_Original | .NET 6.0 | 408.5128 ns | 8.4589 ns | 464 B
|
| CleanUri_WithIds_Updated | .NET 6.0 | 381.8052 ns | 4.4549 ns | 416 B
|
| CleanUri_WithIds_Original | .NET Core 2.1 | 572.6368 ns | 5.4855 ns |
1232 B |
| CleanUri_WithIds_Updated | .NET Core 2.1 | 567.9524 ns | 8.8147 ns |
1176 B |
| CleanUri_WithIds_Original | .NET Core 3.1 | 612.2201 ns | 7.8213 ns |
1200 B |
| CleanUri_WithIds_Updated | .NET Core 3.1 | 594.9918 ns | 6.8069 ns |
1152 B |
| CleanUri_WithIds_Original | .NET Framework 4.8 | 700.4916 ns | 13.6586
ns | 1372 B |
| CleanUri_WithIds_Updated | .NET Framework 4.8 | 699.0997 ns | 10.7405
ns | 1316 B |
| | | | | |
| CleanUri_Dangerous_Original | .NET 10.0 | 225.5236 ns | 4.3965 ns |
472 B |
| CleanUri_Dangerous_Updated | .NET 10.0 | 208.1699 ns | 3.5823 ns | 424
B |
| CleanUri_Dangerous_Original | .NET 6.0 | 336.6692 ns | 3.6419 ns | 456
B |
| CleanUri_Dangerous_Updated | .NET 6.0 | 315.4834 ns | 4.6207 ns | 408
B |
| | | | | |
| CleanUri_Dangerous_IgnoreIds_Original | .NET 10.0 | 185.8492 ns |
3.7319 ns | 440 B |
| CleanUri_Dangerous_IgnoreIds_Updated | .NET 10.0 | 190.3548 ns |
3.4563 ns | 440 B |
| CleanUri_Dangerous_IgnoreIds_Original | .NET 6.0 | 277.9524 ns |
4.8131 ns | 424 B |
| CleanUri_Dangerous_IgnoreIds_Updated | .NET 6.0 | 330.9686 ns | 6.3933
ns | 424 B |
| | | | | |
| CleanUri_Dangerous_WithIds_Original | .NET 10.0 | 233.4651 ns | 4.6167
ns | 480 B |
| CleanUri_Dangerous_WithIds_Updated | .NET 10.0 | 225.7008 ns | 4.5418
ns | 432 B |
| CleanUri_Dangerous_WithIds_Original | .NET 6.0 | 355.9743 ns | 6.1388
ns | 464 B |
| CleanUri_Dangerous_WithIds_Updated | .NET 6.0 | 370.6714 ns | 7.1053
ns | 416 B |




## Other details

Spotted while working on
https://datadoghq.atlassian.net/browse/LANGPLAT-842
@andrewlock andrewlock force-pushed the andrew/http-request-utils branch from cd373af to 3ec7ffa Compare February 18, 2026 09:56
Copy link
Collaborator

@NachoEchevarria NachoEchevarria left a comment

Choose a reason for hiding this comment

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

Thanks!

@andrewlock andrewlock enabled auto-merge (squash) February 18, 2026 11:37
@andrewlock andrewlock merged commit 2b393bc into master Feb 18, 2026
141 of 142 checks passed
@andrewlock andrewlock deleted the andrew/http-request-utils branch February 18, 2026 13:15
@github-actions github-actions bot added this to the vNext-v3 milestone Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:tracer The core tracer library (Datadog.Trace, does not include OpenTracing, native code, or integrations) type:performance Performance, speed, latency, resource usage (CPU, memory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments