-
Notifications
You must be signed in to change notification settings - Fork 862
Batching activity processor #697
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
Closed
Closed
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
ca19aca
rename TestExporter to TestSpanExporter
MikeGoldsmith 27a0b34
add batching activity processor
MikeGoldsmith 437c464
add tests for batching activity processor
MikeGoldsmith 42dcfeb
Merge branch 'master' into batching-activity-processor
cijothomas 74e623d
use Task.Run to start worker
MikeGoldsmith 5231e3e
reuse list for exporting batch
MikeGoldsmith 5bd8692
fix typo - astivities to activities
MikeGoldsmith 1c03a50
Merge branch 'batching-activity-processor' of github.com:open-telemet…
MikeGoldsmith 54fdfe0
Merge branch 'master' into batching-activity-processor
cijothomas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
247 changes: 247 additions & 0 deletions
247
src/OpenTelemetry/Trace/Export/BatchingActivityProcessor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,247 @@ | ||
| // <copyright file="BatchingActivityProcessor.cs" company="OpenTelemetry Authors"> | ||
| // Copyright The OpenTelemetry Authors | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
| // </copyright> | ||
|
|
||
| using System; | ||
| using System.Collections.Concurrent; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using OpenTelemetry.Internal; | ||
|
|
||
| namespace OpenTelemetry.Trace.Export | ||
| { | ||
| /// <summary> | ||
| /// Implements processor that batches activities before calling exporter. | ||
| /// </summary> | ||
| public class BatchingActivityProcessor : ActivityProcessor, IDisposable | ||
| { | ||
| private const int DefaultMaxQueueSize = 2048; | ||
| private const int DefaultMaxExportBatchSize = 512; | ||
| private static readonly TimeSpan DefaultScheduleDelay = TimeSpan.FromMilliseconds(5000); | ||
| private readonly ConcurrentQueue<Activity> exportQueue; | ||
| private readonly int maxQueueSize; | ||
| private readonly int maxExportBatchSize; | ||
| private readonly TimeSpan scheduleDelay; | ||
| private readonly ActivityExporter exporter; | ||
| private readonly List<Activity> batch = new List<Activity>(); | ||
| private CancellationTokenSource cts; | ||
| private volatile int currentQueueSize; | ||
| private bool stopping = false; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="BatchingActivityProcessor"/> class with default parameters: | ||
| /// <list type="bullet"> | ||
| /// <item> | ||
| /// <description>maxQueueSize = 2048,</description> | ||
| /// </item> | ||
| /// <item> | ||
| /// <description>scheduleDelay = 5 sec,</description> | ||
| /// </item> | ||
| /// <item> | ||
| /// <description>maxExportBatchSize = 512</description> | ||
| /// </item> | ||
| /// </list> | ||
| /// </summary> | ||
| /// <param name="exporter">Exporter instance.</param> | ||
| public BatchingActivityProcessor(ActivityExporter exporter) | ||
| : this(exporter, DefaultMaxQueueSize, DefaultScheduleDelay, DefaultMaxExportBatchSize) | ||
| { | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="BatchingActivityProcessor"/> class with custom settings. | ||
| /// </summary> | ||
| /// <param name="exporter">Exporter instance.</param> | ||
| /// <param name="maxQueueSize">Maximum queue size. After the size is reached activities are dropped by processor.</param> | ||
| /// <param name="scheduleDelay">The delay between two consecutive exports.</param> | ||
| /// <param name="maxExportBatchSize">The maximum batch size of every export. It must be smaller or equal to maxQueueSize.</param> | ||
| public BatchingActivityProcessor(ActivityExporter exporter, int maxQueueSize, TimeSpan scheduleDelay, int maxExportBatchSize) | ||
| { | ||
| if (maxQueueSize <= 0) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(maxQueueSize)); | ||
| } | ||
|
|
||
| if (maxExportBatchSize <= 0 || maxExportBatchSize > maxQueueSize) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(maxExportBatchSize)); | ||
| } | ||
|
|
||
| this.exporter = exporter ?? throw new ArgumentNullException(nameof(exporter)); | ||
| this.maxQueueSize = maxQueueSize; | ||
| this.scheduleDelay = scheduleDelay; | ||
| this.maxExportBatchSize = maxExportBatchSize; | ||
|
|
||
| this.cts = new CancellationTokenSource(); | ||
| this.exportQueue = new ConcurrentQueue<Activity>(); | ||
|
|
||
| // worker task that will last for lifetime of processor. | ||
| // Threads are also useless as exporter tasks run in thread pool threads. | ||
| Task.Run(() => this.Worker(this.cts.Token), this.cts.Token); | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override void OnStart(Activity activity) | ||
| { | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override void OnEnd(Activity activity) | ||
| { | ||
| if (this.stopping) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // 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; | ||
| } | ||
|
|
||
| Interlocked.Increment(ref this.currentQueueSize); | ||
|
|
||
| this.exportQueue.Enqueue(activity); | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override async Task ShutdownAsync(CancellationToken cancellationToken) | ||
| { | ||
| if (!this.stopping) | ||
| { | ||
| this.stopping = true; | ||
|
|
||
| // This will stop the loop after current batch finishes. | ||
| this.cts.Cancel(false); | ||
| this.cts.Dispose(); | ||
| this.cts = null; | ||
|
|
||
| // if there are more items, continue until cancellation token allows | ||
| while (this.currentQueueSize > 0 && !cancellationToken.IsCancellationRequested) | ||
| { | ||
| await this.ExportBatchAsync(cancellationToken).ConfigureAwait(false); | ||
| } | ||
|
|
||
| await this.exporter.ShutdownAsync(cancellationToken); | ||
|
|
||
| // there is no point in waiting for a worker task if cancellation happens | ||
| // it's dead already or will die on the next iteration on its own | ||
|
|
||
| // ExportBatchAsync must never throw, we are here either because it was cancelled | ||
| // or because there are no items left | ||
| OpenTelemetrySdkEventSource.Log.ShutdownEvent(this.currentQueueSize); | ||
| } | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| this.Dispose(true); | ||
| } | ||
|
|
||
| protected virtual void Dispose(bool isDisposing) | ||
| { | ||
| if (!this.stopping) | ||
| { | ||
| this.ShutdownAsync(CancellationToken.None).ContinueWith(_ => { }).GetAwaiter().GetResult(); | ||
| } | ||
|
|
||
| if (isDisposing) | ||
| { | ||
| if (this.exporter is IDisposable disposableExporter) | ||
| { | ||
| try | ||
| { | ||
| disposableExporter.Dispose(); | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| OpenTelemetrySdkEventSource.Log.SpanProcessorException("Dispose", e); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private async Task ExportBatchAsync(CancellationToken cancellationToken) | ||
| { | ||
| try | ||
| { | ||
| if (cancellationToken.IsCancellationRequested) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (this.exportQueue.TryDequeue(out var nextActivity)) | ||
| { | ||
| Interlocked.Decrement(ref this.currentQueueSize); | ||
| this.batch.Add(nextActivity); | ||
| } | ||
| else | ||
| { | ||
| // nothing in queue | ||
| return; | ||
| } | ||
|
|
||
| while (this.batch.Count < this.maxExportBatchSize && this.exportQueue.TryDequeue(out nextActivity)) | ||
| { | ||
| Interlocked.Decrement(ref this.currentQueueSize); | ||
| this.batch.Add(nextActivity); | ||
| } | ||
|
|
||
| var result = await this.exporter.ExportAsync(this.batch, cancellationToken).ConfigureAwait(false); | ||
| 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. | ||
| } | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| OpenTelemetrySdkEventSource.Log.SpanProcessorException(nameof(this.ExportBatchAsync), ex); | ||
| } | ||
| finally | ||
| { | ||
| this.batch.Clear(); | ||
| } | ||
| } | ||
|
|
||
| private async Task Worker(CancellationToken cancellationToken) | ||
| { | ||
| while (!cancellationToken.IsCancellationRequested) | ||
| { | ||
| var sw = Stopwatch.StartNew(); | ||
| await this.ExportBatchAsync(cancellationToken).ConfigureAwait(false); | ||
|
|
||
| if (cancellationToken.IsCancellationRequested) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var remainingWait = this.scheduleDelay - sw.Elapsed; | ||
| if (remainingWait > TimeSpan.Zero) | ||
| { | ||
| await Task.Delay(remainingWait, cancellationToken).ConfigureAwait(false); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
57 changes: 57 additions & 0 deletions
57
test/OpenTelemetry.Tests/Implementation/Testing/Export/TestActivityExporter.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| // <copyright file="TestActivityExporter.cs" company="OpenTelemetry Authors"> | ||
| // Copyright The OpenTelemetry Authors | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
| // </copyright> | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Concurrent; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using OpenTelemetry.Trace.Export; | ||
| using System.Diagnostics; | ||
|
|
||
| namespace OpenTelemetry.Testing.Export | ||
| { | ||
| public class TestActivityExporter : ActivityExporter | ||
| { | ||
| private readonly ConcurrentQueue<Activity> activities = new ConcurrentQueue<Activity>(); | ||
| private readonly Action<IEnumerable<Activity>> onExport; | ||
| public TestActivityExporter(Action<IEnumerable<Activity>> onExport) | ||
| { | ||
| this.onExport = onExport; | ||
| } | ||
|
|
||
| public Activity[] ExportedActivities => activities.ToArray(); | ||
|
|
||
| public bool WasShutDown { get; private set; } = false; | ||
|
|
||
| public override Task<ExportResult> ExportAsync(IEnumerable<Activity> data, CancellationToken cancellationToken) | ||
| { | ||
| this.onExport?.Invoke(data); | ||
|
|
||
| foreach (var s in data) | ||
| { | ||
| this.activities.Enqueue(s); | ||
| } | ||
|
|
||
| return Task.FromResult(ExportResult.Success); | ||
| } | ||
|
|
||
| public override Task ShutdownAsync(CancellationToken cancellationToken) | ||
| { | ||
| this.WasShutDown = true; | ||
| return Task.CompletedTask; | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.