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
68 changes: 67 additions & 1 deletion playground/Stress/Stress.ApiService/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
builder.AddServiceDefaults();

builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing.AddSource(TraceCreator.ActivitySourceName, ProducerConsumer.ActivitySourceName))
.WithTracing(tracing => tracing
.AddSource(TraceCreator.ActivitySourceName, ProducerConsumer.ActivitySourceName)
.AddSource("Services.Api"))
.WithMetrics(metrics => metrics.AddMeter(TestMetrics.MeterName));
builder.Services.AddSingleton<TestMetrics>();

Expand Down Expand Up @@ -285,4 +287,68 @@ async IAsyncEnumerable<string> WriteOutput()
return $"Created {TraceCount} traces.";
});

app.MapGet("/nested-trace-spans", async () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
"Sample Text"
))
.ToArray();
ActivitySource source = new("Services.Api", "1.0.0");
ActivitySource.AddActivityListener(new ActivityListener
{
ShouldListenTo = _ => true,
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,
});
using var activity = source.StartActivity("ValidateAndUpdateCacheService.ExecuteAsync");
await Task.Delay(100);
Debug.Assert(activity is not null);
using var innerActivity = source.StartActivity("ValidateAndUpdateCacheService.activeUser",
ActivityKind.Internal, parentContext: activity.Context);
await Task.Delay(100);
Debug.Assert(innerActivity is not null);
using (source.StartActivity("Perform1", ActivityKind.Internal, parentContext: innerActivity.Context))
{
await Task.Delay(10);
}

using (source.StartActivity("Perform2", ActivityKind.Internal, parentContext: innerActivity.Context))
{
await Task.Delay(20);
}

using (source.StartActivity("Perform3", ActivityKind.Internal, parentContext: innerActivity.Context))
{
await Task.Delay(30);
}

using var innerActivity2 = source.StartActivity("ValidateAndUpdateCacheService.activeUser",
ActivityKind.Internal, parentContext: activity.Context);
await Task.Delay(100);
Debug.Assert(innerActivity2 is not null);

using (source.StartActivity("Perform1", ActivityKind.Internal, parentContext: innerActivity2.Context))
{
await Task.Delay(30);
}

using (source.StartActivity("Perform2", ActivityKind.Internal, parentContext: innerActivity2.Context))
{
await Task.Delay(20);
}

using (source.StartActivity("Perform3", ActivityKind.Internal, parentContext: innerActivity2.Context))
{
await Task.Delay(10);
}

return forecast;
})
.WithName("GetWeatherForecast");

app.Run();

public record WeatherForecast(DateOnly Date, int TemperatureC, string Summary);
1 change: 1 addition & 0 deletions playground/Stress/Stress.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
serviceBuilder.WithHttpCommand("/log-message-limit", "Log message limit", commandOptions: new() { Method = HttpMethod.Get, IconName = "ContentViewGalleryLightning" });
serviceBuilder.WithHttpCommand("/multiple-traces-linked", "Multiple traces linked", commandOptions: new() { Method = HttpMethod.Get, IconName = "ContentViewGalleryLightning" });
serviceBuilder.WithHttpCommand("/overflow-counter", "Overflow counter", commandOptions: new() { Method = HttpMethod.Get, IconName = "ContentViewGalleryLightning" });
serviceBuilder.WithHttpCommand("/nested-trace-spans", "Out of order nested spans", commandOptions: new() { Method = HttpMethod.Get, IconName = "ContentViewGalleryLightning" });

builder.AddProject<Projects.Stress_TelemetryService>("stress-telemetryservice")
.WithUrls(c => c.Urls.Add(new() { Url = "https://someplace.com", DisplayText = "Some place" }))
Expand Down
18 changes: 13 additions & 5 deletions src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,31 @@ protected override void OnInitialized()
}
}

