Skip to content

Add minimum span duration filtering#17299

Open
davidfowl wants to merge 4 commits into
mainfrom
davidfowl/span-filtering
Open

Add minimum span duration filtering#17299
davidfowl wants to merge 4 commits into
mainfrom
davidfowl/span-filtering

Conversation

@davidfowl
Copy link
Copy Markdown
Collaborator

Description

Adds minimum span duration filtering so performance profiling can hide very short spans that add noise to trace analysis.

Users can now filter the dashboard trace detail view by entering a minimum duration in milliseconds. The same filter is available through the dashboard telemetry APIs via minDurationMs and through the Aspire CLI via --min-duration / --min-duration-ms for spans and traces.

The duration filter is applied after trace selection filters, so filters like hasError and text search still select matching traces from the full trace while minDurationMs controls which spans are returned or displayed.

User-facing usage

Dashboard telemetry API:

GET /api/telemetry/spans?minDurationMs=50
GET /api/telemetry/traces?minDurationMs=50
GET /api/telemetry/traces/{traceId}?minDurationMs=50

CLI:

aspire telemetry spans --dashboard-url http://localhost:15889 --api-key <key> --min-duration 50
aspire telemetry traces --trace-id <trace-id> --min-duration 50

Command help:

--min-duration, --min-duration-ms <min-duration>  Filter by minimum span duration in milliseconds

Validation

  • dotnet test --project tests/Aspire.Dashboard.Tests/Aspire.Dashboard.Tests.csproj --no-launch-profile -- --filter-class "*.TelemetryApiServiceTests" --filter-class "*.DashboardUrlsTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"
  • dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-launch-profile -- --filter-class "*.TelemetrySpansCommandTests" --filter-class "*.TelemetryTracesCommandTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"
  • dotnet test --project tests/Aspire.Dashboard.Components.Tests/Aspire.Dashboard.Components.Tests.csproj --no-launch-profile -- --filter-class "*.TraceDetailsTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"
  • Standalone dashboard E2E with OTLP test spans, validating:
    • otel spans --min-duration 50
    • otel traces --trace-id <trace-id> --min-duration 50
    • browser trace detail minimum duration textbox hides spans shorter than 50 ms
  • git diff --check

Fixes # (issue)

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

Add dashboard trace-detail filtering for spans shorter than a configured duration, expose the same minDurationMs filter through telemetry APIs, and add CLI --min-duration support for spans and traces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 20, 2026 04:00
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17299

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17299"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a minimum span duration filter across the Dashboard telemetry APIs, Dashboard trace detail UI, and Aspire CLI telemetry commands so very short spans can be hidden to reduce trace noise.

Changes:

  • Adds minDurationMs query support to dashboard telemetry endpoints (spans, traces, trace detail) and applies filtering in TelemetryApiService.
  • Adds a “Minimum duration (ms)” filter control to the Trace Detail page and filters spans client-side in the grid.
  • Adds --min-duration/--min-duration-ms to CLI otel spans / otel traces, plus test coverage and localization resources.
