Skip to content

Commit c2c2b8c

Browse files
committed
Create a more optimized AspNetCoreResourceNameHelper
1 parent 5e4ff26 commit c2c2b8c

File tree

3 files changed

+142
-12
lines changed

3 files changed

+142
-12
lines changed

tracer/src/Datadog.Trace/DiagnosticListeners/AspNetCoreResourceNameHelper.cs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,118 @@ namespace Datadog.Trace.DiagnosticListeners;
1919

2020
internal static class AspNetCoreResourceNameHelper
2121
{
22+
#if NET6_0_OR_GREATER
23+
internal static string SimplifyRoutePattern(
24+
RoutePattern routePattern,
25+
RouteValueDictionary routeValueDictionary,
26+
bool expandRouteParameters)
27+
{
28+
var sb = routePattern.RawText?.Length < 512
29+
? new ValueStringBuilder(stackalloc char[512])
30+
: new ValueStringBuilder(); // too big to use stackallocation, so use array builder
31+
32+
foreach (var pathSegment in routePattern.PathSegments)
33+
{
34+
var addedPart = false;
35+
foreach (var part in pathSegment.DuckCast<AspNetCoreDiagnosticObserver.RoutePatternPathSegmentStruct>().Parts)
36+
{
37+
if (part.TryDuckCast(out AspNetCoreDiagnosticObserver.RoutePatternContentPartStruct contentPart))
38+
{
39+
if (!addedPart)
40+
{
41+
sb.Append('/');
42+
addedPart = true;
43+
}
44+
45+
sb.AppendAsLowerInvariant(contentPart.Content);
46+
}
47+
else if (part.TryDuckCast(out AspNetCoreDiagnosticObserver.RoutePatternParameterPartStruct parameter))
48+
{
49+
var parameterName = parameter.Name;
50+
if (parameterName.Equals("area", StringComparison.OrdinalIgnoreCase)
51+
|| parameterName.Equals("controller", StringComparison.OrdinalIgnoreCase)
52+
|| parameterName.Equals("action", StringComparison.OrdinalIgnoreCase))
53+
{
54+
if (!addedPart)
55+
{
56+
sb.Append('/');
57+
addedPart = true;
58+
}
59+
60+
if (routeValueDictionary.TryGetValue(parameterName, out var value)
61+
&& value is string name)
62+
{
63+
sb.AppendAsLowerInvariant(name);
64+
}
65+
else
66+
{
67+
sb.Append(parameterName);
68+
}
69+
}
70+
else
71+
{
72+
var haveParameter = routeValueDictionary.TryGetValue(parameterName, out var value);
73+
if (!parameter.IsOptional || haveParameter)
74+
{
75+
if (!addedPart)
76+
{
77+
sb.Append('/');
78+
addedPart = true;
79+
}
80+
81+
// Is this parameter an identifier segment? we assume non-strings _are_ identifiers
82+
// so never expand them. This avoids an allocating ToString() call, but means that
83+
// some parameters which maybe _should_ be expanded (e.g. Enum)s currently are not
84+
if (expandRouteParameters
85+
&& haveParameter
86+
&& (value is null ||
87+
(value is string valueAsString
88+
&& !UriHelpers.IsIdentifierSegment(valueAsString, 0, valueAsString.Length))))
89+
{
90+
// write the expanded parameter value
91+
sb.AppendAsLowerInvariant(value as string);
92+
}
93+
else
94+
{
95+
// write the route template value
96+
sb.Append('{');
97+
if (parameter.IsCatchAll)
98+
{
99+
if (parameter.EncodeSlashes)
100+
{
101+
sb.Append("**");
102+
}
103+
else
104+
{
105+
sb.Append('*');
106+
}
107+
}
108+
109+
sb.AppendAsLowerInvariant(parameterName);
110+
if (parameter.IsOptional)
111+
{
112+
sb.Append('?');
113+
}
114+
115+
sb.Append('}');
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
122+
123+
// We never added anything, or we just added the first `/`, no need for explicit ToString()
124+
if (sb.Length <= 1)
125+
{
126+
sb.Dispose();
127+
return "/";
128+
}
129+
130+
return sb.ToString();
131+
}
132+
#endif
133+
22134
internal static string SimplifyRoutePattern(
23135
RoutePattern routePattern,
24136
IReadOnlyDictionary<string, object?> routeValueDictionary,

tracer/src/Datadog.Trace/DiagnosticListeners/SingleSpanAspNetCoreDiagnosticObserver.cs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -272,22 +272,10 @@ private void OnRoutingEndpointMatched(object arg)
272272
var routePattern = routeEndpoint.Value.RoutePattern.DuckCast<RoutePattern>();
273273
// No need to ToLowerInvariant() these strings, as we lower case
274274
// the whole route later
275-
var controllerName = routeValues.TryGetValue("controller", out var raw)
276-
? raw as string
277-
: null;
278-
var actionName = routeValues.TryGetValue("action", out raw)
279-
? raw as string
280-
: null;
281-
var areaName = routeValues.TryGetValue("area", out raw)
282-
? raw as string
283-
: null;
284275

285276
var resourcePathName = AspNetCoreResourceNameHelper.SimplifyRoutePattern(
286277
routePattern,
287278
routeValues,
288-
areaName: areaName,
289-
controllerName: controllerName,
290-
actionName: actionName,
291279
_tracer.Settings.ExpandRouteTemplatesEnabled);
292280

293281
// If we have a PathBase, then we need to do a bunch of encoding etc which requires allocating buffers

tracer/test/Datadog.Trace.Tests/DiagnosticListeners/AspNetCoreResourceNameHelperTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,5 +183,35 @@ public void SimplifyRouteTemplate_CleansValidRouteTemplatesWithDefaults(string t
183183

184184
resource.Should().Be(expected);
185185
}
186+
187+
#if NET6_0_OR_GREATER
188+
[Theory]
189+
[MemberData(nameof(ValidRouteTemplates))]
190+
public void SingleSpan_SimplifyRoutePattern_CleansValidRouteTemplates(string template, string expected, bool expandRouteTemplates)
191+
{
192+
var originalPattern = RoutePatternFactory.Parse(template);
193+
var duckTypedPattern = originalPattern.DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>();
194+
var resource = AspNetCoreResourceNameHelper.SimplifyRoutePattern(
195+
routePattern: duckTypedPattern,
196+
routeValueDictionary: Values,
197+
expandRouteTemplates);
198+
199+
resource.Should().Be(expected);
200+
}
201+
202+
[Theory]
203+
[MemberData(nameof(ValidRouteTemplatesWithExternalDefaults))]
204+
public void SingleSpan_SimplifyRoutePattern_CleansValidRouteTemplatesWithDefaults(string template, string expected, bool expandRouteTemplates)
205+
{
206+
var originalPattern = RoutePatternFactory.Parse(template, Defaults, parameterPolicies: ParameterPolicies);
207+
var duckTypedPattern = originalPattern.DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>();
208+
var resource = AspNetCoreResourceNameHelper.SimplifyRoutePattern(
209+
routePattern: duckTypedPattern,
210+
routeValueDictionary: Values,
211+
expandRouteTemplates);
212+
213+
resource.Should().Be(expected);
214+
}
215+
#endif
186216
}
187217
#endif

0 commit comments

Comments
 (0)