private ValueTask<GridItemsProviderResult<SpanWaterfallViewModel>> GetData(GridItemsProviderRequest<SpanWaterfallViewModel> request)
// Internal to be used in unit tests
internal ValueTask<GridItemsProviderResult<SpanWaterfallViewModel>> GetData(GridItemsProviderRequest<SpanWaterfallViewModel> request)
{
Debug.Assert(_spanWaterfallViewModels != null);

var visibleViewModels = new HashSet<SpanWaterfallViewModel>();
foreach (var viewModel in _spanWaterfallViewModels)
{
if (!viewModel.IsHidden && viewModel.MatchesFilter(_filter, GetResourceName, out var matchedDescendents))
if (viewModel.IsHidden || visibleViewModels.Contains(viewModel))
{
continue;
}

if (viewModel.MatchesFilter(_filter, GetResourceName, out var matchedDescendents))
{
visibleViewModels.Add(viewModel);
foreach (var descendent in matchedDescendents)
foreach (var descendent in matchedDescendents.Where(d => !d.IsHidden))
{
visibleViewModels.Add(descendent);
}
}
}

var page = visibleViewModels.AsEnumerable();
var page = _spanWaterfallViewModels.Where(visibleViewModels.Contains).AsEnumerable();
var totalItemCount = page.Count();
if (request.StartIndex > 0)
{
page = page.Skip(request.StartIndex);
Expand All @@ -103,7 +110,7 @@ private ValueTask<GridItemsProviderResult<SpanWaterfallViewModel>> GetData(GridI
return ValueTask.FromResult(new GridItemsProviderResult<SpanWaterfallViewModel>
{
Items = page.ToList(),
TotalItemCount = visibleViewModels.Count
TotalItemCount = totalItemCount
});
}

Expand Down Expand Up @@ -236,6 +243,7 @@ private async Task OnToggleCollapse(SpanWaterfallViewModel viewModel)
_collapsedSpanIds.Add(viewModel.Span.SpanId);
}

UpdateDetailViewData();
await _dataGrid.SafeRefreshDataAsync();
}

Expand Down
152 changes: 152 additions & 0 deletions tests/Aspire.Dashboard.Components.Tests/Pages/TraceDetailsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,158 @@ public async Task Render_ChangeTrace_RowsRendered()
await AsyncTestHelpers.AssertIsTrueRetryAsync(() => rows.Count == 2, "Expected rows to be rendered.");
}

[Fact]
public async Task Render_SpansOrderedByStartTime_RowsRenderedInCorrectOrder()
{
// Arrange
SetupTraceDetailsServices();

var viewport = new ViewportInformation(IsDesktop: true, IsUltraLowHeight: false, IsUltraLowWidth: false);

var dimensionManager = Services.GetRequiredService<DimensionManager>();
dimensionManager.InvokeOnViewportInformationChanged(viewport);

var telemetryRepository = Services.GetRequiredService<TelemetryRepository>();
telemetryRepository.AddTraces(new AddContext(),
new RepeatedField<ResourceSpans>
{
new ResourceSpans
{
Resource = CreateResource(),
ScopeSpans =
{
new ScopeSpans
{
Scope = CreateScope(),
Spans =
{
CreateSpan(traceId: "1", spanId: "1-1",
startTime: s_testTime.AddMinutes(1),
endTime: s_testTime.AddMinutes(10)),
CreateSpan(traceId: "1", spanId: "2-1",
startTime: s_testTime.AddMinutes(1),
endTime: s_testTime.AddMinutes(10),
parentSpanId: "1-1"),
CreateSpan(traceId: "1", spanId: "3-1",
startTime: s_testTime.AddMinutes(1),
endTime: s_testTime.AddMinutes(10),
parentSpanId: "2-1"),
CreateSpan(traceId: "1", spanId: "3-3",
startTime: s_testTime.AddMinutes(3),
endTime: s_testTime.AddMinutes(5),
parentSpanId: "2-1"),
CreateSpan(traceId: "1", spanId: "3-2",
startTime: s_testTime.AddMinutes(2),
endTime: s_testTime.AddMinutes(6),
parentSpanId: "2-1")
}
}
}
}
});

// Act
var traceId = Convert.ToHexString(Encoding.UTF8.GetBytes("1"));
var cut = RenderComponent<TraceDetail>(builder =>
{
builder.Add(p => p.TraceId, traceId);
builder.AddCascadingValue(viewport);
});

var data = await cut.Instance.GetData(new GridItemsProviderRequest<SpanWaterfallViewModel>());

// Assert
Assert.Collection(data.Items,
item => Assert.Equal("Test span. Id: 1-1", item.Span.Name),
item => Assert.Equal("Test span. Id: 2-1", item.Span.Name),
item => Assert.Equal("Test span. Id: 3-1", item.Span.Name),
item => Assert.Equal("Test span. Id: 3-2", item.Span.Name),
item => Assert.Equal("Test span. Id: 3-3", item.Span.Name));
}