Show a summary per file
File Description
tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs Adds unit tests for minimum-duration filtering behavior in telemetry APIs.
tests/Aspire.Dashboard.Tests/DashboardUrlsTests.cs Verifies minDurationMs is appended to telemetry API URLs.
tests/Aspire.Dashboard.Components.Tests/Shared/FluentUISetupHelpers.cs Updates Fluent UI JS interop test setup for text/number fields.
tests/Aspire.Dashboard.Components.Tests/Pages/TraceDetailsTests.cs Adds component test for minimum-duration filtering in trace details grid.
tests/Aspire.Cli.Tests/Commands/TelemetryTracesCommandTests.cs Tests CLI traces command includes minDurationMs in requests.
tests/Aspire.Cli.Tests/Commands/TelemetrySpansCommandTests.cs Tests CLI spans command includes minDurationMs in requests.
src/Shared/DashboardUrls.cs Adds optional minDurationMs query parameter to telemetry URL builders.
src/Aspire.Dashboard/DashboardEndpointsBuilder.cs Wires minDurationMs from query string into telemetry API service calls.
src/Aspire.Dashboard/Api/TelemetryApiService.cs Implements minimum-duration filtering for spans/traces/trace detail + follow streaming.
src/Aspire.Dashboard/Components/Pages/TraceDetail.razor Adds minimum-duration input control (desktop + mobile toolbars).
src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs Applies minimum-duration filter when computing visible span view models.
src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.css Styles the new minimum-duration filter UI.
src/Aspire.Dashboard/Resources/TraceDetail.resx Adds localized strings for the new UI label/placeholder/title.
src/Aspire.Dashboard/Resources/TraceDetail.Designer.cs Generated accessors for new TraceDetail resource strings.
src/Aspire.Dashboard/Resources/xlf/TraceDetail.cs.xlf Localization entries for new TraceDetail strings (cs).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.de.xlf Localization entries for new TraceDetail strings (de).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.es.xlf Localization entries for new TraceDetail strings (es).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.fr.xlf Localization entries for new TraceDetail strings (fr).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.it.xlf Localization entries for new TraceDetail strings (it).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.ja.xlf Localization entries for new TraceDetail strings (ja).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.ko.xlf Localization entries for new TraceDetail strings (ko).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.pl.xlf Localization entries for new TraceDetail strings (pl).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.pt-BR.xlf Localization entries for new TraceDetail strings (pt-BR).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.ru.xlf Localization entries for new TraceDetail strings (ru).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.tr.xlf Localization entries for new TraceDetail strings (tr).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.zh-Hans.xlf Localization entries for new TraceDetail strings (zh-Hans).
src/Aspire.Dashboard/Resources/xlf/TraceDetail.zh-Hant.xlf Localization entries for new TraceDetail strings (zh-Hant).
src/Aspire.Cli/Commands/TelemetryCommandHelpers.cs Introduces the shared --min-duration/--min-duration-ms option definition.
src/Aspire.Cli/Commands/TelemetrySpansCommand.cs Passes minimum duration through to spans API URL.
src/Aspire.Cli/Commands/TelemetryTracesCommand.cs Passes minimum duration through to traces/trace detail API URLs.
src/Aspire.Cli/Resources/TelemetryCommandStrings.resx Adds description string for the new CLI option.
src/Aspire.Cli/Resources/TelemetryCommandStrings.Designer.cs Generated accessor for the new CLI option description.
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.cs.xlf Localization entry for new CLI option description (cs).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.de.xlf Localization entry for new CLI option description (de).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.es.xlf Localization entry for new CLI option description (es).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.fr.xlf Localization entry for new CLI option description (fr).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.it.xlf Localization entry for new CLI option description (it).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.ja.xlf Localization entry for new CLI option description (ja).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.ko.xlf Localization entry for new CLI option description (ko).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.pl.xlf Localization entry for new CLI option description (pl).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.pt-BR.xlf Localization entry for new CLI option description (pt-BR).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.ru.xlf Localization entry for new CLI option description (ru).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.tr.xlf Localization entry for new CLI option description (tr).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.zh-Hans.xlf Localization entry for new CLI option description (zh-Hans).
src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.zh-Hant.xlf Localization entry for new CLI option description (zh-Hant).

Copilot's findings

Files not reviewed (2)
  • src/Aspire.Cli/Resources/TelemetryCommandStrings.Designer.cs: Language not supported
  • src/Aspire.Dashboard/Resources/TraceDetail.Designer.cs: Language not supported
Comments suppressed due to low confidence (2)

src/Aspire.Cli/Commands/TelemetrySpansCommand.cs:101

  • --min-duration accepts negative/NaN/Infinity values and they’ll be silently treated as “no duration filter” by the dashboard API (since minDurationMs <= 0 or non-finite becomes null). This makes the CLI option misleading. Add validation similar to --limit (e.g., require a finite value >= 0, or reject < 0/non-finite with an InvalidCommand exit and a clear error message).
        var traceId = parseResult.GetValue(s_traceIdOption);
        var hasError = parseResult.GetValue(s_hasErrorOption);
        var dashboardUrl = parseResult.GetValue(s_dashboardUrlOption);
        var apiKey = parseResult.GetValue(s_apiKeyOption);
        var search = parseResult.GetValue(s_searchOption);
        var minimumDuration = parseResult.GetValue(s_minimumDurationOption);

        // Validate --limit value
        if (limit.HasValue && limit.Value < 1)
        {
            return CommandResult.Failure(CliExitCodes.InvalidCommand, TelemetryCommandStrings.LimitMustBePositive);
        }

