-
Notifications
You must be signed in to change notification settings - Fork 782
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
Suppress instrumentation upon calling ActivityExporter.ExportAsync #977
Changes from 4 commits
a76e069
f5dae7b
4c63615
6c289d4
b7921b3
824c14b
d31bd24
f7acf67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,16 +33,28 @@ public abstract class ActivityProcessor : IDisposable | |
/// Activity start hook. | ||
/// </summary> | ||
/// <param name="activity">Instance of activity to process.</param> | ||
public virtual void OnStart(Activity activity) | ||
public void OnStart(Activity activity) | ||
{ | ||
if (Sdk.SuppressInstrumentation) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just wondering out loud here... should the |
||
{ | ||
return; | ||
} | ||
|
||
this.OnStartInternal(activity); | ||
} | ||
|
||
/// <summary> | ||
/// Activity end hook. | ||
/// </summary> | ||
/// <param name="activity">Instance of activity to process.</param> | ||
public virtual void OnEnd(Activity activity) | ||
public void OnEnd(Activity activity) | ||
{ | ||
if (Sdk.SuppressInstrumentation) | ||
{ | ||
return; | ||
} | ||
|
||
this.OnEndInternal(activity); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -101,5 +113,13 @@ protected virtual void Dispose(bool disposing) | |
|
||
this.disposed = true; | ||
} | ||
|
||
protected virtual void OnStartInternal(Activity activity) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alanwest The "Internal" in the name is kind of non-standard. I think we were talking about this before but I lost track. Could we not do? public void Start(Activity activity)
protected virtual void OnStart(Activity activity) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't looked through the changes yet, imagine we have a chain of processors, checking There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Agreed. I actually have this change staged up, just haven't pushed yet. Mostly just picked things up on the PR today and resolved the various conflicts.
Yes, I haven't spent enough time analyzing this yet. Earlier there was an idea from @CodeBlanch to see if this fits better in the sampler. Will consider this soon. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This would require taking an OTel dependency. We want to avoid this need for library authors, no?
Yes, this makes sense for the legacy route. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would worry a bit relying on others to do it perfectly. I'm envisioning a bunch of difficult-to-triage issues being opened when applications go into infinite loops. We could move the check into the ActivityListener? If SuppressInstrumentation is triggered, we return ActivityDataRequest.None from GetRequestedDataUsingContext (or whatever it ends up being called). If we do that, Activity is never created. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If perf looks good, putting the logic in the ActivityListener will work.
Definitely yes. I think most library authors don't need to care about this (infinite loops). Only library authors who build libraries that can be used for exporting task will have to worry about it. Probably on for HTTP / TCP / UDP / Unix domain socket. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More data points:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll try on this on for size tomorrow:
Then try to get some perf numbers. If we're not happy, then I'm definitely ok entertaining scoping this only to certain instrumentation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
} | ||
|
||
protected virtual void OnEndInternal(Activity activity) | ||
{ | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,47 +136,6 @@ public BatchingActivityProcessor(ActivityExporter exporter, int maxQueueSize, Ti | |
}; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void OnEnd(Activity activity) | ||
{ | ||
// because of race-condition between checking the size and enqueueing, | ||
// we might end up with a bit more activities than maxQueueSize. | ||
// Let's just tolerate it to avoid extra synchronization. | ||
if (this.currentQueueSize >= this.maxQueueSize) | ||
{ | ||
OpenTelemetrySdkEventSource.Log.SpanProcessorQueueIsExhausted(); | ||
return; | ||
} | ||
|
||
var size = Interlocked.Increment(ref this.currentQueueSize); | ||
|
||
this.exportQueue.Enqueue(activity); | ||
|
||
if (size >= this.maxExportBatchSize) | ||
{ | ||
bool lockTaken = this.flushLock.Wait(0); | ||
if (lockTaken) | ||
{ | ||
Task.Run(async () => | ||
{ | ||
try | ||
{ | ||
await this.FlushAsyncInternal(drain: false, lockAlreadyHeld: true, CancellationToken.None).ConfigureAwait(false); | ||
} | ||
catch (Exception ex) | ||
{ | ||
OpenTelemetrySdkEventSource.Log.SpanProcessorException(nameof(this.OnEnd), ex); | ||
} | ||
finally | ||
{ | ||
this.flushLock.Release(); | ||
} | ||
}); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
/// <exception cref="OperationCanceledException">If the <paramref name="cancellationToken"/> is canceled.</exception> | ||
public override async Task ShutdownAsync(CancellationToken cancellationToken) | ||
|
@@ -225,6 +184,47 @@ protected override void Dispose(bool disposing) | |
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
protected override void OnEndInternal(Activity activity) | ||
{ | ||
// because of race-condition between checking the size and enqueueing, | ||
// we might end up with a bit more activities than maxQueueSize. | ||
// Let's just tolerate it to avoid extra synchronization. | ||
if (this.currentQueueSize >= this.maxQueueSize) | ||
{ | ||
OpenTelemetrySdkEventSource.Log.SpanProcessorQueueIsExhausted(); | ||
return; | ||
} | ||
|
||
var size = Interlocked.Increment(ref this.currentQueueSize); | ||
|
||
this.exportQueue.Enqueue(activity); | ||
|
||
if (size >= this.maxExportBatchSize) | ||
{ | ||
bool lockTaken = this.flushLock.Wait(0); | ||
if (lockTaken) | ||
{ | ||
Task.Run(async () => | ||
{ | ||
try | ||
{ | ||
await this.FlushAsyncInternal(drain: false, lockAlreadyHeld: true, CancellationToken.None).ConfigureAwait(false); | ||
} | ||
catch (Exception ex) | ||
{ | ||
OpenTelemetrySdkEventSource.Log.SpanProcessorException(nameof(this.OnEnd), ex); | ||
} | ||
finally | ||
{ | ||
this.flushLock.Release(); | ||
} | ||
}); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
private async Task FlushAsyncInternal(bool drain, bool lockAlreadyHeld, CancellationToken cancellationToken) | ||
{ | ||
if (!lockAlreadyHeld) | ||
|
@@ -320,14 +320,18 @@ private async Task<int> ExportBatchAsync(CancellationToken cancellationToken) | |
this.batch.Add(nextActivity); | ||
} | ||
|
||
var result = await this.exporter.ExportAsync(this.batch, cancellationToken).ConfigureAwait(false); | ||
if (result != ExportResult.Success) | ||
using (Sdk.SuppressInstrumentation) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uh oh this is a terrible bug waiting to happen. This should be:
This bug would result in the static instance getting disposed. I think we need to rethink things a bit to prevent this from being possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +100 |
||
{ | ||
OpenTelemetrySdkEventSource.Log.ExporterErrorResult(result); | ||
var result = await this.exporter.ExportAsync(this.batch, cancellationToken).ConfigureAwait(false); | ||
|
||
// we do not support retries for now and leave it up to exporter | ||
// as only exporter implementation knows how to retry: which items failed | ||
// and what is the reasonable policy for that exporter. | ||
if (result != ExportResult.Success) | ||
{ | ||
OpenTelemetrySdkEventSource.Log.ExporterErrorResult(result); | ||
|
||
// we do not support retries for now and leave it up to exporter | ||
// as only exporter implementation knows how to retry: which items failed | ||
// and what is the reasonable policy for that exporter. | ||
} | ||
} | ||
|
||
return this.batch.Count; | ||
|
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 you open to renaming these? I think the more typical .NET-y style for this would be:
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 others are open, I'm open to this. It does mean a changing the public API and would affect anyone out there that has created a custom processor.
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.
Both ways are breaking, no?
Today:
First version:
Proposed version:
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.
Duh. 🤦 Yes, you're right.
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 move this logic up into ActivityListener to avoid the break. Like what's I'm doing on #969. But I think this is a better long-term design. We don't want a lot of stuff in ActivityListener IMO. I was thinking once this is available, I would re-do how that is done to be in the
Start
orStop
implementation of base ActivityProcessor.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.
Normally I would struggle with naming things. This time it is easier for me as the spec said we should use
OnStart
andOnEnd
😄.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.
Ah ha! Yes I wondered about that right after submitting this question. Great! We won't touch it.
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.
@alanwest Separate PR works for me 👍
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.
@CodeBlanch no PR necessary 😄. As Reiley pointed out
OnStart
andOnEnd
are the names specified in the spec for the public API.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.
Got it. Sorry I didn't realize you changed them to Start/OnStart already. That works for me. I think if there wasn't a spec involved something like StartActivity/OnActivityStarted would be better (what you were getting at), but good to stay as close to the spec as possible.