Skip to content

Commit abe49d5

Browse files
Improve timestamp precision of transactions and spans (#1680)
1 parent 2be8df5 commit abe49d5

File tree

6 files changed

+91
-7
lines changed

6 files changed

+91
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- Flatten AggregateException ([#1672](https://github.com/getsentry/sentry-dotnet/pull/1672))
8+
- Improve timestamp precision of transactions and spans ([#1680](https://github.com/getsentry/sentry-dotnet/pull/1680))
89

910
### Features
1011

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using System.Diagnostics;
3+
4+
namespace Sentry.Internal
5+
{
6+
/// <summary>
7+
/// This is a struct-based alternative to <see cref="System.Diagnostics.Stopwatch"/>.
8+
/// It avoids unnecessary allocations and includes realtime clock values.
9+
/// </summary>
10+
internal struct SentryStopwatch
11+
{
12+
private static readonly double StopwatchTicksPerTimeSpanTick =
13+
(double)Stopwatch.Frequency / TimeSpan.TicksPerSecond;
14+
15+
private long _startTimestamp;
16+
private DateTimeOffset _startDateTimeOffset;
17+
18+
public static SentryStopwatch StartNew() => new()
19+
{
20+
_startTimestamp = Stopwatch.GetTimestamp(),
21+
_startDateTimeOffset = DateTimeOffset.UtcNow
22+
};
23+
24+
public DateTimeOffset StartDateTimeOffset => _startDateTimeOffset;
25+
public DateTimeOffset CurrentDateTimeOffset => _startDateTimeOffset + Elapsed;
26+
27+
public TimeSpan Elapsed
28+
{
29+
get
30+
{
31+
var now = Stopwatch.GetTimestamp();
32+
var diff = now - _startTimestamp;
33+
var ticks = (long)(diff / StopwatchTicksPerTimeSpanTick);
34+
return TimeSpan.FromTicks(ticks);
35+
}
36+
}
37+
}
38+
}

src/Sentry/Span.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void SetExtra(string key, object? value) =>
6666
(_extra ??= new Dictionary<string, object?>())[key] = value;
6767

6868
/// <summary>
69-
/// Initializes an instance of <see cref="SpanTracer"/>.
69+
/// Initializes an instance of <see cref="Span"/>.
7070
/// </summary>
7171
public Span(SpanId? parentSpanId, string operation)
7272
{
@@ -77,7 +77,7 @@ public Span(SpanId? parentSpanId, string operation)
7777
}
7878

7979
/// <summary>
80-
/// Initializes an instance of <see cref="SpanTracer"/>.
80+
/// Initializes an instance of <see cref="Span"/>.
8181
/// </summary>
8282
public Span(ISpan tracer)
8383
: this(tracer.ParentSpanId, tracer.Operation)

src/Sentry/SpanTracer.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
4+
using Sentry.Internal;
45

56
namespace Sentry
67
{
@@ -11,6 +12,7 @@ public class SpanTracer : ISpan
1112
{
1213
private readonly IHub _hub;
1314
private readonly TransactionTracer _transaction;
15+
private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew();
1416

1517
/// <inheritdoc />
1618
public SpanId SpanId { get; }
@@ -22,7 +24,7 @@ public class SpanTracer : ISpan
2224
public SentryId TraceId { get; }
2325

2426
/// <inheritdoc />
25-
public DateTimeOffset StartTimestamp { get; } = DateTimeOffset.UtcNow;
27+
public DateTimeOffset StartTimestamp => _stopwatch.StartDateTimeOffset;
2628

2729
/// <inheritdoc />
2830
public DateTimeOffset? EndTimestamp { get; private set; }
@@ -91,7 +93,7 @@ public ISpan StartChild(string operation) =>
9193
public void Finish()
9294
{
9395
Status ??= SpanStatus.UnknownError;
94-
EndTimestamp = DateTimeOffset.UtcNow;
96+
EndTimestamp = _stopwatch.CurrentDateTimeOffset;
9597
}
9698

9799
/// <inheritdoc />

src/Sentry/TransactionTracer.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
44
using System.Linq;
5+
using Sentry.Internal;
56

67
namespace Sentry
78
{
@@ -11,6 +12,7 @@ namespace Sentry
1112
public class TransactionTracer : ITransaction
1213
{
1314
private readonly IHub _hub;
15+
private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew();
1416

1517
/// <inheritdoc />
1618
public SpanId SpanId
@@ -50,7 +52,7 @@ public SentryId TraceId
5052
public string? Release { get; set; }
5153

5254
/// <inheritdoc />
53-
public DateTimeOffset StartTimestamp { get; } = DateTimeOffset.UtcNow;
55+
public DateTimeOffset StartTimestamp => _stopwatch.StartDateTimeOffset;
5456

5557
/// <inheritdoc />
5658
public DateTimeOffset? EndTimestamp { get; internal set; }
@@ -170,8 +172,10 @@ public TransactionTracer(IHub hub, string name, string operation)
170172
/// Initializes an instance of <see cref="Transaction"/>.
171173
/// </summary>
172174
public TransactionTracer(IHub hub, ITransactionContext context)
173-
: this(hub, context.Name, context.Operation)
174175
{
176+
_hub = hub;
177+
Name = context.Name;
178+
Operation = context.Operation;
175179
SpanId = context.SpanId;
176180
ParentSpanId = context.ParentSpanId;
177181
TraceId = context.TraceId;
@@ -224,7 +228,7 @@ public ISpan StartChild(string operation) =>
224228
public void Finish()
225229
{
226230
Status ??= SpanStatus.UnknownError;
227-
EndTimestamp = DateTimeOffset.UtcNow;
231+
EndTimestamp = _stopwatch.CurrentDateTimeOffset;
228232

229233
foreach (var span in _spans)
230234
{
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
namespace Sentry.Tests.Internals;
2+
3+
public class SentryStopwatchTests
4+
{
5+
// Note: We can really can't test this at fine precision. This is just to make sure we're in the right ballpark.
6+
private static readonly TimeSpan TestPrecision = TimeSpan.FromMilliseconds(500);
7+
8+
[Fact]
9+
public void StartDateTimeOffset_IsValid()
10+
{
11+
var sw = SentryStopwatch.StartNew();
12+
var start = sw.StartDateTimeOffset;
13+
14+
start.Should().BeCloseTo(DateTimeOffset.UtcNow, TestPrecision);
15+
}
16+
17+
[Fact]
18+
public void CurrentDateTimeOffset_IsValid()
19+
{
20+
var sw = SentryStopwatch.StartNew();
21+
Thread.Sleep(TimeSpan.FromMilliseconds(100));
22+
var current = sw.CurrentDateTimeOffset;
23+
24+
current.Should().BeCloseTo(DateTimeOffset.UtcNow, TestPrecision);
25+
}
26+
27+
[Fact]
28+
public void Elapsed_IsValid()
29+
{
30+
var sleepTime = TimeSpan.FromMilliseconds(100);
31+
32+
var sw = SentryStopwatch.StartNew();
33+
Thread.Sleep(sleepTime);
34+
var elapsed = sw.Elapsed;
35+
36+
elapsed.Should().BeGreaterThan(TimeSpan.Zero);
37+
elapsed.Should().BeCloseTo(sleepTime, TestPrecision);
38+
}
39+
}

0 commit comments

Comments
 (0)