diff --git a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs index 3bb97ea1c60..cbd11865f7f 100644 --- a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.InteropServices; using Aspire.Dashboard.Extensions; using Aspire.Dashboard.Model; using Aspire.Dashboard.Model.Otlp; @@ -314,14 +315,9 @@ private async Task OnShowPropertiesAsync(SpanWaterfallViewModel viewModel, strin private SpanLinkViewModel CreateLinkViewModel(string traceId, string spanId, KeyValuePair[] attributes, Dictionary traceCache) { - if (!traceCache.TryGetValue(traceId, out var trace)) - { - trace = TelemetryRepository.GetTrace(traceId); - if (trace != null) - { - traceCache[traceId] = trace; - } - } + ref var trace = ref CollectionsMarshal.GetValueRefOrAddDefault(traceCache, traceId, out _); + // Adds to dictionary if not present. + trace ??= TelemetryRepository.GetTrace(traceId); var linkSpan = trace?.Spans.FirstOrDefault(s => s.SpanId == spanId); diff --git a/src/Aspire.Dashboard/Otlp/Model/OtlpApplication.cs b/src/Aspire.Dashboard/Otlp/Model/OtlpApplication.cs index 835bca6431f..a462f0532a7 100644 --- a/src/Aspire.Dashboard/Otlp/Model/OtlpApplication.cs +++ b/src/Aspire.Dashboard/Otlp/Model/OtlpApplication.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using Aspire.Dashboard.Otlp.Storage; using Google.Protobuf.Collections; using OpenTelemetry.Proto.Common.V1; @@ -52,21 +53,20 @@ public void AddMetrics(AddContext context, RepeatedField scopeMetr try { var instrumentKey = new OtlpInstrumentKey(sm.Scope.Name, metric.Name); - if (!_instruments.TryGetValue(instrumentKey, out var instrument)) + ref var instrument = ref CollectionsMarshal.GetValueRefOrAddDefault(_instruments, instrumentKey, out _); + // Adds to dictionary if not present. + instrument ??= new OtlpInstrument { - _instruments.Add(instrumentKey, instrument = new OtlpInstrument + Summary = new OtlpInstrumentSummary { - Summary = new OtlpInstrumentSummary - { - Name = metric.Name, - Description = metric.Description, - Unit = metric.Unit, - Type = MapMetricType(metric.DataCase), - Parent = GetMeter(sm.Scope) - }, - Context = Context - }); - } + Name = metric.Name, + Description = metric.Description, + Unit = metric.Unit, + Type = MapMetricType(metric.DataCase), + Parent = GetMeter(sm.Scope) + }, + Context = Context + }; instrument.AddMetrics(metric, ref tempAttributes); } @@ -97,10 +97,10 @@ private static OtlpInstrumentType MapMetricType(Metric.DataOneofCase data) private OtlpMeter GetMeter(InstrumentationScope scope) { - if (!_meters.TryGetValue(scope.Name, out var meter)) - { - _meters.Add(scope.Name, meter = new OtlpMeter(scope, Context)); - } + ref var meter = ref CollectionsMarshal.GetValueRefOrAddDefault(_meters, scope.Name, out _); + // Adds to dictionary if not present. + meter ??= new OtlpMeter(scope, Context); + return meter; } diff --git a/src/Aspire.Dashboard/Otlp/Model/OtlpInstrument.cs b/src/Aspire.Dashboard/Otlp/Model/OtlpInstrument.cs index bafb1f6f996..8dd11428a95 100644 --- a/src/Aspire.Dashboard/Otlp/Model/OtlpInstrument.cs +++ b/src/Aspire.Dashboard/Otlp/Model/OtlpInstrument.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using Aspire.Dashboard.Otlp.Model.MetricValues; using Google.Protobuf.Collections; using OpenTelemetry.Proto.Common.V1; @@ -74,26 +75,30 @@ private DimensionScope FindScope(RepeatedField attributes, ref KeyValu var comparableAttributes = tempAttributes.AsMemory(0, copyCount); + // Can't use CollectionsMarshal.GetValueRefOrAddDefault here because comparableAttributes is a view over mutable data. + // Need to add dimensions using durable attributes instance after scope is created. if (!Dimensions.TryGetValue(comparableAttributes, out var dimension)) { - dimension = AddDimensionScope(comparableAttributes); + dimension = CreateDimensionScope(comparableAttributes); + Dimensions.Add(dimension.Attributes, dimension); } return dimension; } - private DimensionScope AddDimensionScope(Memory> comparableAttributes) + private DimensionScope CreateDimensionScope(Memory> comparableAttributes) { var isFirst = Dimensions.Count == 0; var durableAttributes = comparableAttributes.ToArray(); var dimension = new DimensionScope(Context.Options.MaxMetricsCount, durableAttributes); - Dimensions.Add(durableAttributes, dimension); var keys = KnownAttributeValues.Keys.Union(durableAttributes.Select(a => a.Key)).Distinct(); foreach (var key in keys) { - if (!KnownAttributeValues.TryGetValue(key, out var values)) + ref var values = ref CollectionsMarshal.GetValueRefOrAddDefault(KnownAttributeValues, key, out _); + // Adds to dictionary if not present. + if (values == null) { - KnownAttributeValues.Add(key, values = new List()); + values = new List(); // If the key is new and there are already dimensions, add an empty value because there are dimensions without this key. if (!isFirst) diff --git a/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs b/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs index 78e954bb27c..d9aa04b7b98 100644 --- a/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs +++ b/src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs @@ -12,6 +12,7 @@ using Aspire.Dashboard.Otlp.Model.MetricValues; using Google.Protobuf.Collections; using Microsoft.Extensions.Options; +using OpenTelemetry.Proto.Common.V1; using OpenTelemetry.Proto.Logs.V1; using OpenTelemetry.Proto.Metrics.V1; using OpenTelemetry.Proto.Resource.V1; @@ -285,6 +286,28 @@ public void AddLogs(AddContext context, RepeatedField resourceLogs RaiseSubscriptionChanged(_logSubscriptions); } + private bool TryAddScope(Dictionary scopes, InstrumentationScope? scope, [NotNullWhen(true)] out OtlpScope? s) + { + try + { + // The instrumentation scope information for the spans in this message. + // Semantically when InstrumentationScope isn't set, it is equivalent with + // an empty instrumentation scope name (unknown). + var name = scope?.Name ?? string.Empty; + ref var scopeRef = ref CollectionsMarshal.GetValueRefOrAddDefault(scopes, name, out _); + // Adds to dictionary if not present. + scopeRef ??= (scope != null) ? new OtlpScope(scope, _otlpContext) : OtlpScope.Empty; + s = scopeRef; + return true; + } + catch (Exception ex) + { + _otlpContext.Logger.LogInformation(ex, "Error adding scope."); + s = null; + return false; + } + } + public void AddLogsCore(AddContext context, OtlpApplicationView applicationView, RepeatedField scopeLogs) { _logsLock.EnterWriteLock(); @@ -293,23 +316,9 @@ public void AddLogsCore(AddContext context, OtlpApplicationView applicationView, { foreach (var sl in scopeLogs) { - OtlpScope? scope; - try - { - // The instrumentation scope information for the spans in this message. - // Semantically when InstrumentationScope isn't set, it is equivalent with - // an empty instrumentation scope name (unknown). - var name = sl.Scope?.Name ?? string.Empty; - if (!_logScopes.TryGetValue(name, out scope)) - { - scope = (sl.Scope != null) ? new OtlpScope(sl.Scope, _otlpContext) : OtlpScope.Empty; - _logScopes.Add(name, scope); - } - } - catch (Exception ex) + if (!TryAddScope(_logScopes, sl.Scope, out var scope)) { context.FailureCount += sl.LogRecords.Count; - _otlpContext.Logger.LogInformation(ex, "Error adding scope."); continue; } @@ -343,14 +352,9 @@ public void AddLogsCore(AddContext context, OtlpApplicationView applicationView, { if (!_logSubscriptions.Any(s => s.SubscriptionType == SubscriptionType.Read && (s.ApplicationKey == applicationView.ApplicationKey || s.ApplicationKey == null))) { - if (_applicationUnviewedErrorLogs.TryGetValue(applicationView.ApplicationKey, out var count)) - { - _applicationUnviewedErrorLogs[applicationView.ApplicationKey] = ++count; - } - else - { - _applicationUnviewedErrorLogs.Add(applicationView.ApplicationKey, 1); - } + ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(_applicationUnviewedErrorLogs, applicationView.ApplicationKey, out _); + // Adds to dictionary if not present. + count++; } } @@ -577,6 +581,7 @@ public Dictionary GetTraceFieldValues(string attributeName) if (value != null) { ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(attributesValues, value, out _); + // Adds to dictionary if not present. count++; } } @@ -604,6 +609,7 @@ public Dictionary GetLogsFieldValues(string attributeName) if (value != null) { ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(attributesValues, value, out _); + // Adds to dictionary if not present. count++; } } @@ -770,23 +776,9 @@ internal void AddTracesCore(AddContext context, OtlpApplicationView applicationV { foreach (var scopeSpan in scopeSpans) { - OtlpScope? scope; - try - { - // The instrumentation scope information for the spans in this message. - // Semantically when InstrumentationScope isn't set, it is equivalent with - // an empty instrumentation scope name (unknown). - var name = scopeSpan.Scope?.Name ?? string.Empty; - if (!_traceScopes.TryGetValue(name, out scope)) - { - scope = (scopeSpan.Scope != null) ? new OtlpScope(scopeSpan.Scope, _otlpContext) : OtlpScope.Empty; - _traceScopes.Add(name, scope); - } - } - catch (Exception ex) + if (!TryAddScope(_traceScopes, scopeSpan.Scope, out var scope)) { context.FailureCount += scopeSpan.Spans.Count; - _otlpContext.Logger.LogInformation(ex, "Error adding scope."); continue; } @@ -1095,13 +1087,15 @@ public List GetInstrumentsSummaries(ApplicationKey key) foreach (var knownAttributeValues in instrument.KnownAttributeValues) { - if (allKnownAttributes.TryGetValue(knownAttributeValues.Key, out var values)) + ref var values = ref CollectionsMarshal.GetValueRefOrAddDefault(allKnownAttributes, knownAttributeValues.Key, out _); + // Adds to dictionary if not present. + if (values != null) { - allKnownAttributes[knownAttributeValues.Key] = values.Union(knownAttributeValues.Value).ToList(); + values = values.Union(knownAttributeValues.Value).ToList(); } else { - allKnownAttributes[knownAttributeValues.Key] = knownAttributeValues.Value.ToList(); + values = knownAttributeValues.Value.ToList(); } } }