[Fact]
public void ToggleCollapse_SpanStateChanges()
{
// Arrange
SetupTraceDetailsServices();

var viewport = new ViewportInformation(IsDesktop: true, IsUltraLowHeight: false, IsUltraLowWidth: false);
var dimensionManager = Services.GetRequiredService<DimensionManager>();
dimensionManager.InvokeOnViewportInformationChanged(viewport);

var telemetryRepository = Services.GetRequiredService<TelemetryRepository>();
telemetryRepository.AddTraces(new AddContext(),
new RepeatedField<ResourceSpans>
{
new ResourceSpans
{
Resource = CreateResource(),
ScopeSpans =
{
new ScopeSpans
{
Scope = CreateScope(),
Spans =
{
CreateSpan(traceId: "1", spanId: "1-1",
startTime: s_testTime.AddMinutes(1),
endTime: s_testTime.AddMinutes(10)),
CreateSpan(traceId: "1", spanId: "2-1",
startTime: s_testTime.AddMinutes(5),
endTime: s_testTime.AddMinutes(10), parentSpanId: "1-1"),
CreateSpan(traceId: "1", spanId: "3-1",
startTime: s_testTime.AddMinutes(6),
endTime: s_testTime.AddMinutes(10), parentSpanId: "2-1")
}
}
}
}
});

var traceId = Convert.ToHexString(Encoding.UTF8.GetBytes("1"));
var cut = RenderComponent<TraceDetail>(builder =>
{
builder.Add(p => p.TraceId, traceId);
builder.AddCascadingValue(viewport);
});

cut.WaitForAssertion(() => Assert.Equal(2, cut.FindAll(".main-grid-expand-button").Count));
// Act and assert

// Collapse the middle span
cut.FindAll(".main-grid-expand-button")[1].Click();

cut.WaitForAssertion(() =>
{
var expandContainers = cut.FindAll(".main-grid-expand-container");
// There should now be two containers since the 3rd level element should now be filtered out
Assert.Collection(expandContainers,
container => Assert.True(container.ClassList.Contains("main-grid-expanded")),
container => Assert.True(container.ClassList.Contains("main-grid-collapsed")));
});

// Collapse the parent span
cut.FindAll(".main-grid-expand-button")[0].Click();
cut.WaitForAssertion(() =>
{
var expandContainers = cut.FindAll(".main-grid-expand-container");
// There should now be one container since the 2nd level element should now be filtered out
Assert.Collection(expandContainers,
container => Assert.True(container.ClassList.Contains("main-grid-collapsed")));
});

// Expand the parent span, we should now see the same two containers as before
cut.FindAll(".main-grid-expand-button")[0].Click();
cut.WaitForAssertion(() =>
{
var expandContainers = cut.FindAll(".main-grid-expand-container");
// There should now be two containers since the 3rd level element should now be filtered out
Assert.Collection(expandContainers,
container => Assert.True(container.ClassList.Contains("main-grid-expanded")),
container => Assert.True(container.ClassList.Contains("main-grid-collapsed")));
});
}

private void SetupTraceDetailsServices()
{
var version = typeof(FluentMain).Assembly.GetName().Version!;
Expand Down
Loading