Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features

- Extended `SentryThread` by `Main` to allow indication whether the thread is considered the current main thread ([#4807](https://github.com/getsentry/sentry-dotnet/pull/4807))
- Add _experimental_ support for [Sentry trace-connected Metrics](https://docs.sentry.io/product/explore/metrics/) ([#4834](https://github.com/getsentry/sentry-dotnet/pull/4834))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 🚫 The changelog entry seems to be part of an already released section ## 6.1.0.
    Consider moving the entry to the ## Unreleased section, please.


### Dependencies

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
using BenchmarkDotNet.Attributes;
using Sentry.Extensibility;
using Sentry.Internal;
using Sentry.Protocol;

namespace Sentry.Benchmarks;

public class StructuredLogBatchProcessorBenchmarks
/// <summary>
/// <see cref="BatchProcessor{TItem}"/> (formerly "Sentry.Internal.StructuredLogBatchProcessor") was originally developed as Batch Processor for Logs only.
/// When adding support for Trace-connected Metrics, which are quite similar to Logs, it has been made generic to support both.
/// For comparability of results, we still benchmark with <see cref="SentryLog"/>, rather than <see cref="SentryMetric"/>.
/// </summary>
public class BatchProcessorBenchmarks
{
private Hub _hub;
private StructuredLogBatchProcessor _batchProcessor;
private BatchProcessor<SentryLog> _batchProcessor;
private SentryLog _log;

[Params(10, 100)]
Expand All @@ -29,7 +35,7 @@ public void Setup()
var clientReportRecorder = new NullClientReportRecorder();

_hub = new Hub(options, DisabledHub.Instance);
_batchProcessor = new StructuredLogBatchProcessor(_hub, BatchCount, batchInterval, clientReportRecorder, null);
_batchProcessor = new BatchProcessor<SentryLog>(_hub, BatchCount, batchInterval, StructuredLog.Capture, clientReportRecorder, null);
_log = new SentryLog(DateTimeOffset.Now, SentryId.Empty, SentryLogLevel.Trace, "message");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
```

BenchmarkDotNet v0.13.12, macOS 26.1 (25B78) [Darwin 25.1.0]
Apple M3 Pro, 1 CPU, 12 logical and 12 physical cores
.NET SDK 10.0.100
[Host] : .NET 8.0.14 (8.0.1425.11118), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 8.0.14 (8.0.1425.11118), Arm64 RyuJIT AdvSIMD


```
| Method | BatchCount | OperationsPerInvoke | Mean | Error | StdDev | Median | Gen0 | Allocated |
|------------------------- |----------- |-------------------- |-------------:|------------:|-------------:|-------------:|-------:|----------:|
| **EnqueueAndFlush** | **10** | **100** | **1,896.9 ns** | **9.94 ns** | **8.81 ns** | **1,894.2 ns** | **0.6104** | **5 KB** |
| EnqueueAndFlush_Parallel | 10 | 100 | 16,520.9 ns | 327.78 ns | 746.51 ns | 16,350.4 ns | 1.1292 | 9.29 KB |
| **EnqueueAndFlush** | **10** | **200** | **4,085.5 ns** | **80.03 ns** | **74.86 ns** | **4,087.1 ns** | **1.2207** | **10 KB** |
| EnqueueAndFlush_Parallel | 10 | 200 | 39,371.8 ns | 776.85 ns | 1,360.59 ns | 38,725.0 ns | 1.6479 | 13.6 KB |
| **EnqueueAndFlush** | **10** | **1000** | **18,829.3 ns** | **182.18 ns** | **142.24 ns** | **18,836.4 ns** | **6.1035** | **50 KB** |
| EnqueueAndFlush_Parallel | 10 | 1000 | 151,934.1 ns | 2,631.83 ns | 3,232.12 ns | 151,495.9 ns | 3.6621 | 31.31 KB |
| **EnqueueAndFlush** | **100** | **100** | **864.9 ns** | **2.16 ns** | **1.68 ns** | **865.0 ns** | **0.1469** | **1.2 KB** |
| EnqueueAndFlush_Parallel | 100 | 100 | 7,414.9 ns | 74.86 ns | 70.02 ns | 7,405.9 ns | 0.5722 | 4.61 KB |
| **EnqueueAndFlush** | **100** | **200** | **1,836.9 ns** | **15.28 ns** | **12.76 ns** | **1,834.9 ns** | **0.2937** | **2.41 KB** |
| EnqueueAndFlush_Parallel | 100 | 200 | 37,119.5 ns | 726.04 ns | 1,252.39 ns | 36,968.9 ns | 0.8545 | 7.27 KB |
| **EnqueueAndFlush** | **100** | **1000** | **8,567.2 ns** | **84.25 ns** | **74.68 ns** | **8,547.4 ns** | **1.4648** | **12.03 KB** |
| EnqueueAndFlush_Parallel | 100 | 1000 | 255,284.5 ns | 5,095.08 ns | 12,593.77 ns | 258,313.9 ns | 1.9531 | 19.02 KB |

This file was deleted.

17 changes: 16 additions & 1 deletion src/Sentry/BindableSentryOptions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Sentry;

/// <summary>
/// Contains representations of the subset of properties in SentryOptions that can be set from ConfigurationBindings.
/// Contains representations of the subset of properties in <see cref="SentryOptions"/> that can be set from ConfigurationBindings.
/// Note that all of these properties are nullable, so that if they are not present in configuration, the values from
/// the type being bound to will be preserved.
/// </summary>
Expand Down Expand Up @@ -56,6 +56,8 @@ internal partial class BindableSentryOptions
public bool? EnableSpotlight { get; set; }
public string? SpotlightUrl { get; set; }

public ExperimentalSentryOptions? Experimental { get; set; }

public void ApplyTo(SentryOptions options)
{
options.IsGlobalModeEnabled = IsGlobalModeEnabled ?? options.IsGlobalModeEnabled;
Expand Down Expand Up @@ -106,11 +108,24 @@ public void ApplyTo(SentryOptions options)
options.EnableSpotlight = EnableSpotlight ?? options.EnableSpotlight;
options.SpotlightUrl = SpotlightUrl ?? options.SpotlightUrl;

if (Experimental is { } experimental)
{
options.Experimental.EnableMetrics = experimental.EnableMetrics ?? options.Experimental.EnableMetrics;
}

#if ANDROID
Android.ApplyTo(options.Android);
Native.ApplyTo(options.Native);
#elif __IOS__
Native.ApplyTo(options.Native);
#endif
}

/// <summary>
/// Bindable Options for <see cref="SentryOptions.ExperimentalSentryOptions"/>.
/// </summary>
internal class ExperimentalSentryOptions
{
public bool? EnableMetrics { get; set; }
}
}
7 changes: 6 additions & 1 deletion src/Sentry/Extensibility/DisabledHub.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Sentry.Internal;
using Sentry.Protocol.Envelopes;
using Sentry.Protocol.Metrics;

namespace Sentry.Extensibility;

Expand Down Expand Up @@ -267,4 +266,10 @@ public void Dispose()
/// Disabled Logger.
/// </summary>
public SentryStructuredLogger Logger => DisabledSentryStructuredLogger.Instance;

/// <summary>
/// Disabled Metrics.
/// </summary>
[Experimental("SENTRYTRACECONNECTEDMETRICS")]
public SentryTraceMetrics Metrics => DisabledSentryTraceMetrics.Instance;
}
7 changes: 6 additions & 1 deletion src/Sentry/Extensibility/HubAdapter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Sentry.Infrastructure;
using Sentry.Protocol.Envelopes;
using Sentry.Protocol.Metrics;

namespace Sentry.Extensibility;

Expand Down Expand Up @@ -37,6 +36,12 @@ private HubAdapter() { }
/// </summary>
public SentryStructuredLogger Logger { [DebuggerStepThrough] get => SentrySdk.Logger; }

/// <summary>
/// Forwards the call to <see cref="SentrySdk"/>.
/// </summary>
[Experimental("SENTRYTRACECONNECTEDMETRICS")]
public SentryTraceMetrics Metrics { [DebuggerStepThrough] get => SentrySdk.Experimental.Metrics; }

/// <summary>
/// Forwards the call to <see cref="SentrySdk"/>.
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions src/Sentry/IHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ public interface IHub : ISentryClient, ISentryScopeManager
/// </remarks>
public SentryStructuredLogger Logger { get; }

/// <summary>
/// Generates and sends metrics to Sentry.
/// </summary>
/// <remarks>
/// Available options:
/// <list type="bullet">
/// <item><see cref="Sentry.SentryOptions.ExperimentalSentryOptions.EnableMetrics"/></item>
/// <item><see cref="Sentry.SentryOptions.ExperimentalSentryOptions.SetBeforeSendMetric{T}(System.Func{SentryMetric{T}, SentryMetric{T}})"/></item>
/// </list>
/// </remarks>
[Experimental("SENTRYTRACECONNECTEDMETRICS")]
public SentryTraceMetrics Metrics { get; }

/// <summary>
/// Starts a transaction.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Sentry.Internal;

/// <summary>
/// A wrapper over an <see cref="System.Array"/>, intended for reusable buffering.
/// A wrapper over an <see cref="System.Array"/>, intended for reusable buffering for <see cref="BatchProcessor{TItem}"/>.
/// </summary>
/// <remarks>
/// Must be attempted to flush via <see cref="TryEnterFlushScope"/> when either the <see cref="Capacity"/> is reached,
Expand All @@ -12,15 +12,15 @@ namespace Sentry.Internal;
/// allowing multiple threads for <see cref="Add"/> or exclusive access for <see cref="FlushScope.Flush"/>.
/// </remarks>
[DebuggerDisplay("Name = {Name}, Capacity = {Capacity}, Additions = {_additions}, AddCount = {AddCount}, IsDisposed = {_disposed}")]
internal sealed class StructuredLogBatchBuffer : IDisposable
internal sealed class BatchBuffer<TItem> : IDisposable
{
private readonly SentryLog[] _array;
private readonly TItem[] _array;
private int _additions;
private readonly ScopedCountdownLock _addLock;

private readonly Timer _timer;
private readonly TimeSpan _timeout;
private readonly Action<StructuredLogBatchBuffer> _timeoutExceededAction;
private readonly Action<BatchBuffer<TItem>> _timeoutExceededAction;

private volatile bool _disposed;

Expand All @@ -31,12 +31,12 @@ internal sealed class StructuredLogBatchBuffer : IDisposable
/// <param name="timeout">When the timeout exceeds after an item has been added and the <paramref name="capacity"/> not yet been exceeded, <paramref name="timeoutExceededAction"/> is invoked.</param>
/// <param name="timeoutExceededAction">The operation to execute when the <paramref name="timeout"/> exceeds if the buffer is neither empty nor full.</param>
/// <param name="name">Name of the new buffer.</param>
public StructuredLogBatchBuffer(int capacity, TimeSpan timeout, Action<StructuredLogBatchBuffer> timeoutExceededAction, string? name = null)
public BatchBuffer(int capacity, TimeSpan timeout, Action<BatchBuffer<TItem>> timeoutExceededAction, string? name = null)
{
ThrowIfLessThanTwo(capacity, nameof(capacity));
ThrowIfNegativeOrZero(timeout, nameof(timeout));

_array = new SentryLog[capacity];
_array = new TItem[capacity];
_additions = 0;
_addLock = new ScopedCountdownLock();

Expand Down Expand Up @@ -72,18 +72,18 @@ public StructuredLogBatchBuffer(int capacity, TimeSpan timeout, Action<Structure
/// Is thread-safe.
/// </summary>
/// <param name="item">Element attempted to be added.</param>
/// <returns>An <see cref="StructuredLogBatchBufferAddStatus"/> describing the result of the thread-safe operation.</returns>
internal StructuredLogBatchBufferAddStatus Add(SentryLog item)
/// <returns>An <see cref="BatchBufferAddStatus"/> describing the result of the thread-safe operation.</returns>
internal BatchBufferAddStatus Add(TItem item)
{
if (_disposed)
{
return StructuredLogBatchBufferAddStatus.IgnoredIsDisposed;
return BatchBufferAddStatus.IgnoredIsDisposed;
}

using var scope = _addLock.TryEnterCounterScope();
if (!scope.IsEntered)
{
return StructuredLogBatchBufferAddStatus.IgnoredIsFlushing;
return BatchBufferAddStatus.IgnoredIsFlushing;
}

var count = Interlocked.Increment(ref _additions);
Expand All @@ -92,24 +92,24 @@ internal StructuredLogBatchBufferAddStatus Add(SentryLog item)
{
EnableTimer();
_array[count - 1] = item;
return StructuredLogBatchBufferAddStatus.AddedFirst;
return BatchBufferAddStatus.AddedFirst;
}

if (count < _array.Length)
{
_array[count - 1] = item;
return StructuredLogBatchBufferAddStatus.Added;
return BatchBufferAddStatus.Added;
}

if (count == _array.Length)
{
DisableTimer();
_array[count - 1] = item;
return StructuredLogBatchBufferAddStatus.AddedLast;
return BatchBufferAddStatus.AddedLast;
}

Debug.Assert(count > _array.Length);
return StructuredLogBatchBufferAddStatus.IgnoredCapacityExceeded;
return BatchBufferAddStatus.IgnoredCapacityExceeded;
}

/// <summary>
Expand Down Expand Up @@ -157,7 +157,7 @@ internal void OnIntervalElapsed(object? state)
/// Returns a new Array consisting of the elements successfully added.
/// </summary>
/// <returns>An Array with Length of successful additions.</returns>
private SentryLog[] ToArrayAndClear()
private TItem[] ToArrayAndClear()
{
var additions = _additions;
var length = _array.Length;
Expand All @@ -173,7 +173,7 @@ private SentryLog[] ToArrayAndClear()
/// </summary>
/// <param name="length">The Length of the buffer a new Array is created from.</param>
/// <returns>An Array with Length of <paramref name="length"/>.</returns>
private SentryLog[] ToArrayAndClear(int length)
private TItem[] ToArrayAndClear(int length)
{
Debug.Assert(_addLock.IsSet);

Expand All @@ -182,14 +182,14 @@ private SentryLog[] ToArrayAndClear(int length)
return array;
}

private SentryLog[] ToArray(int length)
private TItem[] ToArray(int length)
{
if (length == 0)
{
return Array.Empty<SentryLog>();
return Array.Empty<TItem>();
}

var array = new SentryLog[length];
var array = new TItem[length];
Array.Copy(_array, array, length);
return array;
}
Expand Down Expand Up @@ -253,17 +253,17 @@ static void ThrowNegativeOrZero(TimeSpan value, string paramName)
/// A scope than ensures only a single <see cref="Flush"/> operation is in progress,
/// and blocks the calling thread until all <see cref="Add"/> operations have finished.
/// When <see cref="IsEntered"/> is <see langword="true"/>, no more <see cref="Add"/> can be started,
/// which will then return <see cref="StructuredLogBatchBufferAddStatus.IgnoredIsFlushing"/> immediately.
/// which will then return <see cref="BatchBufferAddStatus.IgnoredIsFlushing"/> immediately.
/// </summary>
/// <remarks>
/// Only <see cref="Flush"/> when scope <see cref="IsEntered"/>.
/// </remarks>
internal ref struct FlushScope : IDisposable
{
private StructuredLogBatchBuffer? _lockObj;
private BatchBuffer<TItem>? _lockObj;
private ScopedCountdownLock.LockScope _scope;

internal FlushScope(StructuredLogBatchBuffer lockObj, ScopedCountdownLock.LockScope scope)
internal FlushScope(BatchBuffer<TItem> lockObj, ScopedCountdownLock.LockScope scope)
{
Debug.Assert(scope.IsEntered);
_lockObj = lockObj;
Expand All @@ -272,7 +272,7 @@ internal FlushScope(StructuredLogBatchBuffer lockObj, ScopedCountdownLock.LockSc

internal bool IsEntered => _scope.IsEntered;

internal SentryLog[] Flush()
internal TItem[] Flush()
{
var lockObj = _lockObj;
if (lockObj is not null)
Expand Down Expand Up @@ -300,7 +300,7 @@ public void Dispose()
}
}

internal enum StructuredLogBatchBufferAddStatus : byte
internal enum BatchBufferAddStatus : byte
{
AddedFirst,
Added,
Expand Down
Loading
Loading