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
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ internal record InteractionMessageBarReference(int InteractionId, Message Messag
{
public void Dispose()
{
TelemetryContext?.Dispose();
TelemetryContext.Dispose();
}
}
internal record InteractionDialogReference(int InteractionId, IDialogReference Dialog, ComponentTelemetryContext TelemetryContext) : IDisposable
{
public void Dispose()
{
TelemetryContext?.Dispose();
TelemetryContext.Dispose();
}
}

Expand Down
19 changes: 16 additions & 3 deletions src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,22 @@ private void UpdateSubscription()
_tracesSubscription?.Dispose();
_tracesSubscription = TelemetryRepository.OnNewTraces(_trace.FirstSpan.Source.ApplicationKey, SubscriptionType.Read, () => InvokeAsync(async () =>
{
UpdateDetailViewData();
await InvokeAsync(StateHasChanged);
await _dataGrid.SafeRefreshDataAsync();
if (_trace == null)
{
return;
}

// Only update trace if required.
if (TelemetryRepository.HasUpdatedTrace(_trace))
{
UpdateDetailViewData();
StateHasChanged();
await _dataGrid.SafeRefreshDataAsync();
}
else
{
Logger.LogTrace("Trace '{TraceId}' is unchanged.", TraceId);
}
}));
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/Aspire.Dashboard/Otlp/Model/OtlpSpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,18 @@ public class OtlpSpan
public OtlpScope Scope { get; }
public TimeSpan Duration => EndTime - StartTime;

public OtlpApplication? UninstrumentedPeer { get; internal set; }
public OtlpApplication? UninstrumentedPeer { get => _uninstrumentedPeer; init => _uninstrumentedPeer = value; }

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

private string? _cachedDisplaySummary;
private OtlpApplication? _uninstrumentedPeer;

public void SetUninstrumentedPeer(OtlpApplication? peer)
{
_uninstrumentedPeer = peer;
}

public OtlpSpan? GetParentSpan()
{
Expand Down
26 changes: 22 additions & 4 deletions src/Aspire.Dashboard/Otlp/Model/OtlpTrace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public TimeSpan Duration
}

public OtlpSpanCollection Spans { get; } = new OtlpSpanCollection();
public DateTime LastUpdatedDate { get; private set; }

public int CalculateDepth(OtlpSpan span)
{
Expand All @@ -51,7 +52,7 @@ public int CalculateDepth(OtlpSpan span)

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

public void AddSpan(OtlpSpan span)
public void AddSpan(OtlpSpan span, bool skipLastUpdatedDate = false)
{
if (Spans.Contains(span.SpanId))
{
Expand Down Expand Up @@ -99,6 +100,11 @@ public void AddSpan(OtlpSpan span)
FullName = BuildFullName(span);
}

if (!skipLastUpdatedDate)
{
LastUpdatedDate = DateTime.UtcNow;
}

AssertSpanOrder();

static string BuildFullName(OtlpSpan existingSpan)
Expand Down Expand Up @@ -148,19 +154,20 @@ private void AssertSpanOrder()
}
}

public OtlpTrace(ReadOnlyMemory<byte> traceId)
public OtlpTrace(ReadOnlyMemory<byte> traceId, DateTime lastUpdatedDate)
{
Key = traceId;
TraceId = OtlpHelpers.ToHexString(traceId);
FullName = string.Empty;
LastUpdatedDate = lastUpdatedDate;
}

public static OtlpTrace Clone(OtlpTrace trace)
{
var newTrace = new OtlpTrace(trace.Key);
var newTrace = new OtlpTrace(trace.Key, trace.LastUpdatedDate);
foreach (var item in trace.Spans)
{
newTrace.AddSpan(OtlpSpan.Clone(item, newTrace));
newTrace.AddSpan(OtlpSpan.Clone(item, newTrace), skipLastUpdatedDate: true);
}

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

public void SetSpanUninstrumentedPeer(OtlpSpan span, OtlpApplication? app)
{
if (span.Trace != this)
{
throw new ArgumentException("Span does not belong to this trace.", nameof(span));
}

span.SetUninstrumentedPeer(app);
LastUpdatedDate = DateTime.UtcNow;
}

private sealed class SpanStartDateComparer : IComparer<OtlpSpan>
{
public static readonly SpanStartDateComparer Instance = new SpanStartDateComparer();
Expand Down
49 changes: 40 additions & 9 deletions src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -765,13 +765,34 @@ public Dictionary<string, int> GetLogsFieldValues(string attributeName)
return attributesValues;
}

public bool HasUpdatedTrace(OtlpTrace trace)
{
_tracesLock.EnterReadLock();

try
{
var latestTrace = GetTraceUnsynchronized(trace.TraceId);
if (latestTrace == null)
{
// Trace must have been removed. Technically there is an update (nothing).
return true;
}

return latestTrace.LastUpdatedDate > trace.LastUpdatedDate;
}
finally
{
_tracesLock.ExitReadLock();
}
}

public OtlpTrace? GetTrace(string traceId)
{
_tracesLock.EnterReadLock();

try
{
return GetTraceUnsynchronized(traceId);
return GetTraceAndCloneUnsynchronized(traceId);
}
finally
{
Expand All @@ -787,18 +808,28 @@ public Dictionary<string, int> GetLogsFieldValues(string attributeName)
{
if (OtlpHelpers.MatchTelemetryId(traceId, trace.TraceId))
{
return OtlpTrace.Clone(trace);
return trace;
}
}

return null;
}

private OtlpSpan? GetSpanUnsynchronized(string traceId, string spanId)
private OtlpTrace? GetTraceAndCloneUnsynchronized(string traceId)
{
Debug.Assert(_tracesLock.IsReadLockHeld || _tracesLock.IsWriteLockHeld, $"Must get lock before calling {nameof(GetSpanUnsynchronized)}.");
Debug.Assert(_tracesLock.IsReadLockHeld || _tracesLock.IsWriteLockHeld, $"Must get lock before calling {nameof(GetTraceAndCloneUnsynchronized)}.");

var trace = GetTraceUnsynchronized(traceId);

return trace != null ? OtlpTrace.Clone(trace) : null;
}

private OtlpSpan? GetSpanAndCloneUnsynchronized(string traceId, string spanId)
{
Debug.Assert(_tracesLock.IsReadLockHeld || _tracesLock.IsWriteLockHeld, $"Must get lock before calling {nameof(GetSpanAndCloneUnsynchronized)}.");

// Trace and its spans are cloned here.
var trace = GetTraceAndCloneUnsynchronized(traceId);
if (trace != null)
{
foreach (var span in trace.Spans)
Expand All @@ -819,7 +850,7 @@ public Dictionary<string, int> GetLogsFieldValues(string attributeName)

try
{
return GetSpanUnsynchronized(traceId, spanId);
return GetSpanAndCloneUnsynchronized(traceId, spanId);
}
finally
{
Expand Down Expand Up @@ -938,7 +969,7 @@ internal void AddTracesCore(AddContext context, OtlpApplicationView applicationV
{
if (!TryGetTraceById(_traces, span.TraceId.Memory, out trace))
{
trace = new OtlpTrace(span.TraceId.Memory);
trace = new OtlpTrace(span.TraceId.Memory, DateTime.UtcNow);
newTrace = true;
}
}
Expand All @@ -961,7 +992,7 @@ internal void AddTracesCore(AddContext context, OtlpApplicationView applicationV
{
_spanLinks.Add(link);

var linkedSpan = GetSpanUnsynchronized(link.TraceId, link.SpanId);
var linkedSpan = GetSpanAndCloneUnsynchronized(link.TraceId, link.SpanId);
linkedSpan?.BackLinks.Add(link);
}

Expand Down Expand Up @@ -1090,11 +1121,11 @@ private void CalculateTraceUninstrumentedPeers(OtlpTrace trace)

var appKey = ApplicationKey.Create(name: uninstrumentedPeer.DisplayName, instanceId: uninstrumentedPeer.Name);
var (app, _) = GetOrAddApplication(appKey, uninstrumentedPeer: true);
span.UninstrumentedPeer = app;
trace.SetSpanUninstrumentedPeer(span, app);
}
else
{
span.UninstrumentedPeer = null;
trace.SetSpanUninstrumentedPeer(span, null);
}
}
}
Expand Down
Loading
Loading