src/Aspire.Cli/Commands/TelemetryTracesCommand.cs:99

  • --min-duration accepts negative/NaN/Infinity values and they’ll be silently treated as “no duration filter” by the dashboard API (minDurationMs <= 0 or non-finite becomes null). This makes the CLI option misleading. Add validation similar to --limit (e.g., require a finite value >= 0, or reject < 0/non-finite with an InvalidCommand exit and a clear error message).
        var format = parseResult.GetValue(s_formatOption);
        var limit = parseResult.GetValue(s_limitOption);
        var traceId = parseResult.GetValue(s_traceIdOption);
        var hasError = parseResult.GetValue(s_hasErrorOption);
        var dashboardUrl = parseResult.GetValue(s_dashboardUrlOption);
        var apiKey = parseResult.GetValue(s_apiKeyOption);
        var search = parseResult.GetValue(s_searchOption);
        var minimumDuration = parseResult.GetValue(s_minimumDurationOption);

        // Validate --limit value
        if (limit.HasValue && limit.Value < 1)
        {
            return CommandResult.Failure(CliExitCodes.InvalidCommand, TelemetryCommandStrings.LimitMustBePositive);
        }
  • Files reviewed: 43/45 changed files
  • Comments generated: 2

Comment thread src/Aspire.Dashboard/Api/TelemetryApiService.cs
Comment thread src/Aspire.Dashboard/Api/TelemetryApiService.cs
davidfowl and others added 2 commits May 19, 2026 21:11
# Conflicts:
#	src/Aspire.Dashboard/Components/Pages/TraceDetail.razor
#	src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.css
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/Aspire.Dashboard/Api/TelemetryApiService.cs Outdated
Comment thread src/Aspire.Dashboard/Api/TelemetryApiService.cs
Comment thread src/Aspire.Dashboard/Api/TelemetryApiService.cs
Comment thread src/Aspire.Dashboard/Api/TelemetryApiService.cs Outdated
Copy link
Copy Markdown
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found (performance).

Comment thread src/Aspire.Dashboard/Api/TelemetryApiService.cs Outdated
@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented May 20, 2026

image

no

How about, "Min duration" as the label. The value is a dropdown. Dropdown values are (none), 50ms, 100ms, 500ms, 1s, 5s, 10s. I think that's a good balance between options while providing a clean/easy to use UI.

I worry about what else we add in the future (i.e. a max duration) and putting UI in the horizontal menu won't scale. Duration should really be a filter option, and then the operator would be greater than, less than. Then you enter a value and it appears with other filters.

@davidfowl
Copy link
Copy Markdown
Collaborator Author

Fixed values don’t cut it (though we can be smart about calculating a scale). I think it needs to be possible to enter a value.

I am too worried about horizontal space as it doesn’t scale well for general filters

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidfowl
Copy link
Copy Markdown
Collaborator Author

Acknowledged. I addressed the performance issue from the inline thread and pushed the fix.

@github-actions
Copy link
Copy Markdown
Contributor

CLI E2E Tests unknown — 94 passed, 0 failed, 2 unknown (commit e0f50ee)

