File: Model\ResourcePaneActivity.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Otlp.Storage;
 
namespace Aspire.Dashboard.Model;
 
internal sealed record ResourcePaneActivitySnapshot(
    ResourcePaneActivity Activity, DateTime WindowStart, DateTime WindowEnd, DateTime Refreshed, bool Sampled);
 
internal sealed record ResourcePaneActivity(int Count, int FailedCount, TimeSpan? P95, int DurationSampleCount, DateTime LatestStartTime)
{
    internal static IReadOnlyDictionary<ResourceKey, ResourcePaneActivity> Summarize(GetRequestSpansResponse response) =>
        response.Items.GroupBy(span => span.Resource.ResourceKey).ToDictionary(group => group.Key, group =>
        {
            var durations = group.Select(span => span.Duration).Where(duration => duration >= TimeSpan.Zero).Order().ToArray();
            // Use nearest-rank p95, matching the application summary. These are server/consumer
            // spans, not whole-trace durations or an instrument's histogram percentiles.
            // Twenty valid durations give the empirical distribution its first 5% tail sample.
            // Smaller sets still show activity/failures, but do not imply a meaningful p95.
            var p95 = durations.Length >= 20
                ? (TimeSpan?)durations[(int)Math.Ceiling(durations.Length * 0.95) - 1]
                : null;
            return new ResourcePaneActivity(
                group.Count(),
                group.Count(span => span.Status == OtlpSpanStatusCode.Error),
                p95,
                durations.Length,
                group.Max(span => span.StartTime));
        });
}