-
Notifications
You must be signed in to change notification settings - Fork 764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[AOT] Remove usage of Linq.Expressions and especially lambda compilation #4695
Conversation
In AOT `Expression.Lambda.Compile` method is implemented via an interpreter. So it does work, but it's relatively slow. Additionally it's not fully NativeAOT compatible yet. There are two places where it's used in the source code: * `ExceptionProcessor` uses it to access `Marshal.GetExceptionPointers` method which is not part of netstandard API. So in this case simply ifdef and on net6.0+ access the method directly (as it's visible in the net6.0 APIs) and fallback to the Expression.Lambda solution on down-level platforms. Since AOT/trimming is net6.0+ this fixes the problem. * `ActivityInstrumentationHelper` uses it to access two setters on the `Activity` class which are internal. Using reflection and then `CreateDelegate` will provide basically the same performance (a delegate which directly calls the setter) without a need for `Expression.Lambda` - so the code is simpler. Since the reflection is statically analyzable (all types are known, the property names are known) this code is fully trim and AOT compatible. There's no impact on warning count because the .NET 7 ASP.NET has Linq.Expression dependency as well. On .NET 8 though this removes all warnings from Linq.Expressions.
PropertyInfo sourcePropertyInfo = typeof(Activity).GetProperty("Source"); | ||
var body = Expression.Assign(Expression.Property(instance, sourcePropertyInfo), propertyValue); | ||
return Expression.Lambda<Action<Activity, ActivitySource>>(body, instance, propertyValue).Compile(); | ||
return (Action<Activity, ActivitySource>)typeof(Activity).GetProperty("Source") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I spoke to @tarekgh about this, and asked why these setters aren't exposed publicly (since OpenTelemetry needs to call them), he told me that these setters only need to be called on < net7.0
runtimes. If you look at the callsites, you'll see this is true for 2 of the 3 callsites:
opentelemetry-dotnet/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
Lines 195 to 198 in 0343116
#if !NET7_0_OR_GREATER | |
ActivityInstrumentationHelper.SetActivitySourceProperty(activity, ActivitySource); | |
ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Server); | |
#endif |
Lines 161 to 165 in 0343116
if (!IsNet7OrGreater) | |
{ | |
ActivityInstrumentationHelper.SetActivitySourceProperty(activity, ActivitySource); | |
ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Client); | |
} |
And in Grpc, it always calls it:
Lines 124 to 127 in 0343116
if (activity.IsAllDataRequested) | |
{ | |
ActivityInstrumentationHelper.SetActivitySourceProperty(activity, ActivitySource); | |
ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Client); |
I think the correct thing to do here is to not compile this code on net7.0+
TFMs, and then fix the 2 places above to have #if !NET7_0_OR_GREATER
checks around them that don't already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@utpilla do you agree to change GRPC to not call these on net7.0+?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an additional complication - it would require adding net7.0
as a TFM to Http and Grcp instrumentation assemblies. Http currently uses a boolean which is determined by looking at version of the System.Net.Http
assembly, the trimmer would not be able to figure this out. I don't think we have a runtime property which the trimmer will know a constant value of and can be used to determine the version.
Additionally, Grpc package is currently netstandard2.0
/netstandard2.1
only... we will probably have to change that anyway (to get the annotation attributes), but by default it would only get net6.0
, not net7.0
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+@noahfalk as an "fyi".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Older versions of aspnet and http client used to create the Activity
objects the legacy way using new Activity
. OpenTelemetry wanted to support these old versions of such platforms which needed to set Kind
properly on such activity. This was the need for reflection. Aspnet and http client then moved (in .NET 7.0) to create the Activity object using ActivitySource
which set the Kind
correctly on the created Activity
object so there was no need to do anything more with such objects or use the reflection.
I don't know gRPC scenarios. If it is same as aspnet then there is no need to call reflection on .NET 7.0+.
@cijothomas you were the one implementing that as I recall if you have more input here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know gRPC scenarios.
It looks like it isn't following this guidance of using ActivitySource.CreateActivity/StartActivity
. 😢 For either it's own Activity:
nor its copy of the HTTP System.Net.Http.HttpRequestOut
Activity:
Maybe someone should log an issue on the gRPC client to move away from the "legacy way new Activity(...)
" and use an ActivitySource. Then OpenTelemetry wouldn't need to use reflection here at all.
cc @JamesNK
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
grpc-dotnet was written for .NET Core 3, and ActivitySource
didn't exist then. grpc-dotnet still targets .NET Core 3 + .NET 5, but I'm going to drop the unsupported targets for .NET 8.
Grpc.Net.Client also targets netstandard2.0, but ActivitySource is in the System.Diagnostics.DiagnosticSource NuGet package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
Things to do outside of this PR:
- Update targets for GrpcNetClient instrumentation library
- Check if GrpcNetClient instrumentation library can be updated to listen to ActivitySource on net7+ targets
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## main #4695 +/- ##
==========================================
- Coverage 85.14% 85.10% -0.04%
==========================================
Files 314 314
Lines 12735 12722 -13
==========================================
- Hits 10843 10827 -16
- Misses 1892 1895 +3
|
I don't understand the test failure - it passes locally just fine. |
Why doesn't this change decrease the AOT warning count? opentelemetry-dotnet/test/OpenTelemetry.AotCompatibility.Tests/AotCompatibilityTests.cs Line 88 in 8c7173f
Are there still usages of System.Linq.Expressions hanging around? |
It does on .NET 8. But the test runs on .NET 7 and in .NET 7 ASP.NET uses Linq.Expression is certain places which pull it in -> causing the same warnings. I verified that there are no Linq.Expression usages from OTel after this change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for fixing this, @vitek-karas. LGTM
Towards #3429
In AOT
Expression.Lambda.Compile
method is implemented via an interpreter. So it does work, but it's relatively slow. Additionally it's not fully NativeAOT compatible yet.There are two places where it's used in the source code:
ExceptionProcessor
uses it to accessMarshal.GetExceptionPointers
method which is not part of netstandard API. So in this case simply ifdef and on net6.0+ access the method directly (as it's visible in the net6.0 APIs) and fallback to the Expression.Lambda solution on down-level platforms. Since AOT/trimming is net6.0+ this fixes the problem.ActivityInstrumentationHelper
uses it to access two setters on theActivity
class which are internal. Using reflection and thenCreateDelegate
will provide basically the same performance (a delegate which directly calls the setter) without a need forExpression.Lambda
- so the code is simpler. Since the reflection is statically analyzable (all types are known, the property names are known) this code is fully trim and AOT compatible.There's no impact on warning count because the .NET 7 ASP.NET has Linq.Expression dependency as well. On .NET 8 though this removes all warnings from Linq.Expressions.
@Yun-Ting @eerhardt
Merge requirement checklist
CHANGELOG.md
files updated for non-trivial changes