View all recordings
Status Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View recording
AddPackageWhileAppHostRunningDetached ▶️ View recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View recording
AgentInitCommand_DefaultSelection_InstallsDefaultSkills ▶️ View recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View recording
AllPublishMethodsBuildDockerImages ▶️ View recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View recording
AspireInitSingleFileAppHostRunsViaDotnetRunAppHost ▶️ View recording
AspireInitWithExistingAppHostDirRecreatesMissingNuGetConfigAndPreservesFiles ▶️ View recording
AspireInitWithSolutionFileGeneratesAppHostThatBuildsAgainstChannelHive ▶️ View recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View recording
AspireUpdateRemovesOrphanAppHostPackageVersionWhenSdkAlreadyCurrent ▶️ View recording
Banner_DisplayedOnFirstRun ▶️ View recording
Banner_DisplayedWithExplicitFlag ▶️ View recording
Banner_NotDisplayedWithNoLogoFlag ▶️ View recording
CertificatesClean_RemovesCertificates ▶️ View recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View recording
CreateAndRunAspireStarterProject ▶️ View recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View recording
CreateAndRunEmptyAppHostProject ▶️ View recording
CreateAndRunJavaEmptyAppHostProject ▶️ View recording
CreateAndRunJsReactProject ▶️ View recording
CreateAndRunPythonReactProject ▶️ View recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View recording
CreateAndRunTypeScriptStarterProject ▶️ View recording
CreateJavaAppHostWithViteApp ▶️ View recording
CreateTypeScriptAppHostWithViteApp_UsesConfiguredToolchain ▶️ View recording
DashboardRunWithOtelTracesReturnsNoTraces ▶️ View recording
DeployK8sBasicApiService ▶️ View recording
DeployK8sWithExternalHelmChart ▶️ View recording
DeployK8sWithGarnet ▶️ View recording
DeployK8sWithMongoDB ▶️ View recording
DeployK8sWithMySql ▶️ View recording
DeployK8sWithPostgres ▶️ View recording
DeployK8sWithRabbitMQ ▶️ View recording
DeployK8sWithRedis ▶️ View recording
DeployK8sWithSqlServer ▶️ View recording
DeployK8sWithValkey ▶️ View recording
DeployTypeScriptAppToKubernetes ▶️ View recording
DescribeCommandResolvesReplicaNames ▶️ View recording
DescribeCommandShowsRunningResources ▶️ View recording
DetachFormatJsonProducesValidJson ▶️ View recording
DetachFormatJsonProducesValidJsonWhenRestartingExistingInstance ▶️ View recording
DoListStepsShowsPipelineSteps ▶️ View recording
DocsCommand_RendersInteractiveMarkdownFromLocalSource ▶️ View recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View recording
DoctorCommand_TypeScriptAppHostReportsMissingConfiguredToolchain ▶️ View recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View recording
GlobalMigration_PreservesAllValueTypes ▶️ View recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View recording
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ View recording
InteractiveCSharpInitCreatesExpectedFiles ▶️ View recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View recording
JavaScriptHostingApisRunFromTypeScriptAppHost ▶️ View recording
LatestCliCanStartStableChannelAppHost ▶️ View recording
LatestCliCanStartStableChannelTypeScriptAppHost ▶️ View recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View recording
LogLevelTrace_ProducesTraceEntriesInCliLogFile ▶️ View recording
LogsCommandShowsResourceLogs ▶️ View recording
OtelLogsReturnsStructuredLogsFromStarterApp ▶️ View recording
OtelLogsReturnsStructuredLogsFromStarterAppIsolated ▶️ View recording
PsCommandListsRunningAppHost ▶️ View recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View recording
ResourceCommand_FailedExecution_DisplaysAppHostLogPathAndLogContainsEntries ▶️ View recording
ResourceCommand_FailsWhenInteractionServiceIsRequired ▶️ View recording
ResourceCommand_SetAndDeleteParameterUpdatesDescribeOutput ▶️ View recording
RestoreGeneratesSdkFiles ▶️ View recording
RestoreGeneratesSdkFiles_WithConfiguredToolchain ▶️ View recording
RestoreRefreshesGeneratedSdkAfterAddingIntegration ▶️ View recording
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes ▶️ View recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View recording
RunPublishFailureScenarioAsync ▶️ View recording
RunReportsSyntaxErrorsForDotNetAppHost ▶️ View recording
RunReportsSyntaxErrorsForTypeScriptAppHost ▶️ View recording
SecretCrudOnDotNetAppHost ▶️ View recording
SecretCrudOnTypeScriptAppHost ▶️ View recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View recording
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View recording
StartReportsSyntaxErrorsForDotNetAppHost ▶️ View recording
StartReportsSyntaxErrorsForTypeScriptAppHost ▶️ View recording
StopAllAppHostsFromAppHostDirectory ▶️ View recording
StopJavaPolyglotAppHostUsingApphostDirectory ▶️ View recording
StopNonInteractiveSingleAppHost ▶️ View recording
StopTypeScriptPolyglotAppHostUsingApphostDirectory ▶️ View recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View recording
UpdateProjectChannelToStable_TypeScript_PicksUpStablePackages ▶️ View recording

📹 Recordings uploaded automatically from CI run #26171097209

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants