-
-
Notifications
You must be signed in to change notification settings - Fork 206
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
Support performance #633
Support performance #633
Conversation
I know the interface is really exposed here and ideally we would want to add some methods to make things nicer (i.e. set timestamps automatically, etc.). For now, I want to wire this on Sentry.AspNetCore side first. I have never done that, any guidance on how should I approach this? @bruno-garcia @lucas-zimerman |
Sentry docs say:
Is it true in regards to our event structure? Or should it just implement |
True, the transaction it's basically 'IScope' with some extras (a list of pans, StartTimestamp and some functions for creating a child). |
If it helps https://github.com/lucas-zimerman/ContribSentryDotNet/blob/ref/better_tracing/ContribSentry/Internals/SpanStatus.cs |
for each transaction, I was adding a "transaction Breadcrumb", if you're also going to add the breadcrumb for every transaction, it would be nice to have an option to disable the transaction breadcrumb Breadcrumb example: |
@lucas-zimerman does your project also have AspNetCore integration for transactions? |
There's no middleware for it but it should work just fine if you manually call the StartTransaction where you would like to measure the performance. All you need is an AsyncLocal control and a way for controlling when it starts and when it ends (with or without any error).
|
Only Performance, release health is another pack of new classes. |
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.
#502 (not sure if this will enable release health as well?)
Let's focus on Performance only for now.
Is it true in regards to our event structure? Or should it just implement IScope?
Yeah technically a Transaction is a SentryEvent but we don't want BeforeSend to run for example (and hopefully no EventProcessors too), but we might have to let EventProcessors run if we need to align with other SDKs that did that (by accident).
As there any additional options that need to be added into SentryOptions for this?
Those two. We can add that later, lets go without sampling in this PR to make smaller chunks but keep that in mind.
public bool IsSampled { get; set; } | ||
|
||
private ConcurrentDictionary<string, string>? _tags; | ||
public IReadOnlyDictionary<string, string> Tags => _tags ??= new ConcurrentDictionary<string, string>(); |
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 assume concurrency here might be more of an issue so this approach of instantiating on the getter won't work well here.
We might need to synchronize in order to make sure concurrent calls to get
get the same instance. Maybe a double check lock will do
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.
So Spans have tags? I was thinking that only Transactions could have tags
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.
Maybe just eagerly initialize the instance? It will probably be a smaller overhead than a lock.
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.
one will incur overhead on each new span due to allocating the list then GC'ing it. The other one will cost when accessing it only and only the first time due to the double check lock.
Without measuring we're just guessing, the the transaction I'm happy with always having the list there since more often than not we'll use it, for individual Spans, I don't know
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.
The other one will cost when accessing it only and only the first time due to the double check lock.
Maybe I misunderstood your suggestion, but how will that work? From my understanding, the lock has to be on the getter, which would mean it's acquired/checked every time the property is accessed.
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.
@lucas-zimerman the docs say so, but I also found the docs to be incredibly unreliable too, so...
https://develop.sentry.dev/sdk/event-payloads/span/
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.
If the docs are not good, we should fix it. Please open a PR there or at least raise a flag (open an issue asking for clarification).
src/Sentry/Protocol/Transaction.cs
Outdated
|
||
namespace Sentry.Protocol | ||
{ | ||
public class Transaction : ISpan, IJsonSerializable |
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.
Transaction is actually a whole Event :(
That's how it is today in Sentry, basically an event with a contexts.trace
field and spans
We can try to go with ISpan
for now and see what it looks like.
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.
What is contexts.trace
actually? I've seen it referenced before but still not sure.
Also, "transaction is actually a whole Event", does that affect how we should design the class? We probably can't make use of inheritance anyway due to manual serialization. I'm thinking of doing public class Transactions : ISpan, IScope
, would that be enough?
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.
we could inherit and call the base serializer to add some stuff and the Transaction one to add the rest.
In Java it was split in 2 parts, SentryEvent (current one) and Transaction, and both inherit from some BaseEvent.
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.
we could inherit and call the base serializer to add some stuff and the Transaction one to add the rest.
This will be messy because it will have to add serialization logic before the writer.WriteEndObject()
.
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.
we could call a protected virtual AddMoreStuff(writer)
and implement this in the derived type
@bruno-garcia regarding aspnetcore middleware, do we programmatically add one before endpoint routing middleware and then measure the execution time of |
That sounds like it would work yeah |
@bruno-garcia do we create some spans automatically for nested operations using the middleware? Or just let the user do it themselves. |
@Tyrrrz any outgoing HTTP request, SQL queries would eventually need to become spans, and automatically. We'll get there eventually though, for now if we can instrument some areas like Controller action invocation, and HTTP requests only would bea great start |
@bruno-garcia where should the current transaction be kept? Should it be kept on the current scope? Or should we create a new |
@bruno-garcia @lucas-zimerman can spans be infinitely nested within other spans? or is it just the transaction that can have spans? |
Each Span could have N childrens Not sure if the Doc specifies some limit but I'd guess the only limit is the request size. |
Transaction class has a list of Spans, they are linked in Sentry with their Ids.. the SDK doesn't hold a tree structure. The Scope can hold a Transaction. This was done in the Java SDK, you can peek the implementation there. The Ruby PR is less than a 1k lines: getsentry/sentry-ruby#1108 If the docs are not very clear, we need to improve the docs. |
We also need to consider that |
Ok, so the spans can also have spans inside, but the class representation itself doesn't keep track of the list, right?
Ok, cool.
If you look at the transaction/span docs, they are all over the place. The list of fields does not match the example at the end. In docs it says |
Also, @bruno-garcia
Looking at Java SDK, it looks like that's exactly what happened there: |
Would be nice to get this fixed. Do you mind opening a PR? |
Codecov Report
@@ Coverage Diff @@
## main #633 +/- ##
==========================================
- Coverage 79.91% 77.53% -2.39%
==========================================
Files 153 162 +9
Lines 4814 5212 +398
Branches 1251 1347 +96
==========================================
+ Hits 3847 4041 +194
- Misses 608 784 +176
- Partials 359 387 +28
Continue to review full report at Codecov.
|
Can't understand what failed windows build. |
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.
C# and .NET aren't my strongest skills but a few notes from here and there. The PR looks clean and good as a whole, good job!
/// </summary> | ||
public static EnvelopeItem FromTransaction(Transaction transaction) | ||
{ | ||
var header = new Dictionary<string, object?>(StringComparer.Ordinal) |
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.
Maybe that should be documented as a TODO now, if it is not yet included but could be added to the implementation later on?
Thanks @aleksihakli |
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 are a few things we can take on a new PR, but this is good to merge and put out on a new preview!
@@ -12,6 +14,7 @@ | |||
* Add `SentryScopeStateProcessor` #603 | |||
* Add net5.0 TFM to libraries #606 | |||
* Add more logging to CachingTransport #619 | |||
* Bump Microsoft.Bcl.AsyncInterfaces to 5.0.0 #618 |
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.
It's on the line below (it got some ` on another PR)
* Bump Microsoft.Bcl.AsyncInterfaces to 5.0.0 #618 |
/// Returns a dummy transaction. | ||
/// </summary> | ||
public Transaction CreateTransaction(string name, string operation) => | ||
new Transaction(this, null); |
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.
We should have a singleton NoOp instance here instead.
Having a disabled hub means we should reduce the instrumentations overhead
trans.Sdk.AddPackage(protocolPackageName, nameAndVersion.Version); | ||
} | ||
|
||
ConfigureScope(scope => scope.Transaction = trans); |
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 believe by default we won't do this. The snippets I saw from JS require the caller to decide whether the transaction should be bound to the scope or not.
For now this looks good and lets try like this.
if (@event.Sdk != null) | ||
// SDK Name/Version might have be already set by an outer package | ||
// e.g: ASP.NET Core can set itself as the SDK | ||
if (@event.Sdk.Version == null && @event.Sdk.Name == null) |
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.
Are transactions going through this at the end? Doesn't seem like it since this take SentryEvent.
So transactions will always be reported as sentry.dotnet
?
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.
Yeah... The SDK is set separately when creating the transaction right now. Transactions also don't get all the other default metadata either, which is populated by event processors.
/// </summary> | ||
public static Trace FromJson(JsonElement json) | ||
{ | ||
var spanId = json.GetPropertyOrNull("span_id")?.Pipe(SpanId.FromJson) ?? SpanId.Empty; |
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.
Invalid json blobs will result in broken traces being sent (empty string ids etc)
I think we should be returning nullable and dealing with it.
{ | ||
lock (_lock) | ||
{ | ||
return _spans; |
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 exclusive access to just return the reference, which now can be access outside of any locks by the caller which can iterate over it while another thread is adding to it, which would throw an exception.
We need a new list, with the current items:
return _spans; | |
return new List<Span>(_spans); |
/// </summary> | ||
public void Add(Span span) | ||
{ | ||
lock (_lock) |
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.
Taking a lock to add any and all Spans can possibly create contention if we have a bunch of spans getting added to the same transaction.
Lets revisit this later to add a capped list that won't require exclusive access on all Add
Maybe just a Interlocked.Increment
so we keep track of the count of items in the _span
and using ConcurrentBag
would be an improvement. But we discuss/measure things later, this will do for now.
} | ||
|
||
/// <inheritdoc /> | ||
public SentryLevel? Level { get; set; } |
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.
This probably doesn't make sense, right? What does "level" mean on a Transaction.
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.
Yeah it might be one of those fields that have no place on transaction. It would be really nice to have some "official" list.
{ | ||
if (Random.NextDouble() > _options.TraceSampleRate) | ||
{ | ||
_options.DiagnosticLogger?.LogDebug("Transaction sampled."); |
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.
_options.DiagnosticLogger?.LogDebug("Transaction sampled."); | |
_options.DiagnosticLogger?.LogDebug("Transaction dropped due to random sampling."); |
} | ||
|
||
[Fact] | ||
public void CaptureTransaction_InvalidTransaction_Ignored() |
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.
Why is it ignored?
Maybe: CaptureTransaction_NoName_Ignored
CaptureTransaction_NoOperation_Ignored
and one case per each?
@aleksihakli thanks for helping out with reviewing! |
Took a look and saw that 3.0.0-alpha.7 is now available in package repositories. I'm not sure if I have the time to test drive this before the Christmas holidays, but we'll most probably have the chance to start testing the performance side of things early next year in different environments. Super excited to get this integrated upstream, thanks for the effort folks! Ever since OpBeat was acquired and merged into Elastic APM I've been looking for other viable performance tracking options and think that Sentry is an increasingly exciting option for projects of all size and has a good model with the FOSS and SaaS deployments available. Keep up the great work. |
Thanks for testing @aleksihakli Would you want to discuss this on Discord instead of this PR? It's been merged, I often skip reading messages on merged PR or close issues. There's a channel #dotnet there 👍 |
#502 (not sure if this will enable release health as well?)