Support child actions in aspnet#2139
Conversation
andrewlock
left a comment
There was a problem hiding this comment.
Nice elegant solution! A couple of minor comments, but I'm not sure it's working correctly for the "with feature flag" version?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
306f104 to
2b0498b
Compare
2b0498b to
6998638
Compare
This comment has been minimized.
This comment has been minimized.
Benchmarks Report 🐌Benchmarks for #2139 compared to master:
The following thresholds were used for comparing the benchmark speeds:
Allocation changes below 0.5% are ignored. Benchmark detailsBenchmarks.Trace.AgentWriterBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.AspNetCoreBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.DbCommandBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.ElasticsearchBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.GraphQLBenchmark - Faster 🎉 Same allocations ✔️
|
| Benchmark | base/diff | Base Median (ns) | Diff Median (ns) | Modality |
|---|---|---|---|---|
| Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync‑net472 | 1.158 | 4,178.27 | 3,608.94 |
Raw results
| Branch | Method | Toolchain | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | ExecuteAsync |
net472 | 4.157 μs | 0.0752 μs | 0.0866 μs | 0.1675 | 0.0000 | 0.0000 | 1083 B |
| master | ExecuteAsync |
netcoreapp3.1 | 1.885 μs | 0.0315 μs | 0.0279 μs | 0.0137 | 0.0000 | 0.0000 | 1008 B |
| #2139 | ExecuteAsync |
net472 | 3.616 μs | 0.0519 μs | 0.0433 μs | 0.1675 | 0.0000 | 0.0000 | 1083 B |
| #2139 | ExecuteAsync |
netcoreapp3.1 | 1.879 μs | 0.0310 μs | 0.0275 μs | 0.0142 | 0.0000 | 0.0000 | 1008 B |
Benchmarks.Trace.HttpClientBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | SendAsync |
net472 | 7.004 μs | 0.1397 μs | 0.1494 μs | 0.3489 | 0.0000 | 0.0000 | 2.19 KB |
| master | SendAsync |
netcoreapp3.1 | 4.245 μs | 0.0823 μs | 0.0769 μs | 0.0284 | 0.0000 | 0.0000 | 2.09 KB |
| #2139 | SendAsync |
net472 | 6.403 μs | 0.0709 μs | 0.0554 μs | 0.3508 | 0.0000 | 0.0000 | 2.19 KB |
| #2139 | SendAsync |
netcoreapp3.1 | 4.453 μs | 0.0713 μs | 0.0667 μs | 0.0306 | 0.0000 | 0.0000 | 2.09 KB |
Benchmarks.Trace.ILoggerBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | EnrichedLog |
net472 | 4.049 μs | 0.0662 μs | 0.0586 μs | 0.2866 | 0.0000 | 0.0000 | 1.79 KB |
| master | EnrichedLog |
netcoreapp3.1 | 4.136 μs | 0.0824 μs | 0.1442 μs | 0.0281 | 0.0000 | 0.0000 | 1.94 KB |
| #2139 | EnrichedLog |
net472 | 4.167 μs | 0.0471 μs | 0.0394 μs | 0.2874 | 0.0000 | 0.0000 | 1.79 KB |
| #2139 | EnrichedLog |
netcoreapp3.1 | 4.168 μs | 0.0809 μs | 0.0757 μs | 0.0273 | 0.0000 | 0.0000 | 1.94 KB |
Benchmarks.Trace.Log4netBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | EnrichedLog |
net472 | 187.2 μs | 3.62 μs | 4.44 μs | 0.6540 | 0.1868 | 0.0000 | 5.23 KB |
| master | EnrichedLog |
netcoreapp3.1 | 154.0 μs | 2.48 μs | 2.32 μs | 0.0000 | 0.0000 | 0.0000 | 5.05 KB |
| #2139 | EnrichedLog |
net472 | 178.4 μs | 2.84 μs | 2.66 μs | 0.7112 | 0.2667 | 0.0000 | 5.23 KB |
| #2139 | EnrichedLog |
netcoreapp3.1 | 151.8 μs | 1.95 μs | 1.83 μs | 0.0745 | 0.0000 | 0.0000 | 5.05 KB |
Benchmarks.Trace.NLogBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | EnrichedLog |
net472 | 12.34 μs | 0.238 μs | 0.244 μs | 0.8526 | 0.0000 | 0.0000 | 5.31 KB |
| master | EnrichedLog |
netcoreapp3.1 | 10.61 μs | 0.207 μs | 0.203 μs | 0.0921 | 0.0000 | 0.0000 | 6.28 KB |
| #2139 | EnrichedLog |
net472 | 12.75 μs | 0.180 μs | 0.160 μs | 0.8505 | 0.0000 | 0.0000 | 5.31 KB |
| #2139 | EnrichedLog |
netcoreapp3.1 | 10.70 μs | 0.213 μs | 0.390 μs | 0.0890 | 0.0000 | 0.0000 | 6.28 KB |
Benchmarks.Trace.RedisBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | SendReceive |
net472 | 2.418 μs | 0.0454 μs | 0.0402 μs | 0.1542 | 0.0000 | 0.0000 | 987 B |
| master | SendReceive |
netcoreapp3.1 | 2.267 μs | 0.0445 μs | 0.0437 μs | 0.0139 | 0.0000 | 0.0000 | 984 B |
| #2139 | SendReceive |
net472 | 2.284 μs | 0.0404 μs | 0.0378 μs | 0.1550 | 0.0000 | 0.0000 | 987 B |
| #2139 | SendReceive |
netcoreapp3.1 | 2.160 μs | 0.0429 μs | 0.0877 μs | 0.0138 | 0.0000 | 0.0000 | 984 B |
Benchmarks.Trace.SerilogBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | EnrichedLog |
net472 | 8.576 μs | 0.1526 μs | 0.1428 μs | 0.4456 | 0.0000 | 0.0000 | 2.8 KB |
| master | EnrichedLog |
netcoreapp3.1 | 7.120 μs | 0.1106 μs | 0.1035 μs | 0.0385 | 0.0000 | 0.0000 | 2.61 KB |
| #2139 | EnrichedLog |
net472 | 8.253 μs | 0.1638 μs | 0.1452 μs | 0.4464 | 0.0000 | 0.0000 | 2.8 KB |
| #2139 | EnrichedLog |
netcoreapp3.1 | 7.509 μs | 0.0448 μs | 0.0350 μs | 0.0373 | 0.0000 | 0.0000 | 2.61 KB |
Benchmarks.Trace.SpanBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | StartFinishSpan |
net472 | 922.4 ns | 18.35 ns | 17.16 ns | 0.0678 | 0.0000 | 0.0000 | 433 B |
| master | StartFinishSpan |
netcoreapp3.1 | 932.6 ns | 17.40 ns | 29.06 ns | 0.0059 | 0.0000 | 0.0000 | 432 B |
| master | StartFinishScope |
net472 | 1,132.6 ns | 21.52 ns | 22.10 ns | 0.0807 | 0.0000 | 0.0000 | 514 B |
| master | StartFinishScope |
netcoreapp3.1 | 1,077.3 ns | 15.99 ns | 13.35 ns | 0.0076 | 0.0000 | 0.0000 | 552 B |
| #2139 | StartFinishSpan |
net472 | 933.8 ns | 11.35 ns | 10.61 ns | 0.0681 | 0.0000 | 0.0000 | 433 B |
| #2139 | StartFinishSpan |
netcoreapp3.1 | 912.5 ns | 12.19 ns | 11.40 ns | 0.0060 | 0.0000 | 0.0000 | 432 B |
| #2139 | StartFinishScope |
net472 | 1,160.3 ns | 22.46 ns | 23.06 ns | 0.0805 | 0.0000 | 0.0000 | 514 B |
| #2139 | StartFinishScope |
netcoreapp3.1 | 1,058.1 ns | 13.50 ns | 12.63 ns | 0.0075 | 0.0000 | 0.0000 | 552 B |
| { | ||
| internal static class SharedItems | ||
| { | ||
| public const string HttpContextPropagatedResourceNameKey = "__Datadog.Trace.ClrProfiler.Managed.AspNetMvcIntegration-aspnet.resourcename"; |
There was a problem hiding this comment.
I don't really understand why this constant is stored here, when it's never used with the PushItem / PopItem mechanism.
There was a problem hiding this comment.
It was there and alone in this class in the first place. I decided to use this class as this constant is a key used on the Items dictionary as well.
| } | ||
| else | ||
| { | ||
| Log.Warning("Trying to push an item in HttpContext.Items but a previous object of unhandled type is already stored there"); |
There was a problem hiding this comment.
You could avoid this warning by removing the generic parameter from this method. As far as I can see, it's only ever used to to store Scope objects.
| internal const string HttpContextKey = "__Datadog.Trace.ClrProfiler.Integrations.AspNetMvcIntegration"; | ||
|
|
||
| private const string OperationName = "aspnet-mvc.request"; | ||
| private const string ChildActionOperationName = "aspnet-mvc.request.child-action"; |
There was a problem hiding this comment.
Good catch, question is whether:
- It should be removed as unused
- We should use a different operation name for child actions
I'm pretty sure we discussed it, but can't remember the conclusion 😬
There was a problem hiding this comment.
Thanks for catching that Robert. And indeed, we had dedided to use it Andrew.
Fixed in 9c0cc8d
| if (httpContext is not null) | ||
| { | ||
| httpContext.Items[SharedConstants.HttpContextPropagatedResourceNameKey] = resourceName; | ||
| httpContext.Items[SharedItems.HttpContextPropagatedResourceNameKey] = resourceName; |
There was a problem hiding this comment.
No need to check if the resource name has already been set?
robertpi
left a comment
There was a problem hiding this comment.
Couple of comments, but I think the correction should work.
tonyredondo
left a comment
There was a problem hiding this comment.
I leave a comment but non blocking.
| internal static void PushItem<T>(HttpContext context, string key, T item) | ||
| { | ||
| if (context == null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Storing only the scope by default to avoid allocating a stack if no inner calls are done | ||
| var existingItem = context.Items[key]; | ||
| if (existingItem is null) | ||
| { | ||
| context.Items[key] = item; | ||
| } | ||
| else if (existingItem is Stack<T> stack) | ||
| { | ||
| stack.Push(item); | ||
| } | ||
| else if (existingItem is T previousScope) | ||
| { | ||
| var newStack = new Stack<T>(); | ||
| newStack.Push(previousScope); | ||
| newStack.Push(item); | ||
| context.Items[key] = newStack; | ||
| } | ||
| else | ||
| { | ||
| Log.Warning("Trying to push an item in HttpContext.Items but a previous object of unhandled type is already stored there"); | ||
| } | ||
| } | ||
|
|
||
| internal static T TryPopItem<T>(HttpContext context, string key) | ||
| { | ||
| var item = context?.Items[key]; | ||
| if (item is T storedScope) | ||
| { | ||
| return storedScope; | ||
| } | ||
| else if (item is Stack<T> stack && stack.Count > 0) | ||
| { | ||
| return stack.Pop(); | ||
| } | ||
|
|
||
| return default(T); | ||
| } |
There was a problem hiding this comment.
I'm not sure it worth having Generics here, you are only using Scope object in the calls, also, this Generic defines the type of the stack, so you cannot change the T type in the same context, forcing you to pass the same T in all calls.
There was a problem hiding this comment.
Fixed in 9c0cc8d
I thought initially that I ought to use this for resource names as well and forgot to update afterwards
This comment has been minimized.
This comment has been minimized.
Having a child action would overwrite the scope stored in items. We thus failed to close all spans leading to missing spans. ResourceName needs a special handling in that case to make sure we don't overwrite the resource name with the child action one.
Co-authored-by: Andrew Lock <[email protected]>
7ba502d to
b29e0ed
Compare
Code Coverage Report 📊✔️ Merging #2139 into master will not change line coverage
View the full report for further details: Datadog.Trace Breakdown ✔️
The following classes have significant coverage changes.
The following classes were added in #2139:
View the full reports for further details: |
* Add Child Action in tests to check it breaks * Handle Child actions use case Having a child action would overwrite the scope stored in items. We thus failed to close all spans leading to missing spans. ResourceName needs a special handling in that case to make sure we don't overwrite the resource name with the child action one. * Apply suggestions from code review Co-authored-by: Andrew Lock <[email protected]> * fix trailing whitespaces from github commit * RouteTemplateResourceNamesEnabled works with child actions * implement comments Co-authored-by: Andrew Lock <[email protected]> (cherry picked from commit 3cafe9e)
* Support child actions in aspnet (#2139) * Add Child Action in tests to check it breaks * Handle Child actions use case Having a child action would overwrite the scope stored in items. We thus failed to close all spans leading to missing spans. ResourceName needs a special handling in that case to make sure we don't overwrite the resource name with the child action one. * Apply suggestions from code review Co-authored-by: Andrew Lock <[email protected]> * fix trailing whitespaces from github commit * RouteTemplateResourceNamesEnabled works with child actions * implement comments Co-authored-by: Andrew Lock <[email protected]> (cherry picked from commit 3cafe9e) * Fix build * Fix asp snapshots file names (#2148) * Fix asp snapshots file names * And Owin tests as well * Fix all snapshots files
Currently, when customers used child actions in aspnet, we were actually dropping spans. Indeed, we used to store the scope in
HttpContext.Current.Items. With child actions, we were overwriting this scope and thus not closing the span.I fixed that by adding a stack if we already have a scope in
Items. Also, I added a special handling of the resourcename to not override the resourceName inItemswhen there is already one as this resource is set for the root level span.Also, this PR enforces the resource name to stick to what was requested in the http request when
DD_TRACE_ROUTE_TEMPLATE_RESOURCE_NAMES_ENABLEDis enabled.Jira: APMS-6288
@DataDog/apm-dotnet