Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -8,6 +8,7 @@
- Added distributed tracing without performance for Azure Function Workers ([#2630](https://github.com/getsentry/sentry-dotnet/pull/2630))
- The SDK now provides and overload of `ContinueTrace` that accepts headers as `string` ([#2601](https://github.com/getsentry/sentry-dotnet/pull/2601))
- Sentry tracing middleware now gets configured automatically ([#2602](https://github.com/getsentry/sentry-dotnet/pull/2602))
- Added memory optimisations for GetLastActiveSpan ([#2642](https://github.com/getsentry/sentry-dotnet/pull/2642))

### Fixes

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

BenchmarkDotNet=v0.13.5, OS=macOS Ventura 13.5.2 (22G91) [Darwin 22.6.0]
Apple M1, 1 CPU, 8 logical and 8 physical cores
.NET SDK=7.0.306
[Host] : .NET 6.0.19 (6.0.1923.31806), Arm64 RyuJIT AdvSIMD
ShortRun : .NET 6.0.19 (6.0.1923.31806), Arm64 RyuJIT AdvSIMD

Job=ShortRun IterationCount=3 LaunchCount=1
WarmupCount=3

```
| Method | SpanCount | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|-------------------------------- |---------- |----------:|------------:|----------:|--------:|--------:|----------:|
| **'Create spans for scope access'** | **1** | **32.34 μs** | **26.58 μs** | **1.457 μs** | **4.7607** | **1.5259** | **16.68 KB** |
| **'Create spans for scope access'** | **10** | **101.28 μs** | **239.09 μs** | **13.105 μs** | **9.1553** | **2.5635** | **39.21 KB** |
| **'Create spans for scope access'** | **100** | **628.84 μs** | **1,267.56 μs** | **69.479 μs** | **59.5703** | **18.5547** | **266.14 KB** |
57 changes: 57 additions & 0 deletions benchmarks/Sentry.Benchmarks/LastActiveSpanBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using BenchmarkDotNet.Attributes;
using Perfolizer.Mathematics.Randomization;

namespace Sentry.Benchmarks;

public class LastActiveSpanBenchmarks
{
private const string Operation = "Operation";
private const string Name = "Name";

[Params(1, 10, 100)]
public int SpanCount;

private IDisposable _sdk;

[GlobalSetup(Target = nameof(CreateScopedSpans))]
public void EnabledSdk() => _sdk = SentrySdk.Init(Constants.ValidDsn);

[GlobalCleanup(Target = nameof(CreateScopedSpans))]
public void DisableDsk() => _sdk.Dispose();

[Benchmark(Description = "Create spans for scope access")]
public void CreateScopedSpans()
{
var transaction = SentrySdk.StartTransaction(Name, Operation);
SentrySdk.WithScope(scope =>
{
scope.Transaction = transaction;
for (var i = 0; i < SpanCount; i++)
{
// Simulates a scenario where TransactionTracer.GetLastActiveSpan will be called frequently
// See: https://github.com/getsentry/sentry-dotnet/blob/c2a31b4ead03da388c2db7fe07f290354aa51b9d/src/Sentry/Scope.cs#L567C1-L567C68
CallOneFunction(i);
}
});
transaction.Finish();
}

private void CallOneFunction(int i)
{
var span = SentrySdk.GetSpan()!.StartChild($"One Function {i}");
ThatCallsAnother(i);
span.Finish();
}

private void ThatCallsAnother(int i)
{
var span = SentrySdk.GetSpan()!.StartChild($"Another Function {i}");
AndAnother($"Alternate Description {i}");
span.Finish();
}

private void AndAnother(string description)
{
SentrySdk.ConfigureScope(scope => scope.Span!.Description = description);
}
}
44 changes: 39 additions & 5 deletions src/Sentry/TransactionTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,48 @@ private void AddChildSpan(SpanTracer span)
if (!isOutOfLimit)
{
_spans.Add(span);
_activeSpanTracker.Push(span);
}
}

private class LastActiveSpanTracker
{
private readonly object _lock = new object();

private readonly Lazy<Stack<ISpan>> _trackedSpans = new();
private Stack<ISpan> TrackedSpans => _trackedSpans.Value;

public void Push(ISpan span)
{
lock(_lock)
{
TrackedSpans.Push(span);
}
}

public ISpan? PeekActive()
{
lock(_lock)
{
while (TrackedSpans.Count > 0)
{
// Stop tracking inactive spans
var span = TrackedSpans.Peek();
if (!span.IsFinished)
{
return span;
}
TrackedSpans.Pop();
}
return null;
}
}
}
private readonly LastActiveSpanTracker _activeSpanTracker = new LastActiveSpanTracker();

/// <inheritdoc />
public ISpan? GetLastActiveSpan() => _activeSpanTracker.PeekActive();

/// <inheritdoc />
public void Finish()
{
Expand Down Expand Up @@ -375,11 +414,6 @@ public void Finish(Exception exception, SpanStatus status)
public void Finish(Exception exception) =>
Finish(exception, SpanStatusConverter.FromException(exception));

/// <inheritdoc />
public ISpan? GetLastActiveSpan() =>
// We need to sort by timestamp because the order of ConcurrentBag<T> is not deterministic
Spans.OrderByDescending(x => x.StartTimestamp).FirstOrDefault(s => !s.IsFinished);

/// <inheritdoc />
public SentryTraceHeader GetTraceHeader() => new(TraceId, SpanId, IsSampled);
}