-
Notifications
You must be signed in to change notification settings - Fork 938
Persist Dashboard telemetry and run history in SQLite #18768
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
JamesNK
wants to merge
87
commits into
microsoft:main
Choose a base branch
from
JamesNK:feature/dashboard-run-history
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
87 commits
Select commit
Hold shift + click to select a range
092c896
Persist dashboard run history in SQLite
JamesNK d8aeaaf
Fix
JamesNK de9c229
Add dashboard persistence modes
JamesNK e9d36cc
Embed native libraries in managed bundle
JamesNK 7cfecfe
Default AppHost dashboard persistence to runs
JamesNK af7bbc6
Fix dashboard persistence tests
JamesNK fdc4e59
Address dashboard persistence review feedback
JamesNK 884a6fe
Optimize dashboard SQLite repositories
JamesNK 2ddb14a
Optimize telemetry field value queries
JamesNK 05d0b77
Address dashboard persistence review feedback
JamesNK 9c89b70
Handle in-use dashboard run directories
JamesNK 0c4847a
Use native SQLite string matching
JamesNK c73fd2c
Push trace error filtering into repository
JamesNK 53407a8
Optimize traces page queries
JamesNK 4e2d2b2
Order traces by trace ID
JamesNK 1a6d889
Optimize structured logs page queries
JamesNK ea5cf90
Show dashboard run selector in header
JamesNK 5efcea4
Update selector
JamesNK f44c609
Switch dashboard runs without reloading
JamesNK 2f8f442
Update
JamesNK fc0608c
Address dashboard persistence review feedback
JamesNK db3864b
Fix culture-sensitive dashboard run test
JamesNK 4d94581
Simplify metric telemetry repository API
JamesNK 6bb9c7a
Refactor dashboard repository ownership
JamesNK 1ca6b9c
Fix current-run console log subscription
JamesNK dc34175
Track console log capture by run
JamesNK 9032210
Enable dashboard SQLite connection pooling
JamesNK 72f35f1
Address dashboard run lifecycle review feedback
JamesNK 89ff07c
Add dashboard SQLite tracing and optimize metric ingestion
JamesNK 3106ab5
Optimize dashboard telemetry storage and self-tracing
JamesNK c38c3b5
Address dashboard run history review feedback
JamesNK 8cb31ba
Clean up
JamesNK 1642af8
Optimize dashboard SQLite metric ingestion
JamesNK 9c71f3b
WIP
JamesNK 65ab73a
WIP
JamesNK 5161186
Fix dashboard run schema and URL handling
JamesNK 2a80651
Address dashboard run history review feedback
JamesNK 7bfa93d
Fix dashboard resource snapshot state tracking
JamesNK 93f3b8f
Respect explicit dashboard persistence configuration
JamesNK 7ed8004
Fix resource watcher disposal race
JamesNK 0caeaed
Add shared dashboard activity source
JamesNK 4f375d1
Fix dashboard repository resolution hang
JamesNK bee6b2f
Defer dashboard repository dependencies
JamesNK e4bafe3
Scope dashboard SQLite pool cleanup
JamesNK 7b5c453
Batch dashboard console log inserts
JamesNK 98dbb84
Stream dashboard peer recalculation
JamesNK bddf914
Optimize dashboard run selection loading
JamesNK a1efd7b
Simplify dashboard run IDs
JamesNK 6388684
Add dashboard run persistence controls
JamesNK da8639e
Optimize dashboard trace persistence
JamesNK dd69dff
Validate dashboard span relationships
JamesNK 4a780b1
Improve dashboard telemetry import profiling
JamesNK 3c804da
Optimize dashboard trace ingestion
JamesNK 3e9ed89
Optimize dashboard SQLite batch inserts
JamesNK 5d175cd
Fix ManageDataDialog test service registration
JamesNK 3098ebe
Throttle dashboard telemetry callbacks
JamesNK 5f1d25f
Order instrumented trace resources before peers
JamesNK facd5c2
Fix ManageDataDialog repository test after merge
JamesNK 523a89d
Avoid dashboard OTLP telemetry loop
JamesNK 6d7fcea
WIP
JamesNK 5aa9f43
Improve historical telemetry and metrics queries
JamesNK 44ebcdc
Improve metrics aggregation and chart updates
JamesNK 5ebc325
Use SQLite telemetry in dashboard component tests
JamesNK 6c69393
Fix metric aggregation across dimensions
JamesNK f67224e
Improve metric queries with incremental rollups
JamesNK fa47ba2
Optimize dashboard metric queries
JamesNK c8311fa
Refine dashboard metric rollup intervals
JamesNK cee0951
Move metric instrument storage to repositories
JamesNK c5f6ba2
Use telemetry repository read-only state
JamesNK dd7fd53
Move metric instrument state to in-memory repository
JamesNK 69bc5f0
Improve dashboard metric chart performance
JamesNK 3ade89e
Update dashboard metric refresh and injections
JamesNK 30ce412
Combine scope and attribute lookup
JamesNK 67ab96e
Improve dashboard reliability under telemetry load
JamesNK 6dae9cc
Improve dashboard telemetry storage scalability
JamesNK 2d28438
Address dashboard run history review feedback
JamesNK 6f58399
Fix ApplicationName dashboard client tests
JamesNK 832564e
Improve stress telemetry commands and chart updates
JamesNK 3eeae1a
WIP
JamesNK 82ae07a
Fix disabled argument validation for interactive resource commands
JamesNK 2866ec5
Improve dashboard test performance
JamesNK ec38162
Optimize SQLite generated ID batch inserts
JamesNK 9ad5dcd
Share dashboard databases across data sources
JamesNK c7b7cb7
Centralize dashboard SQLite pool cleanup
JamesNK eee96de
Configure dashboard SQLite connections via connection string
JamesNK 6ce37a7
Initialize dashboard SQLite schema during startup
JamesNK 9661d3c
Improve dashboard telemetry persistence
JamesNK File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
216 changes: 216 additions & 0 deletions
216
benchmarks/Aspire.Dashboard.Benchmarks/SqliteTraceBenchmarks.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,216 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Text; | ||
| using Aspire.Dashboard.Configuration; | ||
| using Aspire.Dashboard.Model; | ||
| using Aspire.Dashboard.Otlp.Model; | ||
| using Aspire.Dashboard.Otlp.Storage; | ||
| using Aspire.Dashboard.ServiceClient; | ||
| using BenchmarkDotNet.Attributes; | ||
| using BenchmarkDotNet.Configs; | ||
| using BenchmarkDotNet.Diagnosers; | ||
| using BenchmarkDotNet.Jobs; | ||
| using BenchmarkDotNet.Toolchains.InProcess.NoEmit; | ||
| using Google.Protobuf; | ||
| using Google.Protobuf.Collections; | ||
| using Microsoft.Extensions.Logging.Abstractions; | ||
| using Microsoft.Extensions.Options; | ||
| using OpenTelemetry.Proto.Collector.Trace.V1; | ||
| using OpenTelemetry.Proto.Common.V1; | ||
| using OpenTelemetry.Proto.Resource.V1; | ||
| using OpenTelemetry.Proto.Trace.V1; | ||
| using OtlpProtoSpan = OpenTelemetry.Proto.Trace.V1.Span; | ||
|
|
||
| namespace Aspire.Dashboard.Benchmarks; | ||
|
|
||
| [MemoryDiagnoser] | ||
| [Config(typeof(Config))] | ||
| public class SqliteTraceBenchmarks | ||
| { | ||
| private const string TraceFileEnvironmentVariable = "ASPIRE_DASHBOARD_TRACE_BENCHMARK_FILE"; | ||
| private const int GeneratedSpanCount = 10_000; | ||
|
|
||
| private string _temporaryDirectory = null!; | ||
| private DashboardSqliteDatabase _database = null!; | ||
| private SqliteTelemetryRepository _repository = null!; | ||
| private RepeatedField<ResourceSpans> _appendResourceSpans = null!; | ||
| private long _appendIndex; | ||
|
|
||
| [GlobalSetup] | ||
| public async Task Setup() | ||
| { | ||
| _temporaryDirectory = Directory.CreateTempSubdirectory("aspire-dashboard-trace-benchmark-").FullName; | ||
| _database = new DashboardSqliteDatabase(Path.Combine(_temporaryDirectory, "dashboard.db")); | ||
| _repository = CreateRepository(_database); | ||
|
|
||
| var resourceSpans = LoadResourceSpans(); | ||
| var context = new AddContext(); | ||
| await _repository.AddTracesAsync(context, resourceSpans); | ||
| if (context.FailureCount != 0) | ||
| { | ||
| throw new InvalidOperationException($"Failed to ingest {context.FailureCount} benchmark spans."); | ||
| } | ||
|
|
||
| _appendResourceSpans = CreateAppendResourceSpans(resourceSpans); | ||
| } | ||
|
|
||
| [GlobalCleanup] | ||
| public void Cleanup() | ||
| { | ||
| _repository.Dispose(); | ||
| _database.ClearPool(); | ||
| _database.Dispose(); | ||
| Directory.Delete(_temporaryDirectory, recursive: true); | ||
| } | ||
|
|
||
| [Benchmark(Description = "SQLite: summarize large trace")] | ||
| public int GetTraceSummaries() | ||
| { | ||
| var response = _repository.GetTraceSummaries(new GetTracesRequest | ||
| { | ||
| ResourceKeys = [], | ||
| StartIndex = 0, | ||
| Count = 100, | ||
| Filters = [] | ||
| }); | ||
|
|
||
| return response.PagedResult.Items.Count; | ||
| } | ||
|
|
||
| [Benchmark(Description = "SQLite: append one span to large trace")] | ||
| public async Task<int> AppendSpan() | ||
| { | ||
| var appendSpan = _appendResourceSpans[0].ScopeSpans[0].Spans[0]; | ||
| appendSpan.SpanId = ByteString.CopyFrom(BitConverter.GetBytes(long.MaxValue - Interlocked.Increment(ref _appendIndex))); | ||
| var context = new AddContext(); | ||
| await _repository.AddTracesAsync(context, _appendResourceSpans); | ||
| return context.SuccessCount; | ||
| } | ||
|
|
||
| private static RepeatedField<ResourceSpans> LoadResourceSpans() | ||
| { | ||
| var traceFile = Environment.GetEnvironmentVariable(TraceFileEnvironmentVariable); | ||
| if (string.IsNullOrWhiteSpace(traceFile)) | ||
| { | ||
| return CreateGeneratedResourceSpans(); | ||
| } | ||
|
|
||
| var request = JsonParser.Default.Parse<ExportTraceServiceRequest>(File.ReadAllText(traceFile)); | ||
| return request.ResourceSpans; | ||
| } | ||
|
|
||
| private static RepeatedField<ResourceSpans> CreateGeneratedResourceSpans() | ||
| { | ||
| var resourceSpans = new ResourceSpans | ||
| { | ||
| Resource = CreateResource("benchmark-app"), | ||
| ScopeSpans = | ||
| { | ||
| new ScopeSpans | ||
| { | ||
| Scope = new InstrumentationScope { Name = "BenchmarkScope" } | ||
| } | ||
| } | ||
| }; | ||
| var scopeSpans = resourceSpans.ScopeSpans[0]; | ||
| var traceId = ByteString.CopyFrom(Encoding.UTF8.GetBytes("benchmark-trace")); | ||
| var startTime = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc); | ||
| for (var spanIndex = 0; spanIndex < GeneratedSpanCount; spanIndex++) | ||
| { | ||
| var spanStartTime = startTime.AddTicks(spanIndex); | ||
| scopeSpans.Spans.Add(new OtlpProtoSpan | ||
| { | ||
| TraceId = traceId, | ||
| SpanId = CreateSpanId(spanIndex), | ||
| ParentSpanId = spanIndex == 0 ? ByteString.Empty : CreateSpanId(spanIndex - 1), | ||
| Name = spanIndex == 0 ? "root-span" : $"span-{spanIndex}", | ||
| Kind = OtlpProtoSpan.Types.SpanKind.Internal, | ||
| StartTimeUnixNano = DateTimeToUnixNanoseconds(spanStartTime), | ||
| EndTimeUnixNano = DateTimeToUnixNanoseconds(spanStartTime.AddMilliseconds(5)) | ||
| }); | ||
| } | ||
|
|
||
| return [resourceSpans]; | ||
| } | ||
|
|
||
| private static RepeatedField<ResourceSpans> CreateAppendResourceSpans(RepeatedField<ResourceSpans> resourceSpans) | ||
| { | ||
| var firstSpan = resourceSpans.SelectMany(resource => resource.ScopeSpans).SelectMany(scope => scope.Spans).First(); | ||
| return | ||
| [ | ||
| new ResourceSpans | ||
| { | ||
| Resource = CreateResource("append-app"), | ||
| ScopeSpans = | ||
| { | ||
| new ScopeSpans | ||
| { | ||
| Scope = new InstrumentationScope { Name = "AppendScope" }, | ||
| Spans = | ||
| { | ||
| new OtlpProtoSpan | ||
| { | ||
| TraceId = firstSpan.TraceId, | ||
| SpanId = ByteString.CopyFrom(Encoding.UTF8.GetBytes("append-span-0001")), | ||
| ParentSpanId = firstSpan.SpanId, | ||
| Name = "appended-span", | ||
| Kind = OtlpProtoSpan.Types.SpanKind.Internal, | ||
| StartTimeUnixNano = firstSpan.EndTimeUnixNano + 100, | ||
| EndTimeUnixNano = firstSpan.EndTimeUnixNano + 200 | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ]; | ||
| } | ||
|
|
||
| private static SqliteTelemetryRepository CreateRepository(DashboardSqliteDatabase database) | ||
| { | ||
| return new SqliteTelemetryRepository( | ||
| database, | ||
| NullLoggerFactory.Instance, | ||
| Options.Create(new DashboardOptions | ||
| { | ||
| TelemetryLimits = new TelemetryLimitOptions { MaxTraceCount = 1_000 } | ||
| }), | ||
| new PauseManager(), | ||
| []); | ||
| } | ||
|
|
||
| private static Resource CreateResource(string name) | ||
| { | ||
| return new Resource | ||
| { | ||
| Attributes = | ||
| { | ||
| new KeyValue { Key = "service.name", Value = new AnyValue { StringValue = name } } | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| private static ByteString CreateSpanId(int spanIndex) => | ||
| ByteString.CopyFrom(Encoding.UTF8.GetBytes($"span-{spanIndex:0000}")); | ||
|
|
||
| private static ulong DateTimeToUnixNanoseconds(DateTime dateTime) | ||
| { | ||
| var timeSinceEpoch = dateTime.ToUniversalTime() - DateTime.UnixEpoch; | ||
| return (ulong)timeSinceEpoch.Ticks * 100; | ||
| } | ||
|
|
||
| private sealed class Config : ManualConfig | ||
| { | ||
| public Config() | ||
| { | ||
| AddJob(Job.Default | ||
| .WithToolchain(InProcessNoEmitToolchain.Instance) | ||
| .WithWarmupCount(1) | ||
| .WithIterationCount(3) | ||
| .WithInvocationCount(1) | ||
| .WithUnrollFactor(1)); | ||
|
|
||
| AddDiagnoser(MemoryDiagnoser.Default); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.