Skip to content

Commit 94ab77b

Browse files
authored
Optimize trace detail page performance (#10308)
1 parent 42d5bbe commit 94ab77b

File tree

9 files changed

+262
-31
lines changed

9 files changed

+262
-31
lines changed

src/Aspire.Dashboard/Components/Interactions/InteractionsProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ internal record InteractionMessageBarReference(int InteractionId, Message Messag
2828
{
2929
public void Dispose()
3030
{
31-
TelemetryContext?.Dispose();
31+
TelemetryContext.Dispose();
3232
}
3333
}
3434
internal record InteractionDialogReference(int InteractionId, IDialogReference Dialog, ComponentTelemetryContext TelemetryContext) : IDisposable
3535
{
3636
public void Dispose()
3737
{
38-
TelemetryContext?.Dispose();
38+
TelemetryContext.Dispose();
3939
}
4040
}
4141

src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,22 @@ private void UpdateSubscription()
261261
_tracesSubscription?.Dispose();
262262
_tracesSubscription = TelemetryRepository.OnNewTraces(_trace.FirstSpan.Source.ApplicationKey, SubscriptionType.Read, () => InvokeAsync(async () =>
263263
{
264-
UpdateDetailViewData();
265-
await InvokeAsync(StateHasChanged);
266-
await _dataGrid.SafeRefreshDataAsync();
264+
if (_trace == null)
265+
{
266+
return;
267+
}
268+
269+
// Only update trace if required.
270+
if (TelemetryRepository.HasUpdatedTrace(_trace))
271+
{
272+
UpdateDetailViewData();
273+
StateHasChanged();
274+
await _dataGrid.SafeRefreshDataAsync();
275+
}
276+
else
277+
{
278+
Logger.LogTrace("Trace '{TraceId}' is unchanged.", TraceId);
279+
}
267280
}));
268281
}
269282
}

src/Aspire.Dashboard/Otlp/Model/OtlpSpan.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,18 @@ public class OtlpSpan
4242
public OtlpScope Scope { get; }
4343
public TimeSpan Duration => EndTime - StartTime;
4444

45-
public OtlpApplication? UninstrumentedPeer { get; internal set; }
45+
public OtlpApplication? UninstrumentedPeer { get => _uninstrumentedPeer; init => _uninstrumentedPeer = value; }
4646

4747
public IEnumerable<OtlpSpan> GetChildSpans() => GetChildSpans(this, Trace.Spans);
4848
public static IEnumerable<OtlpSpan> GetChildSpans(OtlpSpan parentSpan, OtlpSpanCollection spans) => spans.Where(s => s.ParentSpanId == parentSpan.SpanId);
4949

5050
private string? _cachedDisplaySummary;
51+
private OtlpApplication? _uninstrumentedPeer;
52+
53+
public void SetUninstrumentedPeer(OtlpApplication? peer)
54+
{
55+
_uninstrumentedPeer = peer;
56+
}
5157

5258
public OtlpSpan? GetParentSpan()
5359
{

src/Aspire.Dashboard/Otlp/Model/OtlpTrace.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public TimeSpan Duration
3636
}
3737

3838
public OtlpSpanCollection Spans { get; } = new OtlpSpanCollection();
39+
public DateTime LastUpdatedDate { get; private set; }
3940

4041
public int CalculateDepth(OtlpSpan span)
4142
{
@@ -51,7 +52,7 @@ public int CalculateDepth(OtlpSpan span)
5152

5253
public int CalculateMaxDepth() => Spans.Max(CalculateDepth);
5354

54-
public void AddSpan(OtlpSpan span)
55+
public void AddSpan(OtlpSpan span, bool skipLastUpdatedDate = false)
5556
{
5657
if (Spans.Contains(span.SpanId))
5758
{
@@ -99,6 +100,11 @@ public void AddSpan(OtlpSpan span)
99100
FullName = BuildFullName(span);
100101
}
101102

103+
if (!skipLastUpdatedDate)
104+
{
105+
LastUpdatedDate = DateTime.UtcNow;
106+
}
107+
102108
AssertSpanOrder();
103109

104110
static string BuildFullName(OtlpSpan existingSpan)
@@ -148,19 +154,20 @@ private void AssertSpanOrder()
148154
}
149155
}
150156

151-
public OtlpTrace(ReadOnlyMemory<byte> traceId)
157+
public OtlpTrace(ReadOnlyMemory<byte> traceId, DateTime lastUpdatedDate)
152158
{
153159
Key = traceId;
154160
TraceId = OtlpHelpers.ToHexString(traceId);
155161
FullName = string.Empty;
162+
LastUpdatedDate = lastUpdatedDate;
156163
}
157164

158165
public static OtlpTrace Clone(OtlpTrace trace)
159166
{
160-
var newTrace = new OtlpTrace(trace.Key);
167+
var newTrace = new OtlpTrace(trace.Key, trace.LastUpdatedDate);
161168
foreach (var item in trace.Spans)
162169
{
163-
newTrace.AddSpan(OtlpSpan.Clone(item, newTrace));
170+
newTrace.AddSpan(OtlpSpan.Clone(item, newTrace), skipLastUpdatedDate: true);
164171
}
165172

166173
return newTrace;
@@ -171,6 +178,17 @@ private string DebuggerToString()
171178
return $@"TraceId = ""{TraceId}"", Spans = {Spans.Count}, StartDate = {FirstSpan?.StartTime.ToLocalTime():yyyy:MM:dd}, StartTime = {FirstSpan?.StartTime.ToLocalTime():h:mm:ss.fff tt}, Duration = {Duration}";
172179
}
173180

181+
public void SetSpanUninstrumentedPeer(OtlpSpan span, OtlpApplication? app)
182+
{
183+
if (span.Trace != this)
184+
{
185+
throw new ArgumentException("Span does not belong to this trace.", nameof(span));
186+
}
187+
188+
span.SetUninstrumentedPeer(app);
189+
LastUpdatedDate = DateTime.UtcNow;
190+
}
191+
174192
private sealed class SpanStartDateComparer : IComparer<OtlpSpan>
175193
{
176194
public static readonly SpanStartDateComparer Instance = new SpanStartDateComparer();

src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -765,13 +765,34 @@ public Dictionary<string, int> GetLogsFieldValues(string attributeName)
765765
return attributesValues;
766766
}
767767

768+
public bool HasUpdatedTrace(OtlpTrace trace)
769+
{
770+
_tracesLock.EnterReadLock();
771+
772+
try
773+
{
774+
var latestTrace = GetTraceUnsynchronized(trace.TraceId);
775+
if (latestTrace == null)
776+
{
777+
// Trace must have been removed. Technically there is an update (nothing).
778+
return true;
779+
}
780+
781+
return latestTrace.LastUpdatedDate > trace.LastUpdatedDate;
782+
}
783+
finally
784+
{
785+
_tracesLock.ExitReadLock();
786+
}
787+
}
788+
768789
public OtlpTrace? GetTrace(string traceId)
769790
{
770791
_tracesLock.EnterReadLock();
771792

772793
try
773794
{
774-
return GetTraceUnsynchronized(traceId);
795+
return GetTraceAndCloneUnsynchronized(traceId);
775796
}
776797
finally
777798
{
@@ -787,18 +808,28 @@ public Dictionary<string, int> GetLogsFieldValues(string attributeName)
787808
{
788809
if (OtlpHelpers.MatchTelemetryId(traceId, trace.TraceId))
789810
{
790-
return OtlpTrace.Clone(trace);
811+
return trace;
791812
}
792813
}
793814

794815
return null;
795816
}
796817

797-
private OtlpSpan? GetSpanUnsynchronized(string traceId, string spanId)
818+
private OtlpTrace? GetTraceAndCloneUnsynchronized(string traceId)
798819
{
799-
Debug.Assert(_tracesLock.IsReadLockHeld || _tracesLock.IsWriteLockHeld, $"Must get lock before calling {nameof(GetSpanUnsynchronized)}.");
820+
Debug.Assert(_tracesLock.IsReadLockHeld || _tracesLock.IsWriteLockHeld, $"Must get lock before calling {nameof(GetTraceAndCloneUnsynchronized)}.");
800821

801822
var trace = GetTraceUnsynchronized(traceId);
823+
824+
return trace != null ? OtlpTrace.Clone(trace) : null;
825+
}
826+
827+
private OtlpSpan? GetSpanAndCloneUnsynchronized(string traceId, string spanId)
828+
{
829+
Debug.Assert(_tracesLock.IsReadLockHeld || _tracesLock.IsWriteLockHeld, $"Must get lock before calling {nameof(GetSpanAndCloneUnsynchronized)}.");
830+
831+
// Trace and its spans are cloned here.
832+
var trace = GetTraceAndCloneUnsynchronized(traceId);
802833
if (trace != null)
803834
{
804835
foreach (var span in trace.Spans)
@@ -819,7 +850,7 @@ public Dictionary<string, int> GetLogsFieldValues(string attributeName)
819850

820851
try
821852
{
822-
return GetSpanUnsynchronized(traceId, spanId);
853+
return GetSpanAndCloneUnsynchronized(traceId, spanId);
823854
}
824855
finally
825856
{
@@ -938,7 +969,7 @@ internal void AddTracesCore(AddContext context, OtlpApplicationView applicationV
938969
{
939970
if (!TryGetTraceById(_traces, span.TraceId.Memory, out trace))
940971
{
941-
trace = new OtlpTrace(span.TraceId.Memory);
972+
trace = new OtlpTrace(span.TraceId.Memory, DateTime.UtcNow);
942973
newTrace = true;
943974
}
944975
}
@@ -961,7 +992,7 @@ internal void AddTracesCore(AddContext context, OtlpApplicationView applicationV
961992
{
962993
_spanLinks.Add(link);
963994

964-
var linkedSpan = GetSpanUnsynchronized(link.TraceId, link.SpanId);
995+
var linkedSpan = GetSpanAndCloneUnsynchronized(link.TraceId, link.SpanId);
965996
linkedSpan?.BackLinks.Add(link);
966997
}
967998

@@ -1090,11 +1121,11 @@ private void CalculateTraceUninstrumentedPeers(OtlpTrace trace)
10901121

10911122
var appKey = ApplicationKey.Create(name: uninstrumentedPeer.DisplayName, instanceId: uninstrumentedPeer.Name);
10921123
var (app, _) = GetOrAddApplication(appKey, uninstrumentedPeer: true);
1093-
span.UninstrumentedPeer = app;
1124+
trace.SetSpanUninstrumentedPeer(span, app);
10941125
}
10951126
else
10961127
{
1097-
span.UninstrumentedPeer = null;
1128+
trace.SetSpanUninstrumentedPeer(span, null);
10981129
}
10991130
}
11001131
}

0 commit comments

Comments
 (0)