Skip to content

Commit

Permalink
Use CollectionsMarshal.GetValueRefOrAddDefault to optimize adding to …
Browse files Browse the repository at this point in the history
…dictionaries (dotnet#6443)
  • Loading branch information
JamesNK authored Oct 23, 2024
1 parent d0d94bd commit 0f3bccb
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 71 deletions.
12 changes: 4 additions & 8 deletions src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -314,14 +315,9 @@ private async Task OnShowPropertiesAsync(SpanWaterfallViewModel viewModel, strin

private SpanLinkViewModel CreateLinkViewModel(string traceId, string spanId, KeyValuePair<string, string>[] attributes, Dictionary<string, OtlpTrace> 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);

Expand Down
34 changes: 17 additions & 17 deletions src/Aspire.Dashboard/Otlp/Model/OtlpApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,21 +53,20 @@ public void AddMetrics(AddContext context, RepeatedField<ScopeMetrics> 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);
}
Expand Down Expand Up @@ -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;
}

Expand Down
15 changes: 10 additions & 5 deletions src/Aspire.Dashboard/Otlp/Model/OtlpInstrument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -74,26 +75,30 @@ private DimensionScope FindScope(RepeatedField<KeyValue> 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<KeyValuePair<string, string>> comparableAttributes)
private DimensionScope CreateDimensionScope(Memory<KeyValuePair<string, string>> 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<string?>());
values = new List<string?>();

// If the key is new and there are already dimensions, add an empty value because there are dimensions without this key.
if (!isFirst)
Expand Down
76 changes: 35 additions & 41 deletions src/Aspire.Dashboard/Otlp/Storage/TelemetryRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -285,6 +286,28 @@ public void AddLogs(AddContext context, RepeatedField<ResourceLogs> resourceLogs
RaiseSubscriptionChanged(_logSubscriptions);
}

private bool TryAddScope(Dictionary<string, OtlpScope> 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> scopeLogs)
{
_logsLock.EnterWriteLock();
Expand All @@ -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;
}

Expand Down Expand Up @@ -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++;
}
}

Expand Down Expand Up @@ -577,6 +581,7 @@ public Dictionary<string, int> GetTraceFieldValues(string attributeName)
if (value != null)
{
ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(attributesValues, value, out _);
// Adds to dictionary if not present.
count++;
}
}
Expand Down Expand Up @@ -604,6 +609,7 @@ public Dictionary<string, int> GetLogsFieldValues(string attributeName)
if (value != null)
{
ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(attributesValues, value, out _);
// Adds to dictionary if not present.
count++;
}
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -1095,13 +1087,15 @@ public List<OtlpInstrumentSummary> 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();
}
}
}
Expand Down

0 comments on commit 0f3bccb

Please sign in to comment.